首页 > 系统相关 >[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号

时间:2022-12-12 10:34:36浏览次数:62  
标签:函数 链表 flags 信号 Linux 进程 volatile SIGCHLD

1.可重入函数

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_volatile

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

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_可重入函数_02

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

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

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

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

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

2.volatile

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

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

#include <stdio.h>
#include <signal.h>

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条件不满足,退出循环,进程退出。

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_可重入函数_03

但是,我们注意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.c
gcc -o $@ $^ -O2

.PHONY:clean
clean:
rm -f myproc

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_volatile_04

我们再次将程序运行起来

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_SIGCHLD_05

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

#include <stdio.h>
#include <signal.h>

//保持内存的可见性
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;
}

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_可重入函数_06

2.2volatile的作用

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

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

3.SIGCHLD信号

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

3.1SIGCHLD信号的验证

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

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

using namespace std;

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

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

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_SIGCHLD_07

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_volatile_08

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_volatile_09

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_volatile_10

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

ps axj | grep myproc

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_volatile_11

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_可重入函数_12

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

[ Linux ] 可重入函数,volatile 关键字,SIGCHLD信号_可重入函数_13


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

(本篇完)

标签:函数,链表,flags,信号,Linux,进程,volatile,SIGCHLD
From: https://blog.51cto.com/xingyuli/5929132

相关文章

  • linux安装jdk
    1、安装包下载wget--no-check-certificate--no-cookies--header"Cookie:oraclelicense=accept-securebackup-cookie"http://download.oracle.com/otn-pub/java/jdk/......
  • Linux
    Linux的文件bin放二进制文件dev放外接设备,不能直接使用需要被挂载etc配置文件Home除了root用户的其他用户的家目录,类似于windows的user/用户目录proc表示进程,该目录存......
  • volatile关键字的作用、原理
    此文对于部分原理的解释不全,甚至有错误。建议同时参考一文解决内存屏障阅读。在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不......
  • Ubuntu用户得到一个新的Linux Kernel安全更新, 修补10个漏洞
    Canonical今天公布了关于新的Ubuntu Linux内核安全更新的细节,这些更新适用于他们支持的所有Ubuntu版本,以解决由各种研究人员发现的多达10个安全漏洞。新的Ubuntu......
  • Linux IO 监控与深入分析
    引言接昨天电话面试,面试官问了系统IO怎么分析,当时第一反应是使用iotop看系统上各进程的IO读写速度,然后使用iostat看CPU的%iowait时间占比,(%iowait:CPU等待输......
  • 使用这个多功能的 Linux 命令转换音频文件
    SoXSoundExchange甚至可以为你的音频文件添加特效。我工作需要使用音视频媒体,不管你处理哪种媒体,你肯定知道标准化是一种有价值的工具。就像你不会试图把一个分数加到一......
  • Linux常用命令
    常用命令: 一、ls只列出文件名(相当于dir,dir也可以使用)-A:列出所有文件,包含隐藏文件。-l:列表形式,包含文件的绝大部分属性。-R:递归显示。--help:此命令的帮助。 二、cd改变......
  • Linux shell——将两个文件的内容按行交叉合并
    有文件a.txt,内容为:abc有文件b.txt,内容为:123现需要将a.txt和b.txt合并为c.txt,内容为:a1b2c3使用下面的脚本,其中$line是要交叉合并的文件行数。for((i=1;i<=$line;i++));......
  • Linux性能监控之磁盘IO
    使用iostat查看磁盘IO  rkB/s每秒读取数据量kBwkB/s每秒写入数据量kBsvctmI/O请求的平均服务时间,单位毫秒awaitI/O请求的平均等待时间,单位毫秒;值越小,性能越好;ut......
  • linux 监控网络IO、磁盘、CPU、内存
    linux监控网络IO、磁盘、CPU、内存CPU:vmstat,sar–u,top磁盘IO:iostat–xd,sar–d,top网络IO:iftop-n,ifstat,dstat–nt,sar-nDEV23磁盘容量:df–h内存使用:free–m,top......