系列文章:
操作系统详解(1)——操作系统的作用
操作系统详解(2)——异常处理(Exception)
操作系统详解(3)——进程、并发和并行
操作系统详解(4)——进程控制(fork, waitpid, sleep, execve)
操作系统详解(5)——信号(Signal)
操作系统详解(5.1)——信号(Signal)的相关题目
(如果有错误,欢迎指正)
const int num = 4;
volatile sig_atomic sum = 0, child = 0;
volatile sig_atomic n = num;
void handler_chld(int sig){
int status;
while(waitpid(-1, &status, 0) > 0){
if(WIFEXITED(status)){
sum += WEXITSTATUS(status);
n += 1;
child += 1;
}
}
}
int main()
{
sigset_t mask_all, prev_one;
sigemptyset(&mask_all);
sigaddset(&mask_all, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask_all, &prev_one);
signal(SIGCHLD, handler_chld);
while(1){
if(n == 0) exit(0);
if(n == 1) exit(1);
n = n - 1;
if(fork() == 0) continue;
n = n - 1;
if(fork() == 0) continue;
break;
}
sigprocmask(SET_MASK, &prev_ont, NULL);
if(child < 2) pause();
if(n == sum){
printf("f[%d] = %d\n", n, sum);
}
exit(sum);
}
const int num = 9
呢?首先画出程序的流程图
(画图是解决此类题型的重要方法)
可见一共fork了8次
要把SIGCHLD阻塞,肯定是在fork()期间不能被信号干扰。
观察handler内,发现会修改n, child的值。而fork()会使子进程与父进程拥有相同的变量值。以child为例,如果不屏蔽信号,那么很可能会使进程接收到信号后使child+1=1, 那么当fork以后,子进程的child也为1,就无法保证子进程在fork()以后,能够pause()等待回收子进程。
在此例中,pause()是一定可以返回的。
handler里是一个while循环,条件是waitpid, 这样可以保证只需要收到一个信号就可以回收所有子进程,不会出现多个信号发送被覆盖的情况。
假如所有信号都在UNBLOCK之前收到,那么UNBLOCK的时候就会执行handler, 并且回收所有子进程,这样就会使child >=2,不会执行pause().
(所有的父进程的子进程个数一定>=2,下一问会分析)
如果在UNBLOCK之前收到了一个signal:
如果在pause()以后才收到信号,则会打断pause, 并且在handler中等待回收所有的子进程。
上面的流程图虽然表明了n的取值,但是不能反映出父进程与子进程之间的关系。
先举一个简单的例子:假设进程p1经过fork()以后得到p1和p2,二者分别再fork得到p3和p4。
与流程图相对应的话,以最上方的parent为例,它的子进程包括
语言描述比较抽象,图中用笔圈出了关系:
类似的还有:
弄清楚了fork()多次后得到的父子进程的关系,我们就可以着手分析sum的值了. 由题意可知父进程的返回值为所有子进程的返回值之和,且每有一个子进程执行n+1. 用图表示:
故输出是:f[4] = 3
如果改成num = 9
:由上图标红的n=4, n=3, n=2可以知道,sum分别是3, 2, 1.
有sum = num - 1
故num = 9时, sum = 8
输出f[9] = 8
如果你不相信的话,我们假设num = 3, 再画一下流程图:
可以验证sum = 2;
Q3中的问题:由于fork()以后子进程最后一定会直接exit返回,所以跳出while循环的父进程最少拥有两个子进程