第三章  进程管理

姓名:王玮怡  学号:20135116

一、进程

1、进程的含义

  进程是处于执行期的程序以及相关资源的总称,程序本身并不是进程,实际上就是正在执行的代码的实时结果。Linux内核通常把进程也叫“任务”。

2、线程的含义

  执行线程简称线程,是在进程中互动的对象。内核调度的对象是线程而不是进程。Linux系统不区分线程和进程,线程只是一种特殊的进程。

3、进程的执行过程

(1)clone()调用fork(),通过复制一个现有进程来创建一个全新的进程,进程开始存活。其中调用fork()的进程为父进程,新产生的进程为子进程。在该系统调用结束时,在返回点这个相同位置上,父进程回复执行,子进程开始执行。其中,fork()系统调用从内核返回两次:一次回到父进程,另一次返回到新产生的子进程。子进程和父进程的区别在于PID(每个进程唯一)、PPID(父进程的进程号,子进程将其设置为被拷贝进程的PID)和某些资源和统计量。

(2)新的进程调用exec()这组函数,创建新的地址空间,并把新的程序载入其中。

(3)程序通过exit()系统调用推出执行,终结进程并将其占用的资源释放,进程退出执行后为僵死状态,直到父进程调用wait()或waitpid()为止。其中父进程可以通过wait4()系统调用查询子进程是否终结。

二、进程描述符及任务结构

  内核把进程的列表存放在“任务队列”的双向循环链表中。链表中的每一项都是类型为task_struct、称为进程描述符的结构,该结构定义在<linux/sched.h>文件中,进程描述符包含了一个具体进程的所有信息。

1、分配进程描述符

  Linux通过slab分配器动态分配task_struct结构,这样能达到对象复用和缓存着色(cache coloring)的目的。只需在栈底(向下增长的栈)或栈顶(向上增长的栈)创建一个新的结构struct thread_info。

  每个任务的thread_info结构在它的内核栈的尾端分配。结构域中task域存放的是指向该任务实际task_struct的指针。

2、进程描述符的存放

  内核通过一个唯一的进程标识值或PID来标识每个进程。PID是一个数,表示为pid_t隐含类型,实际上就是一个int类型,最大默认值设置为32768(short int短整型的最大值)。最大默认值表示系统中允许同时存在的进程的最大数目,这个值越小,转一圈的速度越快。

  在内核中访问任务通常需要获得指向其task_struct的指针。实际上,内核中大部分处理进程的代码都是直接通过task_struct进行的。

3、进程状态

  进程描述符中的state域描述了进程的当前状态。

(1)进程的五种状态

  • TASK RUNNING(运行):进程是可执行的,表示正在执行或者在运行队列中等待执行。
  • TASK_INTERRUPTIBLE(可中断):进程正在休眠(被阻塞),等待某种条件达成。
  • TASK_UNINTERRUPTIBLE(不可中断):除了就算接收信号也不会被唤醒或准备投入运行外,这个状态与可打断状态一样,通常在进程必须等待时不受干扰或等待时间很快就会发生时出现。
  • __TASK_REACED:被其他进程跟踪的进程,例如,通过ptrace对调试程序进行跟踪。
  • __TASK_STOPPED:进程停滞执行;进程没有投入运行也不能投入运行

(2)进程状态转化

4、设置当前进程状态

  使用set_task_state(task,state)函数将制定的进程设置为制定的状态。

*注:set_current_state(state)和set_task_state(task,state)含义是等同的。

5、进程上下文

  当一个程序调用执行了系统调用或触发了某个异常,它就陷入了内核空间,此时,我们称内核“代表进程执行”并处于进程上下文中。在此上下文中current宏是有效的。

  进程只有通过某些明确定义的接口才能陷入内核执行——对内核的所有访问都必须是必须通过这些接口的。

6、进程家族树

  所有的进程都是PID为1的init进程的后代。进程间的关系存放在进程描述符中,每个task_struct都包含一个指向其父进程task_struct、叫做parent的指针,还包含一个称为children的子进程链表。

三、进程创建

1、写时拷贝

(1)Linux的fork()使用写时拷贝(copy-on-write)页实现,内核并不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝,而fork()的实际开销就是复制父进程的页表以及给子进程创建唯一地进程描述符。

(2)资源的复制只有在需要写入时才进行,在此之前,只是以只读方式共享。

(3)在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量冗余数据。

2、fork()

(1)fork()、vfork()、__clone()库函数都会根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork()。

(2)do_fork()函数调用copy_process()函数,如果copy_process()函数返回成功,新创建的子进程被唤醒并让其投入运行,而内核有意选择子进程先执行。

(3)关于copy_process()函数:

  • 调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前值一致
  • 检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。
  • 子进程着手使自己与父进程区分开来,而task_struct中的大多数数据都依然未被修改。
  • 子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行。
  • copy_process()调用copy_flags()以更新task_struct的flags成员。
  • 调用alloc_pid()为新进程分配一个有效的PID。
  • 根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。
  • 最后,copy_process()做扫尾工作并返回一个指向子进程的指针。

