前面不管我们是使用 signal 信号注册函数还是 sigaction 信号注册函数,我们都只注册了带一个参数的信号处理函数 void handler(int sig)
。
实际上,我们也可以使用带参数的的信号处理函数。signal 函数没办法注册一个带附加参数的信号处理函数,但是 sigaction 可以。具体是通过 sigaction 的第二个参数 struct sigaction 结构体来指定带附加参数的信号处理函数。
1. 回忆 struct sigaction 结构体
1.1 结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
实际上,在某些系统的实现中, sa_handler 与 sa_sigaction 是以联合体的方式实现的,类似下面这样:
struct sigaction {
union {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
}_u;
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
所以我们在使用的时候,只要给其中某一个成员赋值就行了,如果你给两个成员都赋值,会导致另一个被覆盖。
1.2 如何给信号处理函数传递参数
在成员 sa_flags 上加上选项——SA_SIGINFO.
需要注意的是,sa_flags 加上选项 SA_SIGINFO 的含义仅仅是表明:在处理信号的时候,会附带一个 siginfo_t* 类型的参数。它并不表明使用该选项了就必须得 sa_sigaction 成员赋值,换句话说,即使你使用不带参数的信号处理函数,你也可以给 sa_flags 加上 SA_SIGINFO 选项。只不过这样做没什么用。
同样的,就算你不指定 SA_SIGINFO 选项,你也一样可以使用带附加参数的 sa_sigaction。
总之一句话,SA_SIGINFO 仅仅表示在处理信号的时候会附加一个 siginfo_t* 类型的参数(至于你用哪种信号处理函数,无所谓)。
1.3 sa_sigaction 成员
sa_sigaction 成员是一个函数指针。它指示的函数原型必须是下面这样:
void fun(int sig, siginfo_t *siginfo, void *context); // 函数是什么名字无所谓
- 1
该函数的第一个参数表示处理的是哪个信号,第二个参数是一个结构体,第三个参数实际上类型为 ucontext_t 类型的指针,使用的时候应该把 void* 转换为 ucontext_t*,它表示发送进程在发送信号时的上下文,这个参数目前来说没什么用,我们不用理会。这样一来,重心就在 siginfo_t 这个结构体上了。
2 struct siginfo_t 结体体
这个结构体成员众多,不过我们仅仅关心其中的三个值。如果你想看完整版本的,请看文末附录。
- 简化版
struct siginfo_t {
pid_t si_pid; /* 发送信号的进程 ID */
uid_t si_uid; /* 发送信号的进程实际用户 ID */
sigval_t si_value; /* 附加参数(联合体) */
int si_int; /* 实际上这个参数的值就是 si_value,他们相等 */
void *si_ptr; /* 同上 */
}
union sigval_t {
int sival_int;
void *sival_ptr;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
如果一来,我们需要关心的成员数量就减少很多了,便于学习。值得注意的是 si_value 成员、si_int 和 si_ptr 这几个成员,这些成员实际上由用户在发送信号的时候传递的。而且,si_value 的值,和 si_int,si_ptr 的值是完全一致的,后面的实验可以验证。
2.1 新的信号发送函数 sigqueue
可能你比较关心的是用户如何发送信号的时候传递参数到 siginfo_t 结构体中的 si_value?我们学习的 kill 函数并不支持这个功能啊?没关系,linux 系统提供了另一个信号发送函数 sigqueue 帮助我们解决这个问题,它的原型如下:
int sigqueue(pid_t pid, int sig, const union sigval value);
- 1
它的用法和 kill 函数一样,只不过多一个参数,上面这个参数类型 sigval 其实就是 sigval_t,没有任何区别。
union sigval {
int sival_int;
void *sival_ptr;
};
- 1
- 2
- 3
- 4
3. 实例
下面一共有两个程序,分别是 a 和 b。
程序 a 的功能就是使用带附加参数的信号处理函数,然后打印所有附加参数的值。
程序 b 的功能就是给程序 a 发信号,同时附带一个整数。
3.1 程序 a
// a.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig, siginfo_t* siginfo, void* context) {
if (sig == SIGQUIT) printf("hello SIGQUIT\n");
if (siginfo) {
printf("si_signo = %d\n", siginfo->si_signo);
printf("si_errno = %d\n", siginfo->si_errno);
printf("si_code = %d\n", siginfo->si_code);
// printf("si_trapno = %d\n", siginfo->si_trapno); 这个成员依赖于架构
printf("si_pid = %d\n", siginfo->si_pid);
printf("si_uid = %d\n", siginfo->si_uid);
printf("si_status = %d\n", siginfo->si_status);
printf("si_utime = %ld\n", siginfo->si_utime);
printf("si_stime = %ld\n", siginfo->si_stime);
printf("si_value{\n");
printf("\tsival_int = %08x(%d)\n", siginfo->si_value.sival_int, siginfo->si_value.sival_int);
printf("\tsival_ptr = %p\n", siginfo->si_value.sival_ptr);
printf("}\n");
printf("si_int = %08x(%d)\n", siginfo->si_int, siginfo->si_value.sival_int);
printf("si_ptr = %p\n", siginfo->si_ptr);
printf("si_overrun= %d\n", siginfo->si_overrun);
printf("si_timerid= %d\n", siginfo->si_timerid);
printf("si_addr = %p\n", siginfo->si_addr);
printf("si_band = %ld\n", siginfo->si_band);
printf("si_fd = %d\n", siginfo->si_fd);
}
printf("---------------------------------------------\n");
}
int main() {
printf("I'm %d\n", getpid());
struct sigaction act;
act.sa_sigaction = handler; // 使用带附加参数的信号处理函数
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO; // 发送的信号带参数
sigaction(SIGQUIT, &act, NULL);
while(1) {
write(STDOUT_FILENO, ".", 1);
pause();
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
3.2 程序 b
// b.c
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
if (argc < 2) printf("usage: %s <pid>\n", argv[0]);
pid_t pid = atoi(argv[1]);
union sigval val;
while(1) {
scanf("%d", &val.sival_int);
if (sigqueue(pid, SIGQUIT, val) < 0) { // 发送带附加值的信号
perror("sigqueue");
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
3.3 编译与运行
$ gcc a.c -o a
$ gcc b.c -o b
- 1
- 2
- 先运行程序 a
$ ./a
- 1
屏幕打印:
I'm 3959
.
- 1
- 2
- 运行程序 b
再开启一个终端,键入:
$ ./b 3959
- 1
这时候,再键入任意数字
1234
- 1
- 结果
程序 a 会打印:
I'm 3959
.hello SIGQUIT
si_signo = 3 // 注册的是 SIGQUIT 信号
si_errno = 0
si_code = -1
si_pid = 3966 // 这是程序 b 的进程 id 号
si_uid = 1000 // 程序 b 的实际用户 id 号
si_status = 1234 // 发现这个值也是 1234
si_utime = 0
si_stime = 0
si_value{
sival_int = 000004d2(1234) // 这个值是我们通过 sigqueue 传递过来的,后面的都是
sival_ptr = 0x4d2
}
si_int = 000004d2(1234)
si_ptr = 0x4d2
si_overrun= 1000
si_timerid= 3966
si_addr = 0xf7e
si_band = 3966
si_fd = 1000
---------------------------------------------
.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
4. 总结
- 掌握 sa_flags 选项 SA_SIGINFO,知道它的含义
- 掌握 siginfo_t 结构体,知道常用的成员
- 掌握 sigval_t 和 sigval 结构体,知道这个值是从何处而来
- 掌握 sigqueue 信号发送函数
本篇的量有点大,不想写代码的同志,请复制粘贴到你的环境里编译。无论如何,动手练一练。
附录
完整版的 siginfo_t(实际上有些系统实现里,成员会比这个更多)
struct 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 */
sigval_t 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 */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}