信号

信号

信号是UNIX中所使用的进程通信的一种最古老的方法,它是在软件层次上对中断机制的模拟,是一种异步通信方式。信号可以直接进行用户空间进程和内核空间进程之间的交互,内核进程也可以利用它来通知用户进程发生了哪些系统事件。它可以在任何时候发给某一进程而无需知道该进程的状态

一个完整的信号生命周期可分为三个重要阶段,三个重要阶段由四个重要事件刻画,如下图所示:

信号处理

相邻的两个事件的间隔就是一个重要阶段

一个不可靠的信号处理过程是这样的:如果发现该信号已经注册,那么忽略该信号。因此,若前一个信号还未注销又产生了相同的信号就会产生信号丢失。而当可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此信号不会丢失

用户进程对信号的相应有三种方式:

  • 忽略信号:即对信号不做任何处理,有两个信号不能被忽略,SIGKILL和SIGSTOP
  • 捕捉信号:定义信号处理函数,当信号发生时,执行相应的自定义处理函数
  • 执行默认操作:Linux对每种信号都规定了默认操作

常见的信号及其默认操作如下:

信号 含义 默认操作
SIGHUP 该信号在用户终端连接结束时发出,通常是在中端的控制进程结束时,通知同一会话内的各个作业与控制中断不再关联 终止
SIGINT 该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程 终止
SIGQUT 此信号与SIGINT类似,由QUIT字符(通常是Ctrl-\)来控制 终止
SIGILL 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出 终止
SIGFPE 该信号在发生致命的运算错误时发出,这里不仅包括浮点运算错误,还包括溢出及除数为0等其他所有的算数错误 终止
SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理或忽略 终止
SIGALRM 该信号当一个定时器到时的时候发出 终止
SIGSTOP 该信号用于暂停一个进程,并且不能被阻塞、处理或忽略 暂停进程
SIGTSTP 该信号用于交互停止进程,用户键入SUSP字符时(通常是Ctrl-Z)发出 停止进程
SIGCHLD 子进程改变状态时,父进程会收到这个信号 忽略
SIGABORT 进程异常终止时发出

信号发送和捕捉

kill()和raise()

kill()函数可以发送信号给进程或进程组,raise函数向自身发送信号

1
2
3
4
5
#include <signal.h>
#include <sys/types.h>

int kill (pid_t pid, int sig);
int raise (int sig);

例子:

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
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

int main ()
{
pid_t pid;
int ret;

if ((pid = fork()) < 0)
{
printf ("Fork error!\n");
exit (1);
}

if (pid == 0)
{
printf ("Child (pid:%d) is waiting for any signal\n", getpid());

raise(SIGSTOP);
printf ("exit\n");
exit (0);
}
else
{
sleep(1);
if ((waitpid (pid, NULL, WNOHANG)) == 0)
{
if ((ret = kill (pid, SIGKILL)) == 0)
{
printf ("Parent kill %d\n", pid);
}
}

waitpid(pid, NULL, 0);
exit(0);
}
}
/*
输出:
Child (pid:472) is waiting for any signal
Parent kill 472
*/

alarm()和pause()

alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,他就向进程发送SIGALARM信号

pause()函数是用于将调用程序挂起直到捕捉到信号为止

1
2
3
#include <unistd.h>
unsigned int alarm (unsigned int seconds);
int pause (void);

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
int ret = alarm (5);
pause() ;
printf ("I have been waken up \n");
}
/*
输出:
Alarm clock
*/

信号的处理

信号处理的主要方法由两种,一种是使用简单的signal()函数,另一种是使用信号集函数组

信号处理函数

使用signal()函数时,只需指出要处理的信号和处理函数即可。主要用于前32中非实时信号的处理,不支持信号传递信息

Linux还支持一个更强壮、更新的信号处理函数sigaction(),推荐使用

1
2
3
#include <sigal.h>
void (*signal (int signum, void (*handler)(int)))(int);
int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact)

主要介绍一下sigaction()函数:

  • signum:信号代码,可以为除了SIGKILL及SIGSTOP外的任何一个特定有效的信号
  • act:指向结构sigaction的一个实例的指针,指定对特定信号的处理
  • oldact:保存原来对应信号的处理

sigaction的定义如下:

1
2
3
4
5
6
7
struct sigaction
{
void (*sa_handler) (int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore) (void);
}

sa_handler是一个函数指针,指定信号处理函数,这里除了可以是用户自定义的处理函数外,还可以是SIG_DFL或SIG_IGN。它的处理函数只有一个参数,即信号值

sa_mask是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被频闭,在调用信号捕获函数之前,该信号集要加入到信号的信号屏蔽字中

