1.进程的概念
进程就是处于执行期的程序(目标码存放在某种存储介质上〉。但进程并不仅仅局限于一段
可执行程序代码( Unix 称其为代码段, text section)。通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态, 一个或多个具有内存映射的内存地址空间及一个或多个执行线程( thread of execution ),当然还包括用来存放全局变量的数据段等。实际上,进程就是正在执行的程序代码的实时结果。内核需要有效而又透明地管理所有细节。
2 进程描述符及任务结构
atruct t hread_info {
struct task_struct • task;
struct exec_domain • exec_d。main;
_ u32 flags;
_ u32 status;
_u32 cpu;
int preempt_ count;
mm_segment_t addr_limit;
struct restart bl。ck restart bl。ck;
v。id •sysenter_return;
int uaccess_err;}
3.进程描述符的存放
内核通过一个唯一的进程标识值(process identification value)或PID 来标识每个进程。PID 是一个数,表示为pid_t 隐含类型θ,实际上就是一个四类型。为了与老版本的Unix 和Linux 兼容,PID 的最大值默认设置为32768 (sh臼t int 短整型的最大值〉,尽管这个值也可以增加到高达400 万〈<linux/reads.h> 中所定义PID 最大值的限制〉。内核把每个进程的PID 存放在它们各自的进程描述符中。
4进程创建
4.1 写时拷贝
只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资
源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。在页根本不会被写人的情况下(举例来说,fork()后立即调用exec(})它们就无须复制了。fork()的实际开销就是复制父进程的页表以反给予进程创建唯一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据〈地址空间里常常包含数十她的数据〉。由于Unix 强调进程快速执行的能力,所以这个优化是很重要的。
4. 2 fork()
Linux 通过clone()系统调用实现fork() 。这个调用通过一系列的参数标志来指明父、子进程需要共享的资源〈关于这些标志更多的信息请参考本章后面3.4 节〉。fork()、vfork()和一clone()库函数都根据各自需要的参数杨L志去调用clone(),然后由clone()去调用do_fork().do_fork 完成了创建中的大部分工作,它的定义在kemeVfork.c 文件中。该函数调用copy_process()函数,然后让进程开始运行。copy_process()函数完成的工作很有意思:
l )调用dup_task_ struct()为新进程创建一个内核枝、也read_info 结构和task_struct,这些值与当前进程的值相同。此时, 子进程和父进程的描述符是完全相同的。
2 )检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出绘色分配的资源
的限制.
3 )子进程着孚使自己与父进程区别开来。进程描述符内的许多成员都要被清0 或设为初始值.那些不是继承而来的进程描述符成员,主要是统计信息。task_struct 中的大多数数据都依然未被修改.
4 ) 子进程的状态被设置为TASK_UNJNTERRUPTIBLE,以保证它不会投入运行。
5 ) copy _process()调用copy_flags()以更新task_struct 的组ags 成员.表明进程是否拥有超级用户权限的PF_SUPE盯RIV 标志被清0。表明进程还没有调用exec()函数的PF_FOR.KNOEXEC标志被设置。
6 )调用alloc _pid()为新进程分配一个有效的PID。
7 )根据传递给clone()的参数标志, copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享:否则,这些资源对每个进程是不同的,因此被拷贝到这里。的最后, copy_process()傲扫尾工作并返回一个指向子进程的指针。再回到do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行。.因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。
小结:在本章中,我们考察了操作系统中的核心概念一一进程。我们也讨论了进程的一般特性,它为何如此重要,以及进程与线程之间的关系。然后,讨论了Linux 如何存放和表示进程(用task_ struct 和thread_info ),如何创建进程(通过fork(),实际上最终是clone()),如何把新的执行映像装入到地址空间(通过execO 系统调用族〉,如何表示进程的层次关系,父进程又是如何收集其后代的信息(通过wait()系统调用族),以及进程最终如何消亡〈强制或自愿地调用exit()) 。进程是一个非常基础、非常关键的抽象概念,位于每一种现代操作系统的核心位置,也是我们拥有操作系统(用来运行程序〉的最终原因。