【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

相关推荐
奶茶精Gaaa2 小时前
工具分享--F12使用技巧
学习
The森2 小时前
Linux IO 模型纵深解析 01:从 Unix 传统到 Linux 内核的 IO 第一性原理
linux·服务器·c语言·经验分享·笔记·unix
文艺理科生Owen2 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
运维·nginx
期待のcode2 小时前
Redis的主从复制与集群
运维·服务器·redis
翼龙云_cloud2 小时前
腾讯云代理商: Linux 云服务器搭建 FTP 服务指南
linux·服务器·腾讯云
纤纡.2 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
君生我老2 小时前
C++自写list类
c++
久邦科技2 小时前
奈飞工厂中文官网入口,影视在线观看|打不开|电脑版下载
学习
阿猿收手吧!2 小时前
【C++】异步编程:std::async终极指南
开发语言·c++