目录
1. 可重入函数
2. volatile
3. SIGCHLD信号
Linux!
先来谈一下重入函数的概念:重入函数便是在该函数还没有执行完毕便重复进入该函数(一般发生在多线程中);
可重入函数:一个函数一旦重入,对原函数功能不会出现问题(内存泄漏等);
不可重入函数:一个函数一旦重入,对原函数功能有可能出现问题;
什么意思呢?下面给一个具体的例子方便大家理解:
注意:可重入函数/不可重入函数,只是一种特性而已,并无优劣好坏之分;
volatile其实是C语言中的一个关键字,还是比较冷门的,虽然冷门但不意味着不重要;
下面给出几个例子方便大家理解:
示例一:
#include
#include
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
#include
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可保证多条汇编代码的顺序不会发生改变),这个了解下就好;
进程一章讲过用 wait 和 waitpid 函数清理僵尸进程 , 父进程可以阻塞等待子进程结束 , 也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式) 。采用第一种方式 , 父进程阻塞了就不能处理自己的工作了;采用第二种方式 , 父进程在处理自己的工作的同时还要记得时不时地轮询一 下, 程序实现复杂。
其实, 子进程在终止时会给父进程发 SIGCHLD 信号 , 该信号的默认处理动作是忽略, 父进程可以自定义 SIGCHLD 信号的处理函数, 这样父进程只需专心处理自己的工作, 不必关心子进程了, 子进程终止时会通知父进程 , 父进程在信号处理函数中调用wait 清理子进程即可。
事实上 , 由于 UNIX 的历史原因 , 要想不产生僵尸进程还有另外一种办法 : 父进程调用 sigaction 将SIGCHLD 的处理动作置为SIG_IGN, 这样 fork 出来的子进程在终止时会自动清理掉 , 不会产生僵尸进程 , 也不会通知父进程。系统默认的忽略动作和用户用sigaction 函数自定义的忽略通常是没有区别的 , 但这是一个特例。此方法对于 Linux 可用 , 但不保证在其它UNIX 系统上都可用。
下面编写代码对上述所说的内容进行一个验证:
代码1:子进程在退出时候会发送SIGCHLD信号?
#include
#include
#include
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
#include
#include
#include
#include
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
#include
#include
#include
#include
int main()
{
//捕捉信号
signal(SIGCHLD,SIG_IGN);
//创建子进程
if(fork()==0)
{
//child
printf("I am a child!\n");
sleep(1);
return 0;
}
while(1);
return 0;
}