信号处理三大特性

问题:三种注册信号与处理函数的方法又什么区别?

信号的 OneShot 特性

  • System V 风格的 signal 函数,注册的信号处理是一次性的

    • 进程收到信号后,调用由 signal 注册的处理函数
    • 处理函数一旦执行,之后进程通过默认的方式处理后续相同信号
    • 如果想要重复触发,那么必须再次调用 signal 注册处理函数
BSD 风格的 siganl 函数不存在 OnetShot,能够自动反复触发处理函数的调用。
大多数 linux 发行版默认风格的 siganl 函数,行为与 BSD 风格一致

信号的自身屏蔽特性

  • 在信号处理函数执行期间,很可能再次收到信号

    • 即:处理 A 信号的时候,再次收到 A 信号
  • 对于 System V 风格的 signal 函数,会引起信号处理函数的重入【对于共享资源存在缺陷

    • 即:调用处理函数的过程中,再次触发处理函数的调用(打断执行)
System V 案例分析:
    1. 进程收到 A 信号,调用信号处理函数
    2. 在第一次的信号处理函数执行过程中(未结束),此时收到第二次 A 信号
    3. 第一次 A 信号的信号处理函数停止运行 ,执行第二次 A 信号的处理函数
    4. 当第二次 A 信号处理函数执行完成,继续第一次信息处理函数运行

image.png

  • 在注册信号处理函数时:

    • System V 风格的 signal 不屏蔽任何信号
    • BSD 风格的 signal 会屏蔽(排队,不出现打断)当前注册的信号
BSD 案例分析:
    1. 进程收到 A 信号,调用信号处理函数
    2. 在第一次的信号处理函数执行过程中(未结束),此时收到第二次 A 信号
    3. 第二次 A 信号等待
    4. 第一次的信号处理函数执行结束,开始执行第二次 A 的信号处理函数

问:BSD 风格的 signal 函数,处理 A 信号期间,如果收到 B 信号会发生什么?

系统调用的重启特性

  • 系统调用期间,可能收到信号,此使进程必须从系统调用中返回
  • 对于执行较长的系统调用 (write / read), 被信号中断的可能性很大

如果希望信号处理之后,被中断的系统调用能够重启,则可用通过条件 errno == EINTR 判断重启系统调用

信号处理深度实验 - 上

系统调用重入示例代码

pid_t r_wait(int *status)
{
    int ret = -1;

    // 手工重启系统调用的方案
    while ( status && 
            ((ret = wait(status)) == -1) &&  // wait 等待子进程结束,当在等待过程中收到信息,信号处理函数被调用,处理返程,主执行流返回,wait 返回 -1
            (errno == EINTR)); // 信号处理函数执行完成返回,errno 被赋值为 EINTR

    return ret;
}
  • System V 风格的 signal 函数

    • 系统调用被信号中断后,直接返回 -1, 并且 errno == EINTR (需要手动重启)
  • BSD 风格的 signal 函数

    • 系统调用被中断,内核在信号处理函数结束后,自动重启系统调用
大多数 linux 发行版默认风格的 siganl 函数,行为与 BSD 风格一致

编程实验

OneShot 特性

