linux篇【10】:进程信号

news2025/1/16 1:46:01

目录

一.信号入门

1.信号是操作系统内一个内置机制

2.前后台进程的几条命令与ctrl+c

3.信号分类

4.信号产生是异步的

5.进程是如何记住这个信号

(3)存储方式:位图

二.signal ——对某信号设置自定义行为(捕捉)的函数

(1)证明 ctrl + c 是2号信号

(2)ctrl+\:3号信号 ——默认也是终止自己

9号信号:管理员信号,该信号不可被自定义设置(捕捉)。除了D状态进程,其他进程都可杀。

三.用户层产生信号的方式(信号产生前)

1.敲键盘的信号发送过程

2.通过 系统接口 完成对进程发送信号

 (1)kill 

自己写一个mykill命令:

 (2)raise

 (3)abort

① abort()   =   exit()

 ②9号信号不可被捕捉,6号信号是可以被捕捉,但捕捉完还是要退出

3. 软件条件

 ①1秒IO只能不到2w次

 ②但实际CPU 1秒能跑5亿次,证明IO效率低

4.硬件异常产生信号

(1)除零错误 与 越界&&野指针

(2)崩溃了,一定会导致进程终止吗?——答:不一定!

四.core dump 核心转储

1.status中的core dump标记位

(1)core dump 核心转储 详细解释:

(2)观察core dump标记位

(3) core可以用gdb定位错误

五.阻塞信号(信号产生中)

1. 信号其他相关常见概念

2. 在内核中的表示

信号忽略和信号阻塞区别:

3.sigset_t(位图)

4.信号集操作函数

5.sigprocmask

6.sigpending

六.信号的捕捉(信号处理后)

1.用户态和内核态

2.自定义捕捉信号的处理过程

3.第二个捕捉函数 sigaction

(1)sigaction介绍

 killall mysignal——根据进程名杀进程

 (2)sa_mask 解释

(3)handler可以配合使用switch语句:

4.可重入函数

5.volatile

(1)常规情况(无-O2优化,无volatile)

(2)优化情况(有-O2优化,无volatile)

 (3)优化+volatile 情况(有-O2优化,有volatile)

6.SIGCHLD信号


一.信号入门

1.信号是操作系统内一个内置机制

信号是给进程发送的,进程要具备处理信号的能力。
1.该能力一定是预先已经早就有了的(程序员——>写OS的代码,OS帮我们提供)
2.进程能够识别对应的信号
3.进程能够处理对应信号
对于进程来讲,即便是信号还没有产生,我们进程已经具有识别和处理这个信号的能力了,因为信号是操作系统内一个内置的机制

2.前后台进程的几条命令与ctrl+c

  • 前台进程:占有控制终端的进程,其它称为后台进程。前台进程可以ctrl+c杀掉,后台进程不可杀,只能把后台进程转成前台进程再ctrl+c杀掉。

ctrl+c:硬件行为被解释成信号,发送给进程。作用:杀死进程

jobs:查看后台进程

./proc &:把proc这个前台进程放入后台

fg (任务号):把作业号对应的后台进程放入前台 

3.信号分类

1~31:普通信号(我们要学习的信号)。 34~64:实时信号(带RM) 【34】SIGRTMIN

普通信号介绍:1) SIGHUP ——>1是信号的编号,SIGHUP是信号名称。信号就是宏,SIGHUP的值就是1

查看详细信号命令

man 7 signal

4.信号产生是异步的

因为信号产生是异步的(互不干扰,同步反义词),当信号产生的时候,对应的进程可能正在做更重要的事情,我们进程可以暂时不处理这个信号!——进程可能不需要立即处理这个信号。不代表这个信号不会被处理
未来要处理,所以你必须记住这个信号已经来了(要记住:①是否有信号。②什么信号。 )(你在玩游戏,外卖员敲门,你说等一会儿)
①默认动作(吃外卖)
②忽略(把外面放在外卖,不吃)
③自定义动作(把外卖给弟弟吃)
这就是类比了信号的处理,信号的捕捉,递达处理动作。

5.进程是如何记住这个信号

(1)要存储的内容

① 有没有产生【位图比特位的内容1,0】
② 什么信号产生【位图比特位的位置】

(2)存储在哪里:信号内容记录在 进程的PCB中的 

