Linux|信号

news2024/11/15 16:33:31

Linux|信号

  • 信号的概念
  • 信号处理的三种方式
    • 捕捉信号的System Call -- signal
  • 1.产生信号的5种方式
  • 2.信号的保存
    • 2.1 core 标志位
  • 2.信号的保存
    • 2.1 对pending 表 和 block 表操作
    • 2.2 阻塞SIGINT信号 并打印pending表例子
  • 捕捉信号
    • sigaction 函数
    • 验证当前正在处理某信号,则该信号会自动被屏蔽
    • 验证当前信号被处理完之后,会自动解除屏蔽
    • 地址空间中操作系统态
    • 谈谈键盘输入的过程
    • 两个深刻的问题
      • 如何操作系统是怎么运行的
      • 如何理解系统调用
    • 可重入函数
    • volatile
    • sigchild信号

信号的概念

信号:是进程之间异步通知的一种方式,属于软中断。
所谓异步就是 a 和 b 之间没有联系,比如同学a 去上厕所了,老师b还是继续讲课,这称为异步。

信号处理的三种方式

一般情况下是三选一

  1. 忽略此信号
  2. 执行该信号的默认处理动作
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号

捕捉信号的System Call – signal

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2

在这里插入图片描述

sighandler_t signal(int signum, sighandler_t handler);
当我们在键盘中 按ctrl + c 的时候 就会发送一个SIGINT信号,
我们可以用 signal 这个系统调用验证

#include <iostream>
#include <unistd.h>
#include <signal.h>
void hander(int sig)
{
    std::cout<<"catch sig:"<< sig<<std::endl;
}
int main()
{
    signal(2,hander);
    while(true)
    
    return 0;
}

有同学会想我把所有的信号都捕捉了,那个这个进程是不是就刀枪不入了?不是的 因为9号信号 无法捕捉

1.产生信号的5种方式

1. 通过 kill 命令,向指定的进程发信号
2. 通过键盘 ctrl + c
3. 系统调用 kill
在这里插入图片描述

raise(sign) 和 kill(getpid(),sign) 是等价的
alrm 也可以产生信号 alrm的返回值是上一个闹钟的剩余时间
同一个进程同一个时间只能有一个闹钟!

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void hander(int sig)
{
    std::cout<<"catch sig:"<< sig<<std::endl;
}
int main()
{
    signal(2,hander);
    //kill(getpid(),2);
    raise(2);
    sleep(3);
    
    return 0;
}

4.软件条件
比如 管道 我们读端关闭 , 写端还在写,那么就会产生一个SIGPIPE的信号。
5. 异常
a.

void hander(int sg)
{
    std:: cout<< "捕捉到:"<<sg<<std::endl;
}
int main()
{
    signal(8,hander);
    int b = 10 / 0;
    return 0;
}

在这里插入图片描述
可能有同学会问为什么会一直死循环打印捕捉到的8号信号呢?
当处理器检测到除法错误时,它会暂停正常的指令流,保存当前的状态(包括程序计数器和其他寄存器的内容),然后跳转到一个预定义的地址来处理这个异常。这个地址指向的是操作系统的异常处理程序,它可以记录错误、终止进程或采取其他恢复措施,由于进程没有退出,又恢复当前的状态,到cpu中 ,cpu中的溢出标记位又置为1了。(这也回答cpu是怎么检测到除以0的)总的来说就是因为进程一直被调度,所以才出现死循环的情况。

终止进程的本质:释放进程的上下文数据,报告溢出标志数据或其他异常数据
b. 野指针问题:
CR3 + MMU : 将虚拟地址转换为物理地址
CR2:保存主要用于存储最近一次发生的页面错误(page fault)时的线性地址。
在这里插入图片描述
当异常的时候,操作系统检测到CR2中的地址,开始发送信号。

2.信号的保存

2.1 core 标志位

