调用 signal 可以设定特定信号 sig 的处理函数 handler。进程收到另⼀个调用 kill 发送过来的信号 sig 时,便会开始执行 handler 函数。通过这⼀对函数便可以实现最基本的进程间通信,以下面的程序为例:
#include
#include
#include
#include
#include
void sig_routine(int dunno) {
switch (dunno)
{
case 6:
printf("\tI am child process, I receive signal SIGABRT\n");
break;
}
}
int main() {
pid_t pid;
int status;
int sig;
signal(SIGABRT, sig_routine);
if (!(pid = fork())) {
printf("\tHi I am child process !\n");
sleep(10);
return 0;
}
else {
sleep(1);
kill(pid, SIGABRT); // // Sends a SIGABRT (abort program) signal
wait(&status);
if (WIFSIGNALED(status)) {
printf("child process receive signal %d\n", WTERMSIG(status));
}
else {
printf("child process exits normally with %d\n", WTERMSIG(status));
}
}
return 0;
}
运行结果如下:
majn@tiger:~/C_Project/signal_project$ ./signal_demo
Hi I am child process !
I am child process, I receive signal SIGABRT
child process exits normally with 0
这个程序展示了如何在父子进程之间使用信号。它创建一个子进程,父进程向子进程发送一个 SIGABRT
信号,子进程捕获并处理这个信号,然后继续执行并最终正常结束。
程序的关键部分如下:
信号处理函数 sig_routine(int dunno)
:
SIGABRT
信号(其值为6)时,它调用此函数来处理该信号。主程序:
signal(SIGABRT, sig_routine);
,程序为 SIGABRT
信号设置了一个处理函数,即 sig_routine
。fork()
创建子进程。SIGABRT
信号。wait(&status)
等待子进程结束,并获取子进程的结束状态。输出结果如下:
SIGABRT
信号时,它输出 “I am child process, I receive signal SIGABRT”。所以,这个程序的关键是理解信号的默认行为(例如,SIGABRT
默认会终止进程)可以被覆盖,如果为该信号注册了一个处理函数。在这个例子中,处理函数只是简单地打印了一条消息,而不是终止进程。
如果不为该信号注册一个处理函数,则运行结果如下:
#include
#include
#include
#include
#include
int main() {
pid_t pid;
int status;
int sig;
// signal(SIGABRT, sig_routine);
if (!(pid = fork())) {
printf("\tHi I am child process !\n");
sleep(10);
return 0;
}
else {
sleep(1);
kill(pid, SIGABRT); // // Sends a SIGABRT (abort program) signal
wait(&status);
if (WIFSIGNALED(status)) {
printf("child process receive signal %d\n", WTERMSIG(status));
}
else {
printf("child process exits normally with %d\n", WTERMSIG(status));
}
}
return 0;
}
运行结果为:
majn@tiger:~/C_Project/signal_project$ ./signal_demo
Hi I am child process !
child process receive signal 6
第一段代码中,父进程和子进程都为SIGABRT
注册了处理函数sig_routine
。这是因为,在fork
之前,父进程已经调用了signal(SIGABRT, sig_routine);
为SIGABRT
信号注册了处理函数。当fork
被调用时,子进程继承了父进程的信号处理设置,所以子进程同样也为SIGABRT
注册了sig_routine
作为其处理函数。
对于fork()
之上的代码,子进程不会“重新执行”。但这并不意味着子进程不“知道”或不“具有”那部分代码的效果。实际上,子进程是父进程的一个近乎完整的副本,它继承了父进程在fork()
之前的所有内存布局、变量的值、文件描述符、程序计数器、堆和栈的状态等。
当fork()
被调用时,子进程从那个点开始执行,但它继承了父进程在该点之前的所有状态和上下文。
例如,在第一段代码中:
signal(SIGABRT, sig_routine);
尽管这行代码是在fork()
之前执行的,但由于子进程继承了父进程的上下文和状态,子进程也会有相同的信号处理程序设置为SIGABRT
。
所以,虽然子进程不会重新执行fork()
之前的代码,但它确实继承了那些代码执行的结果和状态。