Unix创建进程:首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。Unix将上述步骤分解到两个单独的函数中执行:fork()和exec()。首先fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在于PID、PPID和某些资源和统计量(如挂起的信号)。exec()负责读取可执行文件并将其载入地址空间开始运行。值得一提的是,Linux的fork()采用写时拷贝机制。
fork()
Linux通过clone()系统调用实现fork()。这个调用通过一系列的参数标志来指明父、子进程需要共享的资源。fork()、vfork()和__clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork()。do_fork()完成了创建中的大部分工作,该函数调用copy_process()函数,然后让进程开始运行。copy_process()完成的工作如下:
- 调用
dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符是完全相同的。 - 检查并确保新进程创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。
- 子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。那些不是继承而来的进程描述符成员,主要是统计信息。
task_struct中的大多数数据都依然未被修改。 - 子进程的状态被设置为
TSK_UNINTERRUPTIBLE,以保证它不会被投入运行 copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置- 调用
alloc_pid()为新进程分配一个有效的PID。 - 根据传递给
clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享,否则,这些资源对每个进程是不同的,因此被拷贝到这里 - 最后,
copy_process()做扫尾工作并返回一个指向子进程的指针。
再回到do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始像地址空间写入
#include<unistd.h>pid_t fork(void);pid_t 是进程描述符,实质就是一个int,如果fork函数调用失败,返回一个负数,调用成功则返回两个值:0和子进程ID
vfork()
除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec()。
Linux平台通过clone()系统调用实现fork()。 fork(),vfork()和clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork(), 再然后do_fork()完成了创建中的大部分工作,该函数调用copy_process().做最后的那部分工作。
fork和vfork的区别:
- fork:子进程拷贝父进程的代码段和数据段
- vfork:子进程和父进程共享代码段和数据段
- fork中父子进程的先后运行次序不定
- vfork:保证子进程先运行,子进程exit后父进程才开始被调度运行
- vfork()保证子进程先运行,在它调用exec 或exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
- 就算fork实现了写时拷贝,但其效率仍然没有vfork高,但是vfork在一般平台上都存在问题,所以一般不推荐使用
