20135327个会--读书笔记三

第三章  进程管理

3.1 进程

  • 进程就是处于执行期的程序(目标码存放在某种存储介质上)。
  • 执行线程,简称线程(thread), 是在进程中活动的对象。每个钱程都拥有一个独立的程序计数器、进程技和一组进程寄存器。内核调度的对象是线程,而不是进程。
  • 在现代操作系统中,进程提供两种虚拟机制:虚拟处理器和虚拟内存。
  • 程序本身并不是进程,进程是处于执行期的程序以及相关的资源的总称。
  • 通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用 exec。这组函数 就可以创建新的地址空间,并把新的程序载入其中。
  • 最终,程序通过 exi的系统调用退出执行。

3.2 进程描述符及任务结构

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

3.2.1 分配进程描述符

  • Linux 通过slab分配器分配task_struct结构,这样能达到对象复用和缓存着色的目的。

3.2.2 进程描述符的存放

  • 内核通过一个唯一的进程标识值(process identification value)或PID 来标识每个进程。 PID 是 一个数,表示为 pid_t 隐含类型,实际上就是一个int类型。为了与老版本的Unix和Linux兼容, PID的最大值默认设置为 32768 (short int短整型的最大值),尽管这个值也可以增加到高达400万 (这受 <linux/也reads.h> 中所定义 PID 最大值的限制)。内核把每个进程的 PID 存放在它们各自的进程描述符中。
  • 在内核中,访问任务通常需要获得指向其 task_struct 的指针。

