【Linux篇章】Linux 进程信号1:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲信号产生和保存)

本篇文章将以一个小白视角带你了解什么是Linux中的信号;如何查看Linux中常见常用的信号;如何通过五种方式产生不同的信号;以及产生后是如何保存在进程的三种信号表中的;通过相关代码示例;带你通俗易懂的了解底层原理以及信号相关函数接口调用等。

博主主页,欢迎支持:
羑悻的小杀马特.-CSDN博客

欢迎拜访:羑悻的小杀马特.-CSDN博客****

本篇主题:++秒懂百科之探究Linux进程信号1(产生与保存信号)++

制作日期:2025.05.04

隶属专栏:linux之旅****

目录

一·对Linux信号的认识:

1.1相关概念:

1.2Linux信号示例:

1.3如何查看信号:

二·如何产生信号:

2.1键盘产生信号:

2.2系统指令:

2.3系统调用:

2.3.1kill:

[2.3.2 abort:](#2.3.2 abort:)

2.3.3raise:

2.4软件条件:

2.5硬件异常:

小结一下:

​ 三·如何保存信号:

3.1信号相关用语:

3.2进程如何管理信号:

3.3如何对信号表的操作:

3.3.1sig系列函数:

[3.3.2sigprocmask 函数:](#3.3.2sigprocmask 函数:)

3.3.3sigaction函数:

四.本篇小结:


一·对Linux信号的认识:

1.1相关概念:

信号比如闹钟,红绿灯等等;信号用来中断我们人正在做的事情,是一种事件的异步通知机制。

那么何为异步和为同步:

比如小明一家在吃饭;而小明要打完这把游戏才去吃;此时加人如果等他一起来吃就是同步 ;如果不等它,家人吃饭和小明打游戏同时进行;那就是异步了。

因此,我们要知道;信号是一种给进程发送的,用来进行事件异步通知的机制!信号的产生,相对于进程的运行,是异步机制。

对于linux信号认识需要建立一下结论:

1.信号处理,进程在信号没有产生的时候,早就知道信号该如何处理了(已经写好了对应方法)
⒉信号的处理,不是立即处理,而是可以等一会在处理,合适的时候)进行信号的处理(合适的时候:进程从内核态返回用户态时;进程处于可中断睡眠状态被唤醒时;发生时钟中断;也可以折磨理解;就是系统执行流处于特定的节点,使得内核有机会去检查和处理信号,确保信号能被及时响应,同时又不会影响系统的正常运行和进程的主要执行逻辑。

3.人能识别信号,是提前被"教育"过的,进程也是如此,OS程序员设计的进程,进程早已经内置了对于信号的识别和处理方式!

4.信号源非常多->给进程产生信号的,信号源,也非常多!(后面我们会看到有很多信号)

信号分为这几个部分;下面我们本篇就讲解下产生与保存过程。

1.2Linux信号示例:

下面我们简单举一个linux产生信号并处理它的过程(可能会有接口函数或者其他;后面会讲;只看效果):

以2号信号(SIGINT也就是键盘输入的ctrl c为例):

当我们按下ctrl c它就终止了;下面我们自定义捕捉一下看它收到几号信号(还是只看效果即可):

此时当我们再次按下ctrl c它就会走自定义捕捉方法;而就无法ctrl c杀死它了;此时可以用ctrl \ kill

-9 等方法。

代码展示:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sigcb(int signumber)
{
std::cout << "我是: " << getpid() << ", 我获得了⼀个信号: " << signumber <<
std::endl;
} 
int main()
{
std::cout << "我是进程: " << getpid() << std::endl;
signal(SIGINT/*2*/, sigcb);
while(true){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep(1);
}
}

1.3如何查看信号:

下面我们可以使用特定指令:

cpp 复制代码
kill -l

下面对这些信号进行注释一下:

而下面我们只先研究普通信号也就是1-31号新号。

这些信号各⾃在什么条件下产⽣,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal :

对捕捉信号函数认识:

cpp 复制代码
     #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

前参数就是信号值;后参数是它的捕捉方法(也可自定义);成功就返回这个捕捉函数的指针;否则就返回SIG_ERR。

signal函数仅仅是设置了特定信号的捕捉行为处理方式,并不是直接调用处理动作。如果后续特定信号没有产生,设置的捕捉函数永远也不会被调用!!

拿到信号后有三种处理方式:

**1·默认:**SIG_DFL

**2·忽略:**SIG_IGN

3·自定义:也就是我们写的自定义捕捉函数了

cpp 复制代码
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);

这里可以看出默认和忽略都是定义出来的宏;强转放入函数指针数组而已。

此时我们就应该对signal有一定理解了:

其实可以把它这么认为:就是告诉os只要接收到signum这个信号就走后面的捕捉方法而已。

二·如何产生信号:

先明确结论:

产生信号有五种方式:

1.键盘

2.系统指令

3.系统调用

4.硬件异常

5.软件条件

下面我们就围绕这五点展开讲解:

2.1键盘产生信号:

常见就是这三个:

ctrl c:2 SIGINT: 进程中断(interrupt)

ctrl z:20 SIGTSTP:可以发送停止信号,将当前前台进程挂起到后台.(terminal stop)

ctrl \ : 3 SIGQUIT :使得进程退出产生core文件和coredump标记(后面会讲到)(quit)

ctrl c已经演示过了这里就不过多说了。

下面来看一下ctrl \:

这是自定义捕捉;他不会出现core dump退出;下面看下默认的捕捉:

下面就来了;对于这个core文件后面我们再讲。

测试代码:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
std::cout << "我是: " << getpid() << ", 我获得了⼀个信号: " << signumber <<
std::endl;
} 
int main()
{
std::cout << "我是进程: " << getpid() << std::endl;
//signal(SIGQUIT/*3*/, handler);
while(true){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep(1);
}
}

