信号是软件中断,它提供了一种处理异步事件的手段。
每个信号都以一个 SIG
开头的命令定义,定义位于头文件 <signal.h>
中。
信号都被定义为正整数,不存在编号为 0
的信号,因为发送信号的 kill
函数对 0
有特殊的用途(用于检查进程是否存在)。
有很多条件可以产生信号:
SIGINT
。kill(2)
函数可将信号发送给另一个进程或进程组。kill(1)
命令将信号发送给其他进程。Note
这里使用的 kill
是带有歧义的,它的意思是“向进程发送指定信号”,而不是“杀死某个程序”。
信号是异步事件的一个典型示例, 不能通过检测某个变量来判断是否出现了一个信号, 而是必须告诉内核, “当XX信号发生时,请执行以下动作”。
处理信号有以下三种方法:
SIGKILL
和 SIGSTOP
之外,大部分信号都可以被忽略。使用 signal
函数,可以为指定信号管理一个信号处理器:当进程接到给定信号时,它就会执行所指定的信号处理器。
signal
函数的签名定义如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
其中参数 signum
指定要处理的信号;
handler
参数指定处理信号的函数,
这个函数接受一个整数值,但不返回任何值。
如果处理器设置成功, signal
函数的返回值是之前为这个信号所设置的信号处理器。如果设置失败,则返回 SIG_ERR
。
handler
可以是 SIG_IGN
或者 SIG_DFL
,前者表示忽略信号,后者表示执行信号的默认动作。
以下程序执行一个无限循环,直到接受到指定信号才退出:
// 10-signal.c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void sig_usr(int);
int
main(void) {
if (signal(SIGUSR1, sig_usr) == SIG_ERR) {
printf("set SIGUSR1 handler fail\n");
return 1;
}
for ( ; ; )
pause();
}
void
sig_usr(int signo)
{
printf("receive signal %d \n", SIGUSR1);
printf("bye bye ~\n");
exit(0);
}
我们在后台运行这个程序,并调用 kill
命令向它发送指定的信号:
$ ./10-signal.out &
[1] 4016
$ kill -USR1 4016
receive signal 10
bye bye ~
Tip
signal
函数的语义和实现有关,最好使用 sigaction
函数代替 signal
函数。
如果在进程执行一个阻塞调用的过程中,
系统捕捉到一个信号,
那么这个调用就会被中断,
不再执行,
该系统调用返回出错,
并将 errno
设为 EINTR
。
以下代码展示了一个可能被中断的读操作如何重新启动:
again:
if ((n = read(fd, buf, BUFFSIZE)) < 0) {
if (errno = EINTR)
goto again;
else
// handle error
}
有几个函数可以用于发送和等待信号。
kill
和终端的 kill
命令类似,它可以将一个指定的信号发送给指定的进程或者进程组(实际上, kill
命令就由 kill
函数定义),它的函数签名定义如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
raise
可以向调用进程自身发送一个信号:
#include <signal.h>
int raise(int sig);
alarm
可以在规定的时间之后,向调用进程自身发送一个 SIGALRM
信号:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
因为 SIGALRM
的默认操作是终止进程,因此,要有效地利用 alarm
,通常需要为 SIGALRM
信号设置一个信号处理程序。
pause
函数挂起调用进程,直到捕捉到一个信号为止:
#include <unistd.h>
int pause(void);