linux读书笔记(3章)
标签(空格分隔): 20135328陈都
第三章 进程管理
3.1 进程
-
进程就是处于执行期的程序(目标码存放在某种存储介质上)。但进程并不仅仅局限于一段可执行程序代码( Unix称其为代码段,text section)。通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程(threa do fexecution),当然还包括用来存放全局变量的数据段等。
-
程序本身并不是进程,进程是处于执行期的程序以及相关的资源的总称。实际上,完全可能存在两个或多个不同的进程执行的是同一个程序。并且两个或两个以上并存的进程还可以共享许多诸如打开的文件、地址空间之类的资源。
-
通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用exec。这组函数就可以创建新的地址空间,并把新的程序载入其中。
-
最终,程序通过exi的系统调用退出执行。这个函数会终结进程并将其占用的资源释放掉。父进程可以通过wait4()9系统调用查询子进程是否终结,这其实使得进程拥有了等待特定进程执行完毕的能力。进程退出执行后被设置为僵死状态,直到它的父进程调用wait()或waitpid()为止。
3.2进程描述符及任务结构
进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有其他更多信息(见下图)
3.2.1 分配避程描述符
- 在x86 上, structtbre叫~info在文件<asm/也read_info.h>中定义如下:
每个任务的thread_info 结构在色的内核栓的尾端分配。结构中task 域中存放的是指向该任务实际task_struct 的指针。
3.3进程创建
Unix 的进程创建很特别。许多其他的操作系统都提供了产生(spawn)进程的机制,首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。Unix 采用了与众不同的实现方式,它把上述步骤分解到两个单独的函数中去执行: forkO 和exec()首先,fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在于PID(每个进程唯一)、PPID(父进程的进程号,子进程将其设置为被拷贝进程的PID)和某些资源和统计量(例如,挂起的信号,它没有必要被继承〉。exec()函数负责读取可执行文件并将其载入地址空间开始运行。把这两个函数组合起来使用的效果跟其他系统使用的单一函数的效果相似。
3.3.1 写时拷贝
Linux 的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间, 而是让父进程和子进程共享同-个拷贝.只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。
3.3.2 fork()
Linux 通过clone()系统调用实现fork()。这个调用通过一系列的参数标志来指明父、子进程需要共享的资源。
do_fork 完成了创建中的大部分工作,它的定义在kemeVfork.c 文件中。该函数调用copy_
process()函数,然后让进程开始运行。copy_process()函数完成的工作很有意思:
- 调用dup_task_ struct()为新进程创建一个内核枝、也read_info结构和task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符是完全相同的。
- 检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出绘色分配的资源的限制。
- 子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清0 或设为初始值.那些不是继承而来的进程描述符成员,主要是统计信息。task_struct 中的大多数数据都依然未被修改。
- 子进程的状态被设置为TASK_UNJNTERRUPTIBLE,以保证它不会投入运行。
- copy _process()调用copy_flags()以更新task_struct 的组ags 成员.表明进程是否拥有超级用户权限的PF_SUPE盯RIV标志被清0。表明进程还没有调用exec()函数的PF_FOR.KNOEXEC标志被设置。
- 调用alloc _pid()为新进程分配一个有效的PID。
- 根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享:否则,这些资源对每个进程是不同的,因此被拷贝到这里。
- 最后, copy_process()傲扫尾工作并返回一个指向子进程的指针。
再回到do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行。.因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。
3.3.3 vfork()
除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。
3.4 线程在linux中的实现
钱程机制是现代编程技术中常用的一种抽象概念. 该机制提供了在同一程序内共享内存地址
空间运行的一组线程。这些线程还可以共享打开的文件和其他资源.线程机制支持并发程序设计技术(concurrentprogramming),在多处理器系统上,它也能保证真正的井行处理( parallelism )。Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所
有的钱程都当做进程来实现。
- 传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共辜的资源种类。下表列举了这些clone()周到的参数标志以及它们的作用,这些是在<linux/scbed.h>中定义的。
-
3.4.2 内核编程
内核经常需要在后台执行一些操作。这种任务可以通过内核线程( kernel thread)完成————独立运行在内核空间的标准进程。内核线程和普通的进程阔的区别在于内核线程没有独立的地址空间(实际上指向地址空间的mm指针被设置为NULL ).它们只在内核空间运行,从来不切换到用户空间去.内核进程和普通进程一样,可以被调度,也可以被抢占.
3.5 进程终结
虽然让人伤感,但进程终归是要终结的(说得好像真的很伤感一样)。当一个进程终结时,内核必须释放它所占有的资掠并把这一不幸告知其父进程。
3.5.2 孤儿进程造成的进退维谷
如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白地艳费内存。
3.6 小结
- 在本章中,我们考察了操作系统中的核心概念一一进程。
- 进程是一个非常基础、非常关键的抽象概念,位于每一种现代操作系统核心位置,也是我们拥有操作系统的最终原因。