sa_flag中包含了许多标志位,是对信号进行处理的各个选项。常见可选值如下:

选项 含义
SA_NODEFER\SA_NOMASK 当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动屏蔽此信号
SA_NOCLDSTOP 进程忽略子进程产生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTTOU信号
SA_RESTART 令重启的系统调用起作用
SA_ONESHOT\SA_RESTHAND 自定义信号只执行一次,在执行完毕后恢复信号的系统默认动作

例子如下:

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
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void my_func (int sign_no)
{
if (sign_no == SIGINT)
{
printf ("I have get SIGINT\n");
}
else if (sign_no == SIGQUIT)
{
printf ("I have get SIGQUIT\n");
}
}

void my_signal()
{
signal (SIGQUIT, my_func);
signal (SIGINT, my_func);
}

void my_sigaction ()
{
struct sigaction action;

action.sa_handler = my_func;
sigemptyset (&action.sa_mask);
action.sa_flags = 0;
sigaction (SIGINT, &action, 0);
sigaction (SIGQUIT, &action, 0);
}

int main ()
{
printf ("Waiting for signal SIGINT or SIGQUIT...\n");
//my_signal();
my_sigaction();
pause();
exit (0);
}
/*
输出:
Waiting for signal SIGINT or SIGQUIT...
^CI have get SIGINT
Waiting for signal SIGINT or SIGQUIT...
^\I have get SIGQUIT
*/

信号集函数组

使用信号集函数组处理信号时涉及一系列的函数,这些函数按照调用的先后次序可分为以下几大功能模块:创建信号集合、注册信号处理函数以及检测信号。

其中,创建信号集合主要用于处理用户感兴趣的一些信号,其函数包括以下几个。

  • sigemptyset(): 将信号集合初始化为空
  • sigfillset(): 将信号集和初始化为包含所有已定义的信号的集合
  • sigaddset(): 将指定信号加入到信号集合中
  • sigdelset(): 将指定信号从信号集合中删除
  • sigismember(): 查询指定信号是否在信号集合中
1
2
3
4
5
6
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(sigset_t *set, int signum);

注册信号处理函数主要用于决定进程如何处理信号,只有当信号的状态处于非阻塞状态时才会真正起作用。因此首先使用sigprocmask()函数检测并更改屏蔽字,然后使用sigaction()函数来定义进程接收到特定信号之后的行为

1
2
#include <signal.h>
int sigprocmask (int how, const sigset_t *set, sigset_t oset)

how决定函数的操作方式:

  • SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之外
  • SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合
  • SIG_SETMASK:将当前信号集合设置为信号阻塞集合

set:指定信号集

oset:信号屏蔽字

若set是一个非空指针,则how表示函数的操作方式;若how为空,则表示忽略此操作

因为被阻塞的信号不会传递给进程,所以这些信号就处于“未处理”信号。sigpending()函数允许检测到“未处理”信号,并进一步决定对他们作何处理

1
2
#include <signal.h>
int sigpending (sigset_t *set);

例子如下:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void my_func(int signum)
{
printf ("If you want to quit, please try SIGQUIT!\n");
}

int main()
{
sigset_t set, penset;
struct sigaction action1, action2;

/*初始化信号为空*/
if (sigemptyset (&set) < 0)
{
perror ("sigemptyset");
exit(1);
}

/*添加信号*/
if (sigaddset (&set, SIGQUIT) < 0)
{
perror ("sigemptyset");
exit(1);
}

if (sigaddset (&set, SIGINT) < 0)
{
perror ("sigemptyset");
exit(1);
}

/*添加动作*/
if (sigismember (&set, SIGINT))
{
sigemptyset (&action1.sa_mask);
action1.sa_handler = my_func;
action1.sa_flags = 0;
sigaction (SIGINT, &action1, NULL);
}

if (sigismember (&set, SIGQUIT))
{
sigemptyset (&action2.sa_mask);
action2.sa_handler = SIG_DFL;
action2.sa_flags = 0;
sigaction (SIGQUIT, &action2, NULL);
}

/*屏蔽set中的信号,并等待按任意键*/
if (sigprocmask (SIG_BLOCK, &set, NULL) < 0)
{
perror ("sigprocmask");
exit (1);
}
else
{
printf ("Signal set was blocked , Press any key!\n");
getchar();
}

/*接收set中信号*/
if (sigprocmask (SIG_UNBLOCK, &set, NULL) < 0)
{
perror ("sigprocmask");
exit (1);
}
else
{
printf ("Signal set is unblock state\n");
}

while (1);

return 0;
}

信号
https://carl-5535.github.io/2021/05/31/Linux系统编程/信号/
作者
Carl Chen
发布于
2021年5月31日
许可协议