42-带参数的信号

来源:互联网 发布:天天卡牌淘宝杂货铺 编辑:程序博客网 时间:2024/05/17 01:48

前面不管我们是使用 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);};

实际上,在某些系统的实现中, 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 如何给信号处理函数传递参数

在成员 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); // 函数是什么名字无所谓

该函数的第一个参数表示处理的是哪个信号,第二个参数是一个结构体,第三个参数实际上类型为 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;};

如果一来,我们需要关心的成员数量就减少很多了,便于学习。值得注意的是 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);

它的用法和 kill 函数一样,只不过多一个参数,上面这个参数类型 sigval 其实就是 sigval_t,没有任何区别。

union sigval {    int   sival_int;    void *sival_ptr;};

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;}

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");    }     }}

3.3 编译与运行

$ gcc a.c -o a$ gcc b.c -o b
  • 先运行程序 a
$ ./a

屏幕打印:

I'm 3959.
  • 运行程序 b

再开启一个终端,键入:

$ ./b 3959

这时候,再键入任意数字

1234
  • 结果

程序 a 会打印:

I'm 3959.hello SIGQUITsi_signo  = 3  // 注册的是 SIGQUIT 信号si_errno  = 0si_code   = -1si_pid    = 3966 // 这是程序 b 的进程 id 号si_uid    = 1000 // 程序 b 的实际用户 id 号si_status = 1234 // 发现这个值也是 1234si_utime  = 0si_stime  = 0si_value{        sival_int = 000004d2(1234)  // 这个值是我们通过 sigqueue 传递过来的,后面的都是        sival_ptr = 0x4d2}si_int    = 000004d2(1234)si_ptr    = 0x4d2si_overrun= 1000si_timerid= 3966si_addr   = 0xf7esi_band   = 3966si_fd     = 1000---------------------------------------------.

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 */}
1 0