Linux中的信号都以一个正数表示,以SIG开头,不存在值为0的信号。
内核在某个信号出现时按照三种方式之一进行处理:
<1>忽略此信号(SIG_IGN)。大多数信号的处理方式,但有两种信号不能忽略。SIGKILL和SIGSTOP
<2>捕捉信号。调用用户函数
<3>执行系统默认动作(SIG_DFL)。大多数信号的系统默认动作是终止进程
一、signal函数
#include<signal.h>
void (*signal(int signo, void (*func)(int)))(int);
返回值:若成功则返回信号以前的处理配置(见下),若出错则返回SIG_ERR
signo参数是信号名,func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址。
signal函数原型需要两个参数,返回一个函数指针,而该指针指向的函数无返回值。第一个参数signo是一个整数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。
如果用
typedef void Sigfunc(int);
则可将函数原型写成
Sigfunc *signal(int, Sigfunc *);
#define SIG_ERR (void (*)()) -1
#define SIG_DFL (void (*)()) 0
#define SIG_IGN (void (*)()) 1
一个进程调用fork时,其子进程继承父进程的信号处理方式。
二、中断的系统调用
当捕捉到某个信号时,被中断的是内核中执行的系统调用。
为了支持这种特性,系统调用为分两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用。包括:
1、读某些类型的文件(管道、终端设备以及网络设备)时,如果数据并不存在则可能会使调用者永远阻塞。
2、在写这些类型的文件时, 如果不能立即接受这些数据,则也可能使调用都永远阻塞
3、打开某些类型文件,在某种条件发生之前也可能会使调用都阻塞
4、pause和wait函数
5、某些ioctl操作
6、某些进程间通信函数
为了帮助应用程序使其不必处理被中断的系统调用,4.2BSD引入了某些被中断系统调用的自动重启动。自动重启动的系统调用包括iotcl、read、readv、write、writev、wait和waitpid。其中前5个只有对低速设备进行操作时才会被信号中断,而wait和waitpid在捕捉到信号时总是被中断。
三、可靠信号术语和语义及信号集处理函数
当引发信号的事件发生时,为进程产生一个信号。在产生了信号时,内核通常在进程表中设置一个某种形式的标志。当对信号采取这种动作时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。
进程可以选用信号递送阻塞。如果为进程产生一个选择为阻塞的信号,而且对该信号动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程(a)对此信号解除了阻塞,或者(b)将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。进程在信号递送给它之前仍可改变对该信号的动作。进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。
每个进程都有一个信号屏蔽字(signal mask) ,它规定了当前要阻塞递送到该进程的信号集。对于每种可能的信号,该屏蔽字中都有一位与之对应。对于某种信号,若其对应位已设置,则它当前是被阻塞的。进程可以调用sigprocmask来检测和更改当前信号屏蔽字。
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
sigprocmask函数:
#include<signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
返回值:成功则返回0,失败-1
how: 说明
SIG_BLOCK: set包含了我们希望阻塞的附加信号
SIG_UNBLOCK: set包含了我们希望解除阻塞的信号
SIG_SETMASK: 用新的set信号集代替
如果set是空指针,则不改变该进程的信号屏蔽字,how的字也无意义。
在调用sigprocmask后如果有任何未决的,不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。
注:sigprocmask仅为单线程的进程定义的。
sigpending函数:
#include<signal.h>
int sigpending(sigset_t *set);
返回值:成功返回0,失败返回-1
如果成功,得到的set一定是未决的信号集。
sigaction函数:
sigaction函数的功能是检查或修改与指定信号相关联的处理动作(或同时执行这两种操作)
#include<signal.h>
int sigaction(nt signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
返回值:若成功则返回0,若出错则返回-1
若act指针非空,则要修改其动作。如果oact指针非空,则系统经由oact返回该信号的上一个动作。
struct sigaction{
void (*sa_handler)(int); /*addr of signal handler, or SIG_IGN, or SIG_DFL*/
sigset_t sa_mask; /*additional signals to block*/
int sa_flags; /*signal options*/
/*alternate handler*/
void (*sa_sigaction)(int, siginfo_t *, void *);
};
当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址,则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。
一旦对给定的信号设置了一个动作,那么在调用sigaction显式地改变它之前,该设置就一直有效。
sa_sigaction字段是一个替代的信号处理程序,当在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。对于sa_sigaction和sa_handler字段这两者,其实现可能使用同一存储区,所以应用程序只能一次使用这两个字段中的一个。
#include<signal.h>
int sigsuspend(const sigset_t *sigmask);
返回值:-1,并将error设置成EINTR
将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
四、信号处理中的函数跳转
#include<setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
返回值:若直接调用则返回0,若从siglongjmp调用返回则返回非0值
void siglongjmp(sigjmp_buf env, int val);
如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0 savemask中的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
五、信号产生函数
<1>kill和raise函数
kill 函数将信号发送给进程或进程组,raise 函数则允许进程向自身发送信号。
#include<signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
两个函数的返回值:若成功则返回0,失败则返回-1
kill函数pid参数的4种不同情况:
1、pid > 0 将该信号发送给进程ID为pid的进程
2、pid == 0 将该信号发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有向这些进程发送信号的权限。“所有进程”不包括系统进程集。
3、pid < 0 将该信号发送给其进程组ID等于pid的绝对值,而且发送进程具有向其发送信号的权限。
4、pid == -1 将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程。
<2>alarm和pause函数
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
返回值:0或以前设置的闹钟时间的余留秒数
int pause(void);
返回值:-1,并将errno设置为EINTR
使用alarm函数可以设置一个计时器,在将来某个指定的时间该计时器会超时。当计时器超时时,产生SIGALRM信号。其默认动作是终止调用该alarm函数的进程。经过指定的秒数后,信号由内核产生,由于进程调度的延迟,进程得到控制从而能够处理该信号还需一些时间。
每个进程只能有一个闹钟时间。如果seconds值为0,则取消以前的闹钟时钟,其余留值仍为alarm函数的返回值。
pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回。
<3>abort函数
#include<stdlib.h>
void abort(void);
此函数将SIGABRT信号发送给调用进程(进程不应忽略该信号)。
<4>sleep函数
#include<unistd.h>
unsigned int sleep(unsigned int seconds);
返回值:0或未休眠的秒数
此函数使调用进程被挂起,直到满足以下条件之一:
1、已经过了seconds所指定的墙上时钟时间
2、调用进程捕捉到一个信号并从信号处理程序返回