在这里插入图片描述
当时在进程控制时 waitpid 函数中的 status参数 core dump 标志位 我们现在就马上知道什么意思了。当程序被信号杀死时,会生成一个core的debug文件。 这个core标记位 ,为0不允许生成,为1运行生成debug文件。
在这里插入图片描述
在云服务上 生成这个core文件的功能默认是被关闭的
ulimit - a 查看core file size 的大小
在这里插入图片描述
ulimit -c 【size】 设置一下就好了
也有 可能 生成的core 文件不在当前目录
echo ./core > /proc/sys/kernel/core_pattern 就欧克啦
在这里插入图片描述
一重启就会生成一个core.进程号的文件 如果无限制的重启 就会生成非常多的core文件 所以云服务器就把这个功能关闭了
在这里插入图片描述
调试的时候,我们core-file core文件 把这个debug文件加载进去,调试器就直接显示出错的那一行了!
在这里插入图片描述

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int sum(int star, int end)
{
    int ret = 0;
    for (int i = star; i <= end; i++)
    {
        ret /= 0;
        ret += i;
    }
    return ret;
}
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        sleep(1);
        sum(1, 100);
        exit(0);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        printf("exit code: %d, sig: %d, core dump:%d\n",(status >> 8) &0xff, status &0x7f,(status >> 7) &1);
    }
    return 0;
}

在这里插入图片描述
当我们把ulimit -c设置为 0时 coredump 标记位就为0了 表示 不生成core dump(核心转储)文件
在这里插入图片描述

2.信号的保存

信号的保存就保存在这三张表中,block表,peding表,handler表。
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
执行信号处理的动作称为信号的递达。
信号产生到递达之间称为未决
如果一个信号被阻塞了,那么它永远未决。
在这里插入图片描述
我们用的signal方法sighandler_t signal(int signum, sighandler_t handler); 其中 我们写的handler 就是把函数地址写进对应的handler表下标中 。
两张位图+函数指针数组 == 让进程识别信号

2.1 对pending 表 和 block 表操作

先介绍几个函数

#include <signal.h>
// 清空位图
int sigemptyset(sigset_t *set);
// 所有bit位全为1
int sigfillset(sigset_t *set);
// 把某一bit位置为1
int sigaddset (sigset_t *set, int signo);
// 把某一bit位置为0
int sigdelset(sigset_t *set, int signo);
// 判断某一比特位是不是1
int sigismember(const sigset_t *set, int signo); 

signal.h 给我们提供了 用户级别的位图,这些函数可以用来操作这个位图 sigset_t

//调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
// 获取pending 表
int sigpending (sigset_t * set);

2.2 阻塞SIGINT信号 并打印pending表例子

// 利用上面的函数,我们就是验证 某一信号被阻塞后,是否一直未决
#include <iostream>
#include <signal.h>
#include <unistd.h>
void PrintPending( sigset_t & pending)
{
    for(int sig = 31; sig >= 1; sig--)
    {
        if(sigismember(&pending,sig))
        {
            std::cout<<1;
        }else
        {
            std::cout<<0;
        }
    }
    std::cout<<std::endl;
}

int main()
{
    sigset_t block_set , old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set , SIGINT);
    // 
    sigprocmask(SIG_BLOCK,&block_set,&old_set);
    while(true)
    {
        sigset_t pending;
        sigpending(&pending);
        PrintPending(pending);
        
        sleep(1);
    }

    return 0;
}

捕捉信号

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号
信号可能不会立即被处理,而是在合适的时候处理
,这个合适的时候指的是,从用户态返回内核态的时候进行处理。
用户态:执行我们自己的数据和代码的时候
内核态:执行操作系统的代码和数据的时候
在这里插入图片描述
当信号的处理动作是自定义的信号处理函数时才返回时先到用户态再从内核态到用户态(因为hander方法 和 main 函数不是调用关系并不能直接返回)。
如果是默认 则直接杀死进程了。 忽略则 除了修改pending 表 由 1 变为 0,其他什么也不干。
举例:
户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号
SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler
和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返
回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

sigaction 函数

**int sigaction(int signum, const struct sigaction act, struct sigaction oldact);
和 signal一样都是捕捉信号的,它有一个同名的结构体,但这个结构体我们只关心ssiginfo_t 这个函数指针方法字段在这里插入图片描述

