目录

认识信号

信号的概念

前台进程与后台进程

信号的种类

信号原理

信号的产生

信号集

信号的执行

信号集操作方法

信号集操作

sigprocmask - 阻塞信号集操作

sigpending - 读取当前进程的未决信号集

signal - 设置信号的处理方法

sigaction - 设置和检查信号处理函数


认识信号

信号的概念

说到信号我们并不陌生,生活中充斥着各种信号,如路边的红绿灯、早晨的闹钟、教室的上课铃等。信号的产生意味着我们一般都需要对这个信号做出反应(忽略这个信号也算是一种反应),而进程间的信号也是类似,当一个信号发送给某一个进程,或者说一个进程接收到某种信号,这意味着这个进程需要对这个信号做出某种特定的反应。

信号是一种异步的进程间通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。

前台进程与后台进程

在Linux中,在终端运行的进程叫做前台进程,而其它默默在后端执行或暂停等的进程叫做后台进程。在Linux中,每个终端下只允许存在一个前台进程,但可以存在多个后台进程。

后台进程在默认情况下通常不会获取到标准输入流,即键盘输入的内容。这是因为标准输入输出流(stdin、stdout和stderr)是与进程与终端(TTY)之间直接绑定的。当进程在前台运行时,可以与标准IO流进行交互。而当一个进程被放入后台时,这个后台进程就会脱离与终端的直接绑定,进而就不能再与标准IO流之间进行交互了。

其中,我们可以通过在终端指令后加“&”符号,以指定当前指令是在后台启动,例如:

./a.out &
sleep 10 & 

此外,还可以通过nohup、screen、systemctl等命令达到后台启动进程的效果。

一般我们在运行一个可执行程序时,默认就是在前台运行的,此时的bash进程就会退到后台,变成一个后台进程。直到这个可执行程序结束退出了,bash进程才会重新被唤醒。所以这也就解释了,为什么在执行其它的可执行程序时 bash 是无法进行响应的。

可以通过 jobs 指令来查看当前终端的后台进程。需要注意的是,jobs 指令只能查看当前终端的后台进程,而不是查看整个操作系统的后台进程。而如果 bash 进程退到后台时,我们就无法通过 jobs 指令来查看当前终端的后台进程了。

每一个进程被移入后台后都会有一个任务编号(即下图的1、2、3等数字),任务编号后面有+、-两个标志。+ 代表我fg或bg不指定任务编号时,将该进程调至前台执行。当把带有 + 的进程调至前台执行后,- 标志的进程就自动变成 +,没有标志的自动变成 - 。类似于一个栈,先入后出。

Linux 信号-LMLPHP

可以通过 fg 指令将后台进程提到前台,使用格式为 fg+任务编号。若后台任务中只有一个,则使用该命令时可以省略任务号。而 fg 指令的本质其实就是向进程发送信号,使得后台任务提到前台。

信号的种类

在Linux中,可以通过 kill -l 指令查看Linux下的信号种类,如下图。

Linux 信号-LMLPHP

可以看到,是没有0、32、33号信号的,也就是一共有62个信号。其中我们把 1~31 号信号称为普通信号。其它的称为实时信号,当信号产生必须立即处理就是实时信号。普通信号是有系统定义的默认方法的,而实时信号一般需要自定义方法。所以这里只研究普通信号,不研究实时信号。

这些信号的含义是由POSIX标准定义的,是标准的UNIX信号。每个信号都有一个编号和一个宏定义名称,即 SIGHUP 的本质就是一个为1的宏。

31个普通信号的大致含义如下(参考:Linux中的31个普通信号_CSDN博客

信号原理

信号的产生

Linux中,信号产生的4种方式可以概括为4种:终端快捷键、硬件信号、系统指令、软件条件。

所以归根结底,不管是哪种形式发送的信号,最终映射到到操作系统中一定是那31普通信号。

信号集

而信号其实是有三种状态的,分别是:

而Linux中普通信号只有31种,所以我们可以只用一个四字节的位图来表示每一种信号的二元状态。这个位图结构就是信号集,即 sigset_t 类型。sigset_t 类型对于每种信号用一个比特位表示有效或无效,至于这个类型内部如何存储这些比特位则依赖具体的系统实现,从使用者的角度是不必关心的,使用者只能调用相关函数来操作 sigset_t 类型的变量,比如用 printf 直接打印 sigset_t 变量是没有意义的。

所以内核中其实是有两张信号集位图的,一个是阻塞信号集(blcok),又叫信号屏蔽字,一个未决信号集(pending)。相应的,对于每一种信号都会有对应的处理方法,所以在内核中,信号体系中还是需要维护一个信号处理方法的指针数组,存放着与下标一致的信号的处理方法。图例如下。

信号的执行

在信号递达之后,通常会经过如下几个步骤(如图)

Linux 信号-LMLPHP

信号集操作方法

信号集操作

#include <signal.h>

// 清空信号集合。
int sigemptyset(sigset_t *set);

// 将指定的信号添加到信号集合中。即将位图中的对应位置置1。
int sigaddset (sigset_t *set, int signo);

// 将信号集合设置为包含所有可能的信号。
int sigfillset(sigset_t *set);

// 从信号集合中删除指定的信号。
int sigdelset(sigset_t *set, int signo);

// 检查指定的信号是否包含在信号集合中。
int sigismember(const sigset_t *set, int signo);

/*函数说明:
    set是一个指向sigset_t类型的指针,用于表示信号集合。
    signo是要添加的信号编号。
    函数都是成功返回0,失败返回-1。
*/

sigprocmask - 阻塞信号集操作

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

接口说明:

sigpending - 读取当前进程的未决信号集

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

接口说明:

signal - 设置信号的处理方法

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

接口说明:

sigaction - 设置和检查信号处理函数

#include <signal.h>

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

接口说明:

特别的,act和oact都是struct sigaction类型的指针,struct sigaction结构体的大致内容如下:

struct sigaction
{
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};
03-18 16:39