欢迎光临
我们一直在努力

【Linux】进程信号之捕捉(三)

文章目录

  • 🚩信号捕捉
  • 🚩重谈虚拟地址表
  • sigaction
    • sa_mask
  • 可重入函数
  • volatile
  • SIGCHLD信号

🚩信号捕捉

信号什么时候被捕捉? 🚩从内核态返回到用户态时,进行信号的检测和处理

  • 内核态:允许访问系统的代码和数据
  • 用户态:只能访问用户态的代码和数据

怎么切换内核态用户态?

  • 调用系统调用:操作系统会自己从内核态用户态来回切换
  • int 80 : 从用户态陷入内核态

🚩操作系统本质,基于时钟中断的死循环 时钟中断:计算机中一个芯片,每隔很短的时间内,向操作系统发送时钟中断

一旦发生时钟中断,操作系统进入内核态,处理中断,检查调度,调度完成后返回死循环 所以操作系统会频繁的进入内核态,检查信号是否需要处理 在这里插入图片描述 整个信号处理流程: 用户态因异常等等产生信号,陷入内核态,检查要处理的信号(匹配handler表),回到用户态处理信号,调用系统调用再回到内核态,返回用户态向下执行代码

🚩重谈虚拟地址表

内存一共有4G , 其中1-3G用户态, 3-4G是内核态

用户页表每个进程都有一份——进程具有独立性 内核页表只有一份,每个进程看到的内容都是一样的

进程视角:调用系统调用,就是在相应的地址进行 操作系统视角:任何时候都有进程,调用操作系统代码,就可以随时进行 在这里插入图片描述 内核级页表也映射物理内存,物理内存先存放操作系统

我们怎么知道现在执行内核态还是用户态 CPU ecs寄存器最低2位,0代表内核态,3代表用户态,0就可以访问内核态,int 80指令,从用户态陷入内核态

信号捕捉简化图 在这里插入图片描述

sigaction

在这里插入图片描述

信号捕捉函数,oact保存上次结果,signo要自定义的信号

pending是什么时候由1置0的? 我们调用自定义方式,打印pending,如果都为0,说明信号在处理之前就由1置0

#include <iostream>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void printpending()
{
sigset_t set;
sigpending(&set);
for(int i=31;i>=1;i)
{
if(sigismember(&set,i))
{
cout<<"1";
}
else
cout<<"0";
}
cout<<endl;
}
void myhandler(int signo)
{
cout<<"catch a signo: "<<signo<<endl;
printpending();
}
int main()
{
struct sigaction act,oact;
memset(&act,0,sizeof(act));
memset(&oact,0,sizeof(oact));
act.sa_handler=myhandler;
sigaction(2,&act,&oact);
while(1)
{
cout<<"i am a process getpid():"<<getpid()<<endl;
sleep(1);
}
return 0;
}

在这里插入图片描述 调用自定义处理方式,发现pending都为0,说明在处理之前就置0了

操作系统在处理信号时,会阻塞该信号,防止嵌套调用此信号

void myhandler(int signo)
{
cout<<"catch a signo: "<<signo<<endl;
while(1)
{
printpending();
sleep(1);
}
}

处理函数无限循环 在这里插入图片描述 可以看到阻塞了!

sa_mask

act是一个结构体,里面有变量sigset_t sa_mask,意思是处理信号时,同时阻塞其他信号

int main()
{
struct sigaction act,oact;
memset(&act,0,sizeof(act));
memset(&oact,0,sizeof(oact));
act.sa_handler=myhandler;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,1);
sigaddset(&act.sa_mask,3);
sigaction(2,&act,&oact);
while(1)
{
cout<<"i am a process getpid():"<<getpid()<<endl;
sleep(1);
}
return 0;
}

处理2号时,阻塞1,3号,kill发送信号 在这里插入图片描述

可重入函数

在这里插入图片描述

main函数里有头插函数,我们刚插到一半,还没有head=p,发生异常来信号了,信号中也调用了头插函数, 这就出错了,到最后node2丢失,内存泄漏,