下面来看一下 SIGTSTP也就是ctrl z:

这里就涉及到前后台进程了:

普及一下前后台进程:

./XXX --- 前台进程

./YYY & --- 后台进程

那么它们有啥区别呢:

后台进程,无法从标准输入中获取内容!(也就是键盘)

前台台进程,可以从标准输入中获取内容!(也就是键盘)

但是都可以向标准输出上打印。

后台有多个进程/前台进程只有一个;前台本质,就是要从键盘获取数据的, 键盘只有一个,输入数据 一定是给一个确定的进程的!

比如:

命令行shell就是一个前台进程;如果我们启动前台程序后;它就不能用了;但是如果把前台改成后台运行;他就还可以用;演示一下:

这里我们使用系统指令(后面会讲到)就可以(这里也可同一个shell;但是比较麻烦)

再比如:

之前讲的如果父进程fork后产生子进程然后自己退出;这个子进程就会变成孤儿进程;被后台所"领养";变成后台进程了。(这种就不能用键盘信号杀死了;只能有相关系统指令了)

普及下前后台相关指令:

jobs查看所有的后台任务

fg +任务号,特定的后台进程,提到前台看

ctrl+z:进程切换到后台

bg +任务号:让后台进行继续运行(还是后台运行)

下面来看一下(以ctrl z演示 ):

2.2系统指令:

kill -信号+pid 或者 pkill -信号+文件名字(比如a.out)

kill:

部分程序在接收到终止信号之后,可能会等待用户输入一些确认信息,尽管kill命令通常是强制终止进程,但程序自身的逻辑也许要求它等待用户输入。按下回车键,就相当于提供了一个确认信号,于是程序显示退出信息。

pkill:

2.3系统调用:

下面我们分为kill,abort,raise三个函数来讲解一下:

2.3.1kill:

cpp 复制代码
#include<sys/types.h>
#include<signal.h>
int kill(pid_tpid,intsig);

给指定进程发信号;成功就返回0;否则返回-1.

下面测试一下;我们使用kill函数;给指定进程发送20号新号然后完成自定义捕捉功能。

