42-带参数的信号

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

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 */
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值