第3章进程管理(二)

3.3进程创建

Unix的进程创建很特别。许多其它的操作系统都提供了产生进程的机制,首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。Unix采用与众不同的实现方式,把上述步骤分解到两个单独的函数中去执行:fork()和exec()。首先,fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在PID、PPID和某些资源和统计量。exec()函数负责读取可执行文件并将其载入地址空间开始运行。

1、写时拷贝(Copy-on-write)

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

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

fork()的开销是复制父进程的页表以及给子进程创建唯一的进程描述符。在一般情况下,进程创建后会马上运行一个可执行文件,这种优化可以避免拷贝大量根本就不会使用的数据。由于Unix强调进程快速执行的能力,所以这个优化很重要。

2、fork()

Linux通过clone()系统调用实现fork()。这个调用通过一系列的参数标志来指明父、子进程需要共享的资源。fork()、vfork()、__clone()库函数都根据各自需要的参数标志去调用clone(),然后clone()去调用do_fork()。

do_fork完成创建中的大部分工作,定义在kernel/fork.c文件中。该函数调用copy_process()函数,让进程开始运行。copy_process()函数完成的工作:

1、调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符完全相同。

2、检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。

3、子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清零或设为初始化值。不是继承而来的进程描述符成员,主要是统计信息。task_struct中的大多数数据依然未被修改。

4、子进程状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行。

5、copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置。

6、调用alloc_pid()为新进程分配一个有效的PID。

7、根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里。

8、copy_process()做扫尾工作并返回一个指向子进程的指针。

再回到do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有选择子进程首先执行。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/81070054