测试代码:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了⼀个信号: " << signumber << std::endl;
}
int main()
{
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGTSTP /*20*/, handler);
    while (true)
    {
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
        kill(getpid(), 20);
    }
}

2.3.2 abort:

给自己发6号新信号;并产生core文件及core、 dump标记位。

cpp 复制代码
#include <stdlib.h>
void abort(void);

就像exit函数⼀样,abort函数总是会成功的,所以没有返回值。

测试代码:

cpp 复制代码
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include<iostream>
void handler(int signumber)
{
// 整个代码就只有这⼀处打印
std::cout << "获取了⼀个信号: " << signumber << std::endl;
} 
 
int main()
{
signal(SIGABRT, handler);
while(true)
{
sleep(1);
abort();
}
}

2.3.3raise:

自己给自己发信号。

cpp 复制代码
#include <signal.h>
int raise(int sig);

成功返回0失败返回非0。

下面我们使用raise函数给自己发2号信号;每间隔1s发送一次。

测试代码:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>

void handler(int signumber)
{
// 整个代码就只有这⼀处打印
std::cout << "获取了⼀个信号: " << signumber << std::endl;
} 
int main()
{
signal(2, handler); // 先对2号信号进⾏捕捉
// 每隔1S,⾃⼰给⾃⼰发送2号信号
while(true)
{
sleep(1);
raise(2);
}
}

2.4软件条件:

如之前管道:如果读端关闭写端还在写就返回13号信号:SIGPIPE;并把写端杀死。

这里我们主要讲的是alarm函数所产生的14号信号:SIGALRM。

cpp 复制代码
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

调用 alarm 函数可以设定一个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发SIGALRM 信号,该信号的默认处理动作是终止当前进程。

返回值是当前时间距离上一次闹钟响的时间。【返回值是0或者是以前设定#include <unistd.h>的闹钟时间还余下的秒数。】

需要注意的是:如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

这里我们明白了alarm函数的使用;可以用它做一个测试;得出结论:纯IO(cout...)比cpu直接计算慢的多得多:

两块测试代码:

纯IO:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
int count = 0;
alarm(1);
while(true)
{
std::cout << "count : "
<< count << std::endl;
count++;
} 
return 0;
}

cpu直接计算:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
std::cout << "count : " <<
count << std::endl;
exit(0);
}
 int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
count++;
} 
return 0;
}

下面我们来编写代码测试下alarm函数:

配合一下pause函数:

cpp 复制代码
#include <unistd.h>
int pause(void);

只要给进程发送一个信号才能唤醒它;否则一直暂停在这(返回值就是-1)

pause函数返回 -1 时,errno会被设置为EINTR,这表示函数被一个信号中断。

下面就是我们会制造一个循环的alarm(也就是利用自定义捕捉函数实现):

效果如下:

测试代码:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>

int gcount = 0;
void hanlder(int signo)
{

    std::cout << "gcount : " << gcount << std::endl;
    int n = alarm(1); // 重设闹钟,会返回上⼀次闹钟的剩余时间
    std::cout << "剩余时间 : " << n << std::endl;
}
int main()
{
    alarm(1);
    signal(SIGALRM, hanlder);
    while (true)
    {
        pause();
        std::cout << "我醒来了..." << std::endl;
        gcount++;
    }
}

那么说了这麽多什么就是软件条件呢?

在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号) 等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。

对于OS中的闹钟机制:

这里我们通俗抽象理解一下:

OS中有多个进程;也就意味这每个闹钟可以告诉进程什么时间该干什么;因此OS对于这些闹钟也就是一个先描述后组织形式管理(这里我们抽象理解成小堆形式)

2.5硬件异常:

对于硬件异常;我们这里就从除0错误以及野指针来讲:

除0错误:

效果:

8号 :SIGFPE

Floating-point exception

抛出8信号并产生coredump 文件 。

测试代码:

cpp 复制代码
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("catch a sig : %d\n", sig);
} 
int main()
{
//signal(SIGFPE, handler); // 8) SIGFPE
sleep(1);
int a = 10;
a/=0;
while(1);
return 0;
}

