Linux进程所需具备的4个要素:
1.进程控制块:即task_struct结构,存储进程状态和所占用的资源。基于该结构,进程才能成为内核调度的基本单位,接受调度。
2.系统堆栈空间:进程专属空间,用于存放各种私有数据以及堆栈(含用户态堆栈和内核态堆栈)。
3.进程代码块:只读,所以,可与其他进程共享。
4.独立的用户空间,含堆、BSS、初始化非零数据区等
如果不具有第4个要素,则称其为”线程“。如果完全没有用户空间,称为“内核线程”。
创建子进程的系统调用有3种fork、vfork、clone。这三种函数通过系统调用表映射到sys_fork(), sys_vfork(), sys_conle(),再在这三个函数中去调用do_fork()去做具体的创建进程工作。调用do_fork时只是填写的参数不同而已。区别如下:
fork的实现: do_fork(SIGCHLD,...)
clone的实现: do_fork(CLONE_VM|CLONE_FS|CLONE_FILES| SIGCHLD,...)
vfork的实现: do_fork(CLONE_VFORK|CLONE_VM| SIGCHLD,...)
各FLAG含义如下:
SIGCHLD:所创建的(子)进程终止时,应该发送SIGCHLD信号给父进程
CLONE_VM:子进程与父进程运行于相同的内存空间
CLONE_FS:子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES:子进程与父进程共享相同的文件描述符(filedescriptor)表
CLONE_VFORK:父进程被挂起,直至子进程释放虚拟内存资源
以下总结区别:
- fork
fork创造的子进程复制了父进程资源,包括内存及进程描述符的内容。为避免无意义的内存页面复制,Linux采用的COW(CopyOn Write, 写时复制)来延迟实际页面的复制,只有父进程或子进程对页面进行写时,才分配并复制页面。所以,刚执行fork时的代价仅仅是创建子进程的页表结构和创建一个task_struct结构。
- vfork
由于很多情况下,vfork之后,用户的意图并不是继续执行父进程,而是想调用exec来执行其他程序。所以,这为优化这类情况,Linux引入了vfork。vfork与fork的区别主要体现在两方面:
1)执行vfork时,完全和父进程共享内存,包括堆、BSS、初始化非0数据区等区域。所以,vfork之后对数据的所有修改都会被父进程所见。而fork只是延迟了复制,在修改数据的时间还是需要复制页面。正常使用时,调用vfork之后,应用立即调用exec执行其他程序。
2)vfork时,父进程会被阻塞,直到子进程调用了exec或exit。
- clone
clone()系统调用是fork()的推广形式,它允许新进程指定具体需要与父进程共享哪些元素,如存储空间、文件描述符、信号处理程序等。是否需要阻塞父进程等,所以,比fork更加灵活。
clone与fork/vfork的区别主要有以下几点:
1) fork/vfork是POSIX标准系统调用,而clone是Linux的自定义扩展;
2) fork/vfork都没有参数,而clone带有参数;
3) fork是全部复制,vfork是共享内存,而clone是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags来决定;
4) clone可以指定子进程的函数体,并可以传递参数给子进程的函数体;
5) vfork调用中,子进程先运行,父进程挂起,直到子进程调用了exec或exit之后,父子进程的执行次序才不再有限制;clone中由标志CLONE_VFORK来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行,设置了该标志,则父进程挂起,直到子进程结束为止。
总结:如果想简单,用fork/vfork;如果希望灵活控制或想创建线程、兄弟进程等,则应选择clone。