Linux进程基础行为(一)

1.进程组织结构

1. task_struct ,thread_info 和内核栈

image.png
image.png

在内核中通常current宏获取当前正在运行的task_struct。对于不同的硬件体系current的实现方式不一样,寄存器较多的体系直接用一个寄存器来存储当前进程的task_struct的指针,X86 current把内核栈栈顶指针最后13位(内核栈8KB)清零,计算出thread_info的位置,通过thread_info总task指针指向当前进程的task_truct

2. 进程状态

image.png

3. 进程树

进程树的树根是0号进程idle,是所有进程的祖先。

  1. 0号进程创建1号进程(创建时是内核线程),1号进程负责内核部分的初始化工作及系统配置,创建用于高速缓存和虚拟内存管理的内核线程
  2. 1号进程调用execve()运行磁盘上的init可执行程序,并演变为用户态1号进程,init进程
  3. init进程按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号,2号,……的Getty进程(初始化终端)
  4. getty进程监控到终端连接信号时,通过调用execve()执行login登录程序
  5. 如果登录成功过,login程序通过execve()函数调用shell
  6. Shell进程接收getty进程的pid,取代原来的getty进程。

1. 0号进程idle

  1. idle进程只能从静态地填写thread_info和task_struct
  2. idle进程让CPU陷入空闲循环,空闲运行
  3. 多处理器上刚启动只有一个CPU能运行,只有CPU0上的idle进程完成初始化后才激活其他CPU,并通过copy_process()创建其他CPU的idle进程

3. 进程创建与撤销

在Linux系统中,系统通过fork()函数复制一个现有的进程创建一个新进程。
接着,调用exec()函数创建新进程的地址空间,并把新程序载入其中。
最终,程序通过调用exit()系统调用退出运行。
进程退出后被设置为僵死状态,直到父进程检查之后调用wait()在删除进程描述符资源

1. 进程创建

1. fork ,vfork, clone

fork: 写时复制,赋值父进程的task_struct,thread_info和内核栈,mm_struct,页表
vfork: 没有创建父进程的数据副本,子进程与父进程共享数据。子进程创建之后,子进程立即调用execve()加载新的进程映象,或者与父进程共享进程映象。在子进程退出之前,父进程处于阻塞状态。
clone: fork,vfork,__clone库函数都是通过不同的参数调用clone()函数,来实现进程的创建的。

clone()-->do_fork()(1. copy_process() 2. wake_up_task() 3. 如果是vfork调用的,则使用wait_for_vfork_done() 等待子进程结束或调用execve())
copy_process()
(1)复制父进程的内核栈,thread_info,task_struct
(2)检查创建该子进程后,没有超出系统的资源限制
(3)着手将子进程和父进程区分开来,子进程一些统计信息清空
(4)将子进程的状态标志设置为TASK_UNINTERRUPTIBLE,保证子进程不会运行
(5)更新子进程flag,例如设置子进程没有被exec调用的标志,超级用户权限标志清零
(6)根据传递进clone的参数,子进程拷贝或共享父进程地址空间,打开的文件,文件系统信息等
(7)返回一个指向子进程的指针
wake_up_task()
将子进程添加到调度队列上等待调度获取CPU获取执行。

创建线程:

image.png

线程的用户态堆栈,task_struct,内核栈和thread_info是私有的

创建进程和写时复制:


image.png

2. execve()系统调用

前面进程只是创建了一个与父进程大体一致的子进程(仅仅是资源共享上的区别)。execve使用指定可执行文件替换现有进程上的进程印象。
举一个例子:在Shell中运行一个二进制程序。
(1)Shell进程将参数 运行时环境添加到自己的用户态堆栈中
(2)Shell使用fork()创建一个子进程,放到CPU调度队列上。
(3)子进程执行execve()使用二进制程序替换原来的Shell映象并将EIP指向新程序的入口开始执行。
execve():
(1)execve()--》sys_execve()--》do_execve()——》do_execve_common()
(2)do_execve_common()执行:
(1) unshared_files()为新进程复制一份打开的文件表
(2) kazlloc()分配一个可执行文件的结构体linux_binprm,将可执行文件运行时所要用的参数,运行时环境等信息添加到linux_binprm。
(3)open_exec() 打开可执行文件。具体参考文件系统
(4) 找到负载最小的CPU。
(5)填写linux_binprm实例,创建进程映象,将参数传入新进程的用户态堆栈。
(6)执行新程序,使用过load_elf_binary()执行处理ELF的函数。
(7)如果该程序是静态链接的,则load_elf_binary()直接返回新程序的入口地址,并调用start_thread()将EIP的值置为新程序的入口地址。

2.进程撤销

