1,函数原型
我们需要一个函数告诉进程,当本进程接收到某个信号时,该调用什么对应的函数。类似与JS里注册事件的操作。signal函数就是为了实现这一功能的。
#include <signal.h>
void(*signal(int signo, void(*func)(int)))(int);
这个表达式太复杂了。说明如下:
- signal函数有两个输入参数,第一个输入参数为int,代表要捕获的信号名;
- 第二个输入参数代表捕获到这个信号后进程要执行的操作,所以它是一个函数指针;
- 这个指针指向的函数有一个整型输入参数,代表它处理的信号名,返回void,这个函数称之为信号处理程序或信号捕捉函数;
- signal函数返回类型也是个函数指针,指向在此之前的信号处理程序;
- 如果出错,返回SIG_ERR;
- 如果在进程中该信号被忽略,返回SIG_IGN;
- 如果在进程中,该信号按系统默认操作处理,则返回SIG_DFL;
由于表达式太复杂,我们可以用typedef简化表达式
typedef void Sigfunc(int) //信号处理程序的类型
Sigfunc* signal(int signo, Sigfunc*);
2,例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static void sig_usr(int); //声明信号处理程序
int main(){
//注册SIGUSR1,这本身就是一个预留给用户的信号
if(signal(SIGUSR1, sig_usr) == SIG_ERR) {
printf("can't catch SIGUSR1 !\n");
exit(1);
}
//注册SIGUSR2,这也是一个预留给用户的信号
if(signal(SIGUSR2, sig_usr) == SIG_ERR) {
printf("can't catch SIGUSR2 !\n");
exit(1);
}
// 死循环,为了防止进程退出
while(1){
pause();
}
}
// 定义信号处理程序
static void sig_usr(int signo) {
printf("received signal %d\n", signo);
}
这段代码定义了一个简单的信号处理程序,用来打印捕捉到的信号的值。主函数里,把两个信号SIGUSR1和SIGUSR2绑定到这个信号处理程序。
我们将这段代码编译为study_linux,并在后台执行:
➜ code g++ -g -Wall -std=c++11 -o study_linux study_Linux.c
➜ code ./study_linux&
[1] 380
进程ID已经打印出来是380.
然后通过kill命令向该进程发送SIGUSR1和SIGUSR2命令,看执行的效果:
➜ code kill -USR1 380
received signal 10
➜ code kill -USR2 380
received signal 12
尅看到信号的值已经被打印出来了。我们发现SIGUSR1是10,而SIGUSR2是12.
最后,由于我们写的是死循环,所以用kill命令直接杀死这个进程
➜ code kill 380
[1] + 380 terminated ./study_linux
不带参数的kill命令发出SIGTERM信号。而我们的程序并未捕捉这个信号(事实上,如果我们愿意,是可以修改代码以捕捉这个信号的,这么做可以在进程退出前做一些清理工作),所以使用系统默认的操作。而系统对该信号的默认操作就是终止。所以收到这个信号后,进程终止。
3,补充
- 当我们用fork产生一个子程序的时候,原先注册的信号还能继续用。因为子程序完全复制了父进程的内存印象,以前的信号处理程序还能用;
- 当一个进程exec一个程序之后(还记得狸猫换太子吗?),原先捕捉的信号都失效了,改为了默认操作。原因是,原先绑定的信号处理函数在新的程序里已经不可用了。
- 所以,当我们用先fork,后exec的套路让两个进程并行运行的时候,第二个进程是不会继承第一个进程的信号处理程序的;
- 简单的说就是:真儿子和假儿子的区别。。。。