目录
[单刷APUE系列]第十章——信号[1]信号概念
从前面的文章和实际使用中,大家应该也对信号有一些模糊的认识了。比如,Nginx
使用信号来管理进程的启动关闭,UNIX进程信号是经典的操作异步事件机制。在早期,Unix信号是每个实现都不同的,但是随着Unix标准化的进行,标准提出了统一的信号机制模型。除了共有的信号以外,各自的实现还提供了扩展信号,当然,这个不是重点。
<signal.h>
中定义了所有的信号,我们可以通过编辑这个头文件来查看当前Unix实现提供的信号。 #define SIGHUP 1 /* hangup */#define SIGINT 2 /* interrupt */#define SIGQUIT 3 /* quit */#define SIGILL 4 /* illegal instruction (not reset when caught) */#define SIGTRAP 5 /* trace trap (not reset when caught) */#define SIGABRT 6 /* abort() */...
就是这样列出来的,从上面可以看到,信号不存在为0的编号,在最初的几章里,提到过产生信号的条件
终端按键产生信号,例如:Ctrl+C、Ctrl+\这种形式
硬件产生信号,比如除以0错误等,都是通过中断通知内核,然后产生信号
进程调用 kill 函数发送信号,在前面权限部分就提到过,想要发送信号,则所有者权限需要检查或者说有效用户组是root权限
用户使用 kill 命令,实际上就是调用了 kill 函数
系统产生的软中断
信号是进程交互和异步事件的方式,由于信号出现是不可知的,所以不能说像检查返回值,检查 errno 一样判断是否有信号,系统提供了一套完善的机制来实现,开发者只需要让进程注册函数来处理信号。
进程收到信号后,可以选择3种方式处理行为:忽略信号
捕捉信号
执行系统默认行为
下面列出了通常的信号
No Name Default Action Description1 SIGHUP terminate process terminal line hangup2 SIGINT terminate process interrupt program3 SIGQUIT create core image quit program4 SIGILL create core image illegal instruction5 SIGTRAP create core image trace trap6 SIGABRT create core image abort program (formerly SIGIOT)7 SIGEMT create core image emulate instruction executed8 SIGFPE create core image floating-point exception9 SIGKILL terminate process kill program10 SIGBUS create core image bus error11 SIGSEGV create core image segmentation violation12 SIGSYS create core image non-existent system call invoked13 SIGPIPE terminate process write on a pipe with no reader14 SIGALRM terminate process real-time timer expired15 SIGTERM terminate process software termination signal16 SIGURG discard signal urgent condition present on socket17 SIGSTOP stop process stop (cannot be caught or ignored)18 SIGTSTP stop process stop signal generated from keyboard19 SIGCONT discard signal continue after stop20 SIGCHLD discard signal child status has changed21 SIGTTIN stop process background read attempted from control terminal22 SIGTTOU stop process background write attempted to control terminal23 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2))24 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2))25 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2))26 SIGVTALRM terminate process virtual time alarm (see setitimer(2))27 SIGPROF terminate process profiling timer alarm (see setitimer(2))28 SIGWINCH discard signal Window size change29 SIGINFO discard signal status request from keyboard30 SIGUSR1 terminate process User defined signal 131 SIGUSR2 terminate process User defined signal 2
函数signal
void (*signal(int sig, void (*func)(int)))(int);or in the equivalent but easier to read typedef'd version:typedef void (*sig_t) (int);sig_t signal(int sig, sig_t func);
Unix使用信号的接口就是signal
函数。在前面的章节中提到过signal函数的定义和具体意义。signal函数实际上是ISO C定义的,但是由于这种跨平台函数具有实现的不同,所以windows还是Unix各个实现,实际上都有其特殊的方式。对于函数本身来说,带有两个参数,一个是signo信号名,func的值是一个常亮
1 SIGHUP terminate process terminal line hangup2 SIGINT terminate process interrupt program3 SIGQUIT create core image quit program4 SIGILL create core image illegal instruction5 SIGTRAP create core image trace trap6 SIGABRT create core image abort program (formerly SIGIOT)7 SIGEMT create core image emulate instruction executed8 SIGFPE create core image floating-point exception9 SIGKILL terminate process kill program10 SIGBUS create core image bus error11 SIGSEGV create core image segmentation violation12 SIGSYS create core image non-existent system call invoked13 SIGPIPE terminate process write on a pipe with no reader14 SIGALRM terminate process real-time timer expired15 SIGTERM terminate process software termination signal16 SIGURG discard signal urgent condition present on socket17 SIGSTOP stop process stop (cannot be caught or ignored)18 SIGTSTP stop process stop signal generated from keyboard19 SIGCONT discard signal continue after stop20 SIGCHLD discard signal child status has changed21 SIGTTIN stop process background read attempted from control terminal22 SIGTTOU stop process background write attempted to control terminal23 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2))24 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2))25 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2))26 SIGVTALRM terminate process virtual time alarm (see setitimer(2))27 SIGPROF terminate process profiling timer alarm (see setitimer(2))28 SIGWINCH discard signal Window size change29 SIGINFO discard signal status request from keyboard30 SIGUSR1 terminate process User defined signal 131 SIGUSR2 terminate process User defined signal 2If a process explicitly specifies SIG_IGN as the action for the signal SIGCHLD, the system will not create zombie processes when children of the callingprocess exit. As a consequence, the system will discard the exit status from the child processes. If the calling process subsequently issues a call to wait(2) or equivalent, it will block until all of the calling process's children terminate, and then return a value of -1 with errno set to ECHILD.
上面这些是苹果系统列出的可使用的信号,如果func参数可以指定3种,分别是常量SIG_IGN、常量SIG_DFL或者一个函数指针。常量SIG_IGN表示内核忽略此信号,常量SIG_DFL表示执行内核默认动作,函数指针则是当信号发生时,调用该函数,一般叫做信号处理函数。
结合前文中对signal函数的理解,实际上signal函数要求两个参数,只有一个返回值,signal函数第一个参数是signo信号名,第二个参数是一个函数指针,一般是信号处理函数,最后返回这个信号处理函数指针。由于函数原型非常复杂,一般都是使用typedef将其简化。#define SIG_DFL (void (*)(int))0#define SIG_IGN (void (*)(int))1#define SIG_HOLD (void (*)(int))5#define SIG_ERR ((void (*)(int))-1)
一般声明就是像上面一样。下面是一个例程
#include "include/apue.h"static void sig_usr(int);int main(int argc, char *argv[]){ if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR2"); for ( ; ; ) pause();}static void sig_usr(int signo){ if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo);}
然后编译运行
> ./a.out &[1] 21588> kill -USR1 21588received SIGUSR1> kill -USR2 21588received SIGUSR2> kill 21588[1] + 21588 terminated ./a.out
我们知道,所有的进程都是内核启动的,当启动一个进程的时候,所有的信号都会被设置为系统默认或者忽略,在Unix环境中最典型的一个例子就是shell启动其他进程,为了保证前台和后台对中断信号和退出信号的捕捉,一般都这样写
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);
因为所有都是被子进程继承的,所以在exec时,父进程可以控制子进程的忽略状态,所以需要这样判断。
在上面可以看出,signal函数除了改变当前信号的处理方式以外,还会返回之前的状态,这就非常的繁琐,所以在实际中很少使用了。可重入函数
信号是异步调用,进程自身执行各种正常指令序列,当接受到信号的时候,内核就会通知进程处理,而此时正常的指令序列就会被中断,很容易造成进程空间的破坏,所以现在的Unix实现都会提供在信号处理程序中保证安全调用的函数,通俗的说,就是异步信号安全。至于具体列表,则需要查看具体Unix实现。
在前面学习errno错误信息的时候讲到过,每个进程都维护了一个errno变量,当需要获得的时候就通过extern int errno
的形式获取。当然实际不是这样的,因为有多线程的模型存在,所以实际情况更加复杂。当进程执行正常代码,然后调用的系统函数修改了errno值,如果一个信号被发送给进程,进程的信号处理函数调用了另一个系统函数修改errno值,结果最终返回的时候就是信号处理程序内调用得到的errno,所以在实际开发中,如果需要调用系统函数,需要先保存errno值,然后调用后恢复errno