上节我们讲了Linux信号产生前,产生中,产生后如图:
我们还学习到了一个函数接口
sigprocmask()
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigset_t * set 是修改bolck,odlset是老得block表
我们还学习到了信号处理整个流程图:
下面就是今天的重点
1.可重入函数
我们先看以例子,就能理解什么是可重入函数
当我们进行头插的时候,此时收到信号处理,信号处理函数中也进行了头插,那么此时node2就是无效节点造成内存泄漏,我们所学的大部分都是不可冲入的函数,如STL,boost库,大部分都是不可重入的
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
这个关键字是c的时候有所了解,但是今天我们站在系统角度来理解这个关键字先看一段程序
#include<iostream>
using namespace std;
#include<signal.h>
int flag = 0;
void handler(int signo)
{
flag = 1;
}
int main()
{
signal(2,handler);
while(!flag);
cout<<"hello c++"<<endl;
}
观察程序,当我们发送二号信号的时候,flag被改为1,打印hello c++
此时我们在进行编译器优化,gcc -o volatile volatile.cpp -o3
g++ 编译器优化有4中 O1 O2 O3 O4 4个选项
此时我们从键盘输入ctrl + c进程没有任何反应,此时我对flag加上volatile关键字观察结果
此时程序正常
当我们进行编译器优化时,会把flag变量放入cpu寄存器中,每次都是从寄存器中去取flag,而信号处理中修改的flag身世内存中的值,所有进程没有反应
所以volatile的作用是保存内存的可见性,每次都是从内存取出数据
3.SIGCHLD
进程一章讲过用
wait
和
waitpid
函数清理僵尸进程
,
父进程可以阻塞等待子进程结束
,
也可以非阻 塞地查询是否有子进
程结束等待清理
(
也就是轮询的方式
)
。采用第一种方式
,
父进程阻塞了就不 能处理自己的工作了
;
采用第二种方式
,
父
进程在处理自己的工作的同时还要记得时不时地轮询一 下
,
程序实现复杂。
其实
,
子进程在终止时会给父进程发
SIGCHLD
信号
,
该信号的默认处理动作是忽略
,
父进程可以自 定义
SIGCHLD
信号
的处理函数
,
这样父进程只需专心处理自己的工作
,
不必关心子进程了
,
子进程 终止时会通知父进程
,
父进程在信号处理
函数中调用
wait
清理子进程即可。
请编写一个程序完成以下功能
:
父进程
fork
出子进程
,
子进程调用
exit(2)
终止
,
父进程自定 义
SIGCHLD
信号的处理函数
,
在其中调用
wait
获得子进程的退出状态并打印。
事实上
,
由于
UNIX
的历史原因
,
要想不产生僵尸进程还有另外一种办法
:
父进程调 用
sigaction
将
SIGCHLD
的处理动作
置为
SIG_IGN,
这样
fork
出来的子进程在终止时会自动清理掉
,
不 会产生僵尸进程
,
也不会通知父进程。系统默认的忽
略动作和用户用
sigaction
函数自定义的忽略 通常是没有区别的
,
但这是一个特例。此方法对于
Linux
可用
,
但不保证
在其它
UNIX
系统上都可 用。请编写程序验证这样做不会产生僵尸进程
#include<iostream>
using namespace std;
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
signal(SIGCHLD,SIG_IGN);
if(fork() == 0)
{
cout<<"i an child"<<endl;
sleep(10);
exit(1);
}
while(1){
cout<<"i am father" <<endl;
sleep(1);
}
}
可以看出我们设置了默认处理方式,这样就不会产生僵尸进程。