3.2.3 进程状态

  进程描述符中的 state 域描述了进程的当前状态(见图 3-3)。系统中的每个进程都必然处于隐含类型指数据类型的物理表示是未知的或不相关的,五种进程状态中的一种。该域的值也必为下列五种状态标志之- :

  • TASK_RUNNING (运行)一一进程是可执行的:它或者正在执行,或者在运行队列中等待执行。
  • TASK_INTERRUPTIBLE (可中断)一一进程正在睡眼(也就是说它被阻塞),等待某些条 件的达成。 一且这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会 因为接收到信号而提前被唤醒并随时准备投入运行。
  • TASK_UNINTERRUPTffiLE (不可中断〉 一一除了就算是接收到信号也不会被唤醒或准备 投入运行外,这个状态与可打断状态相同。
  • _TASK_TRACED-一被其他进程跟踪的进程
  • _ TASK_STOPPED (停止〉一一进程停止执行:进程没有投入运行也不能投入运行.通常这种状态发生在接收到 SIGSTOP、 SIGTSTP、 SIGTTIN、 SIGTTOU 等信号的时候。此外, 在调试期间接收到任何信号,都会使进程进入这种状态。

3.2.4 设置当前进程状态

内核经常需要调整某个进程的状态。这时最好使用 set_task_state(task, state)函数:
    set_task_state(task, state);    /*将任务 task 的状态设置为 state */
    该函数将指定的进程设置为指定的状态。

3.2.5 进程上下文

  • 可执行程序代码是进程的重要组成部分。这些代码从一个可执行文件载入到进程的地址空间执行。
  • 系统调用和异常处理程序是对内核明确定义的接口。 进程只有通过这些接口才能陷入内核执行一一对内核的所有访问都必须通过这些接口。

3.2.6 进程家族树

  • Unix 系统的进程之间存在一个明显的继承关系,在Linux系统中也是如此。 所有的进程都是PID为1的init进程的后代。
  • 系统中的每个进程必有一个父进程, 相应的,每个进程也可以拥有零个或多个子进程。拥 有同一个父进程的所有进程被称为兄弟。进程间的关系存放在进程描述符中。每个 task_struct 都包含一个指向其父进程tast_struct、叫做 parent 的指针,还包含一个称为 children 的子进程链表。

3.3 进程创建

  Unix 的进程创建很特别。许多其他的操作系统都提供了产生(spawn)进程的机制,首先在 新的地址空间里创建进程,读入可执行文件,最后开始执行。 Unix 采用了与众不同的实现方式, 它把上述步骤分解到两个单独的函数中去执行: forkO 和 exec():

  • 首先, fork()通过拷贝当前进 程创建一个子进程。子进程与父进程的区别仅仅在于 PID (每个进程唯一)、 PPID (父进程的进程号,子进程将其设置为被拷贝进程的 PID)和某些资源和统计量。
  • exec()函数负责读取可执行文件并将其载入地址空间开始运行。把这两个函数组 合起来使用的效果跟其他系统使用的单一函数的效果相似。

3.3.1 写时拷贝

  • 传统的 fork()系统调用直接把所有的资源复制给新创建的进程。 Linux的 fork()使用写时拷贝 (copy-on-write)页实现。 写 时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间, 而是让父进程和子进程共享同-个拷贝。
  • 只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。
  • fork()的实际开销就是复制父进程的页表以反给予进程创建唯一的进程描述符。

3.3.2 fork()

  • Linux 通过 clone()系统调用实现 fork()。
  • do_fork 完成了创建中的大部分工作,它的定义在 kemeVfork.c 文件中。该函数调用 copy_ process()函数,然后让进程开始运行。 copy_process()函数完成的工作很有意思: 
    • 调用 dup_task_ struct()为新进程创建一个内核枝、也read_info 结构和 task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符是完全相同的。
    • 检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出绘色分配的资源的限制。
    • 子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。
    • 子进程的状态被设置为 TASK_UNJNTERRUPTIBLE,以保证它不会投入运行。
    • copy _process()调用 copy_flags()以更新 task_struct 的组ags 成员。
    • 调用 alloc _pid()为新进程分配一个有效的 PID。
    • 根据传递给 clone()的参数标志, copy_process()拷贝或共享打开的文件、文件系统信息、 信号处理函数、进程地址空间和命名空间等。
    • 最后, copy_process()傲扫尾工作并返回一个指向子进程的指针。
  • 再回到 do_fork()函数,如果 copy_process()函数成功返回,新创建的子进程被唤醒并让其 投入运行。

3.3.3 vfork()

  • 除了不拷贝父进程的页表项外, vfork()系统调用和 fork()的功能相同。
  • vfork()系统调用的实现是通过向 clone()系统调用传递一个特殊标志来进行的。 
    • 在调用 copy_process()时, task_struct 的 vfor_done 成员被设置为 NULL。
    • 在执行 do_fork()时,如果给定特别标志,则 vfork_done 会指向一个特定地址。
    • 子进程先开始执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过 vfork_done 指针向它发送信号。
    • 在调用 mm_release()时,该函数用于进程退出内存地址空间, 并且检查 vfork_done 是否为空,如果不为空,则会向父进程发送信号。
    • 回到 do_fork(),父进程醒来并返回。

3.4 线程在Linux中的实现

  线程机制是现代编程技术中常用的一种抽象概念. 该机制提供了在同一程序内共享内存地址空间运行的一组线程。这些线程还可以共享打开的文件和其他资源。

  Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。 Linux把所有的线程都当做进程来实现。

3.4.1 创建线程

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

3.4.2 内核编程

  • 内核经常需要在后台执行一些操作。 这种任务可以通过内核线程(kernel thread)完成-一 独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(实际上指向地址空间的mm指针被设置为 NULL)。
  • Linux 确实会把一些任务交给内核线程去傲,像 flush 和 ksofirqd 这些任务就是明显的例子。
  • 内核钱程启动后就一直运行直到调用 do_exit()退出,或者内核的其他部分调用 kthread_ stop()退出,传递给 kthread_stop()的参数为 kthread_create()函数返回的 task_struct 结构的地址:
    int kthread_stop(struct task_struct *k)

3.5 进程终结

  • 不管进程是怎么终结的,该任务大部分都要靠 do_exit()(定义于 kemel/exit.c)来完成,它要做下面这些烦琐的工作 :

    • 将 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_ZOMBIE。
    • do_exit()调用 schedule()切换到新的进程 。 do_exit()永不返回。
    • 至此,与进程相关联的所有资源都被释放掉了(假设该进程是这些资源的唯一使用者)。 进程不可运行(实际上也没有地址空间让它运行)并处于 EXIT_ZOMBIE 退出状态。

3.5.1 删除进程描述符

  • 在调用了 do_exit()之后,尽管线程已经僵死不能再运行了,但是系统还保留了它的进程描述符。
  • wait()这一族函数都是通过唯一(但是很复杂)的一个系统调用 wait4()来实现的
  • 当最终需要释放进程描述符时, release_task()会被调用,用以完成以下工作:

    • 它调用_exit_signal(),该函数调用_unhash _process(),后者又调用 detach_pid()从pidhash 上删除该进程,同时也要从任务列表中删除该进程。
    • _exit_signal()释放目前僵死进程所使用的所有剩余资源,并进行最终统计和记录。
    • 如果这个进程是线程组最后一个进程,并且领头进程已经死掉,那么 release_task()就要通知僵死的领头进程的父进程。
    • release_task()调用 put_task_ struct()释放进程内核技和也read_info 结构所占的页,并释放 tast_struct 所占的 slab 高速缓存。
    • 至此,进程描述符和所有进程拙享的资源就全部释放掉了。

3.5.2 孤儿进程造成的进退维谷

  如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成 为孤儿的进程就会在退出时永远处于僵死状态,白白地耗费内存.前面的部分已经有所暗示,对 于这个问题,解决方法是给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init它们的父进程。

  一但系统为进程成功地找到和设置了新的父进程,就不会再有出现驻留僵死进程的危险了。 init 进程会例行调用 wait()来检查其子进程,清除所有与其相关的僵死进程。

3.6 小结

  在本章中,我们考察了操作系统中的核心概念一一进程。我们也讨论了进程的一般特性, 它为何如此重要,以及进程与线程之间的关系。然后,讨论了 Linux 如何存放和表示进程(用 task_ struct 和 thread_info),如何创建进程(通过 fork(),实际上最终是 clone()),如何把新的执 行映像装入到地址空间(通过 execO 系统调用族),如何表示进程的层次关系,父进程又是如何收集其后代的信息(通过 wait()系统调用族),以及进程最终如何消亡(强制或自愿地调用 exit())。 进程是一个非常基础、非常关键的抽象概念,位于每一种现代操作系统的核心位置,也是我们拥有操作系统(用来运行程序)的最终原因。

时间: 2024-11-03 22:34:07

20135327个会--读书笔记三的相关文章

《你必须知道的.NET》读书笔记三:体验OO之美

一.依赖也是哲学 (1)本质诠释:"不要调用我们,我们会调用你" (2)依赖和耦合: ①无依赖,无耦合: ②单向依赖,耦合度不高: ③双向依赖,耦合度较高: (3)设计的目标:高内聚,低耦合. ①低耦合:实现最简单的依赖关系,尽可能地减少类与类.模块与模块.层次与层次.系统与系统之间的联系: ②高内聚:一方面代表了职责的统一管理,一方面又代表了关系的有效隔离: (4)控制反转(IoC):代码的控制器交由系统控制而不是在代码内部,消除组件或模块间的直接依赖: (5)依赖注入(DI): ①

《世界是数字的》读书笔记 三

<世界是数字的>读书笔记 三 第六章 软件系统 操作系统是软件中的基础层,他负责管理计算机硬件,并为其他被称作应用程序的程序运行提供支持. 6.1操作系统 操作系统控制和分配计算机资源.首先,他负责管理CPU,调度和协调当前运行的程序.操作系统通常都需要管理数十个同时运行的进程或任务. 其次,操作系统管理RAM.他把程序加载到内存中以便执行指令. 最后,操作系统管理和协调外接设备的活动. 6.2操作系统怎么工作 计算机启动时首先要加载代码,加载的过程中还要检查硬件,比如哪些设备已经接入电脑,,

悟道—位IT高管20年的职场心经(读书笔记三)

悟道--一位IT高管20年的职场心经 第三章 世事洞明皆学问 职场就是你的大半个世界 是你一辈子也读不完的一大本书 想明白一个道理, 看明白一件事儿, 你就向成功迈进了一步. 1.1  "四行"说 四行是指: 第一,  你自己得行.自己的基础的能力是必须的,得靠自己学习. 第二,  得有人说你行.需要有伯乐,实际上是你得有一个自己的圈子,并且这些人都人认同你. 第三,  说你行的人得行.自己周围的圈子,里面也必须有牛人,只有在牛人的范围内,才能突显你自己的才能. 第四,  你身子骨得行

《R实战》读书笔记三

第二章  创建数据集 本章概要 1探索R数据结构 2使用数据编辑器 3数据导入 4数据集标注 本章所介绍内容概括如下. 两个方面的内容. 方面一:R数据结构 方面二:进入数据或者导入数据到数据结构 理解数据集 一个数据集通常由一个表格组合而成,行表示观测,列表示变量.病人的数据集如表1所示. 表1 病人数据集 数据集能够反映数据结构.数据类型和内容. 数据结构 R数据结构如图2所示. 图2:R数据结构 数据结构即数据的组织方式,R数据结构包括向量.矩阵.数组.数据框和列表等. R向量 R向量是一

《大型网站技术架构》读书笔记三:大型网站核心架构要素

一.性能—响应时间决定用户 (1)浏览器端: ①浏览器缓存: ②使用页面压缩: PS:Gzip压缩效率非常高,通常可以达到70%的压缩率,也就是说,如果你的网页有30K,压缩之后就变成了9K左右.想要启用Gzip压缩,提高浏览速度,可以浏览这篇文章:http://www.chinaz.com/web/2012/1017/278682.shtml ③合理布局页面: CSS:把样式表置于顶部:避免使用CSS表达式(expression_r):使用外部JavaScript和CSS:削减JavaScri

20135327郭皓--读书笔记一

读书笔记一 一.Linux内核简介 1.1 Unix 的历史 Unix 虽然已经使用了 40 年,但计算机科学家仍然认为它是现存操作系统中最强大和最优秀 的系统. 1.2 追寻 Linus 足迹: Linux 简介 Linux 是一个非商业化的产品,这是它最让人感兴趣的特征. 实际上 Linux是一个互联网上 的协作开发项目. Linux 用途广泛,包含的东西也名目繁多. Linux 系统的基础是内核. C库.工具集和系统 的基本工具 一般情况下, Linux这个词汇主要还是指内核. 1.3 操

Struts2技术内幕 读书笔记三 表示层的困惑

表示层能有什么疑惑?很简单,我们暂时忘记所有的框架,就写一个注册的servlet来看看. index.jsp <form id="form1" name="form1" method="post" action="loginServlet"> <table width="357" border="0" align="center"> <t

《淘宝技术这十年》读书笔记 (三). 创造技术TFS和Tair

前面两篇文章介绍了淘宝的发展历程和Java时代的变迁: <淘宝技术这十年>读书笔记 (一).淘宝网技术简介及来源 <淘宝技术这十年>读书笔记 (二).Java时代的脱胎换骨和坚若磐石 马云说过"创新不是为了与对手竞争,而是跟明天竞争",所以这篇文章讲述淘宝的创新技术TFS和Tair及创新的产品. 该篇文章不仅仅对在读大学生非常有所帮助,因为你能从文章中看到很多你需要学习的知识,不仅仅包括数据库.计算机网络.操作系统.数据结构等基础课程:还根据时代的技术变迁讲述了

《算法导论》读书笔记(三)

本章介绍了快速排序及其算法分析,快速排序采用的是分治算法思想,对包含n个数的输入数组,最坏情况下运行时间为θ(n^2),但是平均性能相当好,期望的运行时间为θ(nlgn).另外快速排序能够就地排序(我理解是不需要引入额外的辅助空间,每次划分能确定一个元素的具体位置),在虚拟环境中能很好的工作. 1.快速排序的描述 快速排序算法采用的分治算法,因此对一个子数组A[p-r]进行快速排序的三个步骤为: (1)分解:数组A[p...r]被划分为两个(可能为空)子数组A[p...q-1]和A[q+1...