2.进程管理

进程管理

进程是操作系统的基本概念,本节主要总结Linux内核如何管理进程:进程在内核中如何创建,消亡。

1.进程

进程是处于执行期的程序,但不仅包含可执行的程序代码,还包括其他资源:打开的文件挂起的信号内核内部数据处理器状态一个或多个具有内存映射的内存地址空间和执行线程以及存放全局变量的数据段等。

线程

执行线程,简称线程,是进程中活动的对象。拥有独立的程序计数器进程栈进程寄存器。内核调度的对象是线程而不是进程,在Linux中线程是一种特殊的进程。

2.进程描述符

内核把进程的列表存放在叫做任务队列(task list)的双向循环列表中(列表插入删除复杂度低)。列表的每一项类型都是task_struct称为进程描述符(process description),进程描述符能够完整的描述一个正在执行的程序。

分配进程描述符

Linux通过slab分配task_struct结构,在栈底(向下增长的栈)创建一个新的结构struct thread_info用于存放task_struct的偏移地址,这样方便定位task_struct的实际指针。

进程描述符的存放

内核中大部分处理进程的代码都是直接访问task_struct指针,通过current宏查找当前正在运行进程的进程描述符。但是像x86寄存器较少,因此只能通过内核栈的尾端创建thread_info来计算偏移地址查找task_struct

进程状态

进程描述符中的state域描述了进程的当前状态。进程状态处于下列五种状态之一:

  • TASK_RUNNING(运行)——进程可执行,处于执行中或者运行队列中等待
  • TASK_INTERRUPTIBLE(可中断)——进程正在睡眠(被阻塞),等待某些条件达成。也可以通过接收信号提前被唤醒并随时准备投入运行
  • TASK_UNITTERUPTIBLE(不可中断)——对信号不做相应,其余和可中断状态相同,通常用于重要且不能中断的进程
  • __TASK_TRACED——被其他进程跟踪的进程,例如通过ptrace对调试程序进行跟踪
  • __TASK_STOPPED(停止)——进程停止执行,进程没有投入运行也不能投入运行
进程状态转移图

设置当前进程状态

内核调整某个进程的状态,可以通过如下代码:

set_task_state(task,state);

或者

task->state = state;

设置当前状态,可以通过set_current_state(state)set_task_state(current,state)

进程上下文

一般程序在用户空间执行,一旦程序执行了系统调用或者触发某个异常,它就陷入内核空间(对应第一节内容)。除非在内核空间运行期间有更高优先级的进程需要执行并由调度器做出了相应的调整,否则在内核退出的时候,程序恢复在用户空间继续执行。

系统调用和异常处理程序是对内核明确定义的接口。进程只有通过这些接口才能陷入内核执行,对内核的所有访问必须通过这些接口

进程家族树

Unix系统的进程之间存在明显的继承关系,Linux也是如此。内核在系统启动最后阶段执行了init进程,该进程读取系统初始化脚本并执行其他相关程序,最终完成系统启动的整个过程,PID为1,所以所有进程都是init的后代。因此每个进程标识符都有一个指向父亲的task->parent指针,和子进程链表&task->children

由于任务队列是一个双向循环链表,我们可以通过下面两种方式分别获取前一个和后一个进程:

list_entry(task->tasks.next, struct task_struct, tasks)

list_entry(task->tasks.next, struct task_struct, tasks)

3.进程创建

许多操作系统进程创建过程为,首先在新的地址空间创建进程,读入可执行文件,最后执行。而Unix将上述两个步骤分解到两个单独的函数去执行:fork()exec()

首先,fork()通过拷贝当前进程创建子进程,子进程与父进程区别仅仅在于PID和PPID和某些资源和统计量。

然后,exec()负责读取可执行文件并将其载入地址空间运行。

写时拷贝

Linux的fork()函数进行了一个优化,采用写时拷贝实现。在创建进程阶段,内核并不复制整个地址空间,而是让父进程和子进程共享同一个拷贝

进程只有在需要写入时,才复制数据,这样将页拷贝推迟到写入阶段,可以使Linux进程快速启动,并且往往进程在fork()之后会马上exec(),不会有写入过程(这个优化过程还是相当机智,Linux快启动的灵魂!)

fork()