模拟野指针错误:

测试代码:

cpp 复制代码
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("catch a sig : %d\n", sig);
} 
int main()
{
signal(SIGSEGV, handler);
sleep(1);
int *p = NULL;
*p = 100;
while(1);
return 0;
}

这些都是由硬件异常引起的软中断(后续我们会讲到)。

硬件异常系统如何知道才发信号给进程呢?

这里我们简单说一下:

比如除0;当cpu计算的时候会发生错误;然后因为os是软硬件管理者故会及时知道并发信号。

对于野指针:当mmu拿到野指针的成拟地址并通过cr3对应找去找物理地址发现找不到(转化失败)告诉os

这里其实都是软中断引起的中断号来告诉OS该怎么做。

这里我们不难发现;这两种硬件异常虽然会退出但是都会产生一个core dump标记以及core文件;下面我们来看一下:

总结一下能产生core dump以及core文件的信号:

查看coredump标记代码:

cpp 复制代码
#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
if (fork() == 0)
{
sleep(1);
int a = 10;
a /= 0;
exit(0);
} 
int status = 0;
waitpid(-1, &status, 0);
printf("exit signal: %d, core dump: %d\n", status&0x7F, (status>>7)&1);
return 0;
}

那么还会有个core文件;对于ubuntu是core.XXX

那什么又是core dump文件呢?

返回像上面那样core的信号它就会磁盘产生core文件;但是一般云服务器是看不到的(生产服务禁止搞core文件【可以从debug版本搞】)

但是我们可以用一下方式把它打开:

cpp 复制代码
ulimit -a //查看云服务器禁用的

ulimit -... +字节数 //启动某个禁用文件等

如:

如果ubuntu开启左边后也无法看到core文件可以用一下面指令开启:

cpp 复制代码
sudo bash -c "echo core.%p > /proc/sys/kernel/core_pattern"

此时我们就能看到这个core文件了。

小结一下:

三·如何保存信号:

3.1信号相关用语:

下面我们先普及下有关信号的相关术语:

实际执⾏信号的处理动作称为信号递达(Delivery)

信号从产⽣到递达之间的状态,称为信号未决(Pending)。

进程可以选择阻塞(Block)某个信号。【被阻塞后只会在pending表进行标记;不会执行信号】

被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才执⾏递达的动作.

注意: 阻塞和忽略是不同的,只要信号被阻塞就不会递达,⽽忽略是在递达之后可选的⼀种处理动作;而且当pending已经标记后;当阻塞解除;它会立即执行对应的信号(先清空对应pending表的信号然后再去执行)

3.2进程如何管理信号:

下面一张图通俗理解进程中如何管理信号的:

看图我们就能看到有个新的类型sigset_t。

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储, sigset_t称为信号集,这个类型可以表示每个信号的"有效"或"无效"状态,在阻塞信号集中"有效"和"无效"的含义是该信号是否被阻塞,而在未决信号集中"有效"和"无效"的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的"屏蔽"应该理解为阻塞而不是忽略。

对于它;我们简单理解成一种特殊的整型(为了来模拟位图结构;但是不是整形);会提供类似位图的接口函数去操作它。

3.3如何对信号表的操作:

3.3.1sig系列函数:

首先我们先讲一下sigset_t类型的变量如何对它操作:

cpp 复制代码
#include <signal.h>
int sigemptyset(sigset_t *set);//把这个信号集每一位都变成0【使用信号集的时候需要调用】
int sigfillset(sigset_t *set);//把这个信号集每一位都变成1
int sigaddset(sigset_t *set, int signo);//添加 0变1
int sigdelset(sigset_t *set, int signo);//删除信号集对应的这个信号的位置 1变0
int sigismember(const sigset_t *set, int signo);//判断某个信号是否存在于这个信号集合中(用的位运算) 存在返回1不存在0出错-1

除了最后一个函数剩下的都是成功返回0失败返回-1.

注意:在使⽤sigset_t类型的变量之前,⼀定要调⽤sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

