cpp-doc-信号

概念

  • kill -l 查看信号类型
  • 信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。
  • 信号是因为某些错误条件而产生的,比如内存段冲突,浮点处理器错误或者非法指令等。
  • 信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断。

信号分类

  • 可靠信号
    • 由于原来定义的信号已有许多应用,不好在做改动,最终只好又新增加了一些信号,并在一开始就把他们定义为可靠信号,这些信号支持排队,不会丢失。
    • 信号的发送和安装也出现了新版本,信号的发送函数sigqueue()以及信号安装sigaction()
  • 不可靠信号
    • 进程每次处理信号后,就将信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此如果用户不希望这样的操作,那么就要在信号处理函数的结尾再一次调用signal().重新安装该信号。
    • 早期UNIX下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
    • linux支持不可靠信号,但是对不可靠信号机制做了改进;在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装是在可靠机制上的实现)。因此,linux下的不可靠信号问题主要指的是信号可能丢失。
  • 实时信号
    • SIGRTMIN=31 - SIGRTMAX=63
    • 可靠信号
  • 非实时信号

信号产生

  • 由shell终端根据当前发生的错误(段错误、非法指令等)Ctrl+c而产生相应的信号

    比如: socket通信或者管道通信,如果读端都已经关闭,执行写操作(或者发送数据),将导致执行写操作的进程收到SIGPIPE信号(表示管道破裂)
  • 在shell终端,使用kill或killall命令产生信号

  • 在程序代码中,调用kill系统调用产生信号

  • 信号类型
信号类型 描述 是否可以被捕捉
SIGABORT 进程异常终止 可以
SIGALRM 超时告警 可以
SIGFPE 浮点运算异常 可以
SIGHUP 连接挂断 可以
SIGILL 非法指令 可以
SIGINT 终端中断 (Ctrl+C将产生该信号) 可以
SIGKILL 终止进程 可以
SIGPIPE 向没有读进程的管道写数据 可以
SIGQUIT 终端退出(Ctrl+\将产生该信号) 可以
SIGSEGV 无效内存段访问 可以
SIGTERM 终止 可以
SIGUSR1 用户自定义信号1 可以
SIGUSR2 用户自定义信号2 可以
SIGCHLD 子进程已停止或退出 不可以
SIGCONT 让暂停的进程继续执行 不可以
SIGSTOP 停止执行(即“暂停”) 不可以
SIGTSTP 中断挂起 不可以
SIGTTIN 后台进程尝试读操作 不可以
SIGTTOU 后台进程尝试写 不可以

信号处理

  • 忽略此信号:不采取任何操作,有两个信号不能被忽略(SIGKILL和SIGSTOP)

  • 捕捉信号:内核中断正在执行的代码,转去执行先前注册过的处理程序

  • 执行系统默认操作:默认操作通常是终止进程,这取决于被发送的信号。

信号捕获

信号的捕获,是指,指定接受到某种信号后,去执行指定的函数。

注意:SIGKILL和SIGSTOP不能被捕获,即,这两种信号的响应动作不能被改变。

使用signal

  • 特殊参数:
    SIG_IGN 忽略信号
    SIG_DFL 恢复默认行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig) {
printf("catch one signal:%d\n", sig);
signal(SIGINT, SIG_DFL); // 恢复到默认行为
// signal(SIGINT, SIG_IGN); // 忽略掉信号
}

int main() {
signal(SIGINT, myhandle);
while(1) {
printf("sleep 1 second.. \n");
sleep(1);
}

return 0;
}

使用sigaction

  • 结构struct sigaction

    1
    2
    3
    4
    5
    6
    struct sigaction {
    void (*sa_handler)(int); /* 信号的响应函数 */
    sigset_t sa_mask; /* 屏蔽信号集 */
    int sa_flags; /* 当sa_flags中包含 SA_RESETHAND时,接受到该信号并调用指定的信号处理函数执行之后,把该信号的响应行为重置为默认行为SIG_DFL */
    ...
    }

当sa_mask包含某个信号A时,则在信号处理函数执行期间,如果发生了该信号A,则阻塞该信号A(即暂时不响应该信号),直到信号处理函数执行结束。 即,信号处理函数执行完之后,再响应该信号A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig) {
printf("catch one signal:%d\n", sig);
}