void handler(int signal)
{
    std::cout<<"捕捉到:"<<signal<<std::endl;
    // while(true)
    // {
    //     sigset_t pending;
    //     sigpending(&pending);
    //     Print(pending);
    //     sleep(1);
    // }
    exit(1);
}

int main()
{
    struct sigaction act, oact;
    act.sa_flags = 0;//在这种情况下,信号处理将遵循默认的行为,
    //也就是说,信号处理函数将作为一个普通的函数执行,
    //而不会触发任何 sa_flags 标志所定义的特殊行为。
    act.sa_handler = handler;

    sigemptyset(&act.sa_mask);
    //sigaddset(&act.sa_mask,3); // 顺带屏蔽三号信号

    sigaction(2,&act,&oact);
    while(true)
    {
        std::cout<<"pid: "<<getpid()<<std::endl;
        sleep(1);
    }    
    return 0;
}

在这里插入图片描述

验证当前正在处理某信号,则该信号会自动被屏蔽

我们在hander方法中一直sleep,不退出hander方法,我们再按ctrl + c信号也不会被处理了。这就验证了当前信号正在被处理,则该信号会被自动屏蔽。
在这里插入图片描述

验证当前信号被处理完之后,会自动解除屏蔽

我们设置hander方法睡三秒自动退出。 退出之后又可以捕捉到2号信号则证明了该结论
在这里插入图片描述

地址空间中操作系统态

内核级页表所有进程共享一份用户级页表每一个进程都有一份。操作系统的代码数据都通过内核级页表映射在物理内存中。
在这里插入图片描述

谈谈键盘输入的过程

操作系统怎么知道键盘摁下了? 是一直问键盘吗?当然不是,那不然太浪费cpu资源了
在这里插入图片描述
每一个硬件都有一个中断号,硬盘也不例外,当按下一个键后,通过8529这个芯片向cpu 发出硬件中断,某一个寄存器上就有了键盘的中断号,再在中断向量表中查询对应的键盘读入方法~这样就完成了cpu知道键盘输入的一个过程。
我们学习的信号就是模拟硬件中断实现的!

两个深刻的问题

如何操作系统是怎么运行的

操作系统调用进程谁由来调度操作系统呢?
硬件上有一个时钟,时钟到了就通过中断提醒操作系统该检测进程的时间片,时间片到了就切换进程,否则什么也不做
在这里插入图片描述

如何理解系统调用

  1. 有一个函数指针数组,通过下标 可以找到系统调用,这个下标我们称为系统调用号。
  2. 我们使用系统调用如fork时,会产生内部中断(陷阱),执行系统调用的方法,让cpu找这个函数指针数组。eax 中保存这个函数系统调用号,然后cpu就找到这个系统调用了

可重入函数

在这里插入图片描述
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数

volatile

#include <iostream>
#include <signal.h>
int gflag = 0;
void changeData(int signo)
{
    std::cout<<"gflg:0 -> 1"<<std::endl;
    gflag = 1;
}
int main()
{
    signal(2,changeData);
    while(!gflag);
    std::cout<<"process quit!"<<std::endl;
    
    return 0;
}

在这里插入图片描述
当我们用编译器O1的优化时,main函数 里面又没有修改 gflag的值,于是编译器把内存中的值拷贝到寄存器后,就只看寄存器中的值了。
在这里插入图片描述
怎么解决这个问题呢?
我们可以在gval前 加一个volatile关键字 保证内存的可见性就行了。

sigchild信号

子进程退出的时候会给父进程发送一个sigchild信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
void notice(int sig)
{
    std::cout<<"I am fatherprocess,pid: "<<getpid()<<std::endl;
    std::cout<<"get sig:"<<sig<<std::endl;
}
int main()
{
    signal(SIGCHLD,notice);
    pid_t id = fork();
    if(id == 0)
    {
        std::cout<<"I am childprocess,pid: "<<getpid()<<std::endl;
        sleep(3);
        exit(1);
    }
    sleep(100);
    return 0;
}

在这里插入图片描述

如果不关心 子进程的退出信息则可以把SIGCHLD 的捕捉动作改为SIG_IGN

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>