3、vfork()

(1)除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程推出或执行exec()。

(2)理想情况下,系统最好不要调用vfork(),内核也不用实现它。

(3)vfork()系统调用的实现是通过向clone()系统调用传递一个特殊标志来进行的。

  • 在调用copy_process()时, task_struct 的vfor_done 成员被设置为NULL。
  • 在执行do_fork()时,如果给定特别标志,则vfork_done 会指向一个特定地址。
  • 子进程先开始执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过vfork_done 指针向它发送信号。
  • 在调用mm_release()时,该函数用于进程退出内存地址空间,并且检查vfork_done 是否为空,如果不为空,则会向父进程发送信号。
  • 回到do_fork(),父进程醒来并返回。

四、线程在Linux中的实现

  每个线程都拥有唯一隶属于自己的task_struct,所以在内核中,它看起来就像是一个普通的进程。

1、创建线程

  线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源:

  传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共辜的资源种类。

2、内核线程

(1)内核线程和普通的进程阔的区别在于内核线程没有独立的地址空间(实际上指向地址空间的mm 指针被设置为NULL),它们只在内核空间运行,从来不切换到用户空间去。

(2)内核进程和普通进程一样,可以被调度,也可以被抢占。

(3)内核钱程也只能囱其他内核钱程创建

  • 新的任务是由kthread内核进程通过clone()系统调用而创建的
  • 新创建的进程处于不可运行状态,如果不通过调用wake_up _process()明确地唤醒它,它不会主动运行。
  • 创建一个进程并让它运行起来,可以通过调用ktbread_run()
  • 内核钱程启动后就一直运行直到调用do_exit()退出,或者内核的其他部分调用kthread_stop()退出。

五、进程终结

  一般来说,进程的析构是自身引起的。它发生在进程调用exit()系统调用时,既可能显式地调用这个系统调用,也可能隐式地从某个程序的主函数返回(其实C 语言编译器会在main()函数的返回点后面放置惆用exit()的代码)。

  进程的终结,大部分依靠do_exit():

  • 将tast_struct 中的标志成员设置为PF_EXITING。
  • 调用del_timer_ sync()删除任一内核定时器。根据返回的结果,它确保没有定时器在排队,也设有定时器处理程序在运行。
  • 如果BSD 的进程记账功能是开启的, do_exit()调用acct_update_ integrals()来输出记账信息。
  • 然后调用exit_mm()函数释放进程占用的mm_struct,如果没有别的进程使用它们,就彻底释放它们。
  • 接下来调用sem_ exit()函数。如果进程排队等候IPC信号,它则离开队列。
  • 调用exit_files()和exit_fs(),以分别递藏文件描述符、文件系统数据的引用计数。
  • 接着把存放在task_struct 的exit_code成员中的任务退出代码置为由exit()提供的退出代码,或者去完成任何其他由内核机制规定的退出动作。
  • 调用exit_notify()向父进程发送信号,给子进程重新找养父(线程组中的其他线程或者为init进程),并把进程状态(存放在task_struct 结构的exit_state 中)设成EXIT_ZOMBLE。
  • do_exit()调用schedule()切换到新的进程。

  至此,与进程相关联的所有资源都被释放掉了,线程不可运行(实际上也没有地址空间让它运行)并处于EXIT_ZOMBIE退出状态。

1、删除进程描述符

  wait()这一族函数都是通过唯一(但是很复杂)的一个系统调用wait4()来实现的。它的标准动作是挂起调用它的进程,直到其中的一个子进程退出,此时函数会返回该子进程的PID。

  当最终需要释放进程描述符时,release_task()会被调用,用以完成以下工作:

2、孤儿进程造成的进退维谷

  如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白地艳费内存。

*解决方法:

  给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程.在do_exit()中会调用exit_notify(),该函数会调用forget_original_parent(),而后者会调用find_new _reaper() 来执行寻父过程。

  当一个进程被跟踪时,它的临时父亲设定为调试进程。寻找一个新的父进程的办法:在一个单独的被ptrace 跟踪的子进程链表中搜索相关的兄弟进程一一用两个相对较小的链袭减轻了遍历带来的消耗。

本章总结:

  • Linux 如何存放和表示进程(用task_ struct 和thread_info )
  • 如何创建进程(通过fork(),实际上最终是clone())
  • 如何把新的执行映像装入到地址空间(通过execO 系统调用族)
  • 如何表示进程的层次关系,父进程又是如何收集其后代的信息(通过wait()系统调用族)
  • 进程最终如何消亡(强制或自愿地调用exit())