int main() {
struct sigaction act;
struct sigaction oldact;

act.sa_handler = myhandle;
sigemptyset(&act.sa_mask);
// act.sa_flags = 0;
act.sa_flags = SA_RESETHAND;

sigaction(SIGINT, &act, 0);

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

return 0;
}

发送信号

  • 在shell终端用快捷键产生信号

  • 使用kill,killall命令。

  • 使用kill函数和alarm函数

使用kill函数

给指定的进程发送指定信号

给指定的进程发送信号需要“权限”:

普通用户的进程只能给该用户的其他进程发送信号

root用户可以给所有用户的进程发送信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int workflag = 0;

void work_up_handle(int sig) {
workflag = 1;
}

void work_down_handle(int sig) {
workflag = 0;
}

int main() {
pid_t pd;
char c;

pd = fork();
if(pd == -1) {
// 创建进程失败
printf("fork error!\n");
exit(1);
} else if (pd == 0) {
// 子进程
const char *msg;
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work_up_handle;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR1, &act, 0);

act.sa_handler = work_down_handle;
sigaction(SIGUSR2, &act, 0);

while(1) {
if (!workflag) {
msg = "work_down_handle";
} else {
msg = "work_up_handle";
}

printf("%s\n", msg);
sleep(1);
}
} else {
// 父进程
while(1) {
c = getchar();
if(c == 'A') {
kill(pd, SIGUSR1);
} else if (c == 'a') {
kill(pd, SIGUSR2);
}
}
}

return 0;
}

使用alarm函数

在指定时间之内给该进程本身发送一个SIGALRM信号。

时间的单位是“秒”,实际闹钟时间比指定的时间要大一点。

如果参数为0,则取消已设置的闹钟。

如果闹钟时间还没有到,再次调用alarm,则闹钟将重新定时,每个进程最多只能使用一个闹钟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>

int wakeflag = 0;

void wake_handle(int sig) {
wakeflag = 1;
}

int main(void)
{
int ret;

struct sigaction act;
act.sa_flags = 0;
act.sa_handler = wake_handle;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, 0);

printf("time =%ld\n", time((time_t*)0));

ret = alarm(5);
if (ret == -1) {
printf("alarm error!\n");
exit(1);
}

// 挂起当前进程,直到收到任意一个信号
pause();

if (wakeflag) {
printf("wake up, time =%ld\n", time((time_t*)0));
}

return 0;
}

使用raise函数

给本进程自身发送信号

多信号处理

  • 信号集

信号集,用sigset_t类型表示,实质是一个无符号长整形。
用来表示包含多个信号的集合。

  • 信号集基本操作
函数 描述
sigemptyset 把信号集清空
sigfillset 把所有已定义的信号填充到指定信号集
sigdelset 从指定的信号集中删除指定的信号
sigaddset 从指定的信号集中添加指定的信号
sigismember 判断指定的信号是否在指定的信号集中
sigprocmask 修改进程的“信号屏蔽字”
  • 进程信号屏蔽字

进程的“信号屏蔽字”是一个信号集

想目标进程发送某信号时,如果这个信号在目标进程的信号屏蔽字中,

则目标进程将不会捕获到该信号,即不会执行该信号的处理函数。

当该进程的信号屏蔽字不再包含该信号时,则会捕获这个早已收到的信号(执行对应的函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig)
{
printf("Catch a signal : %d\n", sig);
printf("Catch end.%d\n", sig);
}

int main(void)
{
struct sigaction act, act2;

act.sa_handler = myhandle;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);

sigset_t proc_sig_msk, old_mask;
sigemptyset(&proc_sig_msk);
sigaddset(&proc_sig_msk, SIGINT);

sigprocmask(SIG_BLOCK, &proc_sig_msk, &old_mask);
sleep(5);
printf("had delete SIGINT from process sig mask\n");
sigprocmask(SIG_UNBLOCK, &proc_sig_msk, &old_mask);

while (1) {

}

return 0;
}

获取未处理的信号

当进程的信号屏蔽字中信号发生时,这些信号不会被该进程响应,

可通过sigpending函数获取这些已经发生了但是没有被处理的信号

阻塞式等待信号

函数 描述
pause 阻塞进程,直到发生任一信号后
sigsuspend 用指定的参数设置信号屏蔽字,然后阻塞时等待信号的发生。
即,只等待信号屏蔽字之外的信号