由不同执行流调用相同的函数,访问全局链表,发生错误了,就叫不可重入函数 符合下面条件:

  • 函数中调用malloc和free,malloc也是用全局链表来管理堆的
  • 使用I/O接口,标准i/O库很多用不可重入函数使用全局数据结构

没发生错误就叫可重入函数

怎么避免发生不可重入函数 我们信号函数处理的时候,不使用那些不可重入函数就不会发生错误了,

volatile

int flag=0;
void myhandler(int signo)
{
cout<<"catch a signo: "<<signo<<endl;
//flag=1;
}
int main()
{
signal(2,myhandler);
while(!flag);
cout<<"normal quit"<<endl;
return 0;
}

我们屏蔽flag=1; 在这里插入图片描述 正常, 去掉注释flag=1后,有可能依然是这个结果,为什么? :CPU有自动优化,正常来说flag=1,在虚拟内存地址栈更改了,但是cpu有寄存器,它会保存上下文,也就是保存了flag=0,下次寻找时没有去栈寻找,直接拿寄存器的使用了,所以flag确实更改了,但是cpu没去找

在这里插入图片描述

怎么防止编译器优化,加关键字volatile,告诉编译器不要自动优化,

在这里插入图片描述

SIGCHLD信号

子进程进程退出时,其实是给父进程发送17号信号,父进程忽略处理

void myhandler(int signo)
{
cout<<"'catch a signo"<<signo<<endl;
pid_t rid=waitpid(1,nullptr,0);
}
int main()
{
signal(17,myhandler);
pid_t id=fork();
if(id==0)
{
cout<<"i am child pid: "<<getpid()<<endl;
sleep(5);
cout<<"child quit"<<endl;
exit(0);
}
while(1)
{
sleep(1);
cout<<"i am father pid: "<<getpid()<<endl;
}
return 0;
}

在这里插入图片描述 自动发送17号信号 在这里插入图片描述 我们为什么一定要等待?

  • 等待可以获取退出状态
  • 释放子进程的僵尸

父进程一定要最后退出

如果多个进程同时退出怎么办? 如果多个进程一半退出 怎么办? 优化代码:

void myhandler(int signo)
{
pid_t rid;
while(rid=waitpid(1,nullptr,0)>0)
{
cout<<"catch a signo:"<<signo<<"child quit success"<<endl;
}
}

int main()
{
signal(17,myhandler);

for(int i=1;i<=10;i++)
{
pid_t id=fork();
if(id==0)
{
cout<<"i am child pid: "<<getpid()<<endl;
sleep(5);
cout<<"child quit"<<endl;
exit(0);
}
sleep(1);
}
while(1)
{
sleep(1);
cout<<"i am father pid: "<<getpid()<<endl;
}
return 0;
}

设置为死循环,一旦多个进程退出,循环等待,设置为非阻塞,一半退出,没有等待返回0,退出信号函数 所以:前面进程等待,父进程wait,waitpid函数清理子进程僵尸进程,阻塞父进程不能干自己的事,非阻塞,父进程做自己事情时,还得时不时轮询一下,非常麻烦 🚩而现在,子进程退出会向父进程发送17号信号,我们只需要捕捉信号,在自定义信号函数实现wait即可

题外话: 由于UNIX历史原因,我们将17信号忽略处理,子进程结束会自动清理,不会产生僵尸进程,但是在其他操作系统就不一定管用了

int main()
{
//signal(17,myhandler);
signal(17,SIG_IGN);
for(int i=1;i<=10;i++)
{
pid_t id=fork();
if(id==0)
{
cout<<"i am child pid: "<<getpid()<<endl;
sleep(5);
cout<<"child quit"<<endl;
exit(0);
}
sleep(1);
}
while(1)
{
sleep(1);
cout<<"i am father pid: "<<getpid()<<endl;
}
return 0;
}

在这里插入图片描述 问题来了,之前我们也没有学信号,17信号自动设置的忽略,为什么之前还会产生僵尸进程呢? 其实,我们自己设置忽略,和系统忽略是一样的,但这里是个特例,系统设置的是SIG_DFL缺省动作action是SIG_IGN

赞(0)
未经允许不得转载:171主机测评 » 【Linux】进程信号之捕捉(三)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址