(3)存储方式:位图
 

task_ struct {
uint32_ _t sig;    位图,0000 0010
}

进程的task_ struct是内核的数据结构

只有OS有这个权利,能直接修改这个task_struct内的数据位图
OS是进程的管理者,进程的所有属性的获取和设置,只能由OS来。
无论信号怎么产生,最终一定只能是OS帮我们进行信号的设置!

二.signal ——对某信号设置自定义行为(捕捉)的函数

man 2 signal        

                typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:对哪个信号设置捕捉动作。 handler:自定义对信号的捕捉动作的函数

(1)证明 ctrl + c 是2号信号

这里不是调用hander方法,这里只是设置了一个回调(注册这个方法),让SIGINT(2)产生的时候,该方法才会被调用,如果不产生SIGINT(2),该方法不会被调用!

ctrl + c : 本质就是给前台进程发送2号信号给目标进程,目标进程默认对2号信号的处理,是终止自己

今天更改了对2号信号的处理,设置了用户自定义处理方法


void handler(int signo)
{
    cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl;
    
}
int main()
{   
    //这里不是调用hander方法,这里只是设置了一个回调,让SIGINT(2)产生的时候,该方法才会被调用
    //如果不产生SIGINT(2),该方法不会被调用!
    //ctrl + c : 本质就是给前台进程发送2号信号给目标进程,目标进程默认对2号信号的处理,是终止自己
    //今天更改了对2号信号的处理,设置了用户自定义处理方法
    signal(SIGINT, handler); // 设置所有的信号的处理动作,都是自定义动作

    while (true)
    {
        cout << "我是一个正在运行中的进程: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

(2)ctrl+\:3号信号 ——默认也是终止自己


void handler(int signo)
{
    cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl;
    
}
int main()
{   
    signal(SIGINT, handler); // 设置所有的信号的处理动作,都是自定义动作
    signal(3, handler);
    while (true)
    {
        cout << "我是一个正在运行中的进程: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

这个进程里面自定义这些终止信号后,就只能用9号信号杀死。

9号信号:管理员信号,该信号不可被自定义设置(捕捉)。除了D状态进程,其他进程都可杀

 

三.用户层产生信号的方式(信号产生前)

1.敲键盘的信号发送过程

键盘产生信号,OS给进程发送的信号——发送信号(写入信号):就是OS在位图中把对应信号的位图由0置1,即可完成发送信号(发送信号不如说成写入信号)。详解ctrl+v的信号发送过程:

敲键盘ctrl+v,键盘产生信号后,通过中断的方式告诉OS,CPU直接调用中断向量表中的方法,把ctrl+v这个组合按键读取进来,OS识别此组合按键,OS对组合按键进行解释,解释成信号,OS找到对应进程,在位图中把对应信号的位图由0置1。

2.通过 系统接口 完成对进程发送信号

 (1)kill 

man 2 kill

  int kill(pid_t pid, int sig);

向任意进程pid发送任意信号sig

返回值:成功返回0;失败返回-1

自己写一个mykill命令

 

 (2)raise

man 3 raise

给自己这个进程发送任意信号

 

 (3)abort

man 3 abort

向自己这个进程发送 SIGABRT 这个6号信号

① abort()   =   exit()

 

 ②9号信号不可被捕捉,6号信号是可以被捕捉,但捕捉完还是要退出

3. 软件条件

alarm

man 2 alarm

定闹钟:seconds秒以后给自己这个进程发送信号 14 SIGALRM

 ①1秒IO只能不到2w次

 ②但实际CPU 1秒能跑5亿次,证明IO效率低

4.硬件异常产生信号

(1)除零错误 与 越界&&野指针

进程崩溃的本质,是该进程收到了异常信号。

因为硬件异常,而导致OS向目标进程发送信号,进而导致进程终止的现象!

除零错误: CPU内部,状态寄存器,当我们除0的时候,CPU内的状态寄存器会被由0置1,设置成为 有报错:浮点数越界。CPU的内部寄存器(硬件)会记录报错,OS就会识别到CPU内有报错啦 -> 识别之后OS会确认 ①谁干的?② 是什么报错,OS结合报错会构建信号,并向目标进程发送信号->目标进程在合适的时候处理信号,处理一般信号就是终止进程

越界&&野指针: 我们在语言层面使用的地址(指针), 其实都是虚拟地址,虚拟地址要转成物理地址->然后才能访问物理内存->才可以读取对应的数据和代码

如果虚拟地址有问题,因为地址转化的工作是由(MMU(memory maneger unit 内存管理单元,是硬件)+页表(软件)), 转化过程就会引起问题->表现在硬件MMU上->OS发现硬件出现了问题会确认:①谁干的?② 是什么报错(OS->构建信号) -> 向目标进程发送信号->目标进程在合适的时候->处理信号->终止进程

(2)崩溃了,一定会导致进程终止吗?——答:不一定!

 例如:我们把信号都重新注册了,但是注册的方法里面没有终止程序(exit),那么当发生除0错误时,OS给进程发送8号信号,但是由于没有中断进程,这个异常一直存在,OS会持续不断的发送8号进程,此时崩溃了,但是进程也并没有终止

 小总结:

上面所说的所有信号产生,最终都要由OS来进行执行,为什么?
——OS是进程的管理者
信号的处理是否是立即处理的?
——在合适的时候
信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
——会被进程记录下来,记录在进程PCB中。
一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
——知道
如何理解OS向进程发送信号?能否描述一下完整的发送处理过程? 

——OS将进程PCB中信号位图的比特位由0置1

四.core dump 核心转储

1.status中的core dump标记位

man 7 signal

(1)core dump 核心转储 详细解释:

像3,4,6,8,11这些信号属于代码内部出现问题导致的进程终止(比如4:非法指令。6:abort终止。8:浮点数错误。11:越界。),这些异常可以调试。如果父进程获取子进程退出信息时,若被这几种信号终止,status中的core dump标记位会被置1,并且会生成一个叫 core.22357 的大文件,22357 叫引起core文件(崩溃)的进程pid。

 即:core dump 核心转储定义: core dump 会把进程在运行中,对应的异常上下文数据,core dump(转而存储)到磁盘上,方便调试,同时会把status中的 status- >core dump -> 置1

(2)观察core dump标记位

ulimit -a 打开 core dump文件

 

(3) core可以用gdb定位错误

五.阻塞信号(信号产生中)

1. 信号其他相关常见概念

实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

2. 在内核中的表示

信号在内核中的表示示意图

横着看——block—pending—hander横着每一格对应每一格

pending:未决信号集。(用途:代表是否收到信号)就是一个32bit的位图,存信号标记位,比特位位置是信号编号(第一个比特位代表1号信号),比特位内容代表 是否收到信号。(信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志

hander:函数指针数组。里面存的是处理pending表里面信号的处理方法的函数地址。(signal注册信号方法就是通过改这个数组实现的)补充:多次发送信号时,因为一个信号只有一个比特位去记录,所以(一段时间内)多次发送普通信号时,只能递达一次去执行方法,其他全部丢弃。实时信号是可以记录多次的,就成为队列了。

block:信号屏蔽字(阻塞信号集)。和pending一样的是 比特位位置是信号编号,不同的是 比特位内容代表 是否阻塞该信号——为1:拦截对应信号执行对应的方法,举例:即使pending[0]=1,block[0]=1,1号信号也无法执行hander里面对应的方法( 阻塞信号集也叫做当前进程的 信号屏蔽字(Signal Mask),这里的屏蔽应该理解为阻塞而不是忽略。

①每个信号都有两个标志位分别表示阻塞 (block) 和未决 (pending), 还有一个函数指针表示处理动作。信号产生时, 内核在进程控制块中设置该信号的未决标志, 直到信号递达才清除该标志在上图的例子中,SIGHUP 信号未阻塞也未产生过,当它递达时执行默认处理动作。
②SIGINT 信号产生过 , 但正在被阻塞 , 所以暂时不能递达。虽然它的处理动作是忽略SIG_IGN , 但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。即:block是1时一直是阻塞,pending对应比特位是1就一直保持1,除非未来解除block的阻塞,否则pending对应比特位永远保持1
③SIGQUIT 信号未产生过 , 一旦产生 SIGQUIT 信号将被阻塞 , 它的处理动作是用户自定义函数 sighandler 。如果在进程解除对某信号的阻塞之前这种信号产生过多次, 将如何处理 ? Linux 是这样实现的 : 常规信号在递达之前产生多次只计一次 , 而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号

信号忽略和信号阻塞区别:

        信号忽略是处理信号的方式,处理方式是忽略他;信号阻塞是拦截信号的递达,不让信号被处理

3.sigset_t(位图)

sigset_t就是一个位图

从上图来看 , 每个信号只有一个 bit 的未决标志 , 0 1, 不记录该信号产生了多少次 , 阻塞标志也是这样表示的。 因此, 未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集 , 这个类型可以表示每个信号的“ 有效 无效 状态 , 在阻塞信号集中 有效 无效 的含义是该信号是否被阻塞 , 而在未决信号集中 有效” 无效 的含义是该信号是否处于未决状态。
# define _SIGSET_NWORDS	(1024 / (8 * sizeof (unsigned long int)))
typedef struct
  {
    unsigned long int __val[_SIGSET_NWORDS];
  } __sigset_t;

4.信号集操作函数

sigset_t 类型对于每种信号用一个 bit 表示 有效 无效 状态 , 至于这个类型内部如何存储这些 bit 则依赖于系统实现, 从使用者的角度是不必关心的 , 使用者只能调用以下函数来操作 sigset_ t变量 , 而不应该对它的内部数据做任何解释, 比如用 printf 直接打印 sigset_t 变量是没有意义的
#include <signal.h>
int sigemptyset(sigset_t *set);                                ——对信号集set做清空
int sigfillset(sigset_t *set);                                     ——对信号集set全部置1
int sigaddset (sigset_t *set, int signo);                  ——在特定信号集set 中加上特定信号signo
int sigdelset(sigset_t *set, int signo);                    ——在特定信号集set 中删除特定信号signo
int sigismember (const sigset_t *set, int signo);  ——在特定信号集set 中判断是否有特定信号signo
这四个函数都是成功返回 0, 出错返回 -1 sigismember 是一个布尔函数 , 用于判断一个信号集的有效信号中是否包含某种 信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1

5.sigprocmask

调用函数sigprocmask 可以读取或更改进程的信号屏蔽字 (阻塞信号集),即修改block表
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
假设当前的信号屏蔽字为mask,下表说明了how参数的选项:

const sigset_t *set 是输入型参数,set传入一个信号集,

如果how是 SIG_BLOCK :则把set中包含的信号添加到当前进程的信号屏蔽字block表中(block对应标记位由0置1),当前进程的信号屏蔽字中已经屏蔽的信号不会改变。
如果how是 SIG_UNBLOCK:则把set中包含的信号从到当前进程的信号屏蔽字block表中解除屏蔽block对应标记位由1置0),当前进程的信号屏蔽字中其他已经屏蔽的信号不会改变。
如果how是 SIG_SETMASK:直接把用set这个信号集把 当前进程的信号屏蔽字block表 覆盖

sigset_t *oset 是输出型参数:把原本老的信屏蔽字返回出来,以便恢复,如果不想返回就传nullptr

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

使用示例:

6.sigpending

#include <signal.h>
sigpending
读取当前进程的未决信号集 , 通过 set 参数传出。调用成功则返回 0, 出错则返回 -1

 总结:sigprocmask修改block表;sigpending读取 pending表;signal修改hander表

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

using namespace std;

int cnt = 0;

void handler(int signo)
{
    cout << "我是一个进程,刚刚获取了一个信号: " << signo << " cnt: " << cnt << endl;
    // exit(1);
}

static void showPending(sigset_t *pendings)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(pendings, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main(int argc, char *argv[])
{
    cout << "pid: " << getpid() << endl;

    sigset_t bsig, obsig;
    sigemptyset(&bsig);
    sigemptyset(&obsig);
    // sigfillset();
    for (int sig = 1; sig <= 31; sig++)  //将当前进程阻塞所有信号,并注册所有信号
    {
        sigaddset(&bsig, sig);
        signal(sig, handler);
    }
    // 设置用户级的信号屏蔽字到内核中
    sigprocmask(SIG_SETMASK, &bsig, &obsig);

    // 1. 不断的获取当前进程的pending信号集
    sigset_t pendings;
    int cnt = 0;
    while (true)
    {
        // 1.1 清空信号集
        sigemptyset(&pendings);
        // 1.2 获取当前进程(谁调用,获取谁)的pending信号集
        if (sigpending(&pendings) == 0)
        {
            // 1.3 打印一下刚刚获取到的当前进程的pengding信号集
            showPending(&pendings);
        }
        sleep(1);
        cnt++;    
        if(cnt == 20)    //第20秒时解除2号信号的阻塞
        {
            cout << "解除对2号信号的block...." << endl;
            sigset_t sigs;
            sigemptyset(&sigs);
            sigaddset(&sigs, 2);
            sigprocmask(SIG_UNBLOCK, &sigs, nullptr);
        }
    }

 

六.信号的捕捉(信号处理后)

进程处理信号,不是立即处理的。
合适的时候,是什么时候呢? ?——当当前进程从内核态,切换回用户态的时候,进行信号的检测与处理! 先解释用户态和内核态:

1.用户态和内核态


①OS在不在内存中被加载呢? ?——在
无论进程怎么切换,我们都可以找到内核的代码和数据,前提是你只要能够有权利访问!

②当前进程如何具备权利 访问这个内核页表乃至访问内核数据呢?——要进行身份切换。
        进程如果是用户态的——>只能访问用户级页表 0~3G
        进程如果是内核态的——>访问内核级和用户级的页表 3~4G
③我怎么知道我是用户态的还是内核态的呢?
CPU内部有对应的状态寄存器CR3, CR3有比特位标识当前进程的状态 0:内核态,3:用户态

④0—>3 切到内核态的情况:1.系统调用的时候。2.时间片到了,进程间切换。3.其他等等。执行完毕就继续切回用户态

⑤内核态vs用户态
内核态可以访问所有的代码和数据——内核态具备更高权限
用户态只能访问自己的

⑥我们的程序,会无数次直接或者间接的访问系统级软硬件资源(管理者是OS),本质上,你并没有自己去操作这些软硬件资源,而是必须通过OS- >无数次的陷入内核(1.切换身份3->0 2.切换页表,切到内核级页表)->调用内核的代码->完成访问的动作->结果返回给用户(1.切换身份0->3 2. 切换页表,切到用户级页表)->用户得到结果

⑦while(1);死循环进程普通程序会身份切换吗? —>也会陷入内核,来回切换身份 —>你也有自己的时间片 —>时间片到了的时候->OS收到中断信息,把进程从cpu移走,进程切到内核态,更换内核级页表 —>保护上下文,执行调度算法 —>选择了新的进程 —>恢复新进程的上下文 —>用户态,更换成用户级页表 —>CPU执行的就是新进程的代码!
 

2.自定义捕捉信号的处理过程

①因为一些系统调用例如open(),用户态—>内核态,处理完open的代码后

②本来可以返回代码继续执行了,但是正好处于内核态,就顺便去检测信号,并处理信号。

③处理信号:若是阻塞或无信号(忽略/默认)就直接 内核态—>用户态 返回即可 / 若是非阻塞并有信号的自定义捕捉,就 内核态—>用户态 执行对应的handler方法。(执行handler方法为什么只能是用户态?解释:内核态是什么都可以做的,如果让内核态做用户自定义代码,万一用户写的是一段恶意代码呢? ? ?比如rm根目录等等,这样内核态身份就被恶意利用了,所以内核为了保护自己,就只能用户态执行用户的代码

④执行完信号对应方法后再由 用户态—>内核态 回到内核。此时所有任务都完成:系统接口调用完成,信号捕捉完成。

⑤完成所有任务后,由  内核态—>用户态 通过特殊系统调用返回到当时用户跳出的代码中。

结论:进程的生命周期中,会有很多次机会去陷入内核(中断,陷阱,系统调用,异常...),一定会存在很多次的机会进行内核态返回用户态。进程的信号在被合适的时候处理,合适的时候:从内核态返回到用户态的时候,返回之前顺便检测信号,并处理信号

过程图,跟上面一样,可以忽略:

记忆图 :无穷大画法

无穷大中间交点要在横线下方,则有多少个交点,就证明有多少个状态切换;方向决定了 是内核到用户,还是用户到内核

 

3.第二个捕捉函数 sigaction

(1)sigaction介绍

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

 signum:要对哪个信号自定义捕捉。act:设置成什么动作。oldact:老动作,输出型参数,不要就设置nullptr

只用填sa_handler,sa_mask,2个 ,sa_flags 默认为0不考虑,其他是实时信号的也不考虑、

sa_handler:注册信号的函数处理方法

sa_ mask ——作用:执行某①信号处理函数时,信号集位图sa_ mask中有某②信号,阻塞①信号同时,阻塞 sa_ mask 传入的②信号——(2)是详细解释

 

 例1

 

例2

 killall mysignal——根据进程名杀进程

 (2)sa_mask 解释

sa_ mask ——作用:执行某①信号处理函数时,信号集位图sa_ mask中有某②信号,阻塞①信号同时,阻塞 sa_ mask 传入的②信号

当某个信号的处理函数被调用时 , 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来的信号屏蔽字, 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞到当前处理结束为止

  ctrl c后再ctrl c发现2号信号为1,即:阻塞了2号信号。sigaddset(&act.sa_ mask,3) ; 信号集位图中有3号信号,作用是执行2号信号时阻塞了2号信号同时也阻塞3号信号

(3)handler可以配合使用switch语句:


 void Handler2()
 {
     cout << "hello 2" << endl;
 }
 void Handler3()
 {
     cout << "hello 3" << endl;
 }
 void Handler4()
 {
     cout << "hello 4" << endl;
 }
 void Handler5()
 {
     cout << "hello 5" << endl;
 }
 void Handler(int signo)
 {
     switch (signo)
     {
     case 2:
         Handler2();
         break;
     case 3:
         Handler3();
         break;
     case 4:
         Handler4();
         break;
     case 5:
         Handler5();
         break;
     default:
         break;
     }
 }

 int main()
 {
     signal(2, Handler);
     signal(3, Handler);
     signal(4, Handler);
     signal(5, Handler);

     while (1)
     {
         sleep(1);
     }
 }

4.可重入函数

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函 数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入节点node2操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步,之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点node1真正插入链表中了,node2会内存泄漏。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,即:不能多个执行流调用,被像这样的函数称为 不可重入函数(大部分都是)。反之, 如果一个函数只访问自己的局部变量或参数,在重复被多个执行流调用时不会出现问题,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?

如果一个函数符合以下条件之一则是不可重入的:
        调用了malloc free, 因为 malloc 也是用全局链表来管理堆的。
        调用了标准I/O 库函数。标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构。

5.volatile

该关键字在C 当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下

作用:保持内存的可见性,读取必须从内存中读

(1)常规情况(无-O2优化,无volatile)

正常makefile中 不加-O2 也不加volatile 是和(2)一样的结果,都能把flags修改而终止进程。

(2)优化情况(有-O2优化,无volatile)

但是makefile加了-O2,表明让编译器优化:因为while(!flags)是位运算,只能CPU处理,而且flags一直在被使用,此时CPU自作主张把内存中的flags放入寄存器中,当我们修改flags=1时,只是修改了内存中的flags,没有影响寄存器中的flags,则ctrl c 时执行处理方法,但是寄存器中的flags不会改变,永远不会终止进程。

 (3)优化+volatile 情况(有-O2优化,有volatile)

volatile:保持内存的可见性,读取必须从内存中读

此时要求CPU每次使用flags只能从内存中拿flags放入寄存器中,这样修改flags就可以终止进程。

6.SIGCHLD信号

子进程在 终止/暂停(stop)/继续运行时 会给父进程发SIGCHLD信号 , 该信号的默认处理动作是忽略。

 测试:

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void handler(int signo)
{
    cout << "子进程退出啦,我确实收到了信号: " << signo << " 我是: " << getpid() << endl;
}

int main()
{
    signal(SIGCHLD, handler);
    pid_t id = fork();
    if (id == 0)
    {
        while (true)
        {
            cout << "我是子进程: " << getpid() << endl;
            sleep(1);
        }
        exit(0);
    }

    // parent
    while (true)
    {
        cout << "我是父进程: " << getpid() << endl;
        sleep(1);
    }
}

(1)进程退出发信号

 (2)进程 暂停(stop)/继续运行 发信号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/10259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】进程间通信之消息队列

系列目录 进程间通信——共享内存 进程间通信——信号量 文章目录 一、概念 二、消息队列函数 1.msgget 2.magsnd 3.msgrcv 4.msgctl 三、掌握消息队列操作 一、概念 提供了一种从另一种进程发送一个数据块的方法。而且每个数据块都被认为含有一个类型&#xff0c;接…

Python3《机器学习实战》学习笔记(十):ANN人工神经网络代码详解(数字识别案例以及人脸识别案例)

文章目录一、构建基本代码结构1.1预处理数据的工具包1.2 初始化参数1.3工具类sigmoid1.4工具类矩阵变换1.5初始化theta1.6正向传播1.7反向传播1.8梯度下降1.9训练模块二、MNIST数字识别三、人脸识别四、总结一、构建基本代码结构 1.1预处理数据的工具包 """Dat…

2021年认证杯SPSSPRO杯数学建模C题(第一阶段)破局共享汽车求解全过程文档及程序

2021年认证杯SPSSPRO杯数学建模 C题 破局共享汽车 原题再现&#xff1a; 自 2015 年以来&#xff0c;共享汽车行业曾经“百花齐放”&#xff0c;多个项目获得巨额融资。但因为模式过重、运营成本过高、无法盈利等问题&#xff0c;陆续有共享汽车公司因为资金链断裂而倒闭。据…

RocketMQ存储设计的奥妙

RocketMQ作为一款基于磁盘存储的中间件&#xff0c;具有无限积压能力&#xff0c;并提供高吞吐、低延迟的服务能力&#xff0c;其最核心的部分必然是它优雅的存储设计。 1、存储概述 RocketMQ存储的文件主要包括Commitlog文件、ConsumeQueue文件、Index文件。 RocketMQ将所有…

温振传感器有几种传输方式?

在现代化社会中&#xff0c;各种机器无时无刻参与着我们的日常生活&#xff0c;承担在我们的周围承担起重要作用&#xff0c;轴承、电机、泵体等也成为工业文明中关键存在&#xff0c;它们的温度和状态影响着整个工业自动化系统运行的健康和效率。 长期以来&#xff0c;传感器技…

数字集成电路设计(四、Verilog HDL数字逻辑设计方法)(一)

文章目录1.Verilog语言的设计思想和可综合特性2. 组合电路的设计2.1 数字加法器2.2 数据比较器2.3 数据选择器2.4 数字编码器2.4.1 3位二进制8线-3线编码器2.4.2 8线-3线优先编码器2.4.3 二进制转化十进制8421BCD编码器&#xff08;重要&#xff09;2.4.4 8421BCD十进制余3编码…

ue4使用Niagara粒子实现下雨效果,使用蓝图调节雨量

一、使用Niagara粒子系统实现下雨效果 1. 首先创建一个雨水的材质 新建 — 材质 2. 创建Niagara系统 新建 新建 — FX — Niagara系统 — 来自所选发射器的新系统 — 下一步 — 选择Fountain — 点击号&#xff0c;点击完成 删除下面的“Add Velocity in Cone” 添加“…

矩池云如何自定义端口,访问自己的web项目

本文将向您介绍如何在矩池云租用服务器的时候自定义端口&#xff0c;并将您的 web 项目部署到自定义端口&#xff0c;最后实现在本地通过自定义端口对应链接访问服务。 上传代码和数据 首先&#xff0c;您需要将本地的项目代码和数据上传到矩池云网盘。这里为了方便您测试使用…

类似ps的python工具lama cleaner

Lama Cleaner是个类似ps图片的工具&#xff0c;可以把图片中不想要的部分p掉&#xff0c;或者填补图片中丢失的部分。用下来感觉还蛮靠谱&#xff0c;对于不会ps的人是福音&#xff0c;记录一下。 相关介绍&#xff1a;https://github.com/Sanster/lama-cleaner 1.安装 安装…

react 中 ref 管理列表

背景 最近在看 react 新的官方文档 的时候&#xff0c;看到这么一个标题&#xff0c;How to manage a list of refs using a ref callback&#xff0c;就是一个图片的列表&#xff0c;类似这样 然后点击按钮的时候&#xff0c;通过 scrollIntoView 这个 api 来让他滚动&#…

python生成模拟微信气泡图片

0. 起因 众所周知&#xff0c;借刀杀人最为致命&#xff0c;聊天也是如此。 最近我的群聊画风逐渐变味&#xff1a; 当然&#xff0c;这种图片的生产成本很低&#xff0c;只需在设置页关闭昵称显示&#xff0c;把聊天背景重置为灰色&#xff0c;然后利用截图工具截图&#xf…

【金融项目】尚融宝项目(十三)

25、充值 25.1、需求介绍 25.1.1、投资人充值 **1、需求描述 ** 标的产生后&#xff0c;平台展示标的&#xff0c;投资人就可以在平台投资标的&#xff0c;获取收益&#xff1b;投资人投资标的必须满足以下条件&#xff1a; 充值过程与绑定过程一致&#xff0c;也是在平台发…

Delphi 11.2 Alexandria程序集代码

Delphi 11.2 Alexandria程序集代码 高DPI VCL设计器-VCL设计器现在在设计时使用类似Microsoft Windows的样式&#xff0c;这意味着除非禁用此功能&#xff0c;否则设计器中的控件始终使用此样式绘制。此样式与Windows当前使用的浅色或深色主题相匹配。 编辑器选项卡-在版本11.2…

【3D目标检测】Frustum PointNets for 3D Object Detection from RGB-D Data

目录概述细节网络结构视锥候选框3D实例分割边界框参数回归损失函数概述 首先本文是基于图像和点云的&#xff0c;属于早期的模态融合的成果&#xff0c;是串行的算法&#xff0c;而非并行的&#xff0c;更多的是考虑如何根据图像和点云这两个模态的数据进行3D目标检测。 提出动…

亚马逊平台不给力?来Starday,告诉你什么是真正的高阶玩法

距2021年的亚马逊封号潮已经过去了一段时间&#xff0c;但其影响却依然在跨境电商行业间回荡。从4月份起&#xff0c;亚马逊就开始对违反平台规则的卖家进行封号。此后打击规模持续扩大&#xff0c;到6月中下旬&#xff0c;深圳一批头部卖家均被亚马逊平台下架&#xff0c;遭到…

Coverage-based Greybox Fuzzing as Markov Chain

AFLFast: Coverage-based Greybox Fuzzing as Markov Chain 一、论文阅读 论文来自CCS2016 作者&#xff1a;Marcel Bhme 模糊测试领域巨佬 Abstract 基于覆盖的灰盒模糊测试 Coverage-based Greybox Fuzzing (CGF)。大多数测试用例执行少数高频路径&#xff0c;制定策略倾…

浪潮信息工程师:谈一谈设备透传虚拟机启动慢背后的原因及其优化方法 | 第 51 期

本周「龙蜥大讲堂」预告来啦&#xff01;龙蜥社区邀请了浪潮信息操作系统研发工程师崔士伟分享《设备透传虚拟机的快速启动优化》&#xff0c;快来扫码入群&#xff0c;预定前排小板凳观看直播吧&#xff01; 直播主题及内容介绍 直播主题&#xff1a;设备透传虚拟机的快速启…

360+城市空气质量指数-日度数据、良好天数统计(2001-2022年)

360城市空气质量指数-日度数据、良好天数统计&#xff08;2001-2022年&#xff09; 城市空气质量指数-日度数据、良好天数统计 1、包括&#xff1a;360个城市 2、时间&#xff1a;2001.1-2022.1月 3、样本量&#xff1a;1371937条 4、数据来源&#xff1a;空气质量在线…

使用Excel 表示汽车、摩托车10年免检时间、非常清晰。

1&#xff0c;汽车摩托车10年内年检问题 根据最新的国家法律&#xff1a; http://www.wenjiang.gov.cn/wjzzw/c152333/2022-09/30/content_66efe4febb8040758f3f079cf0baa310.shtml 搜索了下&#xff0c;找到了成都的规定&#xff1a; 近日&#xff0c;公安部、市场监管总局…

中电海康-中电52所面经

中电海康&#xff0c;中电52所面经中电海康面经一面&#xff08;电话面&#xff09;二面&#xff08;现场面&#xff09;自我回顾中电海康面经 一面&#xff08;电话面&#xff09; Redis的使用和配置多线程的使用&#xff0c;线程池的使用SpringBoot的核心注解和流程AOP IOC …