每一个信号都有一个信号处理函数,可以是SIG_IGN, SIG_DFL或者是用户自定义的处理函数。使用用户自定义的处理函数需要注册,注册接口有如下两种。
第一种是signal调用
#include <signal.h>
/**
* sighandler_t是GNU的扩展,如果在glibc下面使用的话,编译的时候需要加上-D_GNU_SOURCE
* 或者手动定义
*/
typedef void (*sighandler_t)(int);
/**
* 为信号signum注册信号处理函数handler
* 成功返回该信号之前的处理函数,失败返回SIG_ERR并将失败原因填写到errno中
*/
sighandler_t signal(int signum, sighandler_t handler);
使用signal调用会有兼容性问题,尤其是移植到其它UNIX系统上,所以推荐使用第二种信号注册函数sigaction,该函数功能相对signal而言,能够提供更多功能。
#include <signal.h>
/**
* 注册信号处理函数,成功返回0,失败返回-1并置errno
* 参数act存储待注册的信号处理函数结构体
* 如果oldact非空的话,旧的信号处理函数会存储到该结构体中
*/
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
该结构在注册信号处理函数sigaction中使用
1. sa_handler是一个参数为信号值的处理函数
2. sa_sigaction也是一个信号处理函数,不过它有三个参数,能够获取到处信号值以外更多
信息,当sa_flags中包含SA_SIGINFO标志位的时候需要用到该函数。
3. sa_mask是信号处理函数执行期间的屏蔽信号集。就是说在信号处理函数执行期间,屏蔽某
些信号。但是不是所有信号都能够被屏蔽,SIGKILL和SIGSTOP这两个信号就无法屏
蔽,因为操作系统自身要能够控制住进程。
4. sa_flags可以是下面这些值的集合:
1. SA_NOCLDSTOP,
这个标志位只用于SIGCHLD信号。父进程可以检测子进程三个事件,子进程终止、
子进程停止、子进程回复。SA_NOCLDSTOP标志位用于控制后两个事件。即一旦父进程
为SIGCHLD信号设置了这个标志位,那么子进程停止和子进程恢复这两件事情,就无需
向父进程发送SIGCHLD信号
2. SA_NOCLDWAIT
这个标志只用于SIGCHLD信号,它可控制子进程终止时候的行为,如果父进程
为SIGCHLD设置了SA_NOCLDWAIT标志位,那么子进程终止退出时,就不会进入僵尸
状态,而是直接自行了断。但是对Linux而言,子进程仍然会发送SIGCHLD信号,这
点和上面的SA_NOCLDSTOP略有不同。
3. SA_ONESHOT和SA_RESETHAND
这两个标志位本质是一样的,表示信号处理函数是一次性的,信号递送出去以后,信号
处理函数便恢复成默认值SIG_DFL.
4. SA_NODEFER和SA_NOMASK
这两个标志位的作用是一样的,信号处理函数执行期间,不阻塞当前信号。
5. SA_RESTART
这个标志位表示,如果系统调用被信号中断,则不返回错误,而是自动重启系统调用。
6. SA_SIGINFO
这个标志位表示信号发送者会提供额外的信息。这种情况下,信号处理函数应该为
三参数的函数。
当sa_flags含有SA_SIGINFO的时候 ,需要使用带三个参数的处理函数:
void
handler(int sig, siginfo_t *info, void *ucontext)
{
...
}
第一个参数 sig 为信号值
第三个参数 ucontext,该结构体提供了进程上下文信息,通常都不会使用到该参数,具体细节
可参考man sigreturn
第二个参数 info 是一个siginfo_t类型的指针,包含了信号更多的信息。该结构体如下:
siginfo_t {
int si_signo; /* 信号值 */
int si_errno; /* An errno value */
int si_code; /* 信号来源,可以通过该值来判断信号来源
* 可选值及含义
* SI_USER : 调用kill 或 raise的用户进程
* SI_TKILL :调用tkill或tgkill的用户进程
* SI_QUEUE : 调用sigqueue的用户进程
* SI_MESGQ : 消息到达POSIX消息队列
* SI_KERNEL : 内核产生的信号
* SI_ASYNCIO : 异步I/O操作完成
* SI_TIMER: POSIX定时器到期
*/
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* 信号发送进程ID */
uid_t si_uid; /* 信号发送进程这是用户ID */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* 使用sigqueue函数发送信号时携带的伴随数据 */
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
(since Linux 3.5) */
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) */
}
上面的sigval_t结构体定义如下:
union sigval {
int sival_int;
void *sival_ptr;
}
通过指定sigqueue函数的第三个参数,可以传递给一个int值或者指针值个目标进程。考虑
到不同的进程有各自独立的地址空间,传递指针到另一个进程几乎没有意义。
参考资料:
1. 《Linux 环境编程,从应用到内核》高峰,李彬著
2. man signal : https://linux.die.net/man/2/signal
man sigaction : http://www.man7.org/linux/man-pages/man2/sigaction.2.html