问题
对于同一个进程,如果存在两个不同的未决实时信号,那么先处理谁?
对于不同的未决实时信号,信号值越低,则优先级越高
信号优先级的概念
信号的本质是一种软中断 (中断有优先级,信号也有优先级)
对于同一个未决实时信号,按照发送先后次序递送给进程
对于不同的未决实时信号,信号值越小优先级越高
不可靠信号与可靠信号同时未决:
- 严格意义上,没有明确规定优先级
- 实际上,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* old_set);
需要思考的问题
- 如何使得多个信号同时未决,且以优先级方式递达进程?
- 如何记录和对比信号的递达次序及发送次序?
- 对于实验中涉及的不可靠信号,是否特殊考虑?
信号优先级实验设计 (发送端)
信号优先级实验设计 (接收端)
test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <time.h>
int find(int* special_sigs, int num, int v)
{
int ret = 0;
for(int i = 0; i < num; i++)
{
if(special_sigs[i] == v)
{
ret = 1;
break;
}
}
return ret;
}
int main(int argc, char* argv[])
{
int pid = atoi(argv[1]);
int num = atoi(argv[2]); // the number of signal
int special_sigs[] = {SIGKILL, SIGSTOP, 32, 33, SIGSEGV, SIGBUS, SIGILL, SIGTRAP, SIGFPE, SIGSYS};
int special_num = sizeof(special_sigs) / sizeof(*special_sigs);
int highly_sigs[] = {SIGSEGV, SIGBUS, SIGILL, SIGTRAP, SIGFPE, SIGSYS};
int highly_num = sizeof(highly_sigs) / sizeof(*highly_sigs);
int sig = 0;
union sigval sv = {0};
int i = 0;
srand((int)time(NULL));
printf("current pid(%d) ...\n", getpid());
for(i = 0; i < num; i++)
{
do
{
sig = rand() % 64 + 1; // 1 - 64
} while(find(special_sigs, special_num, sig));
sv.sival_int = i + 1;
sigqueue(pid, sig, sv);
printf("send signal, %d : %d\n", sv.sival_int, sig);
}
for(i = 0; i < highly_num; i++)
{
sv.sival_int = -(i + 1);
sig = highly_sigs[i];
sigqueue(pid, sig, sv);
printf("send signal, %d : %d\n", sv.sival_int, sig);
}
return 0;
}
第 41 行 - 53 行,发送除了 special_sigs 这个数组中的 num 个信号,包括了不可靠信号和可靠信号
第 55 行 - 62 行,发送硬件相关的信号
发送信号携带的数据为发送信号时的序号,在发送硬件相关信号时,序号为负数,以作区分
main.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
typedef struct _sig_info_t_
{
int sig;
int index;
} SigInfo;
static int g_index = 0;
static SigInfo g_SigInfo[80] = {0};
static void signal_handler(int sig, siginfo_t* info, void* ucontext)
{
g_SigInfo[g_index].sig = info->si_signo;
g_SigInfo[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_RESTART | SA_SIGINFO;
sigfillset(&act.sa_mask);
for(i = 1; i <= 64; i++)
{
sigaction(i, &act, NULL);
}
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
for(i = 0; i < 15; i++)
{
printf("sleeping, times = %d\n", i);
sleep(1);
}
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
for(i = 0; i < g_index; i++)
{
printf("receive signal, %d : %d\n", g_SigInfo[i].sig, g_SigInfo[i].index);
}
return 0;
}
第 34 行 - 第 42 行,将将所有信号的信号掩码置位,是为了在执行信号处理函数的时候,不会被其他的信号给打断,能够确保一个信号处理函数执行完毕之后,再去处理另一个信号
第 44 行 - 54 行,首先屏蔽所有的信号,然后 sleep 15s,这样做是为了在这 15s,让所有发送给当前进程的信号处于未决状态;15s 后,解除所有信号的屏蔽,当前进程会按照信号优先级的顺序去处理多个到达的未决的信号
信号处理函数会去记录信号值和发送方发送该信号的次序
第 56 行 - 59 行,按照信号优先级从高到低的顺序打印出所有接收到的信号值和发送方发送该信号的次序
程序运行结果如下图所示:
接收端最先处理的是与硬件相关的信号,然后再由信号优先级依次处理,信号值越小,优先级越高,越先处理
再论信号处理
信号安全性
什么是安全性?
- 程序能正确且无意外的按照预期方式执行
信号处理的不确定性
- 什么时候信号递达是不确定的 => 主程序被中断的位置是不确定的
当信号递达,转而执行信号处理函数时,不可重入的函数不能调用
- 不可重入函数:函数不能由超过一个任务(线程)所共享,除非能确保函数的互斥 (或者使用信号量,或者在代码的关键部分禁用中断)
下面的程序输出什么?为什么?
程序的输出结果是不确定的
由于 add_func() 函数中使用到了全局变量,所以这个函数是不可重入的函数;在执行到 add_func() 函数时,如果触发了目标信号,则会去执行信号处理函数,由于信号处理函数也会调用 add_func() 函数,等信号处理函数执行完毕回到原先的执行流时,g_current 变量已经被修改,所以程序的输出结果是不确定的
深入信号安全性
不要在信号处理函数中调用不可重入函数 (即:使用了全局变量和静态局部变量的函数)
不要调用函数中存在临界区的函数 (可能产生竞争导致死锁)
不要调用 malloc() 和 free() 函数
不要调用标准 I/O 函数,如:printf() 函数
小问题:如何知道哪些函数是安全的?
man 7 signal-safety
思考
如何编写信号安全的应用程序?