还有个函数就是获取当前的pending表:

cpp 复制代码
#include <signal.h>
int sigpending(sigset_t *set);
//读取当前进程的未决信号集(也就是还没完成;如果完成了标记就是0了),通过set参数传出。
//调⽤成功则返回0,出错则返回-1

3.3.2sigprocmask函数:

下面我们再来介绍一下对block表的更改操作(用到上面的信号集操作函数):

调⽤函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。

cpp 复制代码
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
//返回值:若成功则为0,若出错则为-1

注意:就是取消阻塞时候对pending为1至少执行一个。

下面我们利用上面的函数综合测试一下:

现象是符合预期的;然后得出结论:当取消阻塞时候会先清空pending表对应位置的标记然后再去执行对应的任务。

测试代码:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
void PrintPending(sigset_t &pending)
{
    std::cout << "curr process[" << getpid() << "]pending: ";
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << "\n";
}
void handler(int signo)
{
    std::cout << signo << " 号信号被递达!!!" << std::endl;
    std::cout << "-------------------------------" << std::endl;
    sigset_t pending;
    sigpending(&pending);
    PrintPending(pending);//如果是00000 就说明先清空对应位置pending值再抵达
    std::cout << "-------------------------------" << std::endl;
}
int main()
{
    signal(2, handler);

    sigset_t block_set, old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set, SIGINT); //自己的block
    sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进⾏的内核block表,完成了对2号信号的屏蔽!
    int cnt = 15;
    while (true)
    {
        // 2. 获取当前进程的pending信号集
        sigset_t pending;
        sigpending(&pending);
        // 3. 打印pending信号集
        PrintPending(pending);
        cnt--;
        // 4. 解除对2号信号的屏蔽
        if (cnt == 0)
        {
            std::cout << "解除对2号信号的屏蔽!!!" << std::endl;
            sigprocmask(SIG_SETMASK, &old_set, &block_set);
        }
        sleep(1);
    }
}

3.3.3sigaction函数:

处理捕捉信号;屏蔽信号集等等;这个比signal操作更加全;还可以处理实时信号。

cpp 复制代码
#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • 参数说明
    • signum:要处理的信号编号,例如 SIGINT(通常为 Ctrl+C 产生的中断信号)、SIGTERM(终止信号)等。
    • act:指向 struct sigaction 结构体的指针,用于指定新的信号处理方式。如果为 NULL,则不改变信号处理方式。
    • oldact:指向 struct sigaction 结构体的指针,用于保存原来的信号处理方式。如果不需要保存,可以设为 NULL
  • 返回值 :成功时返回 0,失败时返回 -1,并设置 errno 以指示错误类型。
cpp 复制代码
struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};
  • 成员说明
    • sa_handler:信号处理函数指针,当 sa_flags 未设置 SA_SIGINFO 时使用。该函数接受一个 int 类型的参数,表示接收到的信号编号。
    • sa_sigaction:当 sa_flags 设置了 SA_SIGINFO 时使用的信号处理函数指针。该函数接受三个参数:信号编号、指向 siginfo_t 结构体的指针(包含信号的额外信息)和一个指向 void 的指针(可用于恢复上下文)。
    • sa_mask:在信号处理函数执行期间需要阻塞的信号集合。
    • sa_flags:用于设置信号处理的各种标志,例如 SA_RESTART 表示在信号处理后自动重启被中断的系统调用,SA_SIGINFO 表示使用 sa_sigaction 作为信号处理函数。
    • sa_restorer:已废弃,通常设为 NULL

这里比较常用的就是sa_mask;sa_handler这两个了。

下面来测试一下:

这里我们用sigaction自定义捕捉一下2号信号:

测试代码1:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>

 void sigcb(int sig){
    std::cout<<"signal 是:"<<sig<<std::endl;
 }
int main(){
    struct sigaction ss,old;
    sigset_t st1,st2;
    sigemptyset(&st1);
    ss.sa_mask=st1;
    ss.sa_handler=sigcb;
    sigaction(SIGINT,&ss,&old);
    while(1){
        sleep(1);
        std::cout<<"我在等待信号"<<std::endl;    }
    return 0;
}

