24 进程的创建 fork()

几个系统调用 / 库函数的介绍

  • fork():创建子进程。
  • exit(status):库函数,退出当前进程,是 _exit 的包装。
  • wait(&status):挂起当前进程并等待一个子进程。
  • execve(pathname, argv, envp):加载一个新的程序。

Tip

SUSv3 还规定了 posix_spawn() 函数,相当于 fork + exec,在有些实现中速度会快一些,有的实现中则不是。网上对此的讨论也很多。

fork 和文件共享

fork 后父子进程是可以共享打开的文件描述符的,而且这些文件描述符都指向内核打开文件表的同一个项目,共享文件属性和偏移量。不过可以在打开文件时设置 O_CLOEXEC 选项,使得子进程将来调用 exec 时关闭文件。

vfork 和写时复制

早前 fork 函数没有 copy-on-write 功能,因而在子进程要调用 exec 时,显得不划算。vfork 让父子进程共享内存,只是复制极小部分用来管理进程的数据,并且将父进程挂起,直到子进程调用 exec 或者 _wait 才恢复。如果子进程在此期间做出了别的行为,则行为未定义。

这里的别的行为指:1. 修改了除了 vfork 返回值以外的任何数据;2. 从调用 vfork 的函数中返回;3. 在调用 exec / _wait 之前调用了任何函数。

由于现在已经有了 COW 功能,vfork 显得不是很重要了,现在已经不推荐使用。

fork 后父子进程的调度顺序

不要依赖于父子进程在 fork 后的调度顺序,应该加上适当的同步操作。

Linux 曾经(作者是在 2.2.19 版本进行测试)是父进程在 fork 之后继续执行,而子进程需要等待调度;在 2.6 版本的时候又出现过 fork 后子进程优先得到调度的情况,理由是子进程很可能会调用 exec 替换掉内存,从而避免了父进程首先修改页面导致页面复制开销;在 2.6.32 版本的时候,又改回了 fork 后父进程先得到调度,理由是父进程正在运行过程中,TLB(translation look-aside buffer,用于缓存虚拟地址转换结果)已经存储了大量父进程的信息,先运行父进程性能会更好。

如果想要修改父子进程调度的顺序,可以修改 /proc/sys/kernel/sched_child_runs_first 的值为 0(默认值)/ 非 0。

进程间同步举例

书中有一个 fork 出子进程,并和父进程之间同步的例子。首先是用 sigprocmask 阻塞住 SIGUSR1 信号,然后 fork 出子进程。子进程完成工作之后给父进程发 SIGUSR1 信号。父进程则是(可能先做其他的工作,然后再)用 sigsuspend 来等待 SIGUSR1 信号。如果子进程早就发送了信号,则 SIGUSR1 信号是 pending 的,父进程不会进入休眠状态而直接从 sigsuspend 调用中返回。

这种发信号来表示同步是否比较 tricky?如果有别的进程也给父进程发这个信号(即乱发信号)怎么办?