信号优先级分析

问题:对于同一个进程,如果存在两个不同的未决实时信号,那么先处理谁?

信号优先级的概念

  • 信号的本质是一种软中断(中断有优先级,信号也有优先级)
  • 对于同一个未决信号,按照发送先后顺序送达进程
  • 对于不同的未决实时信号,信号值越小优先级越高
  • 不可靠信号与可靠信号同时未决

    • 严格意义上,没有明确规定优先级
    • 实际上,Linux 优先递送不可靠信号 【隐含,不可靠信号的信号值小】

  • 多个不可靠信号同时未决,优先递送谁?

    • 优先递送硬件相关信号

      • SIGSEGV, SIGBUS, SIGILL, SIGTRAP, SIGFPE, SIGSYS
    • 优先递送信号值小的不可靠信号
    • 不可靠信号优先于可靠信号递送

信号优先级实验设计

  • 目标:验证信号的优先级

    • 场景:不可靠信号 VS 不可靠信号 ; 不可靠 VS 可靠 ; 可靠 VS 可靠
  • 方案: 对目标进程发送 N 次“无”序信号,验证信号递达进程的先后顺序
  • 预备函数:

    • int sigaddset(sigset_t *set, int signum);
    • int sigfillset(sigset_t *set);
    • int sigemptyset(sigset_t *set);
    • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

  • 需要思考的问题

    • 如何使得多个信号同时未决,且以优先级方式递达进程?
    • 如何记录和对比信号的递达次序和发送次序
    • 对于实验中设计的不可靠信号,是否特殊处理?
    • 对于实验中设计的可靠信号,是否特殊处理?(信号 32 与 信号 33 被 NPTL 线程库证用)
    • 。。。

信号优先级实验设计(发送端)

for (i=0; i<num; ++i) {
    int sig = 0;

    do {
        sig = rand() % 64 + 1;
    } while (find(special, strlen, sig));  // 特殊信号判断不合法重新生成

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

    sv.sival_int = i + 1;

    sigqueue(pid, sig, sv);
}

信号优先级实验设计(接收端)

for (i=0; i<=64; ++i) {
    sigaddset(&act.as_mask, i);  // 在处理信号时,屏蔽 i 信号,直到当前信号处理完成,避免被打断
}

for (i=0; i<=64; ++i) {
    sigaction(i, &act, NULL);    // 设置信号处理函数
}

sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);  // 屏蔽所有信号,不响应所有信号

for (i=0; i<15; ++i) {                 // 等待 15s,让所有信号未决
    sleep(1);
    printf("i = %d\n", i);
}

sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);  // 解除屏蔽,优先级高的信号先递达 
typedef struct _sig_info_ {
    int sigk
    int index;
}SigInfo;

static int g_index = 0;
static SigInfo g_sig_arr[80] = {0};

void signal_handler(int sig, siginfo_t *info, void *ucontext)
{
    g_sig_arr[g_index].sig = info->si_signo;  // 优先级越高的信号,数组的下标越小
    g_sig_arr[g_index].index = info->si_value.sival_int;
    g_index++;
}

信号优先级实验

可靠信号

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

typedef struct _sig_info {
    int sig;
    int index;
}SigInfo;

static int g_index = 0;
static SigInfo g_sig_arr[128] = {0};

static void signal_handler(int sig, siginfo_t *info, void *ucontext)
{
    g_sig_arr[g_index].sig= info->si_signo;
    g_sig_arr[g_index].index = info->si_value.sival_int;
    ++g_index;
}

