信号可以被分为两组:
实时信号的工作特点:
实时信号由下列 Posix.1 特性产生 ,它们由包含在传递给信号处理程序的 siginfo_t 结构中的 si_code 值标识。
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 字段以命名异常地址的位置,在一些结构上还可能会填充其它的字段。
在 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 处理。