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?如果有别的进程也给父进程发这个信号(即乱发信号)怎么办?