Linux|信号

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.mfbz.cn/a/778527.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx配置尝试

from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.responses import JSONResponse, FileResponse, HTMLResponse import logging import os from datetime import datetime import uvicorn# 初始化日志 logging.basicConfig(filenamefile_server.lo…

详解AT_dp_l Deque(区间动态规划)

题目 思路 考虑模拟博弈过程。 题目可以看成:先手希望X - Y最大&#xff0c;后手希望X - Y最小。 显然游戏过程中剩下的数必然是连续的一段。设 dp[i,j]​ 表示剩下下标为 [i,j] 的数时&#xff0c;先手&#xff08;并非当前的先手而是开始时的先手&#xff0c;下同&#xf…

[数据结构] 基于交换的排序 冒泡排序快速排序

标题&#xff1a;[数据结构] 基于交换的排序 冒泡排序&&快速排序 水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 &#xff08;一&#xff09;冒泡排序 优化后实现&#xff1a; &#xff08;二&#xff09;快速排序 I、实现方法&#xff1a; &#…

中英双语介绍百老汇著名歌剧:《猫》(Cats)和《剧院魅影》(The Phantom of the Opera)

中文版 百老汇著名歌剧 百老汇&#xff08;Broadway&#xff09;是世界著名的剧院区&#xff0c;位于美国纽约市曼哈顿。这里汇集了许多著名的音乐剧和歌剧&#xff0c;吸引了全球各地的观众。以下是两部百老汇的经典音乐剧&#xff1a;《猫》和《剧院魅影》的详细介绍。 1.…

C++友元函数和友元类的使用

1.友元介绍 在C++中,友元(friend)是一种机制,允许某个类或函数访问其他类的私有成员。通过友元,可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用,例如在类之间共享数据或实现特定的功能。 友元可以分为两种类型:类友元和函数友元。…

推荐好玩的工具之OhMyPosh使用

解除禁止脚本 Set-ExecutionPolicy RemoteSigned 下载Oh My Posh winget install oh-my-posh 或者 Install-Module oh-my-posh -Scope AllUsers 下载Git提示 Install-Module posh-git -Scope CurrentUser 或者 Install-Module posh-git -Scope AllUser 下载命令提示 Install-Mo…

云端AI大模型群体智慧后台架构思考

1 大模型的调研 1.1 主流的大模型 openai-chatgpt 阿里巴巴-通义千问 一个专门响应人类指令的大模型。我是效率助手&#xff0c;也是点子生成机&#xff0c;我服务于人类&#xff0c;致力于让生活更美好。 百度-文心一言&#xff08;千帆大模型&#xff09; 文心一言"…

【Linux】进程创建和终止 | slab分配器

进程创建 fork 1.fork 之后发生了什么 将给子进程分配新的内存块和内核数据结构&#xff08;形成了新的页表映射&#xff09;将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork 返回&#xff0c;开始调度器调度 这样就可以回答之前返回两个值&#xff1f…

线程池理解及7个参数

定义理解 线程池其实是一种池化的技术实现&#xff0c;池化技术的核心思想就是实现资源的复用&#xff0c;避免资源的重复创建和销毁带来的性能开销。线程池可以管理一堆线程&#xff0c;让线程执行完任务之后不进行销毁&#xff0c;而是继续去处理其它线程已经提交的任务。 …

web缓存代理服务器

一、web缓存代理 web代理的工作机制 代理服务器是一个位于客户端和原始&#xff08;资源&#xff09;服务器之间的服务器&#xff0c;为了从原始服务器取得内容&#xff0c;客户端向代理服务器发送一个请求&#xff0c;并指定目标原始服务器&#xff0c;然后代理服务器向原始…

【IT领域新生必看】 Java编程中的重载(Overloading):初学者轻松掌握的全方位指南

