[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号
创始人
2024-04-21 21:26:03
0

目录

1.可重入函数

2.volatile

2.1从信号角度理解volatile的作用

2.2volatile的作用

3.SIGCHLD信号

3.1SIGCHLD信号的验证


1.可重入函数

在数据结构初阶时我们学习过链表,其中当然也学习过链表头插。在此我们复习一下链表头插,我们使用画图来演示。

newnode->next = head->next;
head->next = newnode;

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

 

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称

重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,

如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

因此如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重复的方式使用全局数据结构。

2.volatile

2.1从信号角度理解volatile的作用

今天我们站在信号的角度上理解一下valatile。

#include 
#include int flags = 0;void handler(int signo)
{flags = 1;printf("flags: 0 -> 1\n");
}int main()
{signal(2,handler);while(!flags);printf("进程是正常退出的! flags: %d \n",flags);return 0;
}

我们来看一下这段简单的C语言代码,在标准情况下,程序运行起来时,键入CTRL-C,2号信号被捕捉,执行自定义动作,修改flags=1,while条件不满足,退出循环,进程退出。

但是,我们注意while(!flags)这条语句是检测,是逻辑判断。因此由CPU进行,每次循环检测都要读一下flags这个值,在正常情况下就应该这么做。但是当编译器优化等级很高时,在当前执行流下对flags没有做任何修改,因此会把flags这个值优化到CPU的寄存器内,因此在后续的判断中,CPU只会读取寄存器内flags内的值。但是当我们键入ctrl-c时,向进程发送2号信号。进程捕捉到2号信号会自定义调用handler方法。会将flags的值由0->1,这里注意由于flags是存在内存中的,我们改变的是内存中flags的值,而CPU寄存器内flags的值却没有变。因此CPU读取的flags的值却并没有变。所以当我们键入ctrl-c时,while循环也是不结束的。进程也不会退出。

myproc:myproc.cgcc -o $@ $^ -O2.PHONY:clean
clean:rm -f myproc

gcc中有不同的优化等级,我们在makefile中使用-O2 对gcc编译器进行优化。

我们再次将程序运行起来

那么如何解决呢,我们可以使用volatile关键字。volatile关键字就是要告诉编译器,不准对flags做任何优化,每次CPU计算的时候,拿内存的数据,都必须在内存中拿。

#include 
#include //保持内存的可见性
volatile int flags = 0;void handler(int signo)
{flags = 1;printf("flags: 0 -> 1\n");
}int main()
{signal(2,handler);while(!flags);printf("进程是正常退出的! flags: %d \n",flags);return 0;
}

2.2volatile的作用

根据上面的例子我们可以总结出volatile的作用:

  • volatile作用:保持内存的可见性,告知编译器,该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作。

3.SIGCHLD信号

在进程一章时,我们知道进程退出。其中,子进程退出的时候,不是默默地退出。而是会给父进程发送一个信号。这个信号就是SIGCHLD信号。该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数。这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

3.1SIGCHLD信号的验证

我们用C++代码来验证一下当子进程退出时,父进程捕捉SIGCHLD信号。这段代码也不难......

#include 
#include 
#include using namespace std;void handler(int signo)
{cout<<"子进程退出啦,我确实收到了信号"<

 

我们使用man 7 siganl查看信号发现,SIGCHLD不仅当子进程退出时可以返回给父进程,也可以当子进程暂停。

 

我们再来验证一下暂停,我们发送19号信号是暂停进程,18号信号是恢复进程

 

此时我们来查看当子进程暂停时的状态,我们可以使用下面命令查看

ps axj | grep myproc

  

如果我们杀掉子进程查看状态:发现子进程一进僵尸

注:事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。

(本篇完)

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...