int main(int argc, char *argv[])
{
    struct sigaction act = {0};
    sigset_t set = {0};
    int i = 0;

    printf("current pid(%d) ..\n", getpid());

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

    for (i=1; i<=64; ++i) {
        sigaddset(&act.sa_mask, i);
    }

    for (i=1; i<=64; ++i) {
        sigaction(i, &act, NULL);
    }

    sigfillset(&set);
    sigprocmask(SIG_SETMASK, &set, NULL);

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

    sigemptyset(&set);
    sigprocmask(SIG_SETMASK, &set, NULL);

    for (i=0; i<g_index; ++i) {
        printf("%d : %d\n", g_sig_arr[i].sig, g_sig_arr[i].index);
    }

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

static int find(int *arr, int len, int v)
{
    int i = 0;
    int ret = 0;

    for (i=0; i<len && !ret; i++) {
        ret = ret || (arr[i] == v);
    }

    return ret;
}

int main(int argc, char *argv[])
{
    int pid = atoi(argv[1]);  // precess id
    int num = atoi(argv[2]);  // define the number of signal
    union sigval sv = {0};
    int special[] = {SIGSTOP, SIGKILL, 32, 33};
    int len = sizeof(special) / sizeof(*special);
    int i = 0;

    srand((int)time(NULL));

    printf("current pid(%d) ..\n", getpid());

    for (i=0; i<num; i++) {
        int sig = 0;

        do {
            sig = rand() % 33 + 32;  // 范围控制 [32 - 64]
        }while (find(special, len, sig));

        printf("sig[%d] = %d\n", i, sig);

        sv.sival_int = i + 1;
        sigqueue(pid, sig, sv);
    }

    return 0;
}
输出:
a.out 输出:
wu_tiansong@ubuntu-server:~/test$ ./a.out 
current pid(823293) ..
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
i = 11
i = 12
i = 13
i = 14
35 : 8    ===>  左侧为信号值   右侧为信号发送顺序
36 : 4    ===>  可知数值小的信号值优先递达
39 : 6
51 : 3
58 : 1
59 : 7
59 : 9
61 : 5
63 : 2
64 : 10

test.out 输出
wu_tiansong@ubuntu-server:~/test$ ./test.out 823293 10
current pid(823398) ..
sig[0] = 58    ===>  左侧信号发送顺序    右侧信号值
sig[1] = 63
sig[2] = 51
sig[3] = 36
sig[4] = 61
sig[5] = 39
sig[6] = 59
sig[7] = 35
sig[8] = 59
sig[9] = 64

可靠信号与不可靠信号

test.c 【仅修改】
#include <bits/types/siginfo_t.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <time.h>

static int find(int *arr, int len, int v)
{
    int i = 0;
    int ret = 0;

    for (i=0; i<len && !ret; i++) {
        ret = ret || (arr[i] == v);
    }

    return ret;
}

int main(int argc, char *argv[])
{
    int pid = atoi(argv[1]);  // precess id
    int num = atoi(argv[2]);  // define the number of signal
    union sigval sv = {0};
    int special[] = {SIGSTOP, SIGKILL, 32, 33, SIGSEGV, SIGBUS, SIGILL, SIGTRAP, SIGFPE, SIGSYS};
    int slen = sizeof(special) / sizeof(*special);
    int high[] = {SIGSEGV, SIGBUS, SIGILL, SIGTRAP, SIGFPE, SIGSYS};  // 硬件相关不可靠信号,优先递送
    int hlen = sizeof(high) / sizeof(*high);
    int i = 0;

    srand((int)time(NULL));

    printf("current pid(%d) ..\n", getpid());

    for (i=0; i<num; i++) {
        int sig = 0;

        do {
            sig = rand() % 64 + 1;  // 范围控制 [1 - 64]
        }while (find(special, slen, sig));

        printf("sig[%d] = %d\n", i, sig);

        sv.sival_int = i + 1;
        sigqueue(pid, sig, sv);
    }

    for (i=0; i<hlen; i++) {   ====> 高优先级信号最后发送
        int sig = high[i];

        printf("sig[%d] = %d\n", i, sig);

        sv.sival_int = -(i + 1);
        sigqueue(pid, sig, sv);
    }

    return 0;
}
输出
a.out 输出:
wu_tiansong@ubuntu-server:~/test$ ./a.out 
current pid(827957) ..
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
i = 11
i = 12
i = 13
i = 14
4 : -3       ====> 高优先级信号最先到达
5 : -4
7 : -2
8 : -5
11 : -1
31 : -6
20 : 3
27 : 2
28 : 4
35 : 5
38 : 9
47 : 8
50 : 7
53 : 10
57 : 6
64 : 1


test.out 输出:
wu_tiansong@ubuntu-server:~/test$ ./test.out 827957 10
current pid(828040) ..
sig[0] = 64
sig[1] = 27
sig[2] = 20
sig[3] = 28
sig[4] = 35
sig[5] = 57
sig[6] = 50
sig[7] = 47
sig[8] = 38
sig[9] = 53
sig[0] = 11
sig[1] = 7
sig[2] = 4
sig[3] = 5
sig[4] = 8
sig[5] = 31

信号安全综述

再论信号处理

image.png

图中执行流是在同一个进程中完成的,进一步的每一个进程都有一个默认的线程,叫做主线程。

信号安全

  • 什么是信号安全

    • 程序能正确且无意外的按照预期方式执行
  • 信号处理的不确定性

    • 什么时候信号递达是不确定的 → 主程序被的中断的位置是不确定的
  • 一种不确定性:当信号递达,转而执行信号处理函数时,不可重入的函数不能调用

    • 不可重入函数:函数不能由超过一个任务(线程)所共享,除非能确保函数的互斥(或着使用信号量,或着在代码的关键部分使用禁用中断)

下面的程序输出什么?为什么?

dd_fucn 不可重入函数


#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

static int g_current = -1;

static void add_func(int n)  // 不可重入函数
{
    int i = 0;

    g_current = 0;           // 共享资源

    for (i=1; i<=n; ++i) {
        g_current += i;
        sleep(1);
    }
}

static void signal_handler(int sig, siginfo_t *info, void *ucontext)
{
    write(STDOUT_FILENO, "handler begin\n", 14);

    add_func(5);

    write(STDOUT_FILENO, "handler end\n", 12);
}

int main(int argc, char *argv[])
{
    struct sigaction act = {0};
    int obj_sig = atoi(argv[1]);

    printf("current pid(%d) ..\n", getpid());

    act.sa_sigaction = signal_handler;
    act.sa_flags = SA_NODEFER;

    sigaction(obj_sig, &act, NULL);

    add_func(10);

    printf("g_current = %d\n", g_current);

    return 0;
}
输出:
wu_tiansong@ubuntu-server:~/test$ ./a.out 2
current pid(2759322) ..
^Chandler begin    // ctrl + c , 发出 2 信号 
handler end
g_current = 67     // add_func 不可重入函数被调用了两次,结果不可预期

信号安全性避坑原则

深入信号安全性 (避坑规则)

  • 不要在信号处理函数中调用不可重入函数(即,使用了全局属性的变量的函数)
  • 不要在信号处理函数中调用存在临界区的函数(可能产生竞争导致死锁)
  • 不要调用 mallocfree 函数 (其内部使用了全局变量)
  • 不要调用标准 I/O 函数,如:printf 函数
  • 。。。

小问题:如何知道哪些函数是安全的

  • man 7 signal-safety

image.png

在实际开发过程中,一般不会在信号处理函数中调用 printf 等耗时操作。
信号处理函数的本质是软中断处理程序,那么应尽量做到精炼。

思考

如何编写信号安全的应用程序?


TianSong
734 声望138 粉丝

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