这里对于这个函数我们常用的就是sa_mask;sa_handler,然后sa_flags一般设置成0 ;sa_sigaction一般用来处理实时信号;我们本篇讲的普通信号就不做要求。

当某个信号的处理函数被调⽤时,内核⾃动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时⾃动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么它会被阻塞到当前处理结束为⽌。如果在调⽤信号处理函数时,除了当前信号被⾃动屏蔽之外,还希望⾃动屏蔽另外⼀些信号,则⽤sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。aa_flags字段包含⼀些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数。

简单来说就是:

1·我们当处理一个信号进行捕捉执行对应任务的时候;会默认给它加上屏蔽字:(防止多次收到这个信号一直执行;类似递归式展开造成不必要麻烦);直到这个信号执行完后自动解除屏蔽。

2·如果我们当捕捉某个信号的时候除了不希望它自己干扰;还不希望其他信号进行干扰可以利用sa_mask进行屏蔽;当我们执行完指定信号捕捉这些屏蔽都会自动解除。

下面举个例子来验证下上面所说的1/2条:

这里我们利用的是之前讲的;某信号抵达是先清空pending表对应位置然后再进行执行;如果比如发了2号信号接着又发2号;那么此时如果它再次执行那么pending表对应仍旧是0;反之就是1了;同理如果屏蔽了其他信号pending表对应位置也会显示出来。

注意:这里sa_mask一定要初始化再按照自己需求更改否则就是随机值。

我们屏蔽3 4信号:

先看效果:

测试2代码:

cs 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>



void handler(int signum)
{
    std::cout << "hello signal: " << signum << std::endl;
    while(true)
    {
        //不断获取pending表!
        sigset_t pending;
        sigpending(&pending);
        for(int i = 31; i >= 1; i--)
        {
            if(sigismember(&pending, i))
                std::cout << "1";
            else
                std::cout << "0";
        }
        std::cout << std::endl;
        sleep(1);
    }
    
}
int main()
{
    struct sigaction act, oact;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    act.sa_flags = 0;

    sigaction(SIGINT, &act, &oact); // 对2号信号进行了捕捉, 2,3,4都屏蔽

    while(true)
    {
        std::cout << "hello world: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

对于这个函数;在我们普通信号阶段只需要了解这些就OK。

四.本篇小结:

本篇文章基于博主对Linux信号的学习;整理了相关笔记;来讲述个人对Linux中信号的概念;产生如何保存等方面进行相关通俗的讲解;希望对读者们有所帮助;下一篇:续篇讲进行讲解对于Liunx信号部分剩下的知识如捕捉信号;OS背后是如何运行的等方面相关知识;欢迎订阅。

相关推荐
一大串羊肉串11 分钟前
【从零开始学习RabbitMQ | 第一篇】从异步通信到交换机
分布式·学习·rabbitmq
Zhuai-行淮12 分钟前
施磊老师rpc(三)
linux·vscode·rpc
程序猿不脱发225 分钟前
什么是负载均衡?NGINX是如何实现负载均衡的?
运维·nginx·负载均衡
不爱吃于先生25 分钟前
自监督学习(Self-supervised Learning)李宏毅
人工智能·学习·机器学习
非自律懒癌患者31 分钟前
相同IP和端口的服务器ssh连接时出现异常
服务器·tcp/ip·ssh
一匹电信狗37 分钟前
【Linux我做主】进度条小程序深度解析
linux·运维·服务器·c++·ubuntu·小程序·unix
xency40 分钟前
时间同步服务
linux·服务器
知远同学44 分钟前
docker学习笔记6-安装wordpress
笔记·学习
Silence4Allen1 小时前
Ubuntu 24.04 完整Docker安装指南:从零配置到实战命令大全
linux·ubuntu·docker
李q华1 小时前
关于浏览器页面自动化操作
运维·自动化