fork()
Linux通过clone()系统调用实现fork().这个调用通过一系列的参数标志来指明父,子进程需要共享的资源。fork(),vfork()和_clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork();
do_fork()完成创建中的大部分工作,它在定义的kernel/fork.c文件中。该函数调用copy_process()函数,然后让进程开始运行工作流程如下:
- 调用dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct( 进程是处于执行期的程序以及它所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)的总称。),这些值与当前进程的值相同。此时,子进程和父进程的描述符完全相同;
- 检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源限制。
- 子进程着手使自己与父进程区分开来。子进程描述符里面的许多成员都要被清0或者设为初始化值。那些不是继承而来的进程描述符成员,主要统计信息。task_struct中的大多数据都依然未被修改。
- 子进程的状态被设置为TASK_UNINTREEUPTIBLE,以保证它不会投入运行。
- copy_process()调用copy_flags()以更新task_struct中flags成员。表明进程是否拥有超级用户的权限PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数标志被设置。
- 调用alloc_pid()为新进程分配一个有效的PID。
- 根据传递的clone()的参数标志,copy_process()拷贝或共享打开的文件,文件系统信息,信号处理函数,进程地址空间和命名空间等。在一般的情况下,这些资源会被给定的进程的所有线程共享;
- 最后,copy_process()做扫尾工程,并返回一个指向子进程的指针。
再返回do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行。因为一般子进程都会马上调用execv()函数,这样可以避免写时拷贝的额外开销。如果父进程首先执行的话,有可能会开始向地址空间写入。
vfork()
除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。子进程作为父进程一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或者执行exec().
vfork()系统调用的实现是通过clone()系统调用传递一个特殊标志来进行的。
- 在调用copy_process()时,task_struct的vfor_done成员被设置为NULL。
- 在执行do_fork()时,如果给定特定的标志,则vfork_done会指向一个特定的地址
- 子进程先开始运行,父进程不是马上恢复执行,而是一直的等待,直到子进程通过vfork_done指针向它发送信号。
- 在调用mm_release()时,该函数用于进程退出内存地址空间,并且检查vfork_done是否为空,如果不为空,则向父进程发送信号 。
- 回到do_fork(),父进程醒来并返回。
fork和vfork的区别:
1. fork( )的子进程拷贝父进程的数据段和代码段;vfork( )的子进程与父进程共享数据段
2. fork( )的父子进程的执行次序不确定;vfork( )保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
3. vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
4.当需要改变共享数据段中变量的值,则拷贝父进程。