文章目录
🚩信号捕捉
信号什么时候被捕捉?
🚩从内核态返回到用户态时,进行信号的检测和处理
- 内核态:允许访问系统的代码和数据
- 用户态:只能访问用户态的代码和数据
怎么切换内核态用户态?
- 调用系统调用:操作系统会自己从内核态用户态来回切换
- 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
c
#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了
操作系统在处理信号时,会阻塞该信号,防止嵌套调用此信号
c
void myhandler(int signo)
{
cout<<"catch a signo: "<<signo<<endl;
while(1)
{
printpending();
sleep(1);
}
}
处理函数无限循环

可以看到阻塞了!
sa_mask
act是一个结构体,里面有变量sigset_t sa_mask,意思是处理信号时,同时阻塞其他信号
c
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
c
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号信号,父进程忽略处理
c
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号信号

我们为什么一定要等待?
- 等待可以获取退出状态
- 释放子进程的僵尸
父进程一定要最后退出
如果多个进程同时退出怎么办?
如果多个进程一半退出 怎么办?
优化代码:
c
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信号忽略处理,子进程结束会自动清理,不会产生僵尸进程,但是在其他操作系统就不一定管用了
c
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