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

文章目录

🚩信号捕捉

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

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

怎么切换内核态用户态?

  • 调用系统调用:操作系统会自己从内核态用户态来回切换
  • 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

相关推荐
深紫色的三北六号6 小时前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash10 小时前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
哈基咪怎么可能是AI20 小时前
为什么我就想要「线性历史 + Signed Commits」GitHub 却把我当猴耍 🤬🎙️
linux·github
十日十行2 天前
Linux和window共享文件夹
linux
端平入洛2 天前
delete又未完全delete
c++
Sinclair2 天前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
木心月转码ing2 天前
WSL+Cpp开发环境配置
linux
祈安_2 天前
C语言内存函数
c语言·后端
端平入洛3 天前
auto有时不auto
c++
Rockbean3 天前
用40行代码搭建自己的无服务器OCR
服务器·python·deepseek