赞
踩
目录
3.宏函数获取子进程的退出状态(退出码)和判断是否收到终止信号
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程
- #include <unistd.h>
- pid_t fork(void);
-
- 返回值:自进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
父进程和子进程的虚拟地址空间以及页表都是各自有一份的,但是对应数据虚拟地址映射到的物理地址是同一个。
默认所有的数据都是只读的,如果有子进程想要写某个数据(页表项100),会发生写时拷贝,就会发生缺页中断,先拷贝一份数据到新的物理地址空间,然后修改子进程页表的映射关系,让子进程对应数据的虚拟地址映射到新的物理地址,然后中断结束,让子进程来修改数据。
Q:如何保证进程独立性?(如何保证父子进程间的独立性)——写时拷贝
我们的main函数返回0就是表示代码运行完毕,结果正确。(这个返回值就是进程的退出码)
我们可以通过echo $?查看最近一次进程退出时的退出码
- [zebra@VM-8-12-centos test6]$ echo $?
- 0
第二次第三次echo的时候输出的是前一次echo这个进程的退出码。实际上每个命令执行都是一个进程,都会有一个退出码,这个退出码就是main函数的返回值,每个进程都有一个main函数
不同的错误码对应不同的错误信息,当然我们也可以自己设计一种错误码和错误信息的映射。
正常终止(可以通过 echo $? 查看进程退出码):
1. 从main返回(代表进程退出,从函数返回也一样,叫做函数返回)
2. 调用exit
3. _exit
异常退出:
ctrl + c,信号终止
- #include <unistd.h>
- void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值(status有很多宏值,比如EXIT_SUCCESS)
e.g.:我们就算不加上\n,最后main return或者程序exit的时候也会把缓存区中的内容刷新到显示器上面。
- printf("hello,zebra");
- sleep(5);
- exit(EXIT_SUCCESS);
-
- return 0;
_exit则是强制终止进程,不会进行进程后续的收尾工作,比如刷新缓冲区。
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit
在这里也变相说明了我们所谓的缓冲区是用户层面的缓冲区,不再操作系统层面
系统层面,少了一个进程: free PCB,free,mm_struct,free页表和各种映射关系,代码+数据申请的空间也要释放掉掉
父进程执行fork之后,会创建子进程,子进程一般都是要帮助父进程完成某种任务,此时父进程需要通过wait/waitpid等待子进程退出。
1.通过获取子进程退出的信息,可以得到子进程的执行结果
2.既然父进程要拿到子进程的执行结果,也就需要保证父进程在子进程之后退出,使用wait可以保证时序问题,即让子进程先退出,父进程后退出。
3.子进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放子进程占用的资源。
- #include<sys/types.h>
- #include<sys/wait.h>
- pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
Pid=-1,等待任意一个子进程。与wait等效。(如果这里给的pid和子进程对不上,会等待失败)
Pid>0.等待其进程ID与pid相等的子进程。
为了确定是三种退出情况中的哪一种
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
说明:
所以我们可以通过判断是否收到终止信号(0-6,低7位)来判断进程是否是正常终止,如果是正常终止(低7位全是0),我们再去看退出状态(8-15,8个比特位)
如果我们要获取8-15这8位退出状态(退出码),可以让status右移8位,然后和000...000 1111 1111(也就是(status>>8)&0xFF)&一下(因为status是32位的,右移8位后,我们要把前面的24位置0)
如果我们要获取0-6这7位终止信号,可以让status&0x7F(我们只需要获取低7位就行)
如果正常退出,退出信号都是0;如果异常退出,退出信号就不是0,此时前面的退出状态也就没有意义
首先我们需要判断进程是否收到退出信号,如果收到了,那就是异常退出的;如果没有收到退出信号,那就是正常退出,退出码也就有效了
bash是命令行启动的所有进程的父进程。bash是通过wait方式得到子进程的退出结果,所以我们echo $?能够查到子进程的退出码
waitpid是一个系统调用接口,肯定是用户来调的。用户调用waitpid让父进程等待子进程退出并变成僵尸状态,然后从进程的pcb中获取exit_code和signal,组合起来放到status中,父进程中的status和用户层的status也就都发生了变化,用户就可以看到进程的退出码和终止信号了。(实际上这个status是一个指针,作为一个输出型参数,所以在操作系统内部修改status会直接影响用户层的status)
WNOHANG: 设置等待方式为非阻塞
若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
首先都是等待的一种方式,都是父进程在等子进程退出(子进程退出是一种条件)
阻塞的本质:其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态
返回的本质:进程的PCB从等待队列拿到R队列,从而被CPU调度
0:阻塞等待
WNOHANG: 设置等待方式为非阻塞
全称是which no hang,表示不会被hang住
我们进行非阻塞等待时,需要考虑三种情况,并进行基于非阻塞等待的轮询方案
每次循环调用waitpid,通过返回值判断子进程是否退出
情况一:这次查询子进程还没退出,父进程做自己的事情
情况二:这次查询子进程退出了
情况三:调用waitpid出错,返回错误信息
- while(1){
- pid_t ret = waitpid(id, &status, WNOHANG);
- if(ret == 0){
- //子进程没有退出,但是waitpid等待是成功的,需要父进程重复进行等待
- printf("Do father things!\n");
- }
- else if(ret > 0){
- //子进程退出了,waitpid也成功了,获取到了对应的结果
- printf("fahter wait: %d, success, status exit code: %d, status exit signal: %d\n", ret, (status>>8)&0xFF, status&0x7F);
- break;
- }
- else{ //ret < 0
- //等待失败
- perror("waitpid");
- break;
- }
- sleep(1); //每隔一秒进行一次轮询
- }
阻塞就是调用waitpid,如果子进程没退出,就干等
非阻塞就是调用一次waitpid,然后继续执行后面的代码,需要自己设计一个基于非阻塞等待的轮询方案
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。