Linux信号补充

上节我们讲了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);
        }
}

可以看出我们设置了默认处理方式,这样就不会产生僵尸进程。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值