《Linux内核分析与实现》 第五周 读书笔记

第3章 进程管理

20135307张嘉琪linux


3.1 进程

  • 进程就是处于执行期的程序(目标码存放在某种存储介质上),但进程并不只仅局限于一段可执行程序代码。一般进程还要包含其余资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具备内存映射的内存地址空间及一个或多个执行线程。固然还包括用来存放全局变量的数据段等,实际上,进程就是正在执行的程序代码的实时结果,内核须要有效而又透明地管理全部细节。算法

  • 执行线程,简称线程,是在进程中活动的对象,每一个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器,内核调度的对象是线程,而不是进程,在传统的Linux系统中,一个进程只包含一个线程,但如今的系统中,包含多个线程的多线程程序司空见惯。Linux系统的线程实现很是特别:它对线程和进程并不特别区分,编程

  • 对Linux而言,线程只不过是一种特殊的进程罢了缓存

  • 在现代操做系统中,进程提供两种虚拟机制:虚拟处理器和虚拟内存数据结构

  • 在现代Linux内核中,fork()其实是由clone()系统调用实现的。多线程

3.2 进程描述符及任务结构

  • 内核把进程的列表存放在叫作任务队列的双向循环链表中。并发

  • 链表中的每项都是类型为task_struct、称为进程描述符的结构,该结构定义在<linux/sched.h>文件中。函数

3.2.1 分配进程描述符

  • Linux以经过slab分配器分配task_struct结构,这样能达到对到对象复用和缓存着色的目的。这样作是为了让那些像x86那样寄存器较少的硬件体系结结构只要经过栈指针就能够估算出他的位置。

  • 每一个任务的thread_info结构在他的内核栈的尾端分配。

3.2.2 进程描述符的存放

  • 内核经过一个惟一的进程标识值或PID来标识每一个进程,内核把每一个进程的PID存放在它们各自的进程描述符中。优化

  • 在内核中,访问任务一般须要得到指向其taskstruct的指针,实际上,内核中大部分处理进程程描述符的速度就显得尤其重要。硬件体系结构不一样,该宏的实现也不一样,它必须针对专门的硬件体系结构作处理,有的硬件体系结构能够拿出―个专门寄存器来存放指向当前进程taskstruct的指针,用于加快访问速度。操作系统

3.2.3 进程状态

  • 五种进程状态:运行、可中断、不可中断、被其余进程跟踪的进程、中止。

3.2.4 设置当前进程状态

  • 内核须要常常调整某个进程的状态:settaskstate(task,state)函数

3.2.5 进程上下文

  • 可执行程序代码是进程的重要组成部分。这些代码从一个可执行文件载入到进程的地址空间执行。通常程序在用户空间执行。当一个程序调执行了系统调用(参见第5章)或者触发了某个异常,它就陷入了内核空间。此时,咱们称内核“表明进程执行”并处于进程上下文中。在此上下文中current宏是有效的。除非在此间隙有更高优先级的进程须要执行并由调度器作出了相应调整,不然在内核退出的时候,程序恢复在用户空间会继续执行。 系统调用和异常处理程序是对内核明肯定义的接口。进程只有经过这些接口才能陷入内核执行——对内核的全部访问都必须经过这些接口。

3.2.6 进程家族树

Unⅸ系统的进程之间存在—个明显的继承关系,在Linux系统中也是如此。全部的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本并执行其余的相关程序,最终完成系统启动的整个过程。

3.3 进程建立

3.3.1 写时拷贝

  • 传统的fork()系统调用直接把全部的资源复制给新建立的进程,这种实现过于简单而且效率低下,由于它拷贝的数据也许并不共享,更糟的状况是,若是新进程打算当即执行一个新的映像,那么全部的拷贝都将前功尽弃。Linux的fork()使用写时拷贝页实现,写时拷贝是一种能够推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。

  • 只有在须要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝,也就是说资源的复制只有在须要写入的时候才进行,在此以前,只是以只读方式共享,这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行在页根本不会被写入的状况下它们就无须复制了。

  • fork()的实际开销就是复制父进程的页表以及给子进程建立惟一的进程描述符。在通常状况下,进程建立后都会立刻运行一个可执行的文件,这种优化能够避免拷贝大量根本就不会被使用的数据(地址空间里经常包含数十兆的数据)因为Unix强调进程快速执行的能力,因此这个优化是很重要的。

3.3.2 fork()27

  • copy_process()完成的工做:

    1. 调用duptaskstruct()为新进程建立一个内核栈、threadinfo结构和taskstruct,这些值与当前进程的值相同,此时,子进程和父进程的描述符是彻底相同的。
    2. 检查并确保新建立这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制
    3. 子进程着手使本身与父进程区别开来,进程描述符内的许多成员都要被清0或设为初始值,那些不是继承而来的进程描述符成员,主要是统计信息,task_struct中的大多数数据都依然未被修改
    4. 子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行
    5. copyprocess()调用copyflags以更新task_struct的flags成员
    6. 调用alloc_pid()为新进程分配一个有效的PID
    7. 根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等,在通常状况下,这些资源会被给定进程的全部线程共享;不然,这些资源对每一个进程是不一样的,所以被拷贝到这里。

3.3.3 vfork()

3.4 线程在Linux中的实现

线程机制是现代编程技术中经常使用的一种抽象概念,该机制提供了在同―程序内共享内存地址空间运行的―组线程,这些线程还能够共享打开的文件和其余资源,线程机制支持并发程序设计技术,在多处理器系统上,它也能保证真正的并行处理。 Linux实现线程的机制很是独特,从内核的角度来讲,它并无线程这个概念,Linux把全部的线程都当作进程来实现,内核并无准备特别的调度算法或是定义特别的数据结构来表征线程,相反,线程仅仅被视为―个与其余进程共享某些资源的进程,每一个线程都拥有惟一隶属于本身task_struct,因此在内核中,它看起来就像是一个普通的进程(只是线程和其余一些进程共享某些资源,如地址空间)。

3.4.1 建立线程

进程的建立与普通进程的建立相似,只不过在调用clone()时须要传递一些参数标志来指明所须要共享的资源。

3.4.2 内核线程

内核常常须要在后台执行一些操做,这种任务能够经过内核线程完成——独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间。它们只在内核空间运行,历来不切换用户空间去,内核进程和普通进程同样,能够被调度,也能够被抢占。 Linux确实会把一些任务交给内核线程去作,像flush和ksofirqd这些任务就是明显的例子,在装有Linux系统的机子上运行ps -ef命令,你能够看到内核线程,有不少!这些线程在系统启动时由另一些内核线程建立,实际上,内核线程也只能由其余内核线程建立,内核是经过从kthreadd内核进程中衍生出全部新的内核线程来自动处理这一点的,在<linux/kthreadd>中申明有接口。

3.5 进程终结

当一个进程终结时,内核必须释放它所占有的资源并把这一不幸告知其父进程。

3.5.1 删除进程描述符

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

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

3.6 小结

  • 在本章中,咱们考察了操做系统中的核心概念——进程,咱们它为什么如此重要,以及进程与线程之间的关系,然也讨论了进程的通常特性,而后,讨论了Linux如何存放和表示进程,如何建立进程,如何把新的执行映像装入到地址空间,如何表示进程的层次关系,父进程又是如何收集其后代的信息以及进程最终如何消亡。

  • 进程是一个很是基础、很是关键的抽象概念,位于每一种现代操做系统的核心位置,也是咱们拥有操做系统(用来运行程序)的最终缘由。

相关文章
相关标签/搜索