POSIX 实时信号
信号可以被分为两组:
- 信号值在 SIGRTMIN 与 SIGRTMAX 之间的实时信号
- 其它所有的信号,如 SIGALRM,SIGINT,SIGKILL 等
实时信号的工作特点:
- 信号被入队。同一个信号多次触发时,事件以 FIFO 的方式入队。
- 当多个在 SIGRTMIN 到 SIGRTMAX 之间未屏蔽的信号被入队时,数值小的信号会在数值大的信号前发送,这意味着 SIGRTMIN 信号的优先级最高。
- 当一个非实时信号触发时,信号处理函数的唯一参数是信号值。实时信号比这些信号携带更多的信息,实时信号的信号处理函数支持带一个 siginfo_t 结构体类型的信息。
- 一些新的函数被定义以支持实时信号,这些新函数允许发送者传输一个 sigval 结构。例如 sigqueue 函数替代 kill 函数来发送信号到某些进程。
实时信号由下列 Posix.1 特性产生 ,它们由包含在传递给信号处理程序的 siginfo_t 结构中的 si_code 值标识。
- SI_ASYNCIO 异步 I/O 请求完成
- SI_MESGQ 新的消息被投递到空的 Posix 消息队列中
- SI_QUEUE 使用 sigqueue 函数发送信号
- SI_TIMER 一个 timer_settime 函数设置的定时器时间耗尽
- SI_USER 此信号使用 kill 函数发送
linux sigaction 函数
linux 中可以使用 sigaction 函数注册实时信号的处理函数,非实时的信号处理函数也可以使用此函数注册并携带更多的信息,其原型如下:
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
signum 为待设定的信号值,act 为信号处理函数属性的封装结构,oldact 用于返回旧的配置。sigaction 结构体的定义如下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_handler 用以兼容旧的只以信号值为参数的信号处理函数注册,sa_sigaction 为新的信号处理函数原型,sa_mask 设定信号掩码,sa_flags 设置一些 flags,sa_restorer 用于设定从信号处理函数中恢复旧的现场需要执行的函数。在 linux 内核信号机制需要解决的两个关键问题 这篇博文中有对 sa_restorer 的相关描述,libc 中的实现是一个调用 rt_sigreturn 系统调用的函数。
siginfo_t 结构的定义如下:
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
union sigval si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count;
POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
void *si_call_addr; /* Address of system call instruction
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
不同的信号会填充 siginfo_t 结构中的不同字段,例如 sigaction manual 中对 SIGBUS、SIGSEGV 等信号填充的字段的描述信息如下:
* SIGILL, SIGFPE, SIGSEGV, SIGBUS, and SIGTRAP fill in si_addr with the address of the fault. On
some architectures, these signals also fill in the si_trapno field.
Some suberrors of SIGBUS, in particular BUS_MCEERR_AO and BUS_MCEERR_AR, also fill in
si_addr_lsb. This field indicates the least significant bit of the reported address and there‐
fore the extent of the corruption. For example, if a full page was corrupted, si_addr_lsb con‐
tains log2(sysconf(_SC_PAGESIZE)). When SIGTRAP is delivered in response to a ptrace(2) event
(PTRACE_EVENT_foo), si_addr is not populated, but si_pid and si_uid are populated with the re‐
spective process ID and user ID responsible for delivering the trap. In the case of seccomp(2),
the tracee will be shown as delivering the event. BUS_MCEERR_* and si_addr_lsb are Linux-spe‐
cific extensions.
The SEGV_BNDERR suberror of SIGSEGV populates si_lower and si_upper.
The SEGV_PKUERR suberror of SIGSEGV populates si_pkey.
这些信号会填充 si_addr 字段以命名异常地址的位置,在一些结构上还可能会填充其它的字段。
siginfo_t 的一个使用场景
在 dpdk 支持网卡热插拔技术需要解决的几个关键问题 这篇文章中,我描述了使用 sigbus 信号携带的 siginfo_t 结构中的 si_addr 字段来处理网卡热拔出时程序仍旧访问网卡 bar 空间触发总线异常的问题。
dpdk 中注册 sigbus 信号处理函数的代码如下:
struct sigaction action;
.................................
sigemptyset(&mask);
sigaddset(&mask, SIGBUS);
action.sa_flags = SA_SIGINFO;
action.sa_mask = mask;
action.sa_sigaction = sigbus_handler;
sigbus_need_recover = !sigaction(SIGBUS, &action, &sigbus_action_old);
可以看到它设置了 SA_SIGINFO 标志以支持 SIGBUS 信号携带一个 siginfo_t 结构,在 sigbus_handler 函数中使用如下代码访问 si_addr 字段:
ret = rte_bus_sigbus_handler(info->si_addr);
rte_bus_sigbus_handler 中负责查询异常的地址是否属于某个总线,并调用总线注册的 sigbus_handler 处理。