信号优先级分析
问题:对于同一个进程,如果存在两个不同的未决实时信号,那么先处理谁?
信号优先级的概念
- 信号的本质是一种软中断(中断有优先级,信号也有优先级)
- 对于同一个未决信号,按照发送先后顺序送达进程
- 对于不同的未决实时信号,信号值越小优先级越高
不可靠信号与可靠信号同时未决
- 严格意义上,没有明确规定优先级
- 实际上,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
信号安全综述
再论信号处理
图中执行流是在同一个进程中完成的,进一步的每一个进程都有一个默认的线程,叫做主线程。
信号安全
什么是信号安全
- 程序能正确且无意外的按照预期方式执行
信号处理的不确定性
- 什么时候信号递达是不确定的 → 主程序被的中断的位置是不确定的
一种不确定性:当信号递达,转而执行信号处理函数时,不可重入的函数不能调用
- 不可重入函数:函数不能由超过一个任务(线程)所共享,除非能确保函数的互斥(或着使用信号量,或着在代码的关键部分使用禁用中断)
下面的程序输出什么?为什么?
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 不可重入函数被调用了两次,结果不可预期
信号安全性避坑原则
深入信号安全性 (避坑规则)
- 不要在信号处理函数中调用不可重入函数(即,使用了全局属性的变量的函数)
- 不要在信号处理函数中调用存在临界区的函数(可能产生竞争导致死锁)
- 不要调用
malloc
和free
函数 (其内部使用了全局变量) - 不要调用标准
I/O
函数,如:printf
函数 - 。。。
小问题:如何知道哪些函数是安全的
- man 7 signal-safety
在实际开发过程中,一般不会在信号处理函数中调用 printf 等耗时操作。
信号处理函数的本质是软中断处理程序,那么应尽量做到精炼。
思考
如何编写信号安全的应用程序?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。