10.1 引言
- 信号是软件中断;
- 信号提供了一种处理异步事件的方法。
10.2 信号概念
- 每个信号都有一个名字,这些名字都以3个字符
SIG
开头; - 在头文件<signal.h>中,信号名都被定义为正整数常量(信号编号);
- 不存在编号为0的信号,0编号值称为空信号。
当某个信号出现时,可按下列3个方式之一进行处理:
(1)忽略此信号,SIGKILL和SIGSTOP这两种信号不能被忽略;
(2)捕捉信号,即在信号发生时,调用一个用户函数;不能捕捉SIGKILL和SIGSTOP信号;
(3)执行系统默认动作。
10.3 函数signal
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
// 返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR
- signo参数是信号名;
- func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址:
(1)如果指定SIG_IGN,则向内核表示忽略此信号(SIGKILL和SIGSTOP不能忽略);
(2)如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作;
(3)当指定函数地址时,则在信号发生时,调用该函数,称这种处理为捕捉该信号,称此函数为信号处理程序。 signal
函数要求两个参数,返回一个函数指针,该指针所指向的函数无返回值:
(1)第一个参数signo是一个整型数;
(2)第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值;
(3)signal
的返回值是一个函数地址,该函数有一个整型参数;当调用signal
设置信号处理程序时,第二个参数是指向该函数(信号处理程序)的指针,返回值则是指向在此之前的信号处理程序的指针。
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1
exec
函数将原先设置为要捕捉的信号都更改为默认动作;- 当一个进程调用
fork
时,其子进程继承父进程的信号处理方式。
10.4 不可靠的信号
10.5 中断的系统调用
10.6 可重入函数
10.7 SIGCLD语义
BSD的SIGCHLD信号语义:子进程状态改变后产生此信号,父进程需要调用一个wait
函数以检测发生了什么。
10.8 可靠信号术语和语义
- 产生:当造成信号的事件发生时,为进程产生一个信号;
- 递送:当一个信号产生时,内核通常在进程表中以某种形式设置一个标志,当对信号采取了这种动作时,称向进程递送了一个信号;
- 未决的:在信号产生和递送之间的时间间隔内,该信号是未决的;
- 信号屏蔽字:规定当前要阻塞递送到该进程的信号集合(信号集,sigset_t)。
10.9 函数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参数有以下4种不同的情况:
(1)pid > 0:将该信号发送给进程ID为pid的进程;
(2)pid == 0:将该信号发送给与发送进程属于同一进程组的所有进程,而且发送进程具有权限向这些进程发送信号;
(3)pid < 0:将该信号发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程;
(4)pid == -1:将该信号发送给发送进程有权限向它们发送信号的所有进程。- 信号编号0被定义为空信号,如果signo参数是0,则
kill
仍执行正常的错误检查,但不发送信号,这常被用来确定一个特定进程是否仍然存在;如果向一个并不存在的进程发送空信号,则kill
返回-1,errno被设置为ESRCH。
10.10 函数alarm和pause
alarm
函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时,超时产生SIGALRM信号:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
// 返回值:0或以前设置的闹钟时间的余留秒数
- 参数seconds的值是产生信号SIGALRM需要经过的时钟秒数;
- 每个进程只能有一个闹钟时间;如果在调用
alarm
时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm
函数调用的值返回,以前注册的闹钟时间则被新值代替; - 如果有以前注册的尚未超时的闹钟时间,而且本次调用的seconds值是0,则取消以前的闹钟时间,其余留值仍作为
alarm
函数的返回值。
pause
函数使调用进程挂起直至捕捉到一个信号:
#include <unistd.h>
int pause(void);
// 返回值:-1,errno设置为EINTR
- 只有执行了一个信号处理程序并从其返回时,
pause
才返回,返回-1,errno设置为EINTR。
10.11 信号集
信号集是一个能表示多个信号的数据类型,POSIX.1定义数据类型sigset_t以包含一个信号集合,并且定义了下列5个处理信号集的函数:
#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);
// 4个函数返回值:若成功,返回0;若出错,返回-1
int sigismember(const sigset_t *set, int signo);
// 返回值:若真,返回1;若假,返回0
- 函数
sigemptyset
初始化由set指向的信号集,清除其中所有信号; - 函数
sigfillset
初始化由set指向的信号集,使其包含所有信号; - 所有应用程序在使用信号集前,要对该信号集调用
sigemptyset
或sigfillset
一次; - 函数
sigaddset
将一个信号添加到已有的信号集中; - 函数
sigdelset
则从信号集中删除一个信号。
10.12 函数sigprocmask
函数sigprocmask
可以检测或更改,或同时进行检测和更改进程的信号屏蔽字:
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
// 返回值:若成功,返回0;若出错,返回-1
- 若oset是非空指针,进程的当前信号屏蔽字通过oset返回;
- 若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字:
- 不能阻塞SIGKILL和SIGSTOP信号;
- 如果set是个空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
10.13 函数sigpending
sigpending
函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的:
#include <signal.h>
int sigpending(sigset_t *set);
// 返回值:若成功,返回0;若出错,返回-1
- 未决信号集通过set参数返回。
10.14 函数sigaction
sigaction
函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作:
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
// 返回值:若成功,返回0;若出错,返回-1
- 参数signo是要检测或修改其具体动作的信号编号;
- 若act指针非空,则要修改其动作;
- 如果oact指针非空,则系统经由oact指针返回该信号的上一个动作。
10.15 函数sigsetjmp和siglongjmp
sigsetjmp
和siglongjmp
函数用于在信号处理程序中进行非局部转移:
#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
从其中恢复保存的信号屏蔽字。
当调用一个信号处理程序时,被捕捉到的信号加到进程的当前信号屏蔽字中;当从信号处理程序返回时,恢复原来的屏蔽字。
10.16 函数sigsuspend
sigsuspend
函数的功能是在一个原子操作中先恢复信号屏蔽字,然后使进程休眠:
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
// 返回值:-1,并将errno设置未EINTR
- 进程的信号屏蔽字设置为由sigmask指向的值;
- 在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起;
- 如果捕捉到一个信号而且从该信号处理程序返回,则
sigsuspend
返回,并且该进程的信号屏蔽字设置为调用sigsuspend
之前的值; - 此函数没有成功返回值,如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。
10.17 函数abort
abort
函数的功能是使程序异常终止:
#include <stdlib.h>
void abort(void);
- 此函数将SIGABRT信号发送给调用进程(进程不应忽略此信号)。
10.18 函数system
10.19 函数sleep、nanosleep和clock_nanosleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
// 返回值:0或未休眠完的秒数
- 此函数使调用进程被挂起直到满足下面两个条件之一:
(1)已经过了seconds所指定的墙上时钟时间,返回值是0;
(2)调用进程捕捉到一个信号并从信号处理程序返回,返回值是未休眠完的秒数。
nanosleep
函数与sleep
函数类似,但提供了纳秒级的精度:
#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
// 返回值:若休眠到要求的时间,返回0;若出错,返回-1
- 这个函数挂起调用进程,直到要求的时间已经超时或者某个信号中断了该函数;
- reqtp参数用秒和纳秒指定了需要休眠的时间长度;
- 如果某个信号中断了休眠间隔,进程并没有终止,remtp参数指向的timespec结构就会被设置为未休眠完的时间长度;如果对未休眠完的时间并不感兴趣,可以把该参数置为NULL;
clock_nanosleep
函数提供了使用相对于特定时钟的延迟时间来挂起调用线程:
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *reqtp, struct timespec *remtp);
// 返回值:若休眠要求的时间,返回0;若出错,返回错误码
- clock_id参数指定了计算延迟时间基于的时钟;
- flags参数用于控制延迟是相对的还是绝对的:
(1)flags为0时表示休眠时间是相对的;
(2)如果flags值设置为TIMER_ABSTIME,表示休眠时间是绝对的。
10.20 函数sigqueue
sigqueue
函数实现信号排队功能:
#include <signal.h>
int sigqueue(pid_t pid, int signo, const union sigval value);
// 返回值:若成功,返回0;若出错,返回-1
sigqueue
函数只能把信号发送给单个进程,可以使用value参数向信号处理程序传递整数和指针值,除此之外,sigqueue
函数与kill
函数类似。
10.21 作业控制信号
POSIX.1认为有以下6个与作业控制有关:
- SIGCHLD:子进程已停止或终止;
- SIGCONT:如果进程已停止,则使其继续运行;
- SIGSTOP:停止信号(不能被捕捉或忽略);
- SIGTSTP:交互式停止信号;
- SIGTTIN:后台进程组成员读控制终端;
- SIGTTOU:后台进程组成员写控制终端。
10.22 信号名和编号
psignal
函数打印与信号编号对应的字符串:
#include <signal.h>
void psignal(int signo, const char *msg);
- 字符串msg(通常是程序名)输出到标准错误文件,后面跟随一个冒号和一个空格,再后面对该信号的说明,最后是一个换行符;如果msg为NULL,只有信号说明部分输出到标准错误文件。
如果在sigaction
信号处理程序中有siginfo结构,可以使用psiginfo
函数打印信号信息,它的工作方式与psignal
类似:
#include <signal.h>
void psiginfo(const siginfo_t *info, const char *msg);
如果只需要信号的字符描述部分,也不需要把它写到标准错误文件中,可以使用strsignal
函数:
#include <string.h>
char *strsignal(int signo);
// 返回值:指向描述该信号的字符串的指针
Solaris提供一对函数,一个函数将信号编号映射为信号名,另一个则反之:
#include <signal.h>
int sig2str(int signo, char *str);
int str2sig(const char *str, int *signop);
// 两个函数的返回值:若成功,返回0;若出错,返回-1
sig2str
函数将给定信号编号翻译成字符串,并将结果存放在str指向的存储区;str2sig
函数将给出的信号名翻译成信号编号,该信号编号存放在signop指向的整型中。
10. 23 实例代码
chapter10