第三章:进程管理-进程创建

Unix创建进程分为两个步骤,fork()和exec()。
  • 首先fork()通过拷贝当前进程创建一个子进程,子进程与父进程的区别仅仅在于PID(每个进程唯一)、PPID(父进程的进程号,子进程将其设置为被拷贝进程的PID)和某些资源和统计量(例如挂起信号、它没有必要被继承)。
  • exec()函数负责读取可执行文件并将其载入地址空间开始运行。
把以上两个函数合并起来使用的效果跟其他系统使用的单一函数的效果相似。
 
写时拷贝copy-on-write
传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单且低效,也许它拷贝的数据并不共享,最坏的情况是如果一个新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。
Linux的fork()使用的是写时拷贝页(copy-on-write)实现。写时拷贝页是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享拷贝。
只有在写入的时候,数据才会被复制,从而使各个进程用于自己的拷贝。就是说资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。在页根本不会被写入的情况下,它们就无须复制。
fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。一般情况下使用时拷贝技术可以避免拷贝大量根本不会被使用的数据。。由于Unix强调进程快速执行的能力,因此这个优化很重要。
 
fork()
Linux通过clone()系统调用实现fork()。
fork()、vfork()和__clone()库函数都根据自身需要的参数标志去调用clone(),然后由clone()去调用do_fork()。
do_fork()完成创建的大部分工作,该函数调用copy_process()函数,然后让进程开始运行,copy_process()函数完成的工作包括以下部分:
  1. 调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同,此时子进程和父进程的描述符是完成相同的。
  2. 检测并创建子进程,当前用户所用于的进程数没有超过给它分配的资源的限制。
  3. 子进程着手与父进程区别开来。
  4. 子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行。
  5. copy_process()调用copy_flags()以更新task_struct的falgs成员。
  6. 调用alloc_pid()为新进程分配一个有效的pid
  7. 根据传入的clone()的参数标志,copy_process()拷贝或者共享打开的文件、文件系统信息、信号处理函数、进程 地址空间和命名空间等。
  8. 最后copy_process()做扫尾工作并返回一个执行子进程的指针。
在回到do_fork(),如果copy_process()函数返回成功,新建的子进程被唤醒并让其投入运行。
 
vfork()
除了拷贝父进程的页表项外,vfork()系统调用与fork()的功能相同。
子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或者执行exec()。
现在由于fork()引入了写时拷贝页技术并且明确子进程优先执行,vfork()的好处就仅限于不拷贝父进程的页表项了,
vfork()系统调用的实现时通过向clone()系统调用传入一个特殊标志来进行的:
  1. 在调用copy_process()时,task_struct的vfor_done成员被设置为NULL
  2. 在执行do_fork()时,如果给定特殊标志,则vfork_done会指向一个特殊地址
  3. 子进程先执行,父进程不是马上恢复执行,而是一直等待,直到子进程通过v_fork_done指针向它发送信号
  4. 在调用mm_release()时,该函数用于进程退出内存地址空间,并检查vfork_don是否为空。
  5. 回到do_fork(),父进程醒来并返回。

猜你喜欢

转载自www.cnblogs.com/use-D/p/10674592.html