《Linux内核设计与实现》第三章学习笔记的更多相关文章

  1. LINUX内核设计与实现第三周读书笔记

    LINUX内核设计与实现第三周读书笔记 第一章 LINUX内核简介 1.1 Unix的历史 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统. 1973年, ...

  2. Linux内核设计与实现 第三章

    1. 进程和线程 进程和线程是程序运行时状态,是动态变化的,进程和线程的管理操作都是由内核来实现的. Linux中的进程于Windows相比是很轻量级的,而且不严格区分进程和线程,线程不过是一种特殊的 ...

  3. 《Linux内核分析》之第三章读书笔记

    进程管理 进程是处于执行期的程序以及相关的资源的总称,也称作任务.执行线程,简称线程,是在进程中活动的对象. 可以两个或两个以上的进程执行同一个程序 也可以两个或两个以上并存的进程共享许多资源 内核调 ...

  4. 《Linux内核设计与实现》第四周读书笔记——第五章

    <Linux内核设计与实现>第四周读书笔记--第五章 20135301张忻 估算学习时间:共1.5小时 读书:1.0 代码:0 作业:0 博客:0.5 实际学习时间:共2.0小时 读书:1 ...

  5. linux及安全《Linux内核设计与实现》第一章——20135227黄晓妍

    <linux内核设计与实现>第一章 第一章Linux内核简介: 1.3操作系统和内核简介 操作系统:系统包含了操作系统和所有运行在它之上的应用程序.操作系统是指整个在系统中负责完成最基本功 ...

  6. 《Linux内核设计与实现》Chapter 3 读书笔记

    <Linux内核设计与实现>Chapter 3 读书笔记 进程管理是所有操作系统的心脏所在. 一.进程 1.进程就是处于执行期的程序以及它所包含的资源的总称. 2.线程是在进程中活动的对象 ...

  7. 《Linux内核设计与实现》Chapter 1 读书笔记

    <Linux内核设计与实现>Chapter 1 读书笔记 一.Unix的特点 Unix从Multics中产生,是一个强大.健壮和稳定的操作系统. 特点 1.很简洁 2.在Unix系统中,所 ...

  8. 《Linux内核设计与实现》Chapter 2 读书笔记

    <Linux内核设计与实现>Chapter 2 读书笔记 一.获取内核源码 1.使用Git 我们曾经在以前的学习中使用过Git方法 $ git clone git://git.kernel ...

  9. 《Linux内核设计与实现》Chapter 5 读书笔记

    <Linux内核设计与实现>Chapter 5 读书笔记 在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口的作用是: 使应用程序受限地访问硬件设备 提供创建新进程与已 ...

  10. 《Linux内核设计与实现》Chapter 18 读书笔记

    <Linux内核设计与实现>Chapter 18 读书笔记 一.准备开始 一个bug 一个藏匿bug的内核版本 知道这个bug最早出现在哪个内核版本中. 相关内核代码的知识和运气 想要成功 ...

随机推荐

  1. tidb集群某个节点报错之:node_exporter-9100.service failed

    今天启动集群tidb时出现一个错误,是某个tikv节点报错:node_exporter-9100.service  failed 一个节点的问题会导致整个集群启动失败.去此节点下的日志文件中查找,发现 ...

  2. PHP PC端支付宝扫码支付

    前面的文章已经描述过在蚂蚁金服开放平台创建应用签约等流程,详见:PHP App端支付宝支付,这里就不多说了,剩下的分两步,第一步是支付前的准备工作,也就是整合支付类文件,我已经整合好可以直接用,代码开 ...

  3. February 7th, 2018 Week 6th Wednesday

    We are all resigned to death: it is life we aren't resigned to. 我们可以屈从于死神,但我们却不能让生活任意摆布. Of all the ...

  4. January 29th, 2018 Week 05th Monday

    Losing all hope was freedom. 彻底绝望就是真正的自由. Losing all the hopes, and we are free to challenge everyth ...

  5. JFreeChart柱状图单组柱子的不同颜色显示

    JFreeChart柱状图中单组柱子用不同颜色来显示的实现方法是自定义一个Renderer来继承BarRenderer类,然后重载getItemPaint(int i,int j)方法. 实现效果如下 ...

  6. Django view 视图

    request.method 判断请求方式 8种 GET : 获取一个页面 POST: 提交数据 PUT : 上传 HEAD: 不用上传就获取数据 DELETE: 删除 Request-URL 标识的 ...

  7. 20175310 《Java程序设计》第5周学习总结

    20175310 <Java程序设计>第5周学习总结 本周博客: <20175310 迭代和JDB - 20175310xcy - 博客园> https://www.cnblo ...

  8. Python2.7-内置函数

    具体参见:https://docs.python.org/2/library/functions.html#file 1.进制转换:bin(x), oct(x), hex(x) 把一个十进制数分别转换 ...

  9. node.js 基础二 开启服务器监听

    1.server.js 2.监听 一 server.js 二 监听 运行server.js后,浏览器打开:http://localhost:8888/ //====================== ...

  10. ubuntu root 密码是随机的! root权限下设置共享文件夹

    一.Ubuntu的默认root密码是随机的,即每次开机都有一个新的root密码.我们可以在终端输入命令 sudo passwd,然后输入当前用户的密码,enter, 二.终端会提示我们输入新的密码并确 ...