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