#include <signal.h>
- void (*signal(int signo, void (*func)(int)))(int)
- 成功返回前一个信号布署,错误返回SIG_ERR。
signal 函数的原型指定函数需要两个参数并返回一个无返回的函数的指针。signal函数的第一个参数,signo是一个整型。第二个参数是一个接受一个整型参数而无返回的函数的指针。signal返回的指针指向接受单个整型参数(信号号)而无返回的函数。当我们调用signal来建立信号处理器时,第二个参数是一个函数指针。signal的返回值是前一个信号处理器的指针。
如果我们检查系统的头文件<signal.h>,我们很可能发现下面形式的声明:
/* Fake signal functions. */
#define SIG_ERR ((__sighandler_t) -1) /* Error return. */
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
这些常量被用来代替“指向带一个整型参数而无返回的函数的指针”,signal的第二个参数和signal的返回值。这三个值不必是-1、0、1。它们必须是三个不可能是任何声明的函数的地址的值。多数UNIX系统使用了展示的值。
下面的代码展示了一个简单的信号处理器,它捕获两个用户定义的信号的任一个并打印信号号:
- #include <signal.h>
- static void sig_usr(int); /* one handler for both signals */
- int main(void)
- {
- if (signal(SIGUSR1, sig_usr) == SIG_ERR) {
- printf("can't catch SIGUSR1\n");
- exit(1);
- }
- if (signal(SIGUSR2, sig_usr) == SIG_ERR) {
- printf("can't catch SIGUSR2\n");
- exit(1);
- }
- for (;;)
- pause();
- }
- static void
- sig_usr(int signo) /* argument is signal numer */
- {
- if (signo == SIGUSR1)
- printf("received SIGUSR1\n");
- else if (signo == SIGUSR2)
- printf("received SIGUSR2\n");
- else
- printf("received signal %d\n", signo);
- }
在10.10节,我们描述pause函数,它简单的暂停调用进程,直到一个信号被收到。
我们在后台运行这个程序,然后使用kill命令向它发送信号。注意在UNIX系统的术语“kill”是一个不恰当的名字。kill命令和kill函数只是向一个进程或进程组发送一个信号。信号是否终止进程取决于哪个信号被发送和进程是否捕获这个信号。运行结果为:
$ ./a.out &
[1] 3594
$ kill -USR1 3594
received SIGUSR1
$ kill -USR2 3594
received SIGUSR2
$ kill 3594
[1]+ 已终止 ./a.out
当我们发送SIGTERM信号时,进程被终止,因为它不捕获这个信号,而这个信号的默认动作是终止。
程序启动
当一个程序执行时,所有信号的状态都是默认或忽略。通常,所有信号被设置被它们的默认动作,除非调用exec的进程正忽略这个信号。确切地说,exec函数改变任何被被捕获的的信号的布署为它们的默认动作,而不改变其它信号的状态。(自然地,一个被一个调用exec的进程捕获的信号不能被新程序的相同函数捕获,因为调用者的信号处理函数很可能在新的被执行的程序文件里没有意义。)
一个确切的例子是一个交互外壳如何为后台进程处理中断和退出信号。对于一个不支持工作控制的外壳,当我们在后台执行一个进程时,比如cc main.c &,外壳自动把后台进程的中断和退出信号的布署设置为忽略。这样如果我们输入中断字符,它不会影响后台进程。如果不这么做而我们输入中断字符,那么它不会只终止前台进程,还有所有的后台进程。
许多捕获这两个信号的交互程序有如下的代码:
void sig_int(int), sig_quit(int);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, sig_int);
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
signal(SIGQUIT, sig_quit);
这样做,进程只捕获当前没有被忽略的信号。
这两个signal的调用也显示了signal函数的局限:我们不能不改变布署来决定当前的布署。我们在本章后面看到sigaction函数如何允许我们不改变它也能决定一个信号的布署。
一些常用的Signal 如下:
Signal | Description |
SIGABRT | 由调用abort函数产生,进程非正常退出 |
SIGALRM | 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
SIGBUS | 某种特定的硬件异常,通常由内存访问引起 |
SIGCANCEL | 由Solaris Thread Library内部使用,通常不会使用 |
SIGCHLD | 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略 |
SIGCONT | 当被stop的进程恢复运行的时候,自动发送 |
SIGEMT | 和实现相关的硬件异常 |
SIGFPE | 数学相关的异常,如被0除,浮点溢出,等等 |
SIGFREEZE | Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP | 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送 |
SIGILL | 非法指令异常 |
SIGINFO | BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程 |
SIGINT | 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程 |
SIGIO | 异步IO事件 |
SIGIOT | 实现相关的硬件异常,一般对应SIGABRT |
SIGKILL | 无法处理和忽略。中止某个进程 |
SIGLWP | 由Solaris Thread Libray内部使用 |
SIGPIPE | 在reader中止之后写Pipe的时候发送 |
SIGPOLL | 当某个事件发送给Pollable Device的时候发送 |
SIGPROF | Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR | 和系统相关。和UPS相关。 |
SIGQUIT | 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程 |
SIGSEGV | 非法内存访问 |
SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
SIGSTOP | 中止进程。无法处理和忽略。 |
SIGSYS | 非法系统调用 |
SIGTERM | 请求中止进程,kill命令缺省发送 |
SIGTHAW | Solaris专用,从Suspend恢复时候发送 |
SIGTRAP | 实现相关的硬件异常。一般是调试异常 |
SIGTSTP | Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN | 当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU | 当Background Group的进程尝试写Terminal的时候发送 |
SIGURG | 当out-of-band data接收的时候可能发送 |
SIGUSR1 | 用户自定义signal 1 |
SIGUSR2 | 用户自定义signal 2 |
SIGVTALRM | setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING | Solaris Thread Library内部实现专用 |
SIGWINCH | 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU | 当CPU时间限制超时的时候 |
SIGXFSZ | 进程超过文件大小限制 |
SIGXRES | Solaris专用,进程超过资源限制的时候发送 |