【hello Linux】可重入函数、volatile和SIGCHLD信号

目录

1. 可重入函数

2. volatile

3. SIGCHLD信号


Linux!🌷 

1. 可重入函数

先来谈一下重入函数的概念:重入函数便是在该函数还没有执行完毕便重复进入该函数(一般发生在多线程中);

可重入函数:一个函数一旦重入,对原函数功能不会出现问题(内存泄漏等);

不可重入函数:一个函数一旦重入,对原函数功能有可能出现问题;

什么意思呢?下面给一个具体的例子方便大家理解:

  • main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
  • 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。
如果一个函数符合以下条件之一则是不可重入的 :
  • 调用了mallocfree,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

注意:可重入函数/不可重入函数,只是一种特性而已,并无优劣好坏之分;

2. volatile

volatile其实是C语言中的一个关键字,还是比较冷门的,虽然冷门但不意味着不重要;

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

下面给出几个例子方便大家理解:

示例一: 

  #include <stdio.h>    
  #include <signal.h>                                                                                                                                           
      
  int flag = 0;    
      
  void handler(int signo)    
  {    
    printf("change flag 0 to 1!\n");    
    flag = 1;    
  }    
      
  int main()    
  {    
    signal(2,handler);    
    while(!flag);    
    printf("process quit normal!\n");    
    return 0;    
  }    

标准情况下,键入 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 , while 条件不
满足,退出循环,进程退出;

示例二:gcc 进行一定程度的优化

对 makefile 文件进行修改再编译,-O3表示优化级数; 

优化情况下,键入 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 ,但是 while 条
件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很
明显, while 循环检查的flag,并不是内存中最新的flag,这就存在了数据二异性的问题。
while 检测的flag其实已经因为优化,被放在了CPU寄存器当中。

示例三:volatile进行修饰

  #include <stdio.h>    
  #include <signal.h>    
      
  volatile int flag = 0;                                                                                                                           
      
  void handler(int signo)    
  {    
    printf("change flag 0 to 1!\n");    
    flag = 1;    
  }    
      
  int main()    
  {    
    signal(2,handler);    
    while(!flag);    
    printf("process quit normal!\n");    
    return 0;    
  }   

可以看到结果正确;

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

vloatile 还有一个作用:防止指令重排(一条C/C++指令进行编译后可能形成多条汇编代码,

volatile可保证多条汇编代码的顺序不会发生改变),这个了解下就好;

 3. SIGCHLD信号

进程一章讲过用 wait waitpid 函数清理僵尸进程 , 父进程可以阻塞等待子进程结束 , 也可以非阻
塞地查询是否有子进程结束等待清理(也就是轮询的方式) 。采用第一种方式 , 父进程阻塞了就
不能处理自己的工作了;采用第二种方式 , 父进程在处理自己的工作的同时还要记得时不时地轮
询一 下, 程序实现复杂。
其实, 子进程在终止时会给父进程发 SIGCHLD 信号 , 该信号的默认处理动作是忽略, 父进程
可以自定义 SIGCHLD 信号的处理函数, 这样父进程只需专心处理自己的工作, 不必关心子进
程了, 子进程终止时会通知父进程 , 父进程在信号处理函数中调用wait 清理子进程即可。
事实上 , 由于 UNIX 的历史原因 , 要想不产生僵尸进程还有另外一种办法 : 父进程调用 sigaction
SIGCHLD 的处理动作置为SIG_IGN, 这样 fork 出来的子进程在终止时会自动清理掉 , 不会产生
僵尸进程 , 也不会通知父进程。系统默认的忽略动作和用户用sigaction 函数自定义的忽略通常
是没有区别的 , 但这是一个特例。此方法对于 Linux 可用 , 但不保证在其它UNIX 系统上都可用。

下面编写代码对上述所说的内容进行一个验证:

代码1:子进程在退出时候会发送SIGCHLD信号?

#include <stdio.h>    
#include <unistd.h>    
#include <signal.h>    
                                                                                            
void handler(int signo)    
{    
  printf("get a signal,signo:%d\n",signo);    
}    
    
int main()    
{    
  //对信号捕捉    
  signal(SIGCHLD,handler);    
    
  //创建子进程    
  if(fork()==0)    
  {    
    //child    
    printf("I am a child,I quit...!\n");    
    sleep(1);    
    return 0;    
  }    
  //parent    
  while(1)    
  {    
    ;    
  }    
  return 0;    
}    

 经过证实,子进程在退出时会发送 17)SIGCHLD 信号;

代码2:可以在SIGCHLD自定义捕捉时,waitpid子进程?

#include <stdio.h>    
#include <signal.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
void handler(int signo)    
{    
  printf("get a signal!signo:%d\n",signo);    
  sleep(1);    
  while(1)    
  {    
    waitpid(-1,NULL,WNOHANG);    
  }    
}    
    
int main()    
{    
  //捕捉信号    
  signal(SIGCHLD,handler);    
  //创建子进程    
  if(fork()==0)    
  {    
    //child    
    printf("I am a child!\n");    
    sleep(1);    
    return 0;                                                         
  }    
  while(1);    
  return 0;    
}    

 由上我们可以看到子进程的3种状态的切换,S(在1S输出I am a child)——>Z(sleep1S后才waitpid)——>被清理;

代码3:直接在收到SIGCHLD时,进行忽略处理,不形成僵尸,直接处理;

#include <stdio.h>    
#include <signal.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
int main()    
{    
  //捕捉信号    
  signal(SIGCHLD,SIG_IGN);                                                             
  //创建子进程    
  if(fork()==0)    
  {    
    //child    
    printf("I am a child!\n");    
    sleep(1);    
    return 0;    
  }    
  while(1);    
  return 0;    
}    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瞳绣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值