处理信号
信号是在程序运行过程中系统突然发生一些特殊事件时,用来通知正在运行的程序的手段。例如当shell中有一个程序正在运行时,用户突然按下了ctrl + C
,系统就会想正在运行的程序发送SIGINT
信号,该信号表示一个中断。系统用这种方式告诉正在运行的程序,当前正发生的一些事件,便于程序对这些突发时间进行处理。
信号的定义在头文件signal.h
中,其中常用的有:
信号 | 含义 |
---|---|
SIGABORT | 进程异常终止 |
SIGALRM | 超时警告 |
SIGFPE | 浮点运算异常 |
SIGHUP | 连接挂断 |
SIGILL | 非法指令 |
SIGINT | 终端中断 |
SIGKILL | 终止进程(此信号不能被捕获或忽略) |
SIGSTOP | 停止执行(此信号不能被捕获或忽略) |
SIGPIPE | 向无读进程的管道写数据 |
SIGQUIT | 终端退出 |
SIGSEGV | 无效内存段访问 |
SIGTERM | 终止 |
SIGUSR1 | 用户定义信号1 |
SIGUSR2 | 用户定义信号2 |
如果程序接收到上述信号的任何一个,并且实现没有捕获它,就会终止运行。进程捕获一个信号的意思是,在程序中定义一个信号处理函数用来处理这个信号。如何捕获一个信号会在后面介绍。
下面的信号是对程序的一个提醒,就算程序不捕获也不会导致程序退出。
信号 | 含义 |
---|---|
SIGCHLD | 子进程已经停止或退出 |
SIGCONT | 继续执行暂停进程 |
SIGTSTP | 终端挂起 |
SIGTTIN | 后台进程尝试读操作 |
SIGTTOU | 后台进程尝试写操作 |
发送信号
在shell中可以使用kill
命令来向一个进程发送信号,之前还以为这个命令只能杀死进程。具体可以查看kill
的手册。在C语言中,可以使用同名的系统调用来实现相同的操作。但是在向一个进程发送信号的时候,要注意当前程序是否有向目标进程发送信号的权限。通常来说一个用户只能向自己的程序发送信号,也就是说目标进程的用户ID必须跟发送程序的一样,并且超级用户可以向所有的进程发送信号。
这个系统调用的定义如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill()
函数将参数sig
给定的信号发送给pid
指定的进程,如果成功返回0
,失败时返回-1
并设置errno
变量,该变量在头文件errno.h
中定义。
上面的系统调用会在调用时立即向指定进程发送信号,下面有一个函数alarm()
,可以在给定时间之后向本进程发送一个SIGALRM
信号,注意这里是向自己发送一个SIGALRM
信号。
就算多次调用alarm()
函数也不会让程序在规定时间后发出多个SIGALRM
信号,而是重置倒计时的时间,如果想让程序发出多个SIGALRM
信号,需要等到原来的信号发出之后再调用alarm()
函数重新倒计时。
该系统调用的定义为:
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
捕捉信号
捕捉信号指的是当程序接受到一个信号时使用指定的信号处理函数来对信号进行处理。注意要在接到信号之前就对信号进行捕捉,也就是定义处理该信号的函数。捕获信号使用sigaction()
函数来实现,它的定义如下:
#include <signal.h>
int sigaction(int sig, const struct sigaction * act, struct sigaction * oact);
- 这个函数在捕获成功时返回
0
,失败时返回-1
,并设置errno
变量。 sig
参数表示要捕获的信号,它的值可以是上面介绍的信号。act
参数是要对该信号进行的操作,sigaction
结构体中有信号处理函数,以及另外的一些用于控制处理行为的变量。- 当
sigaction()
函数将当前信号的处理方式定义为参数act
的时候,将该信号原来的处理方式存放在oact
中。
sigaction()
参数表中sigaction
结构体在头文件signal.h
中定义,下面是它至少应该包含的成员。
#include <signal.h>
struct sigaction{
void (*) (int) sa_handler;
sigset_t sa_mask;
int sa_flags;
}
sa_handler
是用来处理信号的函数,它除了可以是一个函数之外,还可以是SIG_IGN
和SIG_DFL
分别表示忽略信号和恢复默认行为。sigset_t
表示的是一个信号集,是多个信号的集合,sa_mask
成员表示在处理信号的过程中,屏蔽信号集sa_mask
中的信号。sa_flags
用于改变信号的行为,它的取值和含义如下:信号 含义 SA_NOCLDSTOP 子进程停止时不产生SIGCHLD信号(指暂停,返回时仍然会产生信号) SA_RESETHAND 将对此信号的处理方式在信号处理函数的入口处重置为SIG_DFL SA_RESTART 重启可中断的函数而不是给出EINTR错误 SA_NODEFER 捕获到信号时不将它添加到屏蔽字中
信号集
信号集是一组信号的集合,这个集合被存储在一个sigset_t
类型的变量中,这个类型在头文件signal.h
中定义。此外,signal.h
中还定义了一组用来操作这个信号集的函数:
#include <signal.h>
int sigaddset(sigset_t * set, int signo); //添加一个信号到信号集
int sigemptyset(sigset_t * set); //将信号集初始化为空
int sigfillset(sigset_t * set); //将所有信号添加到信号集中
int sigdelset(sigset_t * set, int signo); //从信号集中删除一个信号
int sigismember(sigset_t * set, int signo); //判断信号是否存在于信号集中
int sigprocmask(int how, const sigset_t * set, sigset_t * oset);//将set中的信号屏蔽,并将原来的屏蔽信号集保存到oset中
上面的函数在执行成功时都会返回0
,失败时返回-1
并设置errno
。
sigprocmask()
用set
中的信号对当前程序的屏蔽信号集进行操作,并且把原来的屏蔽信号集写入到oset
中。如果新信号集set
是一个空指针,那么该函数的意义便成为了获取当前程序的屏蔽信号集。
how
参数表示set
信号集的用途,它可以是下面的值:
宏 | 含义 |
---|---|
SIG_BLOCK | 把参数set中的信号添加到信号屏蔽字中 |
SIG_SETMASK | 把信号屏蔽字设置为参数set中的信号 |
SIG_UNBLOCK | 从信号屏蔽字中删除参数set中的信号 |
下面是一个例子:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/errno.h>
#include <sys/types.h>
void ding(int sig){
printf("Ding!\n");
}
int main(void){
pid_t pid;
printf("Alarm starting.\n");
struct sigaction sa;
sa.sa_handler = ding;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, 0);
pid = fork();
switch (pid){
case -1:
printf("Forking process failed.\n");
return 1;
case 0:
sleep(5);
kill(getppid(), SIGALRM);
return 0;
}
pause();
printf("Done.\n");
return 0;
}