int main()
{
    signal(SIGCHLD,SIG_IGN);
    pid_t id = fork();
    
    if(id == 0)
    {
        int cnt = 5;
        while(cnt--)
        {
            std::cout<<"child process runing"<<std::endl;
          
            std::cout<<"cnt:"<<cnt<<std::endl;
            sleep(1);
        }
        exit(1);
    }
    while(true)
    {
        std::cout<<"father process runing"<<std::endl;
        sleep(1);
    }

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1901328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win11自动删除文件的问题,安全中心提示

win11自动删除文件的问题&#xff0c;解决方法&#xff1a; 1.点击任务栏上的开始图标&#xff0c;在显示的应用中&#xff0c;点击打开设置。 或者点击电脑右下角的开始也可以 2.点击设置。也可以按Wini打开设置窗口。 3.左侧点击隐私和安全性&#xff0c;右侧点击Windows安全…

学习笔记——动态路由——IS-IS中间系统到中间系统(区域划分)

三、IS-IS区域划分 根据IS-IS路由器邻居关系&#xff0c;可以将IS-IS划分为两个区域——骨干区域和非骨干区域。&#xff08;注意&#xff0c;这里的区域不是上文中提到的Area ID&#xff09;由L2的IS-IS邻居构成的区域为骨干区域&#xff0c;由L1的IS-IS邻居构成的区域为非骨…

动态路由--RIP配置(思科cisco)

一、简介 RIP协议&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;是一种基于距离矢量的动态路由选择协议。 在RIP协议中&#xff0c;如果路由器A和网络B直接相连&#xff0c;那么路由器A到网络B的距离被定义为1跳。若从路由器A出发到达网络B需要…

在原有的iconfont.css文件中加入新的字体图标

前言&#xff1a;在阿里图标库中&#xff0c;如果你没有这个字体图标的线上项目&#xff0c;那么你怎么在本地项目中的原始图标文件中添加新的图标呢&#xff1f; 背景&#xff1a;现有一个vue项目&#xff0c;下面是这个前端项目的字体图标文件。现在需要新开发功能页&#x…

论文阅读--Simple Baselines for Image Restoration

这篇文章是 2022 ECCV 的一篇文章&#xff0c;是旷视科技的一篇文章&#xff0c;针对图像恢复任务各种网络结构进行了梳理&#xff0c;最后总结出一种非常简单却高效的网络结构&#xff0c;这个网络结构甚至不需要非线性激活函数。 文章一开始就提到&#xff0c;虽然在图像复原…

DB-GPT-PaperReading

DB-GPT: Empowering Database Interactions with Private Large Language Models 1. 基本介绍 DB-GPT 旨在理解自然语言查询,提供上下文感知响应,并生成高精度的复杂 SQL 查询,使其成为从新手到专家的用户不可或缺的工具。DB-GPT 的核心创新在于其私有 LLM 技术,该技术在…

星辰宇宙动态页面vue版,超好看的前端页面。附源码与应用教程(若依)

本代码的html版本&#xff0c;来源自“山羊の前端小窝”作者&#xff0c;我对此进行了vue版本转换以及相关应用。特此与大家一起分享~ 1、直接上效果图&#xff1a; 带文字版&#xff1a;文字呼吸式缩放。 纯净版&#xff1a; 默认展示效果&#xff1a; 缩放与旋转后&#xf…

【C语言】第四十二弹---一万六千字教你从0到1实现通讯录

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 目录 1、通讯录分析和设计 1.1、通讯录的功能说明 1.2、程序的分析和设计 1.2.1、数据结构的分析 1.2.2、文件结构设计 2、通讯录的结构分析 2.1、创建通…

mysql5.6的安装步骤

1.下载mysql 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 在这里我们下载zip的包 2.解压mysql包到指定目录 3. 添加my.ini文件 # For advice on how to change settings please see # http://dev.mysql.com/doc/refman/5.6/en/server-configurat…

docker-compose Install gitlab 17.1.1

gitlab 前言 GitLab 是一个非常流行的开源 DevOps 平台,用于软件开发项目的整个生命周期管理。它提供了从版本控制、持续集成/持续部署(CI/CD)、项目规划到监控和安全的一系列工具。 前提要求 Linux安装 docker docker-compose 参考Windows 10 ,11 2022 docker docker-c…

11.x86游戏实战-汇编指令add sub inc dec

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;10.x86游戏实战-汇编指令lea 首先双击下图红框位置 然后在下图红框位置输入0 然…

瞰景Smart3D使用体验分享

引言 作为一名建筑设计师&#xff0c;我一直在寻找能够提升工作效率和设计质量的软件工具。瞰景Smart3D&#xff08;Smart3D&#xff09;是一款备受推崇的3D建模和设计软件&#xff0c;广泛应用于建筑、工程和施工&#xff08;AEC&#xff09;行业。经过一段时间的使用&#x…

MySQL表的增删改查(CRUD)

MySQL表的增删改查&#xff08;CRUD&#xff09; 文章目录 MySQL表的增删改查&#xff08;CRUD&#xff09;1. Create1.1 单行数据 全列插入1.2 多行数据 指定列插入1.3 插入否则更新1.4 替换 2. Retrieve2.1 SELECT 列2.1.1 全列查询2.1.2 指定列查询2.1.3 查询字段为表达式…

荞面打造的甜蜜魔法:甜甜圈

食家巷荞面甜甜圈是一款具有特色的美食。它以荞面为主要原料&#xff0c;相较于普通面粉&#xff0c;荞面具有更高的营养价值&#xff0c;富含膳食纤维、维生素和矿物质。荞面甜甜圈的口感可能会更加扎实和有嚼劲&#xff0c;同时带着荞面特有的谷物香气。在制作过程中&#xf…

一款纯 js 实现的大模型应用服务 FastGPT 解读

背景介绍 最近被不同的人安利了 FastGPT 项目&#xff0c;实际上手体验了一下&#xff0c;使用流程类似之前调研过的 Dify, 包含的功能主要是&#xff1a;任务流的编排&#xff0c;知识库管理&#xff0c;另外还有一些外部工具的调用能力。使用页面如下所示&#xff1a; 实际…

游戏服务器搭建选VPS还是专用服务器?

游戏服务器搭建选VPS&#xff0c;VPS能够提供控制、性能和稳定性。它不仅仅是让游戏保持活力。它有助于减少延迟问题&#xff0c;增强您的游戏体验。 想象一下&#xff1a;你正沉浸在一场游戏中。 胜利在望。突然&#xff0c;屏幕卡住——服务器延迟。 很崩溃&#xff0c;对…

Drools开源业务规则引擎(四)- 规则流(rule flow)及手把手教你构建jBPM项目

文章目录 Drools开源业务规则引擎&#xff08;四&#xff09;- 规则流&#xff08;rule flow&#xff09;及手把手教你构建jBPM项目1.什么是规则流2.构建jBPM项目2.1.添加maven依赖2.2.创建kmodule.xml2.3.新建drl文件和bpmn2文件2.4.测试方法2.5.日志输出 3.Eclipse安装jBPM流…

24西安电子科技大学马克思主义学院—考研录取情况

01、马克思主义学院各个方向 02、24马克思主义学院近三年复试分数线对比 PS&#xff1a;马院24年院线相对于23年院线增加15分&#xff0c;反映了大家对于马克思主义理论学习与研究的热情高涨&#xff0c;也彰显了学院在人才培养、学科建设及学术研究等方面的不断进步与成就。 6…

Apache Seata应用侧启动过程剖析——注册中心与配置中心模块

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata应用侧启动过程剖析——注册中心与配置中心模块 前言 在Seata的应用侧&#xf…

Docker逃逸CVE-2019-5736、procfs云安全漏洞复现,全文5k字,超详细解析!

Docker容器挂载procfs 逃逸 procfs是展示系统进程状态的虚拟文件系统&#xff0c;包含敏感信息。直接将其挂载到不受控的容器内&#xff0c;特别是容器默认拥有root权限且未启用用户隔离时&#xff0c;将极大地增加安全风险。因此&#xff0c;需谨慎处理&#xff0c;确保容器环…