当前位置: 首页 > news >正文

Linux信号——信号的处理(3)

信号是什么时候被处理?

进程从内核态,切换到用户态的时候,信号会被检测处理。
内核态:操作系统的状态,权限级别高
用户态:你自己的状态

关于内核态和用户态

进程地址空间第三谈

页表分为两部分,一个是用户级页表,一个是内核级页表。用户页表项标记为用户可访问,内核页表项标记为特权级访问,这样对于内核级的一些重要文件,用户没有权利访问,更加安全。
在这里插入图片描述

所谓的系统调用本质其实是一个函数指针数组。
在这里插入图片描述

  1. 我们使用系统调用或者访问系统数据,其实还是在我们进程的地址空间内进行跳转的。

系统调用:正文代码中使用系统调用函数,操作系统到虚拟地址空间的内核空间中找到相应的函数指针,然后到内核中访问,最后将返回值设置到正文代码。
在这里插入图片描述

  1. 进程无论如何切换,总能找到OS

我们访问OS,本质是通过我的进程的地址空间的那1GB内核空间来访问的。
不同的进程的命令行参数,栈区,堆区,初始化数据,未初始化数据,正文代码不一样,所以他们各自私有一个用户级页表,但是他们的内核空间是完全一样的,所以他们共用同一份内核级页表,都可以进行系统调用。
在这里插入图片描述

  1. 操作系统是如何运行的

信号技术本来就是通过软件的方式,来模拟的硬件中断,硬件中断是操作系统能够运行和发挥作用的关键机制之一。
硬件中断:硬件中断是由硬件设备发出的信号,用于通知CPU有重要事件需要处理。当硬件中断发生时,CPU会暂停当前执行的指令流,转而执行与该中断相关的处理程序。
OS的周期时钟中断:非常高的频率,非常短的时间,给CPU发送中断——CPU不断进程处理中断。
总结:操作系统是一个死循环,不断在接受外部的其他硬件中断。

  1. 操作系统不相信任何用户

必须要能区分当前用户的运行模式,否则用户直接访问操作系统里的各种数据,并进行修改,造成严重的安全隐患,所以就有了用户态和内核态来区分当前用户的运行模式。

CPU会对当前身份进行标识,当代码需要使用内核数据时,操作系统会对当前身份进行审核,若是0运行访问,若是3不允许访问内核。
在这里插入图片描述

信号是如何被处理?

在这里插入图片描述
在这里插入图片描述
在信号处理过程(捕捉)中,一共会有4次的状态切换(内核态和用户态)。

捕捉信号还有其他方式吗?

#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数说明
signum: 要操作的信号编号(如 SIGINT、SIGTERM 等)
act: 指向新信号动作结构的指针,如果为 NULL 则不改变当前处理方式
oldact: 输出型参数,用于保存原信号动作结构的指针,如果为 NULL 则不保存
返回值
成功时返回 0,失败时返回 -1 并设置 errno。

struct sigaction {void     (*sa_handler)(int);         // 信号处理函数void     (*sa_sigaction)(int, siginfo_t *, void *); // 替代的信号处理函数sigset_t sa_mask;                    // 执行处理函数时要阻塞的信号int      sa_flags;                   // 修改行为的标志void     (*sa_restorer)(void);      
};

关于sa_mask变量

  1. 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字(屏蔽该信号)
  2. 如果我们处理完对应的信号,该信号默认也会从信号屏蔽字中进行移除。(解除屏蔽该信号)
    为什么会这样?原因:不想让信号,嵌套式地对同一个信号进行捕捉处理。

用例子解释:

#include<iostream>
#include<signal.h>
void Print(sigset_t pending)
{std::cout<<" curr process pending: ";for(int sig = 31;sig > 0;sig--){if(sigismember(&pending,sig)) std::cout << "1";else std::cout <<"0";}std::cout << std::endl;
}
void handler(int signo)
{std::cout << "signal: " << signo <<std::endl;//不断获取当前进程的pending信号集并打印sigset_t pending;sigemptyset(&pending);while(true){sigpending(&pending);Print(pending);sleep(1);}
}int main()
{struct sigaction act,oact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(2,&act,&oact);while(true)sleep;
}

在这里插入图片描述

在调用信号处理函数时,除了当前信号被自动屏蔽外,还希望自动屏蔽另外一些信号,则要用sa_mask字段说明。
例子:

int main()
{struct sigaction act,oact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask,3);sigaddset(&act.sa_mask,4);sigaddset(&act.sa_mask,5);//除了屏蔽当前正在处理的函数,还屏蔽3,4,5号信号sigaction(2,&act,&oact);while(true)sleep;
}

子进程信号版的进程退出

