首先我们需要注册信号,即当进程发现它的子进程结束时,请求操作系统调用的特定函数。信号注册函数原型如下:
#include
void (*signal(int signo,void (*func)(int)))(int);
signal函数注册了部分特殊情况(第一参数)
eg:
①子进程终止调用mychild函数
signal(SIGCHILD,mychild);
②到达alarm注册时间调用timeout函数
signal(SIGALRM,timeout);
③输入CTRL+C调用keycontrol函数
signal(SIGINT,keycontrol);
以上就是信号注册的过程,在验证前我们介绍下alarm函数。
#include
unsigned int alarm(unsigned int seconds);
下面将验证信号处理的相关实例
首先定义两个信号处理函数timeout、keycontrol并注册SIGALRM和SIGINT信号,之后提供100*3的等待时间,每隔两秒就会触发超时。
#include
#include
#include
void timeout(int sig)
{
if (sig == SIGALRM)
puts("Time out!");
alarm(2);
}
void keycontrol(int sig)
{
if (sig == SIGINT)
puts("CTRL+C pressed");
}
int main(int argc, char *argv[])
{
signal(SIGALRM, timeout);
signal(SIGINT, keycontrol);
alarm(2);
for (int i = 0; i < 3; i++)
{
puts("wait...");
sleep(100);
}
return 0;
}
结果并不如人意,只输出了三次Time out,理论上每两秒触发超时处理,应该输出上百次。这是因为触发某个信号将唤醒由于sleep函数而进入阻塞的进程后,进程一旦唤醒就不会再进入睡眠状态。所以for循环立即结束开始下一次循环,共输出3次超时。
同理连按下三次CTRL+C程序同样会结束
sigaction()函数可以完全代替signal()函数而且在不同UNIX系统中完全相同,更安全稳定。下面将介绍sigaction()函数可替换signal()函数的功能。
#include
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
其中struct sigaction声明如下
struct sigaction
{
void (*sa_handler)(int);//信号处理函数的地址
sigset_t sa_mask; //初始化为0
int sa_flags; //初始化为0
}
eg
注册sigaction()前需要做如下操作
void timeout(int sig);
int main(int argc, char *argv[])
{
struct sigaction act;
act.sa_handler = timeout;
sigemptyset(&act.sa_mask);//sa_mask初始化为0
act.sa_flags = 0;
sigaction(SIGALRM, &act, 0);
.....
下面用一段完整代码说明sigaction()函数的使用方法
#include
#include
#include
void timeout(int sig)
{
if (sig == SIGALRM)
puts("Time out!");
alarm(2);
}
int main(int argc, char *argv[])
{
int i;
struct sigaction act;
act.sa_handler = timeout;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM, &act, 0);
alarm(2);
for (i = 0; i < 3; i++)
{
puts("wait...");
sleep(100);
}
return 0;
}
可以看出输出和signal()函数相同,可以完全替代signal()函数。
下面我们将以sigaction函数为例,说明信号处理技术消灭僵尸进程的流程。
当子进程终止时将产生SIGCHLD信号,我们通过sigaction函数调用waitpid函数来回收子进程,并打印信息在控制台。waitpid函数在上一节有介绍。
#include
#include
#include
#include
#include
void read_childproc(int sig)
{
int status;
pid_t id = waitpid(-1, &status, WNOHANG);//1表示可以回收任意子进程
if (WIFEXITED(status)) //回收成功输出进程ID以及返回值
{
printf("Removed proc id: %d \n", id);
printf("Child send: %d \n", WEXITSTATUS(status));
}
}
int main(int argc, char *argv[])
{
pid_t pid;
struct sigaction act;
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, 0);
pid = fork();
if (pid == 0) //子进程1
{
puts("Hi! I'm child process");
sleep(10);
return 12;
}
else
{
printf("Child proc id: %d \n", pid);
pid = fork();
if (pid == 0) //子进程2
{
puts("Hi! I'm child process");
sleep(10);
exit(24);
}
else //父进程
{
int i;
printf("Child proc id: %d \n", pid);
for (i = 0; i < 5; i++)//父进程每5秒输出wait...
{
sleep(5);
puts("wait...");
}
}
}
return 0;
}
输出如下,注意sigaction唤醒sleep后不会继续阻塞,所以wait输出的时机需要仔细思考。由于sleep(10)子进程运行共10秒,所以父进程先输出两次wait,之后回收两个子进程并唤醒sleep,唤醒一次立即输出一次wait;最后等待5秒输出最后一次wait程序结束。