文章目录 引言什么是方法重载&#xff08;Overloading&#xff09;&#xff1f;方法重载的基本示例 方法重载的规则1. 参数列表必须不同示例&#xff1a; 2. 返回类型可以相同也可以不同示例&#xff1a; 3. 访问修饰符可以相同也可以不同示例&#xff1a; 4. 可以抛出不同的异…

【踩坑】解决undetected-chromedriver报错cannot connect to-chrome

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 网上方法都试了&#xff0c;什么指定version_main、添加option等。 最终&#xff0c;放弃&#xff0c;直接换selenium自己的吧&#xff1a; from web…

【论文解读】LivePortrait:具有拼接和重定向控制的高效肖像动画

&#x1f4dc; 文献卡 英文题目: LivePortrait: Efficient Portrait Animation with Stitching and Retargeting Control;作者: Jianzhu Guo; Dingyun Zhang; Xiaoqiang Liu; Zhizhou Zhong; Yuan Zhang; Pengfei Wan; Di ZhangDOI: 10.48550/arXiv.2407.03168摘要翻译: *旨在…

IDEA配Git

目录 前言 1.创建Git仓库&#xff0c;获得可提交渠道 2.选择本地提交的项目名 3.配置远程仓库的地址 4.新增远程仓库地址 5.开始进行commit操作 6.push由于邮箱问题被拒绝的解决方法&#xff1a; 后记 前言 以下操作都是基于你已经下载了Git的前提下进行的&#xff0c…

基于机器学习(支持向量机,孤立森林,鲁棒协方差与层次聚类)的机械振动信号异常检测算法(MATLAB 2021B)

机械设备异常检测方法流程一般如下所示。 首先利用传感器采集机械运行过程中的状态信息&#xff0c;包括&#xff0c;振动、声音、压力、温度等。然后采用合适的信号处理技术对采集到机械信号进行分析处理&#xff0c;提取能够准确反映机械运行状态的特征。最后采用合理的异常决…

算法系列--分治排序|再谈快速排序|快速排序的优化|快速选择算法

前言:本文就前期学习快速排序算法的一些疑惑点进行详细解答,并且给出基础快速排序算法的优化版本 一.再谈快速排序 快速排序算法的核心是分治思想,分治策略分为以下三步: 分解:将原问题分解为若干相似,规模较小的子问题解决:如果子问题规模较小,直接解决;否则递归解决子问题合…

Debezium报错处理系列之第110篇: ERROR Error during binlog processing.Access denied

Debezium报错处理系列之第110篇:ERROR Error during binlog processing. Last offset stored = null, binlog reader near position = /4 Access denied; you need at least one of the REPLICATION SLAVE privilege for this operation 一、完整报错二、错误原因三、解决方法…

智能化客户服务:提升效率与体验的新模式

在数字化浪潮的推动下&#xff0c;客户服务领域正经历着一场深刻的变革。智能化客户服务的兴起&#xff0c;不仅重塑了企业与客户之间的互动方式&#xff0c;更在提升服务效率与增强客户体验方面展现出了巨大潜力。本文将深入探讨智能化客户服务的新模式&#xff0c;分析其如何…

Error in onLoad hook: “SyntaxError: Unexpected token u in JSON at position 0“

1.接收页面报错 Error in onLoad hook: "SyntaxError: Unexpected token u in JSON at position 0" Unexpected token u in JSON at position 0 at JSON.parse (<anonymous>) 2.发送页面 &#xff0c;JSON.stringify(item) &#xff0c;将对象转换为 JSO…

InspireFace-商用级的跨平台开源人脸分析SDK

InspireFace-商用级的跨平台开源人脸分析SDK InspireFaceSDK是由insightface开发的⼀款⼈脸识别软件开发⼯具包&#xff08;SDK&#xff09;。它提供了⼀系列功能&#xff0c;可以满⾜各种应⽤场景下的⼈脸识别需求&#xff0c;包括但不限于闸机、⼈脸⻔禁、⼈脸验证等。 该S…