1.信号概念
信号是一种软件中断,通知程序某种事件的发生。常见的信号有SIGABRT(当进程调用abort函数的时候自动发送), SIGALRM(当timer被触发的时候自动发送),等等。
下面的情况可以产生信号:
- 按下CTRL+C产生SIGINT
- 硬件中断,如除0,非法内存访问(SIGSEV)等等
- Kill函数可以对进程发送信号
- Kill命令。实际上是对Kill函数的一个包装
- 软件中断。如当Alarm Clock超时(SIGURG),当Reader中止之后又向管道写数据(SIGPIPE),等等
当信号发生的时候,可以有三种选择(称之为Disposition of the signal或者Action associated with a signal)
1).忽略信号。大部分信号都可以Ignore,除了SIGKILL和SIGSTOP,这是为了提供一个确定的方法来Stop或者Kill一个Process。此外,如果我们忽略部分硬件异常产生的信号,进程的行为未定义。
2).捕捉信号。可以让内核来调用我们所指定的函数。SIGKILL和SIGSTOP无法捕捉。
3).执行缺省行为。如果不做任何处理,则执行缺省动作。大部分信号的缺省行为都是中止进程。
部分信号的缺省行为不仅中止进程,同时还会产生core dump,也就是生成一个名为core的文件,其中保存了退出时进程内存的镜像,可以用来调试。在下面情况,不会生成core文件:
a.当前进程不属于当前用户
b.当前进程不属于当前组
c.用户在当前目录下无写权限
d.Core文件已存在,用户无写权限
e.文件过大,超过RLIMIT_CORE
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专用,进程超过资源限制的时候发送 |
Linux支持的信号列表如下。很多信号是与机器的体系结构相关的
信号值 默认处理动作 发出信号的原因
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断(如break键被按下)
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)发出的退出指令
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF Kill信号
SIGSEGV 11 C 无效的内存引用
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道
SIGALRM 14 A 由alarm(2)发出的信号
SIGTERM 15 A 终止信号
SIGUSR1 30,10,16 A 用户自定义信号1
SIGUSR2 31,12,17 A 用户自定义信号2
SIGCHLD 20,17,18 B 子进程结束信号
SIGCONT 19,18,25 进程继续(曾被停止的进程)
SIGSTOP 17,19,23 DEF 终止进程
SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键
SIGTTIN 21,21,26 D 后台进程企图从控制终端读
SIGTTOU 22,22,27 D 后台进程企图从控制终端写
处理动作一项中的字母含义如下
A 缺省的动作是终止进程
B 缺省的动作是忽略此信号,将该信号丢弃,不做处理
C 缺省的动作是终止进程并进行内核映像转储(dump core),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
D 缺省的动作是停止进程,进入停止状况以后还能重新进行下去,一般是在调试的过程中(例如ptrace系统调用)
E 信号不能被捕获
F 信号不能被忽略
2.signal函数
UNIX系统的信号特性的最简单的接口是signal函数。
#include <signal.h> void (*signal(int signo, void (*func)(int)))(int) //成功返回前一个信号布署,错误返回SIG_ERR。
signo 参数只是上一节的信号名。func的值是常量SIG_IGN,常量SIG_DFL或当接到信号发生时要调用的函数的地址。如果我们指定SIG_IGN,则向内核表示忽略此信号。(记住我们不能忽略SIGKILL和SIGSTOP)。当指定SIG_DFL时,我们设置信号相关的动作为默认值,signal函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。第一个参数signo是一个整型数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。
3.不可靠的信号
早期的UNIX系统的Signal是不稳定的:
1). 信号可能会丢失
2). 在信号处理函数中,需要重复注册信号,否则下次收不到信号
3). 不支持阻塞信号
4.可重入函数
大多数函数不可重入原因:
1).已知它们使用静态数据结构
2).它们调用malloc和free函数
3).它们是标准I/O函数
5.kill和raise函数
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。
#include <signal.h> int kill(pid_t pid, int signo); int raise(int signo); //两者成功都返回0,错误返回-1
调用raise(signo)等价于kill(getpid(), signo);
kill的pid参数有四种情况:
1).pid > 0, 信号被发送给进程ID为pid的进程;
2).pid == 0,信号被发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有向这些进程发送信号的权限。注意术语“所有进程”不包括实现定义的系统进程集。对于多数UNIX系统,这个系统进程集包括内核进程和init(pid 1);
3).pid < 0, 将该信号发送给ID等于pid的绝对值,且发送者对其有发送信号的权限的所有进程。如上,所有进程集不包括系统进程。
4).pid == -1,将该信号发送给发送进程有权限向它们发送喜好的系统上的所有进程。和前面一样,不包含特定的系统进程。
6.alarm和pause函数
1).使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其认动作是终止该进程。
#include <unistd.h> unsigned int alarm(unsigned int seconds); //返回0或上次设置的警报到现在的时间。
如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时间则被新值代换。如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。
2).pause函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h> int pause(void); //返回-1,errno设置为EINTR。
只有执行了一个信号处理程序并从其返回时, pause才返回。在这种情况下, pause返回-1,errno设置为EINTR。
7.信号集
POSIX.1定义数据类型sigset_t以包含一个信号集,并且定义了下列五个处理信号集的函数
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); //成功返回0,错误返回-1. int sigismember(const sigset_t *set, int signo); //真返回1,假返回0,错误返回-1.
函数sigemptyset初始化set指向的信号集,清除其中所有信号。函数sigfillset初始化信号集使其所有的信号。函数sigaddset向一个已存在的集合加入一个信号,而sigdelset从一个信号删除一个信号。
8.sigprocmask函数
调用函数sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。
#include <signal.h> int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); //成功返回0,错误返回-1
首先, oset是非空指针,进程的当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。下表说明了how可选用的值。SIGBLOCK是或操作,而SIGSETMASK则是赋值操作。
how | 描述 |
SIG_BLOCK | 进程的新信号掩码是它当前掩码和set指向的信号集的并集,也就是说,set包含了我们想阻塞的额外的信号。 |
SIG_UNBLOCK | 进程的信号掩码是当前信号掩码和set指向的信号集的反码的交集。也就是说,集合包含了我们想反阻塞的信号。 |
SIG_SETMASK | 进程的新信号掩码被set指向的信号集的指代替 |
如果set是个空指针,则不改变该进程的信号屏蔽字, how的值也无意义
9.sigpending函数
sigpending返回对于调用进程被阻塞不能递送和当前未决的信号集。该信号集通过set参数返回。
#include <signal.h> int sigpending(sigset_t *set); //成功返回0,错误返回-1
被此函数阻塞的信号,不会递送调用进程,直到该信号不再被阻塞
10.sigaction函数
sigaction函数的功能是检查或修改(或两者)与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。在本书末尾用sigaction函数实现了signal。
#include <signal.h> int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact); //成功返回0,错误返回-1.
其中,参数signo是要检测或修改具体动作的信号的编号数。若act指针非空,则要修改其动作。如果oact指针非空,则系统返回该信号的原先动作。此函数使用下列结构:
struct sigaction { void (*sa_handler)(int); /* addr of signal handler, or SIG_IGN, or SIG_DFL. */ sigset sa_mask; /* additional signals to block */ int sa_flags; /* signal options */ /* alternate handler */ void (*sa_sigaction)(int, siginfo_t *, void *); };
当更改信号动作时,如果sa_handler指向一个信号捕捉函数的地址(不是常数SIGIGN或SIGDFL),则samask字段说明了一个信号集,在调用信号捕捉函数之前,该信号集要加到进程的信号屏蔽字中。
abort函数
abort函数的功能是使程序异常终止
#include <stdlib.h> void abort(void); //函数决不返回
此函数将SIGABRT信号发送给调用进程。进程不应忽略此信号。
system函数
System函数除了会执行可执行文件创建子进程之外,还会设置sigmask为忽略SIGINT,SIGQUIT并阻塞 SIGCHLD。
1). 忽略SIGINT和SIGQUIT的原因是,只有子进程应该处理这两个signal,而父进程无需处理
2). 阻塞SIGCHLD的原因是,System函数需要知道子进程的结束,而父进程不应该先提前知道,以免提前调用wait函数使得System函数本身无法获得进程的退出值
sleep函数
#include <unistd.h> unsigned int sleep(unsigned int seconds); //返回0或未睡眠的秒数。
此函数使调用进程被挂起直到:
(1) 已经过了seconds所指定的墙上时钟时间;
(2) 该进程捕捉到一个信号并从信号处理程序返回。
如同alarm信号一样,由于某些系统活动,实际返回时间比所要求的会迟一些