Linux下的进程通信方式之信号

Linux下的通信方式之管道

第二十章——信号的基本概念

20.1 信号的概念和概述

信号是进程之间通信的一种方式,有时也称之为软中断。

一个(具有合适权限的)进程能够向另一进程发送信号。信号的这一用法可作为一种同
步技术,甚至是进程间通信(IPC)的原始形式。进程也可以向自身发送信号。

信号可以分为两大类:

  1. 有内核向进程通知事件的信号,构成了所谓的标准信号。在 Linux 中标准型号的的编号从 1~31 共 31 个。
  2. 第二类型号主要就是实时信号

信号因某些事件而产生。信号产生后,会于稍后被传递给某一进程,而进程也会采取某
些措施来响应信号。在产生和到达期间,信号处于等待(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
2
3
#include <signal.h>

void (*signal(int signum, void (*handler)(int)))(int);

参数
signal() 函数用来设置信号处理函数。第一个参数 signum 是信号编号,第二个参数 handler 是信号处理函数的地址。函数返回原来的信号处理函数的地址。

返回值

  • case 3:如果 发生了错误,则会将 errno 设置为 SIG_ERR。
  • case 4:如果设置成功,则返回原来的信号处理函数的地址。

使用注意点

  • 还可以使用 SIG_IGNSIG_DFL来表示不同的功能。SIG_IGN 来表示忽略该信号。如果信号专为此进程而生,那么内核会默默将其丢弃。进程甚至从未知道曾经产生了该信号。SIG_DFL 表示将对于该信号的处置重置为默认值。

sigaction() 函数

1
2
3
#include <signal.h> 

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数

sigaction() 函数用来设置信号处理函数,并提供更多的控制选项。第一个参数 signum 是信号编号,第二个参数 act 是指向 sigaction 结构体的指针,该结构体包含了信号处理函数和其他控制选项。第三个参数 oldact 是一个可选的指针,用来接收原来的信号处理函数的地址。函数返回 0 表示成功,-1 表示出错。

1
2
3
4
5
6
struct sigaction {
void (*sa_handler)(int); /* 信号处理函数的地址 */
sigset_t sa_mask; /* 信号掩码 */
int sa_flags; /* 信号掩码 */
void (*sa_restorer)(void); /* 信号处理程序的状态恢复函数 */
};

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
2
3
#include <signal.h>

int kill(pid_t pid, int sig);

参数

  • pid 是进程 ID,可以是进程 ID 或进程组 ID。
  • sig 是信号编号。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • 如果 pid 大于 0,那么会发送信号给由 pid 指定的进程。
  • 如果 pid 等于 0,那么会发送信号给与调用进程同组的每个进程,包括调用进程自身。发送信号给当前进程组中的所有进程。
  • 如果 pid 小于 −1 ,那么会向组 ID 等于该 pid 绝对值的进程组内所有下属进程发送信号。
  • 如果 pid 等于``−1,那么信号的发送范围是:调用进程有权将信号发往的每个目标进程,除去init`(进程 ID 为 1)和调用进程自身。这个过程也被称之为:广播

raise() 函数

这个函数的功能就是想自己发送信号

1
2
3
#include <signal.h>

int raise(int sig);

参数

  • sig 是信号编号。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • raise() 函数的作用是向调用进程发送信号。

killpg() 函数

函数向某一进程组的所有成员发送一个信号。

1
2
3
#include <signal.h>

int killpg(int pgrp, int sig);

参数

  • pgrp 是进程组 ID。
  • sig 是信号编号。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • killpg() 函数的作用是向进程组 pgrp 中的所有进程发送信号。

pthread_kill() 函数

1
2
3
#include <pthread.h>

int pthread_kill(pthread_t thread, int sig);

参数

  • thread 是线程 ID。
  • sig 是信号编号。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • pthread_kill() 函数的作用是向线程发送信号。

20.8 信号的描述

这一节主要就是两个函数:

  • strsignal() 函数用来获取信号的描述信息。
  • psignal() 函数用来向进程发送信号,并附带信号的描述信息。

strsignal() 函数

每个信号都有一串与之相关的可打印说明。这些描述位于数组 sys_siglist 中。例如,可以用 sys_siglist[SIGPIPE] 来获取对 SIGPIPE 信号(管道断开)的描述。然而,较之于直接引用 sys_siglist 数组,还是推荐使用 strsignal() 函数。

1
2
3
#include <signal.h>

const char *strsignal(int sig);

参数

  • sig 是信号编号。

返回值

  • 成功:返回指向信号描述信息的指针。
  • 出错:返回 NULL。

使用注意点

  • strsignal() 函数的作用是获取信号的描述信息。

psignal() 函数

psignal() 函数(在标准错误设备上)所示为 msg 参数所给定的字符串,后面跟有一个冒号,随后是对应于 sig 的信号描述。和 strsignal() 一样,psignal() 函数也对本地设置敏感。

1
2
3
#include <signal.h>

void psignal(int sig, const char *s);

参数

  • sig 是信号编号。
  • s 是信号描述信息。

返回值

  • 无。

alarm() 函数

1
2
3
#include <signal.h>

unsigned int alarm(unsigned int seconds);

参数

  • 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
2
3
#include <signal.h>

int sigemptyset(sigset_t *set);

参数

  • set 是指向信号集的指针。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • sigemptyset() 函数用来初始化信号集。

sigfillset() 函数

1
2
3
#include <signal.h>

int sigfillset(sigset_t *set);

参数

  • set 是指向信号集的指针。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • sigfillset() 函数用来将信号集设置为包含所有信号。

sigaddset() 函数

1
2
3
#include <signal.h>

int sigaddset(sigset_t *set, int signum);

参数

  • set 是指向信号集的指针。
  • signum 是信号编号。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • sigaddset() 函数用来向信号集中添加一个信号。

sigdelset() 函数

1
2
3
#include <signal.h>

int sigdelset(sigset_t *set, int signum);

参数

  • set 是指向信号集的指针。
  • signum 是信号编号。

返回值

  • 成功:返回 0
  • 出错:返回 -1

使用注意点

  • sigdelset() 函数用来从信号集中删除一个信号。

sigismember() 函数

1
2
3
#include <signal.h>

int sigismember(const sigset_t *set, int signum);

参数

  • set 是指向信号集的指针。
  • signum 是信号编号。

返回值

  • 如果信号集中包含 signum 信号,则返回 1
  • 如果信号集中不包含 signum 信号,则返回 0

使用注意点

  • sigismember() 函数用来判断信号集中是否包含某个信号。

sigprocmask() 函数

1
2
3
#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数

  • how 是操作类型,可以是 SIG_BLOCKSIG_UNBLOCKSIG_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)` 函数用来设置或修改信号处理掩码。

Linux下的进程通信方式之信号
https://ysc2.github.io/ysc2.github.io/2024/08/17/Linux下的进程通信方式之信号/
作者
Ysc
发布于
2024年8月17日
许可协议