进程要终结的时候会调用exit()函数,最终通过调用do_exit()这个函数来结束进程的。
(1)简单的说,就是将各个引用计数器减一,若有计数器归0,则释放相应的内存。
(2)然后调用exit_notify()告知父进程为子进程找养父,并把进程状态设置为EXIT_ZOMBIE.最后调用schedule()切换到其他进程。
(3)自此该进程不会被再次调用。此时该进程占用的内存仅为内存栈,thread_info和task_struct。
(4)当父进程调用wait或waitpid检测到僵尸进程提供的信息之后,将进程所持有的剩余内存释放。至此进程终结。wait挂起当前进程知道一个子进程退出。

1.孤儿进程,僵尸进程

孤儿进程: 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程: 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

4. 进程切换

进程切换主要包括:1.当前进程信息压入内核栈 2. 切换CPU页表寄存器 3.切入进程工作现场恢复
进程切换是通过schedule()函数完成的。schedule中最核心的就是switch_mm()切换进程映象,内核堆栈和硬件上下文的切换switch_to()

1. 页表切换

switch_mm() load_cr3(next->pgd)

image.png

2.内核堆栈切换

每个进程的内核空间都是一样的,因此,尽管在switch_mm()中切换了页表但是对switch_to没有影响,所以说switch_to是平滑的。

image.png

switch_to(pre,next,last)
pre:当前进程 next:下一个进程 last:上一个进程
(a)页表切换后内核空间分布状况
(b)将当前进程的内核栈栈底和进程状态信息保存在内核栈中。
(c)将当前进程的栈顶ESP保存在prev->thread.sp中,将ESP = next->thread.sp,此时完成了内核切换,同时current宏根据P1的ESP找到P1的thread_info,进而找到task_struct。此时,内核状态为(d)
(e)将P0的EIP保存到pre->thread.ip 此时状态为(f)
(g)执行__switch_to()完成一些浮点部件现场保留和恢复
(i)EIP=next->thread.ip,继续执行P1上次schedule()被打断的指令,在通过系统调用返回返回到用户空间执行代码。其中,内核态和用户态切换是通过任务状态段TSS,来获取内核或用户态的堆栈地址的。

5. 进程调度

1. 基本框架

实时任务调度算法:FIFO和RR。普通任务完全公平调度算法

image.png

四个调度类优先级:stop_sched_class,rt_sched_class, fair_sched_class,idle_sched_class,NUll
运行队列:

image.png
image.png

put_prev_task():通知当前进程准备调出
pick_next_task()选择要调入的进程

2. CFS

CFS为每一个进程设置一个虚拟时间,调度时总是选择虚拟时间推进缓慢的进程先运行。优先级越高,虚拟时间推进越慢,所占用CPU时间就越高。
虚拟时间推进:

image.png

内核中并不直接使用进程的优先级数据值,而是将优先级转换为权重计算虚拟时间的推进。任务的nice值每降低1,则多获得10%的CPU时间。

挑选下一个任务:普通任务的运行队列的结构是红黑树。每次挑选红黑树最左侧的叶子节点,将叶子节点缓存起来,每次挑选时,只要从这个缓存区取出该进程就行,如果该缓存区为空,则调用idle进程。

调度延迟:CFS为无线小调度周期设置了一个“目标延迟”默认是20ms,即保证每个可运行的进程都应该至少运行一次的某个时间间隔.。越小的周期带来越好的交互性,同时也接近完美。如果挥动进程的数目超过该上限, 则延迟周期也成比例的线性扩展.。周期长度是
__sched_period = sysctl_sched_latency * nr_running / sched_nr_latency

3.实时策略

FIFO:不适用时间片,可以一直运行下去,除非有更高优先级的任务抢占
RR:是带有时间片的FIFO,只能在同一优先级的任务中轮询。

6 其他

1 进程和线程区别

  1. 进程是资源分配的基本单位,线程是调度的基本单位。
  2. 实体间(进程间,线程间,进线程间)通信方式的不同
    进程:A.共享内存 B.消息队列 C.信号量 D.有名管道 E.无名管道 F.信号 G.文件 H.socket
    线程: 线程间的通信方式上述进程间的方式都可沿用,且还有自己独特的几种:A.互斥量 B.自旋锁 C.条件变量 D.读写锁 E.线程信号 G.全局变量
  3. 进程有父子关系,线程只有一个父线程,其他都为子线程

多线程的优势:

  1. 理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  2. 理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
  3. 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  4. 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  5. 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

2 用户线程 内核线程

两种类型的内核线程:
线程按周期性间隔运行,检测特定资源的使用,在用量超出或者低于预置的限制时采取行动
在线程启动后则一直等待,直到内核线程请求执行某一特定的操作。
不同之处在于:
内核线程只工作在内核态中;而用户线程则既可以运行在内核态(执行系统调用时),也可以运行在用户态;
内核线程没有用户空间,所以对于一个内核线程来说,它的0 ~ 3G的内存空间是空白的,它的current->mm是空的,与内核使用同一张页表;而用户线程则可以看到完整的0~4G内存空间。

3.进程状态

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容