多进程编程

fork、vfork 、exec系列 系统调用

fork()、vfork()、__clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork().
do_fork()调用copy_process(),然后让进程开始运行。
copy_process():

  1. 调用dup_task_struct为新进程创建一个内核栈、thread_info结构、task_struct,这些值与当前进程得值相同。此时子进程和父进程描述符完全相同。
  2. 检查新创建这个子进程后,当前用户所拥有得进程数目没有超出给它分配的资源得限制。
  3. 子进程进程描述符许多成员都要清0或设为初始值。这些主要是统计信息和信号位图、挂起的信号集。task_struct的大多数数据都依然未被修改。
  4. 子进程状态设置为TASK_UNITERRUPTABLE,以保证它不会投入运行。
  5. copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0.表明进程还没调用exec()函数的PF_FORKNOEXEC被设置。
  6. 调用alloc_pid()为新进程分配一个有效的PID
  7. 根据传递给clone()的参数标志,copy_process拷贝或共享打开的文件、信号处理函数等。
  8. 最后,copy_process返回一个指向子进程的指针。

再回到do_frok()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让他其投入运行。内核有意选择子进程首先执行。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。

创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,而且文件描述符的引用计数+1,不仅如此,父进程的用户根目录、当前工作目录等变量的引用计数均会+1。


vfork():

除了不拷贝父进程的页表项外,vfork()与fork()功能相同。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,知道子进程退出或执行exec()。子进程不能向地址空间写入。在过去的时间,并未使用写时拷贝来实现fork()。现在执行fork()时引入写时拷贝并明确子进程限制性,vfork好处仅限于不拷贝父进程的页表了。如果Linux将来fork()有了写时拷贝页表项,vfork就彻底没用了。

子进程退出内存地址空间后,会给父进程发送信号。父进程收到这个信号会醒来。如果一切执行顺利,子进程在新的地址空间里运行而父进程也恢复了在原地址空间的运行。


#include<unistd.h>
extern char** environ;
int execv(const char* path,char* const argv[],char* const envp[]);

path参数指定可执行文件的完整路径,argv接收参数数组会传递给新程序的main函数,envp用于设置新程序的环境变量,如果未设置它,则新程序将使用由全局变量environ执行的环境变量。(不懂这里环境变量是什么。。。.T_T)

一般情况下,exec函数是不返回的,除非出错。它出错时返回-1,并设置errno。如果没出错,则原程序中exec调用之后的代码都不会执行。exec函数不会关闭原程序打开的文件描述符,除非该文件描述符设置了类似SOCK_CLOEXEC的属性。


僵尸进程

在每个进程退出时,内核释放该进程的所有资源,包括打开文件、占用内存等。但仍未留保留一定信息(PID、退出状态、运行时间),子进程调用exit之后还会占用内存(内核栈、thread_info、task_struct)并没有真正的被销毁。

  1. 子进程运行结束之后,父进程读取其退出状态之前,称该进程处于僵尸态。
  2. 父进程结束或异常终止,而子进程继续运行。此时子进程的PPID被os设为1(init进程),此时子进程也处于僵尸态。

总之就是该进程结束后,但还没等到别的进程为它收尸,这就是僵尸态。

pid_t wait(int* stat_loc);
pid_t waitpid(pid_t pid,int* stat_loc,int options);
  • wait将阻塞直到该进程的某个子进程结束运行为止,并返回结束运行的子进程的PID,并将子进程的推出信息存储与stat_loc参数指向的内存。子进程状态信息有 正常结束(返回退出码)、被未捕获的信号终止、意外终止等。
  • waitpid(通过options可非阻塞),只等待指定子进程运行结束才返回,如pid==-1,语义就是等待任一子进程结束返回。options参数最常用的是WNOHANG,这样waitpid调用就是非阻塞的。如果没等到的话waitpid()返回0,调用失败返回-1并设置errno。

在已发生的情况下执行费阻塞调用才能提高程序的效率,对于waitpid()而言,我们最好在某个子进程退出之后再调用它。所以可以在父进程中捕捉到SIGCHLD信号,并在信号处理函数中调用waitpid()。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值