子进程退出,父进程不wait,子进程就会僵尸。
子进程退出,不是默默退出的,会在退出的时候,向父进程发送信号(17.SIGHLD
如何证明?

#include<iostream>
#include<signal.h>
#include<unistd.h>
void handler(int signo)
{std::cout <<"child quit, father get a signo: "<< signo << std::endl;
}
int main()
{signal(SIGCHLD,handler);pid_t id = fork();if(id == 0){//childint cnt = 5;while(cnt--){std::cout<<"I am child process: "<<getpid()<<std::endl;sleep(1);}std::cout<<"child process died"<<std::endl;exit(0);}//fatherwhile(true) sleep(1);return 0;
}

结果:确实子进程退出时向父进程发送了17号信号
在这里插入图片描述
所以当子进程退出时,发送17号信号,刚好我们将信号进行捕获,自定义处理信号,将该子进程进行回收,就有如下代码。

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void handler(int signo)
{std::cout <<"child quit, father get a signo: "<< signo << std::endl;
}void CleanupChild(int signo)
{//v1// if(signo == SIGCHLD)// {//     pid_t rid = waitpid(-1,nullptr, 0);//-1表示任意一个子进程//     if(rid >0)//     {//         std::cout << "wait child success: " << rid << std::endl;//     }// }//v2-同时退出一百个进程// if(signo == SIGCHLD)// {// //但是如果同时要回收100个子进程,这时pending位图中在短时间内只能保存一次信号,所以加上循环,不停的回收//     while(true)//     {//         pid_t rid = waitpid(-1,nullptr, 0);//-1:表示任意一个子进程//         if(rid >0)//         {//             std::cout << "wait child success: " << rid << std::endl;//         }//         else if(rid <= 0) break;//     }// }//v3-50个进程退出,50个进程没有退出if(signo == SIGCHLD){//但是如果同时要回收100个子进程,这时pending位图中在短时间内只能保存一次信号,所以加上循环,不停的回收while(true){pid_t rid = waitpid(-1,nullptr, WNOHANG);//-1:表示任意一个子进程//以非阻塞方式等待if(rid >0){std::cout << "wait child success: " << rid << std::endl;}else if(rid <= 0) break;}}std::cout <<"wait sub process done"<<std::endl;
}
int main()
{signal(SIGCHLD,CleanupChild);pid_t id = fork();if(id == 0){//childint cnt = 5;while(cnt--){std::cout<<"I am child process: "<<getpid()<<std::endl;sleep(1);}std::cout<<"child process died"<<std::endl;exit(0);}//fatherwhile(true) sleep(1);return 0;
}

版本v1
特点

  1. 使用阻塞方式等待(options=0)
  2. 每次只能回收一个子进程

问题

  1. 如果有多个子进程同时退出,可能会丢失信号
  2. 阻塞调用可能会影响主程序执行

版本v2
改进

  1. 通过循环可以回收多个子进程,解决了多个子进程同时退出的问题。

问题

  1. 仍然是阻塞调用,如果子进程没有退出会一直阻塞。

版本v3
优点

  1. 使用 WNOHANG 非阻塞选项,不会阻塞主程序执行。
  2. 可以一次性回收所有已退出的子进程。

更简单的回收子进程的方式

直接忽略它,将SIGCHLD设置成SIG_IGN,即 signal(SIGCHLD,SIG_IGN);
这样子进程在终止时会被自动清理掉,不会产生僵尸进程,也不会通知父进程。
缺点:仅仅只是退出进程,无法通过自定义函数的方式获取子进程的id,退出码等相关信息。

:SIGCHLD的默认处理动作是IGN(忽略),为什么还要手动设置成SIG_IGN?
这是系统的一个特性,仅在Linux环境下。
设置系统层的IGN,进程终止时,会产生僵尸进程。
设置用户层的SIG_IGN,系统会将进程终止,不产生僵尸进程。

总结:
在这里插入图片描述


http://www.mrgr.cn/news/97581.html

相关文章:

  • QT6(12)3.3.1 Qt元对象系统概述:QObject 类与 QMetaObject 类,类型转换 qobject_cast<T>()。3.3.3 属性系统:帮助文档,
  • 【题解-Acwing】798. 差分矩阵
  • 【第十三届“泰迪杯”数据挖掘挑战赛】【2025泰迪杯】【代码篇】A题解题全流程(持续更新)
  • vue3 处理文字 根据文字单独添加class
  • linux第三次作业
  • JVM核心机制:类加载×字节码引擎×垃圾回收机制
  • 使用Docker安装及使用最新版本的Jenkins
  • el-table,新增、复制数据后,之前的勾选状态丢失
  • STM32江科大----IIC
  • 高安全等级车规芯片在星载控制终端上的应用
  • Nodejs回调函数
  • python应用之使用pdfplumber 解析pdf文件内容
  • 使用stm32cubeide stm32f407 lan8720a freertos lwip 实现udp client网络数据转串口数据过程详解
  • JavaScript基础--22-call、apply 和 bind
  • #MongoDB 快速上手
  • springcloud进阶
  • Python星球日记 - 第10天:模块与包
  • php调用大模型应用接口实现流式输出以及数据过滤
  • 原子操作(cpp atomic)
  • UE4初学笔记