因为笔者之前的文章里面有错误,今天发现,立马做个修改。在下面我的一段关于sigchld信号相对于直接调用wait函数的好处时,我说调用wait函数要一直检测子进程是否执行完其实是错误的, wait是阻塞函数,当主进程调用wait函数的时,主进程处于阻塞状态,并没有一直检测的动作,他只是在等待,假设我们有三个子进程(编号1,2,3)假设2,3进程先执行完,然而1号子进程还没有执行完,那么主进程的wait就一直处于阻塞状态,这时候2,3可能产生僵尸进程。这里sigchld和wait的区别就很明显了。
先来看看信号的基本概念:
信号kill-l查看linux信号及其宏定义编号,其中1~31非实时编号(发送的信号可能丢失,不支持信号排队),31~64实时信号,发送的信号都会被接收(支持信号排队)
信号的定义在/usr/include/bits/signum.h
1.信号是软件中断
2.信号是异步信号
3.信号的来源:
(1)、硬件来源:主要是硬件的驱动产生,如键盘,鼠标等
(2)、软件来源:主要是一些信号函数、比如kill()、raise()、alarm()、setitimer()等函数,软件来源包括一些非法运算等操作,软件设置条件(gdb调试),信号由内核产生
#信号的处理
进程会采取三种方式响应和处理信号
1.忽略信号,sigkill和sigstop永远不被忽略,忽略硬件异常、进程启动时,sigusr1和sigusr2被忽略
2.执行默认操作
3.捕获信号。告诉内核信号出现时调用自己的处理函数,SIGKILL和SIGSTOP不能被捕获
#信号登记
void(*signal(int signo,void (*func)(int))(int)
signo--要登记的信号编号或者信号宏
func--信号处理函数指针、忽略信号(SIG_IGN)、默认信号(SIG_DEF)
今天我就不针对每个信号详细介绍了,你也没必要知道那么多信号,今天介绍一个很重要的信号,SIGCHLD这个信号的作用如下:
SIGCHLD 子进程状态发生变化产生该信号(子进程运行结束)父进程调用wait函数,回收子进程的进程表项,task_struct结构体。有了这个信号父进程不需要处于阻塞状态,任然可以干其他事情,当子进程结束时发送一个SIGCHLD信号给父进程,父进程调用wait回收子进程,避免僵尸进程的产生,提高了资源利用率。
再了解这个信号之前,先来简单了解一下wait()函数:
pid_t wait(int *status)//状态
<unistd.h>
<sys/wait.h>
等待子进程退出并回收,防止僵尸进程(子进程运行结束,但是内核中的task_struct没有释放)产生,凡是调用wait()就会阻塞,父进程等待,定时检查子进程是否执行完毕
返回子进程id
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子进程id,这是wait的非阻塞版本。
wait和waitpid区别:
1.在一个子进程终止前,wait使调用者阻塞
2.waitpid有一个选择项,可以使调用者不阻塞
3。waitpid可以等待指定的一个子进程,wait等待所有的子进程,返回任意一个种植的子进程状态。
子进程在运行中有暂停信号如果想要显示暂停信号的信号码不能使用wait()要用waitpid()
waitpid的宏WNOHANG(非阻塞)WUNTRACED(监听信号)
我们处理僵尸进程有两种方式:
1.kill -9 父进程 让init进程回收僵尸进程
2.wait() 和 waitpid()让父进程等待回收子进程
下面我们来使用信号实现解决和避免僵尸进程的第三种的方式:
[cpp] view plain copy
- #include<stdio.h>
- #include<stdlib.h>
- #include<sys/wait.h>
- #include<signal.h>
- void sig_handler(int signo)
- {
- printf("child process %d stop\n", signo);
- }
- void out(int n)
- {
- for(int i = 0; i < n; ++i)
- {
- printf("%d out %d\n", getpid(), i);
- sleep(1);
- }
- }
- int main(void)
- {
- if(signal(SIGCHLD, sig_handler) == SIG_ERR)
- {
- perror("sigchld error");
- exit(1);
- }
- pid_t pid = fork();
- if(pid < 0)
- {
- perror("fork error");
- exit(1);
- }
- else if(pid > 0)
- {
- out(100);
- }
- else
- {
- out(20);
- }
- return 0;
- }
这段代码使用signal系统调用来捕获信号,我们在signal里面注册了SIGCHLD信号,程序中我们让子进程现执行完,然后捕获子进程执行完毕的信号。
下面是运行结果部分截图:
这里进程pid3546为父进程3547为子进程
我们再次运行程序来观察程序的运行状态,把程序编译gcc signal_sigchld.c -o child
运行程序./child
使用命令ps -aux|grep child 来观察程序运行状态,下面是结果截图
你可以看到父子进程在子进程运行到20以前都处于S+即运行状态,当子进程到达20的时候,signal捕获到子进程退出的信号SIGCHLD
这时候子进程状态变为Z+即僵尸进程。
所以我们说了那么多,为什么SIGCHLD没有处理这个僵尸进程呢,这里我们要搞清楚,SIGCHLD只是子进程在运行结束的时候产生的一i个信号,我们要想处理这个僵尸状态,还是要用到上面说的两种方式。最好就是父进程调用wait(),你可能有要问,既然都要用到wait,那抹干吗多此一举使用信号呢?首先要知道父进程调用wait以后处于阻塞状态,父进程不能干其他事情,使用效率降低,资源利用率低下,增加了开销,而调用信号以后,当子进程执行完毕以后,自动产再生一个信号给父进程,父进程收到信号以后就调用wait挥手子进程没有释放的资源。这样我的感觉就是子进程化被动为主动。父进程的工作也轻松了不少,可以做自己想做的事情。
所以为了避免僵尸进程的产生我们修改上面的代码中的sig_handler函数如下:
[cpp] view plain copy
- void sig_handler(int signo)
- {
- printf("child process %d stop\n", signo);
- wait(0);
- }
当父进程捕获到SIGCHLD后调用wait。
按照上述步骤重新编译,运行,用ps观察进程运行状态:
上面是子进程运行到20之前,下面看子进程运行完毕,父进程捕获到SIGCHLD以后
这里你发现子进程没有显示,是因为紫禁城已经被回收释放掉了。
这就是处理僵尸进程的第三种方式。
也是一种异步处理方式。