fork()
生成一个进程,实际上就是把当前进程复制一份。 除了一些特殊的方面(比如,进程
号),新的进程(子进程)和老的进程(父进程)几乎一模一样。fork()虽然只调
用了一次,但会在父进程和子进程中分别返回(等于是一共返回了两次),父进程中
返回所创建子进程的pid,子进程中返回 0。在 fork() 结束后,父进程和子进程
的执行顺序不确定,由高度程序决定谁先执行。不过可以在父进程中调用wait()等
待子进程结束。
例子
1 |
|
wait(0)中的参数0表示等待任一(进程组 ID 相同的)子进程结束,也可以将fork()的返回值保存下来,作为wait()的参数。
vfork()
从vfork()的产生原因来理解它会比较容易。因为fork()操作会将当前进程的任何
资源几乎完全复制一份,其中包括了地址空间。一般fork()调用后都会跟着调用execve(),用新的内存镜像取代原来的内存镜像,当地址空间很大时,复制的操作会
很费时,而且又是做无用功,所以就产生了vfork()。
vfork()产生的子进程与父进程共享地址空间,就没有了复制产生的开销。而且vfork()保证父进程在子进程调用execve()或exit()之前不会执行。
例子
1 |
|
注意程序不能使用return语句退出(至少子进程不能使用),原因我猜测是return语句会使得main函数的栈被清空,因为是使用的同一个内存空间,子进程
把栈清空后,父进程的栈就被破坏了,于是就出错了。使用exit()可以避免这个问题。
运行结果如下:
1 | $ clang vfork.c |
可以看到父进程中变量的值确实被修改过了。
clone()
clone()可以更细粒度地控制与子进程共享的资源,因而参数也更复杂。原型如下:
1 | int clone(int (*fn)(void *), void *child_stack, |
根据 man 2 clone 上的描述,这个原型并不是最底层的系统调用,而是封装过的,
真正的系统调用这里不给出,有兴趣可以查看手册。
在clone()创建子进程成功后,应付执行函数(fn(arg))。因为要执行不同的函数,
当然就不能和父进程共用一个栈了,第二个参数child_stack就为子进程提供了栈空
间。参数flags提供了克隆过程中的各个控制选项,具体还是查看手册。
手册中也说了,clone()的用途之一就是创建线程,多个线程并行地运行在同一个地
址空间里。
例子
1 |
|
这个程序模仿了上面vfork()的功能。程序最开始的#define _GNU_SOURCE不能省
略,不然会报错,虽然没有完全测试,但好像必须放在所有头文件的上面。
References
Linux 手册:man 2 clone
linux下 fork(),vfork(),clone()的用法及区别
CLONE_VM undeclared