信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似 之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。
一个(具有合适权限的)进程能够向另一进程发送信号。信号的这一用法可作为一种同 步技术,甚至是进程间通信(IPC)的原始形式。进程也可以向自身发送信号。然而,发往进 程的诸多信号,通常都是源于内核。 引发内核为进程产生信号的各类事件如下。
- 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号 给相关进程。硬件异常的例子包括执行一条异常的机器语言指令,诸如,被 0 除,或 者引用了无法访问的内存区域。
- 用户键入了能够产生信号的终端特殊字符。其中包括中断字符(通常是 Control-C)、 暂停字符(通常是 Control-Z)。
- 发生了软件事件。例如,针对文件描述符的输出变为有效,调整了终端窗口大小,定时器到期,进程执行的 CPU 时间超限,或者该进程的某个子进程退出。
信号分为两大类。
- 第一组用于内核向进程通知事件,构成所谓传统或者标准信号。Linux 中标准信号的编号范围为 1~31。
- 另一组信号由实时信号构成
信号产生后,会于稍后被传递给某一进程,而进程也会采取某 些措施来响应信号。在产生和到达期间,信号处于等待(pending)状态。
通常,一旦(内核)接下来要调度该进程运行,等待信号会马上送达,或者如果进程正在 运行,则会立即传递信号(例如,进程向自身发送信号)。然而,有时需要确保一段代码不为 传递来的信号所中断。为了做到这一点,可以将信号添加到进程的信号掩码中—目前会阻塞 该组信号的到达。如果所产生的信号属于阻塞之列,那么信号将保持等待状态,直至稍后对其 解除阻塞(从信号掩码中移除)进程可使用各种系统调用对其信号掩码添加和移除信号。 信号到达后,进程视具体信号执行如下默认操作之一。
- 忽略信号:也就是说,内核将信号丢弃,信号对进程没有产生任何影响(进程永远都 不知道曾经出现过该信号)。
- 终止(杀死)进程:这有时是指进程异常终止,而不是进程因调用 exit()而发生的正常终止。
- 产生核心转储文件,同时进程终止:核心转储文件包含对进程虚拟内存的镜像,可将其加载到调试器中以检查进程终止时的状态。
- 停止进程:暂停进程的执行。
- 于之前暂停后再度恢复进程的执行。
除了根据特定信号而采取默认行为之外,程序也能改变信号到达时的响应行为。也将此 称之为对信号的处置(disposition)设置。程序可以将对信号的处置设置为如下之一。
- 采取默认行为。这适用于撤销之前对信号处置的修改、恢复其默认处置的场景。
- 忽略信号。这适用于默认行为为终止进程的信号。
- 执行信号处理器程序。
信号处理器程序是由程序员编写的函数,用于为响应传递来的信号而执行适当任务。例 如,shell 为 SIGINT 信号(由中断字符串 Control-C 产生)提供了一个处理器程序,令其停止 当前正在执行的工作,并将控制返回到(shell 的)主输入循环,并再次向用户呈现 shell 提示符。 通知内核应当去调用某一处理器程序的行为,通常称之为安装或者建立信号处理器程序。调用信号处理器程序以响应传递来的信号,则称之为信号已处理(handled),或者已捕获(caught)。
请注意,无法将信号处置设置为终止进程或者转储核心(除非这是对信号的默认处置)。效果最为近似的是为信号安装一个处理器程序,并于其中调用 exit()或者 abort()。abort()函数为进程产生一个 SIGABRT 信号,该信号将引发进程转储核心文件并终止
以下列表介绍了各种信号。
| 名称 | 信号值 | 描述 | 默认 |
|---|---|---|---|
| SIGABRT / SIGIOT | 6 | 中止进程 | core |
| SIGALRM | 14 | 实时定时器过期( alarm()或 setitimer()) | term |
| SIGBUS | 7 (SAMP=10) | 内存访问错误 | core |
| SIGCHLD / SIGCLD | 17 (SA=20, MP=18) | 终止或者停止子进程 | ignore |
| SIGCONT | 18 (SA=19, M=25, P=26) | 若停止则继续 | cont |
| SIGEMT | undef (SAMP=7) | 硬件错误 | term |
| SIGFPE | 8 | 算术异常 | core |
| SIGHUP | 1 | 挂起 | term |
| SIGILL | 4 | 非法指令 | core |
| SIGINT | 2 | 终端中断 | term |
| SIGIO /SIGPOLL | 29 (SA=23, MP=22) | I/O 时可能产生 | term |
| SIGKILL | 9 | 必杀(确保杀死) | term |
| SIGPIPE | 13 | 管道断开 | term |
| SIGPROF | 27 (M=29, P=21) | 性能分析定时器过期 | term |
| SIGINFO / SIGPWR | 30 (SA=29, MP=19) | 电量行将耗尽 | term |
| SIGQUIT | 3 | 终端退出 | core |
| SIGSEGV | 11 | 无效的内存引用 | core |
| SIGSTKFLT | 16 (SAM=undef, P=36) | 协处理器栈错误 | term |
| SIGSTOP | 19 (SA=17, M=23, P=24) | 确保停止 | stop |
| SIGSYS / SIGUNUSED | 31 (SAMP=12) | 无效的系统调用 | core |
| SIGTERM | 15 | 终止进程 | term |
| SIGTRAP | 5 | 跟踪/断点陷阱 | core |
| SIGTSTP | 20 (SA=18, M=24, P=25) | 终端停止 | stop |
| SIGTTIN | 21 (M=26, P=27) | 后台进程组从终端读取 | stop |
| SIGTTOU | 22 (M=27, P=28) | 后台进程组向终端写 | stop |
| SIGURG | 23 (SA=16, M=21, P=29) | 套接字上的紧急数据 | ignore |
| SIGUSR1 | 10 (SA=30, MP=16) | 用户自定义信号 1 | term |
| SIGUSR2 | 12 (SA=31, MP=17) | 用户自定义信号 2 | term |
| SIGVTALRM | 26 (M=28, P=20) | 虚拟定时器过期 | term |
| SIGWINCH | 28 (M=20, P=23) | 终端窗口尺寸发生变化 | ignore |
| SIGXCPU | 24 (M=30, P=33) | 突破对 CPU 时间的限制 | core |
| SIGXFSZ | 25 (M=31, P=34) | 突破对文件大小的限制 | core |
改变信号处置:signal()
signal()的返回值是之前的信号处置。像 handler 参数一样,这是一枚指针,所指向的是带 有一个整型参数且无返回值的函数
/*
返回值:
若成功,返回之前的信号处理函数
若出错,返回 SIG_ERR
*/
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
/*
第一个参数 signo,标识希望修改处置的信号编号,
第二个参数 handler,则标识信号抵达时所调用函数的地址
*/
// 信号处理函数
#define SIG_DFL (void (*)(int))0 // 默认信号处理
#define SIG_IGN (void (*)(int))1 // 忽略信号
#define SIG_HOLD (void (*)(int))5
#define SIG_ERR ((void (*)(int))-1) //信号函数出错
发送信号 kill(),raise()
一个进程能够使用 kill()系统调用向另一进程发送信号
/*
两函数返回值:
若成功,返回 0;
若出错,返回 -1 ;
*/
#include <signal.h>
int kill(pid_t pid, int signo) ; // 信号发送给进程或者进程组
/*
pid取值有下列4种情况:
(1) pid >0 将该信号发送给进程ID为pid的进程
(2) pid == 0 将该信号发送与发送进程属于同一进程组的所有进程,
发送进程具有权限向这些进程发送信号,所有进程不包括实现定义的系统进程集
(3) pid < 0 将该信号发送给与其进程组ID等于 pid 绝对值
发送进程具有权限向这些进程发送信号,所有进程不包括实现定义的系统进程集
(4) pid == -1 将该信号发送给进程有权限向它们发送信号的所有进程。所有进程不包括实现定义的系统进程集
发送信号的权限规则
(1) 超级用户可以将信号发送给任一进程,非超级用户,发送者的实际用户ID或有效用户ID
必须等于接受者的实际用户ID或者有效用户ID。
如果支持_POSIX_SAVED_IDS,则检查接收者的保持设置用户ID(而不是有效用户ID)
(2) 权限测试有个特例,如果被发送的信号是SIGCONT,则进程可将其发送属于同一会话的任一其他进程
(3) 信号编号为0 定义为空信号,如果signo参数为0,则kill 仍执行正常的错误检查,但不发送信号。向一个不存在的进程发送信号,kill返回 -1, errno被设置为 ESRCH .
这常被用来检查进程是否存在。不过测试进程是否存在的操作不是原子操作,且进程PID会重用,所以测试并无多大价值。
*/
int raise(int signo); // 进程发送信号给自身 raise(signo) = kill(getpid(),signo)
信号集 sigset_t
多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t.
/*
四个函数返回值:
若成功,返回 0;
若出错,返回 -1;
*/
#include <signal.h>
int sigemptyset(sigset_t * set); //初始化一个信号集set,并清空其中所有信号
int sigfillset(sigset_t * set); // 初始化一个信号集set,使其填充所有的信号
int sigaddset(sigset_t * set, int signo); // 向信号集set中添加一个signo信号
int sigdelset(sigset_t * set, int signo); // 向信号集set中删除一个signo 信号
/*
返回值:
若真,返回 1;
若假,返回 0;
*/
int sigismember(const sigset_t * set, int signo); //判断信号集set中是否包含signo信号
设置信号掩码(阻塞信号传递)sigprocmask()
一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改,或同时进行检测和更改进程的信号屏蔽字。
#include <signal.h>
/*
返回值:
若成功,返回0;
若出错,返回−1
*/
int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
/*
(1) 若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回
(2) 若set是非空指针,则参数how指示如何修改当前的信号屏蔽字
(3) 若set为空指针,则不改变进程的信号屏蔽字,how也无意义
(4) 如果有任何等待的信号因对 sigprocmask()的调用而解除了锁定,
那么在此调用返回前至少会传递一个信号。换言之,如果解除了对某个等待信号的锁定,
那么会立刻将该信号传递给进程。
*/
// how的取值为:
#define SIG_BLOCK 1 /* block specified signal set */ // 添加阻塞信号集
#define SIG_UNBLOCK 2 /* unblock specified signal set */ // 清除阻塞信号集
#define SIG_SETMASK 3 /* set specified signal set */// 重置为新的阻塞信号集
sigprocmask 是仅为单线程进程定义的。信号掩码实际属于线程属性,在多线程进程 中,每个线程都可使用 pthread_sigmask() 函数来独立检查和修改其信号掩码
获取处于等待状态的信号 sigpending()
如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号填加到进程的等待信号 集中。当(且如果)之后解除了对该信号的锁定时,会随之将信号传递给此进程。为了确定 进程中处于等待状态的是哪些信号,可以使用 sigpending()。
/*
返回值:
若成功 返回 0
若出错 返回 -1
*/
#include <signal.h>
int sigpending(sigset_t * set); // set返回,调用进程阻塞的不能递送的信号,信号已传递但是被阻塞
改变信号处置:sigaction ()
检测,更改或者同时进行检测和更改指定信号相关联的处理动作
/*
返回值:
若成功 返回 0
若出错 返回 -1
*/
#include <signal.h>
int sigaction(int signo, const struct sigaction * act, struct sigaction * oact);
/*
(1) 参数 signo 是要检测和修改其具体动作的信号编号
(2) 若 act 指针为非空,则要修改其动作
(3) 如果 oact 指针非空,则系统经由oact指针返回该信号的上一个动作
*/
struct sigaction {
void (*sa_handler) (int); /* signal handler */
sigset_t sa_mask; /* signal mask to apply */
int sa_flags; /* see signal options below */
void (*sa_sigaction)(int ,siginfo_t * ,void *);
};
sa_handler 信号处理函数的地址 (包括 SIG_IGN 或者 SIG_DEL) , 当进程在调用sa_handler的时候,signo信号会被加入到进程的信号屏蔽字当中,调用完成会恢复。
sa_mask 字段是一个信号集,在调用 sa_handler 之前,这个信号集要加入到进程的信号屏蔽字中,仅当从信号捕捉函数 sa_handler 返回时再把进程屏蔽字恢复为原先的值 若同一种信号多次发生,通常并不将它们加入队列,所以如果在某种信号被阻塞时,它发生了5次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次
sa_flag 取值
- 若设置了
SA_SIGINFO,调用信号处理函数void sa_sigaction(int signo,siginfo_t * info ,void * context); - 若未设置了 SA_SIGINFO,调用信号处理函数
void sa_handler(int signo);SA_INTERRUPT由此信号中断的系统调用不自动重启,默认处理方式SA_NOCLDSTOP若 signo 是SIGCHLD, 调用进程的子进程停止时(作业控制)不产生此信号,但子进程终止时仍旧产生此信号SA_NOCLDWAIT若signo是SIGCHLD,调用进程的子进程终止时不产生僵死的进程,若调用进程随后调用wait,则阻塞到所有子进程都终止,此时返回-1,errno设置为ECHILD.SA_NODEFER当捕捉到此信号,在执行其信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask包括了此信号)。注意,此种类型对应早期的不可靠信号 。SA_ONSTACK针对此信号调用处理器函数时,使用了由 sigaltstack()安装的备选栈SA_RESETHAND当捕获该信号时,会在调用处理器函数之前将信号处置重置为默认值(即 SIG_DFL)(默认情况下,信号处理器函数保持建立状态,直至进一步调用 sigaction()将其显式解除。)SA_RESTART自动重启由信号处理器程序中断的系统调用
sa_sigaction 信号处理函数
- 函数第一个参数是信号
- 第二个参数 siginfo_t
// siginfo_t必须至少包括 si_signo si_code成员
// 若信号是SIGCHLD,则设置 si_pid ,si_status 和 si_uid 字段,
// 若信号是 SIGBUS,SIGILL,SIGFPE或者SIGSEGV,则 si_addr 包含造成故障的根源地址,该地址可能并不准确。si_errno字段包含错误编号
typedef struct __siginfo {
int si_signo; /* signal number */
int si_errno; /* errno association */
int si_code; /* signal code */
pid_t si_pid; /* sending process */
uid_t si_uid; /* sender's ruid */
int si_status; /* exit value */
void *si_addr; /* faulting instruction */
union sigval si_value; /* signal value */
long si_band; /* band event for SIGPOLL */
unsigned long __pad[7]; /* Reserved for Future Use */
} siginfo_t;
// 实时信号的伴随数据。
union sigval {
/* Members as suggested by Annex C of POSIX 1003.1b. */
int sival_int;
void *sival_ptr;
};
| 信号 | si_code的值 | 信号来源 |
|---|---|---|
| 任意(所有) | SI_ASYNCIO | 异步 I/O(AIO)操作已经完成 |
| SI_KERNEL | 从内核发送(例如,来自于终端驱动程序的信号) | |
| SI_MESGQ | 消息到达 POSIX 消息队列(自 Linux 2.6.6) | |
| SI_QUEUE | 利用 sigqueue()从用户进程发出的实时信号 | |
| SI_SIGIO | SIGIO 信号(仅 Linux 2.2 支持) | |
| SI_TIMER | POSIX(实时)定时器到期 | |
| SI_TKILL | 调用 tkill()或 tgkill()的用户进程(自 Linux 2.4.19) | |
| SI_USER | 调用 kill()或 raise()的用户进程 | |
| SIGBUS | BUS_ADRALN | 无效的地址对齐 |
| BUS_ADRERR | 不存在的物理地址 | |
| BUS_MCEERR_AO | 硬件内存错误,动作为可选(自 Linux 2.6.32) | |
| BUS_MCEERR_AR | 硬件内存错误,动作为必需(自 Linux 2.6.32) | |
| BUS_OBJERR | 对象特有的硬件错误 | |
| SIGCHLD | CLD_CONTINUED | 因 SIGCONT 信号,子进程得以继续执行(自 Linux 2.6.9) |
| CLD_DUMPED | 子进程异常终止,并产生核心转储 | |
| CLD_EXITED | 子进程退出 | |
| CLD_KILLED | 子进程异常终止,且不产生核心转储 | |
| CLD_STOPPED | 子进程停止 | |
| CLD_TRAPPED | 受到跟踪的子进程停止 | |
| SIGFPE | FPE_FLTDIV | 浮点除 0 |
| FPE_FLTINV | 无效的浮点操作 | |
| FPE_FLTOVF | 浮点溢出 | |
| FPE_FLTRES | 浮点结果不精确 | |
| FPE_FLTUND | 浮点下溢 | |
| FPE_INTDIV | 整型除 0 | |
| FPE_INTOVF | 整型溢出 | |
| FPE_SUB | 下标超出范围 | |
| SIGILL | ILL_BADSTK | 内部栈错误 |
| ILL_COPROC | 协处理器错误 | |
| ILL_ILLADR | 非法地址模式 | |
| ILL_ILLOPC | 非法操作码 | |
| ILL_ILLOPN | 非法操作数 | |
| ILL_ILLTRP | 非法陷入 | |
| ILL_PRVOPC | 特权级操作码 | |
| ILL_PRVREG | 特权级寄存器 | |
| SIGPOLL / SIGIO | POLL_ERR | I/O 错误 |
| POLL_HUP | 设备断开 | |
| POLL_IN | 输入数据有效 | |
| POLL_MSG | 输入消息有效 | |
| POLL_OUT | 输出缓冲区有效 | |
| POLL_PRI | 高优先级输入有效 | |
| SIGSEGV | SEGV_ACCERR | 映射对象的无效权限 |
| SEGV_MAPERR | 未映射为对象的地址 | |
| SIGTRAP | TRAP_BRANCH | 进程分支陷入 |
| TRAP_BRKPT | 进程断点 | |
| TRAP_HWBKPT | 硬件断点/监测点 | |
| TRAP_TRACE | 进程跟踪陷入 |
- 第三个参数 ucontext_t,该结构提供了所谓的用户上下文信息,用于描述调用 信号处理器函数前的进程状态,其中包括上一个进程信号掩码以及寄存器的保存值,例如程 序计数器(cp)和栈指针寄存器(sp)
/* Userlevel context. */
typedef struct ucontext_t
{
unsigned long int __ctx(uc_flags);
struct ucontext_t *uc_link;
stack_t uc_stack;
mcontext_t uc_mcontext;
sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
__extension__ unsigned long long int __ssp[4];
} ucontext_t;
等待信号:pause()
调用 pause()将暂停进程的执行,直至信号处理器函数中断该调用为止(或者直至一个未 处理信号终止进程为止)。
#include<unsitd.h>
// 总是返回 -1 并把 errno 设置为 EINTR
int pause(void);
处理信号时,pause()遭到中断,并总是返回−1,并将 errno 置为 EINTR。
异常终止进程:abort()
函数 abort()终止其调用进程,并生成核心转储。
#include <stdlib.h>
// 此函数将SIGABRT信号发送给调用者(进程并不理会对此信号的阻塞和忽略)
void abort(void) ;
/*
让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清理操作
,如果进程不在信号处理程序中终止自己,当信号处理程序返回时,abort终止进程
*/
abort 并不理会进程对此信号的阻塞和忽略。让进程捕捉 SIGABRT 的意图是:在进程终止之前由其执行所需的清理操作。如果进程并不在信号处理程序中终止自己,POSIX.1声明当信号处理程序返回时,abort终止该进程。如果abort调用终止进程,则它对所有打开标准I/O流的效果应当与进程终止前对每个流调用fclose相同。
在信号处理器函数中执行非本地跳转 sigsetjmp()和 siglongjmp()
#include <setjmp.h>
/*
返回值:
若直接调用,返回 0;
若从siglongjmp调用返回,则返回 siglongjmp 设置的 val ;
*/
int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);
/*
这两个函数和 setjmp,longjmp 之前的唯一区别是 sigsetjmp 增加了一个参数 savemask
savemask 非0 ,则 sigsetjmp 在 env 中保存进程当前信号屏蔽字,调用 siglongjmp时
如果带非 0 的 savemask sigsetjmp 调用已经保存了env ,则 siglongjmp 从其中恢复保存的信号屏蔽字
*/
在备选栈中处理信号: sigaltstack()
在调用信号处理器函数时,内核通常会在进程栈中为其创建一帧。不过,如果进程对栈 的扩展突破了对栈大小的限制时,栈的增长过大,以至于会 触及到一片映射内存 或者向上增长的堆,又或者栈的大小已经直逼 RLIMIT_STACK 资源限制,这些都会造成这种情况的发生
当进程对栈的扩展试图突破其上限时,内核将为该进程产生 SIGSEGV 信号。不过,因为 栈空间已然耗尽,内核也就无法为进程已经安装的 SIGSEGV 处理器函数创建栈帧。结果是, 处理器函数得不到调用,而进程也就终止了(SIGSEGV 的默认动作)。 如果希望在这种情况下确保对 SIGSEGV 信号处理器函数的调用,就需要做如下 工作。
- 分配一块被称为“备选信号栈”的内存区域,作为信号处理器函数的栈帧。
- 调用 sigaltstack(),告知内核该备选信号栈的存在。
- 在创建信号处理器函数时指定 SA_ONSTACK 标志,亦即通知内核在备选栈上为处理器函数创建栈帧。
#include <sighal.h>
/*
成功返回 0
错误返回 -1
*/
int sigaltstack (const stack_t * sigstack, stack_t * old_sigstack)
/*
- 参数 sigstack 所指向的数据结构描述了新备选信号栈的位置及属性。
- 参数 old_sigstack 指向的结构则用于返回上一备选信号栈的相关信息(如果存在)
两个参数之一均可为 NULL。
例如,将参数 sigstack 设为 NULL 可以发现现有备选信号栈,并且不用将其改变。
不为 NULL 时,这些参数所指向的数据结构类型如下
*/
typedef struct
{
void *ss_sp;
int ss_flags;
size_t ss_size;
} stack_t ;
/*
字段 ss_sp 和 ss_size 分别指定了备选信号栈的位置和大小。在实际使用信号栈时,内核
会将 ss_sp 值自动对齐为与硬件架构相适宜的地址边界
*/
# define SIGSTKSZ sysconf (_SC_SIGSTKSZ) 作为划分备选栈大小的典型值
# define MINSIGSTKSZ SIGSTKSZ 作为调用信号处理器函数所需的最小值
ss_flags 可以包含如下值之一
-
SS_ONSTACK 如果在获取已创建备选信号栈的当前信息时该标志已然置位,就表明进程正在备选信号栈上执行。当进程已经在备选信号栈上运行时,试图调用 sigaltstack()来创建一个新的备选信号栈将会产生一个错误(EPERM)。
-
SS_DISABLE 在 old_sigstack 中返回,表示当前不存在已创建的备选信号栈。如果在 sigstack 中指定,则会禁用当前已创建的备选信号栈
实时信号
定义于 POSIX.1b 中的实时信号,意在弥补对标准信号的诸多限制。较之于标准信号,其 优势如下所示。
-
实时信号的信号范围有所扩大,可应用于应用程序自定义的目的。而标准信号中可供 应用随意使用的信号仅有两个:SIGUSR1 和 SIGUSR2。
-
对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程, 那么将会多次传递信号。相反,如果某一标准信号已经在等待某一进程,而此时即使 再次向该进程发送此信号的实例,信号也只会传递一次。
-
当发送一个实时信号时,可为信号指定伴随数据(一整型数或者指针值),供接收进 程的信号处理器获取。
-
不同实时信号的传递顺序得到保障。如果有多个不同的实时信号处于等待状态,那么 将率先传递具有最小编号的信号。换言之,信号的编号越小,其优先级越高。如果是 同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时 的顺序保持一致
Linux 内核则定义了 32 个不同的实时信号,编号范围为 32~63。
- RTSIG_MAX 常 量 则 表 征 实 时 信 号 的 可 用 数 量,
- SIGRTMIN 表示可用实时信号编号的最小值
- SIGRTMAX 表示可用实时信号编号的最大值。
- 可通过 getrlimit(RLIMIT_SIGPENDING) 获取排队实时信号的数量的最大限制
#define RTSIG_MAX 32
/* Return number of available real-time signal with highest priority. */
extern int __libc_current_sigrtmin (void) __THROW;
/* Return number of available real-time signal with lowest priority. */
extern int __libc_current_sigrtmax (void) __THROW;
#define SIGRTMIN (__libc_current_sigrtmin ())
#define SIGRTMAX (__libc_current_sigrtmax ())
/* Maximum number of pending signals. */
__RLIMIT_SIGPENDING = 11,
#define RLIMIT_SIGPENDING __RLIMIT_SIGPENDING
对实时信号的区分方式有别于标准信号,不再使用常量来定义。指代实时信号编号则可以采用 SIGRTMIN+x 的形式,表达式(SIGRTMIN + 1)就表示第二个实时信号。
发送处理实时信号 sigqueue()
使用 sigqueue()发送信号所需要的权限与 kill() 的要求一致。也可以发送空信号(即信号 0),其语义与 kill()中的含义相同。(不同于 kill(),sigqueue()不能通过将 pid 指定为负值而向整个进程组发送信号。)
#include <signal.h>
// 成功返回 0 ,错误返回 -1
int sigqueue (pid_t pid, int sig, const union sigval val)
/*
pid : 给哪个进程发送信号
sig :发送信号的编号
val : 实时信号传递的伴随数据。
*/
由其选择对联合体(union)中的 sival_int 属性还是 sival_ptr 属性进行设置。sigqueue()中很少使用 sival_ptr,因为指针的作用范围在进程内部,对于另一进程几乎没有意义。该字段得以一展身手之处,应该是在使用 sigval 联合体的其他函数中
可以像标准信号一样,使用常规(单参数)信号处理器来处理实时信号,也以用带有 3 个参数的信号处理器函数来处理实时信号,其建立则会用到 SA_SIGINFO 标志。 对于一个实时信号而言, 会在 siginfo_t 结构中设置如下字段。
- si_signo 字段,其值与传递给信号处理器函数的第一个参数相同。
- si_code 字段表示信号来源,内容为上节表中所示各值之一。对于通过 sigqueue()发送 的实时信号来说,该字段值总是为 SI_QUEUE。
- si_value 字段所含数据,由进程于使用 sigqueue()发送信号时在 value 参数(sigval union)中指定。正如前文指出,对该数据的解释由应用程序决定。(若信号由 kill()发送,则 si_value 字段所含信息无效。)
- si_pid 字段 信号发送进程的进程 ID
- si_uid 字段 信号发送进程的实际用户ID
使用掩码来等待信号:sigsuspend()
将解除信号阻塞和挂起进程这两个动作封装成一个原子操作
/*
返回值:
-1,并将 errno 设置为EINTR(无成功返回值);
*/
#include <setjmp.h>
int sigsuspend(const sigset_t * sigmask);
/*
进程的信号屏蔽字设置为由sigmask指向的值,在捕捉到一个信号或发生了一个会终止该进程的信号之前,
该进程会被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend 返回,
并且该进程的信号屏蔽字设置为调用sigsupend之前的值
*/
调用siguspend(),相当于以不可中断方式执行如下操作
sigprocmask(SIG_SETMASK,&mask,prevMask); // Assign new mask
pause();
sigprocmask(SIG_SETMASK,&prevMask,NULL); // Restore old mask
以同步方式等待信号 sigwaitinfo()
可以利用 sigwaitinfo()系统调用来同步接收信号。
#include <signal.h>
/*
成功 返回传递的信号值
错误 返回 -1
*/
int sigwaitinfo (const sigset_t * set, siginfo_t * info);
int sigtimedwait (const sigset_t * set,siginfo_t * info,
const struct timespec * timeout)
/*
set : 需要接收的信号集
info : siginfo_t ,信号详细信息
timeout : 指定了允许 sigtimedwait()等待一个信号的最大时长
*/
sigwaitinfo()系统调用挂起进程的执行,直至 set 指向信号集中的某一信号抵达。如果调用 sigwaitinfo()时,set 中的某一信号已经处于等待状态,那么 sigwaitinfo()将立即返回,传递来的信号就此从进程的等待信号队列中移除,并且将返回信号编号作为函数结果。info 参数如果 不为空,则会指向经过初始化处理的 siginfo_t 结构,其中所含信息与提供给信号处理器函数 的 siginfo_t 参数
sigwaitinfo()所接受信号的传递顺序和排队特性与信号处理器所捕获的信号相同,就是说, 不对标准信号进行排队处理,对实时信号进行排队处理,并且对实时信号的传递遵循低编号 优先的原则。
应该对 set 中信号集的阻塞与调用 sigwaitinfo()结合起来使用。(即便某一信号 遭到阻塞,仍然可以使用 sigwaitinfo()来获取等待信号。)如果没有这么做,而信号在首次调 用 sigwaitinfo()之前,或者两次连续调用 sigwaitinfo()之间到达,那么对信号的处理将只能依照其当前处置。
通过文件描述符来获取信号 signalfd()
Linux 提供了(非标准的)signalfd()系统调用;利用该调用可以创建一 个特殊文件描述符,发往调用者的信号都可从该描述符中读取。signalfd 机制为同步接受信号 提供了 sigwaitinfo()之外的另一种选择
#include <sys/signalfd.h>
/*
成功: 返回文件描述符
错误 : 返回 -1
*/
int signalfd (int fd, const sigset_t * mask, int flags)
/*
fd :
如果指定 fd 为−1,那么 signalfd()会创建一个新的文件描述符,用于读取 mask 中的信号;
否则,将修改与 fd 相关的 mask 值,且该 fd 一定是由之前对 signalfd()的一次调用创建而成。
mask :
一个信号集,指定了有意通过 signalfd 文件描述符来读取的信号。如同
sigwaitinfo()一样,通常也应该使用 sigprocmask()阻塞 mask 中的所有信号,
以确保在有机会读取这些信号之前,不会按照默认处置对它们进行处理.
flags :
SFD_CLOEXEC 为新的文件描述符设置 close-on-exec(FD_CLOEXEC)标志
SFD_NONBLOCK 为底层的打开文件描述设置 O_NONBLOCK 标志,以确保不会阻塞未来的读操作
*/
创建文件描述符之后,可以使用 read()调用从中读取信号。提供给 read()的缓冲区必须 足够大,至少应能够容纳一个 signalfd_siginfo 结构
struct signalfd_siginfo
{
uint32_t ssi_signo;
int32_t ssi_errno;
int32_t ssi_code;
uint32_t ssi_pid;
uint32_t ssi_uid;
int32_t ssi_fd;
uint32_t ssi_tid;
uint32_t ssi_band;
uint32_t ssi_overrun;
uint32_t ssi_trapno;
int32_t ssi_status;
int32_t ssi_int;
uint64_t ssi_ptr;
uint64_t ssi_utime;
uint64_t ssi_stime;
uint64_t ssi_addr;
uint16_t ssi_addr_lsb;
uint16_t __pad2;
int32_t ssi_syscall;
uint64_t ssi_call_addr;
uint32_t ssi_arch;
uint8_t __pad[28];
};