system_v.c
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int main(void)
{
    sysv_signal(SIGINT, signal_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Chandler : sig = 2 // 键盘 ctrl + c, 信号被捕获
^C                  // 信号未被捕获,进程结束
bsd.c
#define _XOPEN_SOURCE    600
#define _POSIX_C_SOURCE  200800L

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int main(void)
{
    bsd_signal(SIGINT, signal_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Chandler : sig = 2  // 信号被持续捕获
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
linux_default.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int main(void)
{
    signal(SIGINT, signal_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}
wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Chandler : sig = 2  // 信号被持续捕获
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2

信号重入实验

system_v.c
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void delay_handler(int sig)
{
    int i = 0;

    sysv_signal(SIGINT, delay_handler);  // 手工注册信号处理函数,规避 oneshot 特性

    printf("begin delay handler ...\n");

    for (i=0; i<5; ++i) {
        printf("sleep %d\n", i);
        sleep(1);
    }

    printf("end delay handler ...\n");
}

int main(void)
{
    sysv_signal(SIGINT, delay_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Cbegin delay handler ...
sleep 0
sleep 1
^Cbegin delay handler ...  // 信号处理函数重入
sleep 0
sleep 1
sleep 2
sleep 3
sleep 4
end delay handler ...
sleep 2
sleep 3
sleep 4
end delay handler ...
bsd.c
#define _XOPEN_SOURCE    600
#define _POSIX_C_SOURCE  200800L

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void delay_handler(int sig)
{
    int i = 0;

    printf("begin delay handler ...\n");

    for (i=0; i<5; ++i) {
        printf("sleep %d\n", i);
        sleep(1);
    }

    printf("end delay handler ...\n");
}

int main(void)
{
    bsd_signal(SIGINT, delay_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Cbegin delay handler ...
sleep 0
sleep 1
sleep 2
sleep 3
^Csleep 4       // ctrl + c 再次触发信号
end delay handler ...
begin delay handler ...
sleep 0        // 排队,等待当前信号处理完成之后才开始执行
sleep 1
sleep 2
sleep 3
sleep 4
end delay handler ...
linux_default.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void delay_handler(int sig)
{
    int i = 0;

    printf("begin delay handler ...\n");

    for (i=0; i<5; ++i) {
        printf("sleep %d\n", i);
        sleep(1);
    }

    printf("end delay handler ...\n");
}

int main(void)
{
    signal(SIGINT, delay_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Cbegin delay handler ...
sleep 0
sleep 1
sleep 2
sleep 3
^Csleep 4       // ctrl + c 再次触发信号
end delay handler ...
begin delay handler ...
sleep 0        // 排队,等待当前信号处理完成之后才开始执行
sleep 1
sleep 2
sleep 3
sleep 4
end delay handler ...

信号处理深度实验 - 下 (系统调用重启)

system_v.c
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int r_read(char *data, int len)
{
    int ret = -1;

    while (data &&
           ((ret = read(STDIN_FILENO, data, len)) == -1) &&
           (errno == EINTR)) {
            printf("restart syscall mannually ...\n");
    }

    if (ret != -1) {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32] = {0};

    sysv_signal(SIGINT, signal_handler);

    r_read(buf, 32);

    printf("input: %s\n", buf);

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
1234^Chandler : sig = 2
restart syscall mannually ...      // 系统调用重启
bsd.c
#define _XOPEN_SOURCE    600
#define _POSIX_C_SOURCE  200800L

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int r_read(char *data, int len)
{
    int ret = -1;

    while (data &&
           ((ret = read(STDIN_FILENO, data, len)) == -1)) {
            printf("restart syscall mannually ...\n");
    }

    if (ret != -1) {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32] = {0};

    bsd_signal(SIGINT, signal_handler);

    r_read(buf, 32);

    printf("input: %s\n", buf);

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
1234^Chandler : sig = 2
1234^Chandler : sig = 2

restart syscall mannually ... 未被打印,证明 bsd 风格会自动重启系统调用
linux_default.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int r_read(char *data, int len)
{
    int ret = -1;

    while (data &&
           ((ret = read(STDIN_FILENO, data, len)) == -1)) {
            printf("restart syscall mannually ...\n");
    }

    if (ret != -1) {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32] = {0};

    signal(SIGINT, signal_handler);

    r_read(buf, 32);

    printf("input: %s\n", buf);

    return 0;
}
wu_tiansong@ubuntu-server:~/test$ ./a.out 
1234^Chandler : sig = 2
1234^Chandler : sig = 2

表现与 bsd 风格一致
restart syscall mannually ... 未被打印,证明 bsd 风格会自动重启系统调用
问题:BSD 风格的 signal 函数,处理 A 信号期间,如果收到 B 信号会发生什么?

答: bsp 风格的信号处理函数,可以防止同一个信号的重入。但是在处理不同信号时,会有重入问题的发生。

main.c
#define _XOPEN_SOURCE    600
#define _POSIX_C_SOURCE  200800L

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

void delay_handler(int sig)
{
    int i = 0;

    printf("begin delay handler ...\n");

    for (i=0; i<5; ++i) {
        printf("sleep %d\n", i);
        sleep(1);
    }

    printf("end delay handler ...\n");
}

int main(void)
{
    bsd_signal(40, signal_handler);

    bsd_signal(SIGINT, delay_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}
test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    printf("send sig(%d) to process (%d)...\n", sig, pid);

    kill(pid, sig);

    raise(SIGINT);

    while (1) {
        printf("while...\n");
        sleep(1);
    }

    return 0;
}

终端1,输出:

u_tiansong@ubuntu-server:~/test$ ./a.out
^Cbegin delay handler ...
sleep 0
sleep 1
handler : sig = 40  // 终端 2 此时发送信号,可以看到,信号处理函数立即执行
sleep 2
sleep 3
sleep 4
end delay handler ...

终端2, 输出:

wu_tiansong@ubuntu-server:~/test$ ps -ajx | grep "a.out"
4044972 4045207 4045207 4044972 pts/12   4045207 S+    1007   0:00 ./a.out
4039252 4045263 4045262 4039252 pts/13   4045262 S+    1007   0:00 grep --color=auto a.out
wu_tiansong@ubuntu-server:~/test$ ./test.out 4045207 40
send sig(40) to process (4045207)...

注意事项

  • 并非所有的系统调用对信号中断都表现相同行为

    • 一些系统调用支持信号中断后自动重启

      • read(), write(), wait(), waitpid(), ioctl(), ...
    • 一些系统调用完全不支持中断后自动重启

      • poll(), select(), usleep(), ...
可通过下面命令查询:man 7 signal 
函数OneShot屏蔽自身 A A屏蔽其它 A B重启系统调用
signal(...)falsetruefalsetrue
sysv_signal(...)truefalsefalsefalse
bsd_signal(...)falsetruefalsetrue

在信号处理上,Linux 系统通常更接近 BSD 风格的操作;
默认的 signal 函数在不同的 Linux 发行版上语义可能不同【即不同 Linux 发行版的 signa 可能会有不同表现】,从代码以执行角度,避免直接使用 signal(...) 函数

初探现代信号处理

现代信号处理注册函数

#include <signal.h>

int sigaction(int signum,                              // 信号值
              const struct sigaction *act,             // 信号处理方式
              struct sigaction *oldact);               // 原有的处理信号的方式,通常指定为 NULL,不使用

struct sigaction {
    void (*sa_handler)(int);                          // 信号处理函数,简易版本
    void (*sa_sigaction)(int, siginfo_t *, void*);    // 信号处理函数,复杂版本,可携带更多信息
    sigset_t sa_mask;                                 // 描述信号处理期间,屏蔽的信号集合
    int sa_flags;                                     // 信号处理特性标志位,例如,OneShot、Restart
    void (*sa_restorer)(void);                        // 废弃不再使用
};

信号屏蔽与标记

  • sigset_t sa_mask

    • 信号屏蔽,sa_mask = SIGHUP | SIGINT | SIGUSER1; (说明,在处理信号时,会屏蔽右侧三个信号,直到当前信号处理完成)
    • 注意:并不是所有信号都可以被屏蔽,如:SIGKILL、SIGSTOP
  • int sa_flags

    • 信号特性: sa_flags = SA_ONESHOT | SA_RESTART;
    • 特殊特性(SA_SIGINFO), 信号处理时能够收到额外的附加信息

      • 信号发送也是一种进程间的通讯方式,可通过设置 SA_SIGINFO 完成

信号状态小知识

  • 信号产生

    • 信号来源,如: SI_KERNEL, SI_USER, SI_TIMER, ...
  • 信号未决

    • 从信号产生到信号被接收的状态 (处于未决状态的信号必然是存在的)
  • 信号传送达

    • 信号送达进程,被进程接收(忽略,默认处理,自定义处理)

信号屏蔽 VS 信号阻塞

  • 信号屏蔽 (信号未决)

    • 信号处理函数执行期间,被屏蔽的信号不会被递送给进程(针对多个信号)
    • sa_mask = SIGHUP | SIGINT | SIGUSER1;
  • 信号阻塞 (信号未决)

    • 信号处理函数执行期间,当前信号不会传递给进程(仅指 ”当前信号“
    • sa_flags = SA_RESTART | SA_NODEFER; [SA_RESTART 支持系统调用自动重启 ; SA_NODEFER 不阻塞当前信号]
sigset_t sa_mask 是一个信号集,在调用该信号捕捉函数之前,将需要block的信号加入这个sa_mask,【仅当信号捕捉函数正在执行时,才阻塞sa_mask中的信号】,当从信号捕捉函数返回时进程的信号屏蔽字复位为原先值。

Q1:这个复位动作是sigaction函数内部处理,还是由调用者自己处理呢?
  由sigaction函数自动复位,不用我自己再去处理。

Q2:设置sa_mask的目的?
  在调用信号处理程序时就能阻塞某些信号。注意仅仅是在信号处理程序正在执行时才能阻塞某些信号,如果信号处理程序执行完了,那么依然能接收到这些信号。
    在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号,也就是说自己也被阻塞,除非设置了SA_NODEFER。
    因此保证了在处理一个给定的信号时,如果这种信号再次发生,通常并不将它们排队,所以【如果在某种信号被阻塞时它发生了5次,那么对这种信号解除阻塞后,其号处理函数通常只会被调用一次】。

Q3:对于不同信号,当信号A被捕捉到并信号A的handler正被调用时,信号B产生了,
  3.1如果信号B没有被设置阻塞,那么正常接收信号B并调用自己的信号处理程序。另外,如果信号A的信号处理程序中有sleep函数,那么当进程接收到信号B并处理完后,sleep函数立即返回(如果睡眠时间足够长的话)
  3.2如果信号B有被设置成阻塞,那么信号B被阻塞,直到信号A的信号处理程序结束,信号B才被接收并执行信号B的信号处理程序。

  如果在信号A的信号处理程序正在执行时,信号B连续发生了多次,那么当信号B的阻塞解除后,信号B的信号处理程序只执行一次。
  如果信号A的信号处理程序没有执行或已经执行完,信号B不会被阻塞,正常接收并执行信号B的信号处理程序。

Q4:对于相同信号,当一个信号A被捕捉到并信号A的handler正被调用时,
  4.1 又产生了一个信号A,第二次产生的信号被阻塞,直到第一次产生的信号A处理完后才被递送;
  4.2 如果连续产生了多次信号,当信号解除阻塞后,信号处理函数只执行一次。

现代信号处理实验

现代信号处理注册示例

int main(void)
{
    struct sigaction act = {0};

    act.sa_handler = delay_handler;
    act.sa_flags = SA_RESTART | SA_NODEFER;

    sigaddset(&act.sa_mask, 40);
    sigaddset(&act.sa_mask, SIGINT);

    sigaction(40, &act, NULL);
    sigaction(SIGINT, &act, NULL);

    return 0;
}
main.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int r_read(char *data, int len)
{
    int ret = -1;

    while (data &&
           ((ret = read(STDIN_FILENO, data, len)) == -1)) {
            printf("restart syscall mannually ...\n");
    }

    if (ret != -1) {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32] = {0};

    struct sigaction act = {0};

    act.sa_handler = signal_handler;
    act.sa_flags = SA_RESTART | SA_ONESHOT;

    sigaddset(&act.sa_mask, 40);
    sigaddset(&act.sa_mask, SIGINT);

    sigaction(40, &act, NULL);
    sigaction(SIGINT, &act, NULL);

    r_read(buf, 32);

    printf("input: %s\n", buf);

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
^Chandler : sig = 2
^C

传统的 sys_v, bsd, 以及默认风格的 signal 函数,都是由 sigaction 实现的
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
}

int main(void)
{
    signal(SIGINT, signal_handler);

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ strace ./a.out 
execve("./a.out", ["./a.out"], 0x7fff6cce8e00 /* 34 vars */) = 0
brk(NULL)                               = 0x55ac932af000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffce2952210) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=60964, ...}) = 0
mmap(NULL, 60964, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f038e7c0000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300A\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\207\2631\3004\246E\214d\316\t\30099\351G"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029592, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f038e7be000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\207\2631\3004\246E\214d\316\t\30099\351G"..., 68, 880) = 68
mmap(NULL, 2037344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f038e5cc000
mmap(0x7f038e5ee000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f038e5ee000
mmap(0x7f038e766000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19a000) = 0x7f038e766000
mmap(0x7f038e7b4000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f038e7b4000
mmap(0x7f038e7ba000, 13920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f038e7ba000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f038e7bf540) = 0
mprotect(0x7f038e7b4000, 16384, PROT_READ) = 0
mprotect(0x55ac9251f000, 4096, PROT_READ) = 0
mprotect(0x7f038e7fc000, 4096, PROT_READ) = 0
munmap(0x7f038e7c0000, 60964)           = 0

// 注意这里, sa_mask 屏蔽 SIGINT 信号;sa_flags (SA_RESTORER等同于SA_ONESHOT)自定义信号处理函数只生效一次,系统调用自动重启
rt_sigaction(SIGINT, {sa_handler=0x55ac9251d149, sa_mask=[INT], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f038e60f090}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

现代信号发送与处理

现代信号发送

#include <signal.h>

int sigqueue(pid_t pid,                   // 给指定进程发送信号
             int sig,                     // 信号值
             const union sigval value);   // 信号附带的数据(4字节) [进程间通讯的一种方式]

union sigval {
    int sival_int;
    void *sival_ptr;                     // 对于 linux 进程,指针成员无意义 (因为 linux 进程间是相互隔离的)
};
  • sigqueue(...) 的黄金搭档是 sigaction(...)
  • sa_flags 设置 SA_SIGINFO 标志位,可使用三参数信号处理函数
void handler(int sig, 
            siginfo_t *info,      // 信号携带的数据
            void *ucontext)       // 类型为 ucontext_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
                                 (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) */
   }

现代信号发送处理示例

main.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig, siginfo_t *info, void *ucontext)
{
    printf("handler : sig = %d\n", sig);
    printf("handler : info->si_signo = %d\n", info->si_signo);
    printf("handler : info->si_code = %d\n", info->si_code);
    printf("handler : info->si_pid = %d\n", info->si_pid);
    printf("handler : info->si_value = %d\n", info->si_value.sival_int);
}

int main(void)
{
    char buf[32] = {0};

    struct sigaction act = {0};

    act.sa_sigaction = signal_handler;
    act.sa_flags = SA_SIGINFO;

    sigaction(40, &act, NULL);
    sigaction(SIGINT, &act, NULL);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./a.out 
handler : sig = 40
handler : info->si_signo = 40
handler : info->si_code = -1
handler : info->si_pid = 91743
handler : info->si_value = 123456
test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);
    union sigval sv = {123456};

    printf("current pid(%d) ..\n", getpid());
    printf("send sig(%d) to process (%d)...\n", sig, pid);

    sigqueue(pid, sig, sv);

    raise(SIGINT);

    while (1) {
        printf("while...\n");
        sleep(1);
    }

    return 0;
}

输出:

wu_tiansong@ubuntu-server:~/test$ ./test.out 91483 40
current pid(91743) ..
send sig(40) to process (91483)...

问题:利用信号搞进程间通讯靠谱吗?

TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