由前面介绍我们了解了进程需要fork()拷贝父进程的信息,Linux通过clone()系统调用实现fork(),其功能主要通过cope_process()函数实现:

  1. 调用dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct,这些值与父进程完全相同
  2. 检查并确保创建子进程后,当前用户的进程数没有超过限制
  3. 区分子进程和父进程,讲进程描述符中许多成员清零或初始化(主要是统计信息),多数数据仍未修改
  4. 子进程的状态设置为TASK_UNINTERRUPTIBLE,保证其不会被运行
  5. 调用copy_flags()更新进程描述符的flag成员,表明是否拥有超级用户权限的标志PF_SUPERPRIV标志清零,表明进程没有调用exec()函数的PF_FORKNOEXEC标志被设置。
  6. 调用alloc_pid()为新进程分配一个有效PID
  7. 根据传递给clone()的参数标志,cope_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。通常对于制定进程的线程,这些资源都是共享;否则,这些资源对每个进程都是不同的,往往需要拷贝到这里。
  8. copy_process()扫尾,并返回一个指向子进程的指针

一般内核会有意让子进程先执行,减小写时拷贝可能的开销。

vfork()

对于vfork(),其不拷贝父进程的页表项,子进程会作为父进程的一个线程执行,父进程被阻塞,直到子进程退出或者执行exec()。子进程不能向地址空间写入。

4.线程在Linux中实现

Linux中线程只是共享父进程资源的轻量进程,其创建方式和普通进程类似,只是在调用clone()时,需要传递一些参数标志位,表明需要共享的资源:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

而普通的进程为:

clone(SIGHLD, 0);

其中CLONE_VM——父子进程共享地址空间;CLONE_FS——共享文件系统信息;CLONE_FILES——共享打开的文件;CLONE_SIGHAND——共享信号处理函数和被阻断的信号;

5.进程终结

进程终结一般是自身引起的,它发生在进程调用exit()系统调用时。当进程接收到它不能处理且不能忽略的信号或者异常时,也可能被动终结。不管什么原因终结,进程终结的大部分工作由do_exit()完成:

  1. task_struct的标志成员设置为PF_EXITING
  2. 调用del_timer_sync()删除任意内核定时器。根据返回结果,确保没有定时器在排队,也没有定时器处理程序在运行
  3. 若BSD的进程记账功能开启的,调用acct_update_integrals()来输出记账信息
  4. 调用exit_mm()函数释放进程占用的mm_struct,若没有其他进程使用,就彻底释放
  5. 调用sem_exit()。若进程排队等候IPC信号,则它离开队列
  6. 调用exit_files()exit_fs()分别递减文件描述符、文件系统数据的引用次数,若为0,可以释放
  7. 接着把存放在task_struct的exit_code成员中的任务退出代码设置为由exit()提供的退出代码,或者去完成其他由内核机制规定的退出动作。退出代码存放在这里供父进程随时检索
  8. 调用exit_notify()向父进程发生信号,给子进程重新找养父,养父为线程组中的其他线程或者init进程,并设置task_structexit_stateEXIT_ZOMBIE
  9. 调用schedule()切换到新进程

至此进程相关的所有资源都被释放掉了,并处于EXIT_ZOMBIE状态,仅剩内核栈、thread_info结构和task_struct结构用于给父进程提供信息。父进程检索信息后,或者通知内核那是无关信息后,将该内存释放,归还系统使用。

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

推荐阅读更多精彩内容

  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,088评论 0 23
  • Linux 进程管理与程序开发 进程是Linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源,...
    JamesPeng阅读 2,447评论 1 14
  • 本文转载自实验楼:多进程(一) 概述 进程的概念这里就不再过多的赘述了,市面上几乎关于计算机操作系统的书都有详细的...
    mnikn阅读 487评论 0 0
  • 大清早,一路听着自己录的音,开开心心的去单位上班。 心想,难得心情这么愉悦,想必今天一定是美好的一天。 走在上班的...
    4e57b8f24a48阅读 440评论 1 2
  • 摘自徐静蕾的朗读者 世界给我的第一个记忆是,我躺在奶奶的怀里,拼命地哭,打着挺儿,也不知道是为什么,哭得好伤心。奶...
    大老童阅读 292评论 0 0