Linux下的进程通信方式之信号
Linux下的通信方式之管道
第二十章——信号的基本概念
20.1 信号的概念和概述
信号是进程之间通信的一种方式,有时也称之为软中断。
一个(具有合适权限的)进程能够向另一进程发送信号。信号的这一用法可作为一种同
步技术,甚至是进程间通信(IPC)的原始形式。进程也可以向自身发送信号。
信号可以分为两大类:
- 有内核向进程通知事件的信号,构成了所谓的标准信号。在 Linux 中标准型号的的编号从 1~31 共 31 个。
- 第二类型号主要就是实时信号。
信号因某些事件而产生。信号产生后,会于稍后被传递给某一进程,而进程也会采取某
些措施来响应信号。在产生和到达期间,信号处于等待(pending)状态。通常,一旦(内核)接下来要调度该进程运行,等待信号会马上送达,或者如果进程正在运行,则会立即传递信号(例如,进程向自身发送信号)。然而,有时需要确保一段代码不为传递来的信号所中断。
为了做到这一点,可以将信号添加到进程的信号掩码中目前会阻塞该组信号的到达。如果所产生的信号属于阻塞之列,那么信号将保持等待状态,直至稍后对其
解除阻塞(从信号掩码中移除)。进程可使用各种系统调用对其信号掩码添加和移除信号。
信号到达后,进程视具体信号执行如下默认操作之一。(注意这里是进程执行的操作)
- 忽略该信号
- 杀死该进程
- 产生核心转储文件
- 停止该进程
- 于之前暂停后再度恢复进程的执行。
同时程序可以将对信号的处置设置为如下之一。(注意这里是程序设置的操作)
- 忽略该信号
- 采取该信号的默认操作
- 调用一个信号处理函数
请注意,无法将信号处置设置为终止进程或者转储核心(除非这是对信号的默认处置)。
效果最为近似的是为信号安装一个处理器程序,并于其中调用 exit()或者 abort()。abort()函数(21.2.2 节)为进程产生一个 SIGABRT 信号,该信号将引发进程转储核心文件并终止。
20.2 信号类型和默认行为
此前曾提及,Linux 对标准信号的编号为 1~31。然而,Linux 于 signal(7)手册页中列出的信号名称却超出了 31 个。名称超出的原因有多种。有些名称只是其他名称的同义词,之所以定义是为了与其他 UNIX 实现保持源码兼容。其他名称虽然有定义,但却并未使用。
在 Linux 中的标准信号类型如下:
20.3 信号处理
这一节主要就是两个函数的使用:
signal()
函数用来设置信号处理函数。sigaction()
函数用来设置信号处理函数,并提供更多的控制选项。
signal() 函数
1 |
|
参数:signal()
函数用来设置信号处理函数。第一个参数 signum
是信号编号,第二个参数 handler
是信号处理函数的地址。函数返回原来的信号处理函数的地址。
返回值:
- case 3:如果 发生了错误,则会将
errno
设置为 SIG_ERR。 - case 4:如果设置成功,则返回原来的信号处理函数的地址。
使用注意点:
- 还可以使用
SIG_IGN
和SIG_DFL
来表示不同的功能。SIG_IGN
来表示忽略该信号。如果信号专为此进程而生,那么内核会默默将其丢弃。进程甚至从未知道曾经产生了该信号。SIG_DFL
表示将对于该信号的处置重置为默认值。
sigaction() 函数
1 |
|
参数:
sigaction()
函数用来设置信号处理函数,并提供更多的控制选项。第一个参数 signum
是信号编号,第二个参数 act
是指向 sigaction
结构体的指针,该结构体包含了信号处理函数和其他控制选项。第三个参数 oldact
是一个可选的指针,用来接收原来的信号处理函数的地址。函数返回 0 表示成功,-1 表示出错。
1 |
|
sigaction
结构体包含了信号处理函数的地址、信号掩码、信号掩码标志、信号处理程序的状态恢复函数。
返回值:
- case 1:如果
signum
不是有效的信号编号,则返回 -1。 - case 2:如果
act
为 NULL,则表示忽略该信号,返回 0。 - case 3:如果
act
指向的sigaction
结构体的sa_handler
成员不是信号处理函数的地址,则返回 -1。 - case 4:如果设置成功,则返回 0。
20.4 信号处理器简介
和对于中断的处理是类似的。
20.5-20.7 信号的发送方式
这里小节主要就是几个函数:
kill()
函数用来向进程发送信号。raise()
函数用来向自身发送信号。pthread_kill()
函数用来向线程发送信号。killpg()
函数用来向某个进程组中的所有进程发送信号。
kill() 函数
1 |
|
参数:
pid
是进程 ID,可以是进程 ID 或进程组 ID。sig
是信号编号。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
- 如果
pid
大于0
,那么会发送信号给由 pid 指定的进程。 - 如果
pid
等于0
,那么会发送信号给与调用进程同组的每个进程,包括调用进程自身。发送信号给当前进程组中的所有进程。 - 如果
pid
小于−1
,那么会向组ID
等于该pid
绝对值的进程组内所有下属进程发送信号。 - 如果
pid
等于``−1,那么信号的发送范围是:调用进程有权将信号发往的每个目标进程,除去
init`(进程 ID 为 1)和调用进程自身。这个过程也被称之为:广播
raise() 函数
这个函数的功能就是想自己发送信号。
1 |
|
参数:
sig
是信号编号。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
raise()
函数的作用是向调用进程发送信号。
killpg() 函数
函数向某一进程组的所有成员发送一个信号。
1 |
|
参数:
pgrp
是进程组 ID。sig
是信号编号。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
killpg()
函数的作用是向进程组pgrp
中的所有进程发送信号。
pthread_kill() 函数
1 |
|
参数:
thread
是线程 ID。sig
是信号编号。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
pthread_kill()
函数的作用是向线程发送信号。
20.8 信号的描述
这一节主要就是两个函数:
strsignal()
函数用来获取信号的描述信息。psignal()
函数用来向进程发送信号,并附带信号的描述信息。
strsignal() 函数
每个信号都有一串与之相关的可打印说明。这些描述位于数组 sys_siglist 中。例如,可以用 sys_siglist[SIGPIPE]
来获取对 SIGPIPE
信号(管道断开)的描述。然而,较之于直接引用 sys_siglist
数组,还是推荐使用 strsignal()
函数。
1 |
|
参数:
sig
是信号编号。
返回值:
- 成功:返回指向信号描述信息的指针。
- 出错:返回 NULL。
使用注意点:
strsignal()
函数的作用是获取信号的描述信息。
psignal() 函数
psignal()
函数(在标准错误设备上)所示为 msg
参数所给定的字符串,后面跟有一个冒号,随后是对应于 sig
的信号描述。和 strsignal()
一样,psignal()
函数也对本地设置敏感。
1 |
|
参数:
sig
是信号编号。s
是信号描述信息。
返回值:
- 无。
alarm() 函数
1 |
|
参数:
seconds
是等待时间(以秒为单位)。
返回值:
- 成功:返回以前的闹钟时间(以秒为单位)。
- 出错:返回
0
。
使用注意点:
alarm()
函数用来设置闹钟。- 如果在
seconds
秒内没有收到任何信号,那么进程会收到SIGALRM
信号。
20.9 信号集
信号集用于表示某一个信号是否被阻塞,本质上就是一个数组所实现的 bitmap
,使用结构体 sigset_t
来表示。
信号集是一个数据结构,用来表示一组信号。信号集可以用来表示阻塞的信号集合,或者是信号处理函数的集合。
多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t
。SUSv3规定了一系列函数来操纵信号集,现在将描述这些函数。
对于信号集主要就是下面这些函数:
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(const sigset_t *set, int signum)
函数用来判断信号集中是否包含某个信号。int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
函数用来设置或修改信号处理掩码。int sigandset(sigset_t *dest, const sigset_t *left, const sigset_t *right)
函数用来求两个信号集的交集。int sigorset(sigset_t *dest, const sigset_t *left, const sigset_t *right)
函数用来求两个信号集的并集。int sigpending(sigset_t *set)
函数用来获取当前进程的未决信号集。int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
函数用来设置或修改信号处理函数。
sigemptyset() 函数
1 |
|
参数:
set
是指向信号集的指针。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
sigemptyset()
函数用来初始化信号集。
sigfillset() 函数
1 |
|
参数:
set
是指向信号集的指针。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
sigfillset()
函数用来将信号集设置为包含所有信号。
sigaddset() 函数
1 |
|
参数:
set
是指向信号集的指针。signum
是信号编号。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
sigaddset()
函数用来向信号集中添加一个信号。
sigdelset() 函数
1 |
|
参数:
set
是指向信号集的指针。signum
是信号编号。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
sigdelset()
函数用来从信号集中删除一个信号。
sigismember() 函数
1 |
|
参数:
set
是指向信号集的指针。signum
是信号编号。
返回值:
- 如果信号集中包含
signum
信号,则返回1
。 - 如果信号集中不包含
signum
信号,则返回0
。
使用注意点:
sigismember()
函数用来判断信号集中是否包含某个信号。
sigprocmask() 函数
1 |
|
参数:
how
是操作类型,可以是SIG_BLOCK
、SIG_UNBLOCK
、SIG_SETMASK
。set
是指向信号集的指针。oldset
是指向原信号集的指针。
how 参数指定了 sigprocmask()函数想给信号掩码带来的变化。
SIG_BLOCK
:将set
中的信号添加到当前进程的信号掩码中。SIG_UNBLOCK
:将set
中的信号从当前进程的信号掩码中移除。SIG_SETMASK
:将set
中的信号设置为当前进程的信号掩码。
返回值:
- 成功:返回
0
。 - 出错:返回
-1
。
使用注意点:
sigprocmask()
函数用来设置或修改信号处理掩码。将原来的信号掩码存储在oldset
中,并且按照how
参数的要求修改当前进程的信号掩码。- 如果想获取信号掩码而又对其不作改动,那么可将
set
参数指定为空,这时将忽略how
参数。 - 如果
oldset
参数不为空,那么函数将把原信号掩码保存到oldset
中。
使用示例:
#include <signal.h>
#include <stdio.h>
sigset_t get_procsig(void)
{
sigset_t mask, oldmask;
if(sigprocmask(0, NULL, &oldmask) == -1){
perror("sigprocmask");
exit(1);
}else{
return oldmask;//这里是获取原来进程的信号掩码
}
}
void
## 20.10 信号掩码
上面我们提到了什么是**信号掩码**,其实就是一个信号集。信号掩码是一个进程的属性,用来阻塞或忽略某些信号。
这一小节主要的函数就是下面这几个:
1. `int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)` 函数用来设置或修改信号处理掩码。