操作系统——信号

将信号分为以上四个阶段

1.信号注册:是针对信号处理方式的规定,进程收到信号时有三种处理方式:默认动作,忽略,自定义动作。如果不是自定义动作,这一步可以忽略。这个步骤要使用到signal/sigaction接口

2.信号产生:就是操作系统向进程发出信号 

3.信号保存

4.信号捕捉处理


信号有哪些

1-31是普通信号 34-62是实时信号 

Action列指的是当信号被发送到一个进程时,默认操作系统采取的动作。具体的动作类型和含义如下:

  1. Term (Terminate)

    • 终止进程。此操作表示操作系统将结束进程的执行。这是大多数信号的默认动作。
  2. Core (Terminate and Dump Core)

    • 终止进程并生成核心转储文件。核心转储文件包含了进程在被终止时的内存状态,可以用于调试目的。想了解core的调试看这篇文章
  3. Ign (Ignore)

    • 忽略信号。进程接收到信号时,操作系统不会采取任何动作,也不会通知进程。
  4. Stop

    • 停止进程的执行。进程被暂停,直到接收到继续信号(如SIGCONT)。
  5. Cont (Continue)

    • 继续执行被停止的进程。此操作恢复一个之前被暂停的进程的执行。

理解信号

信号和生活中的信号是一样的。例如下课铃声就是一个信号,上学的第一天,老师会告诉我们下课铃声响起的时候就可以下课休息——信号规定。当一节课的下课铃声响起,我们收到这个信号,但是老师想拖堂,我们先将这个信号保存到大脑,等老师讲完才会对下课信号处理。从下课铃声响起到真正下课这段时间就是时间窗口。信号产生了并不代表现在就要处理,进程会选择在合适的时间进行处理。

信号注册

signal

signal是将signum这个信号的处理方式进行自定义

注意:信号9和信号19不可以修改,因为进程终止和停止的权利必须由操作系统掌握

例子:

将信号1自定义捕捉

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

using namespace std;
void fun(int signum)
{
    cout << "get signum" << signum << endl;
}

int main()
{
    signal(1,fun);
    while(1)
    {
        cout << "process running pid:" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

运行程序发送信号1

sigaction

了解信号保存信号处理后再了解这个接口!!!!

sigaction结构体中,第一个是自定义动作函数指针,第三个是处理信号时要屏蔽的信号,其他的暂时不考虑。

act表示信号处理的方式,oact表示之前信号处理的方式。

例子:

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

using namespace std;

void PrintPend(sigset_t& set)
{
    for(int signo = 31; signo >= 1; signo--)
    {
        if(sigismember(&set, signo)) cout << 1;
        else cout << 0;
    }
    cout << endl;
}
void header(int sig)
{
    sigset_t set;
    sigemptyset(&set);
    while(1)
    {
        sigpending(&set);
        PrintPend(set);
        sleep(1);
    }
}
int main()
{
    struct sigaction act,oact;
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);
    act.sa_handler = header;
    sigaction(SIGINT, &act, &oact);

    while(1)
    {
        cout << "process running:" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

3 4 5 信号都被屏蔽了,处理信号的过程发送信号只会先保存  

当操作系统处理信号调用自定义动作时先将对应信号pend置为0,为了防止信号的嵌套处理,还会自动将当前信号屏蔽。

sa_mask可以自己设置要屏蔽的信号

信号发送

什么是信号发送

信号是由OS向进程发送的,信号就一定保存在进程中。普通信号有31个,以位图的形式储存到进程PCB的一个int类型中。实时信号与普通信号的区别就是:实时信号收到后必须立即处理不会等待,实时信号是存储在进程的一个队列中。所以发信号就是操作系统修改对应的int值或者队列

信号发送方式

键盘组合键

例如:

ctrl+c,信号2中断进程

ctrl+\,信号3退出进程

键盘组合键是怎么发出信号的呢?

原理 

键盘写入完毕后,会向CPU发送硬件中断包括中断号,CPU告诉操作系统,操作系统通过中断号到中断向量表寻找中断号所对应的方法地址,使用该方法将键盘缓冲区的数据写到OS缓冲区,操作系统拿到数据后对进程发出信号 

另外,键盘只能向前台进程(哪个进程能获取键盘输入,哪个进程就是前台进程)发送信号。Linux中一个登录只能有一个前台进程,可以有多个后台进程。

当我们./运行一个程序时,前台进程就是正在运行的程序,ctrl+c就会终止当前进程。

如果在运行时./后面加上&,当前进程就会以后台进程的方式运行,ctrl+c无效。因为前台进程是bash,此时键盘任何输入都会给bash,也就意味着这时可以使用命令行

kill命令

kill signum PID

系统调用接口

kill 

向其他进程发送信号

样例:

写一个可以给其他进程发信号的程序

//myprocess.cc
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{
    while(1)
    {
        cout << "process: " << getpid() << " running" << endl;
        sleep(1);
    }
    return 0;
}

//mykill.cc
#include <iostream>
#include <sys/types.h>
#include <signal.h>


using namespace std;
void Usage(const char* argv)
{
    cout << argv << " pid " << "sig" << endl;
}

int main(int argc, const char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
    }
    else{
        int n = kill(stoi(argv[1]), stoi(argv[2]));
        if(n == -1)
        {
            perror("kill fail");
            return -1;
        }
    }
    return 0;
}

raise

向当前进程发送信号

实际上调用了kill接口,相当于kill(getpid(), sig)

abort

让当前进程终止

实际上调用了kill接口,相当于kill(getpid(), 6)

注意:信号6是由其他进程发来的,不会让进程退出

alarm

在设定时间过后,发送信号

返回前一个定时器的剩余时间(以秒为单位),如果之前没有设置定时器,则返回0

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;
void fun(int sig)
{
    int n = alarm(5);
    cout << "get alarm" << "time:" << n <<endl;
}

int main()
{
    //alarm收到信号后默认退出进程,进行自定义信号捕捉
    signal(SIGALRM, fun);
    alarm(5);
    while(1)
    {
        cout << "process running" << endl;
        sleep(1);
    }
}

异常

例如遇到除0错误时,CPU在运算的过程中出现错误,会将这个情况告诉操作系统,再由操作系统给进程发信号,中断进程。操作系统即是硬件设备的管理者也是进程的管理者

如果将这个信号自定义捕捉,并且捕捉的动作不会让进程退出,会怎么样呢?

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

using namespace std;
void fun(int signum)
{
    cout << "get signum" << signum << endl;
    sleep(1);
}

int main()
{
    signal(8,fun);
    int a= 1/0;
    return 0;
}

操作系统会一直给进程发信号。因为进程收到信号未关闭,进程会一直被CPU调度运行,一直出现错误。

信号保存

信号有几种状态:

递达(delivery):实际执行信号的处理动作

未决(panding):从信号被发出到递达之间的状态

阻塞(block):进程可以阻塞某个信号,当该信号别发出时,不会递达,只有当信号解除阻塞时才会递达

阻塞和忽略不同,忽略是递达后的处理方式

信号保存主要就是通过阻塞实现的


信号在内核中的表示:

block位图表示信号是否被阻塞,pending位图表示信号是否发出,handler是函数指针数组,存储了信号的处理方法,SIG_DFL是默认方法,SIG_IGN是忽略,还可以指向用户区自己定义的方法。

操作系统提供了block(阻塞信号集/信号屏蔽字)和pending(未决信号集)的数据类型sigset_t还有相应的系统调用接口

信号集操作接口

sigemptyset将所有标志位都置为0,sigfillset将所有标志位都置为1

sigaddset/sigdelset :增加/删除signum信号所对应的位置

sigismember检测signum在set中是否为1

修改屏蔽信号字接口

how可以以下有几个值:

SIG_BLOCK:set中包含了希望添加到当前屏蔽信号字的信号

SIG_UNBLOCK:set包含了希望从当前屏蔽信号字删除的信号

SIG_SETMASK:将屏蔽信号字设置为set

set就是用来更改信号屏蔽字的屏蔽字参数,oset用来存储更改信号屏蔽字之前的屏蔽字参数

显示未决信号集的接口

将未决信号集拷贝到set

实例

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;

void hander(int sig)
{
    cout << "get signal:" << sig << endl;
}

int main()
{
    signal(2,hander);
    sigset_t set, oset;
    sigemptyset(&set);
    sigemptyset(&oset);
    sigaddset(&set, SIGINT);
    //设置屏蔽信号字
    sigprocmask(SIG_BLOCK, &set, &oset);
        
    int cnt = 5;
    while (1)
    {
        sigset_t pending;
        sigpending(&pending);
        //展示未决信号集
        for (int i = 31; i >= 1; i--)
        {
            if (sigismember(&pending, i))
                cout << "1";
            else
                cout << "0";
        }
        cout << endl;
        sleep(3);

        cnt--;
        if(cnt == 0)//解除屏蔽信号字
        {
            sigprocmask(SIG_SETMASK, &oset, nullptr);
        }
    }

    return 0;
}

 注意,和信号捕捉一样,信号9和信号19不可以被阻塞

信号处理

什么时候处理

结论:当进程从内核态变为用户态,操作系统会进行信号的检测和处理。

内核态:进程访问操作系统的代码和数据

用户态:进程访问自己的代码和数据

CPU中有一些寄存器的标志位可以区分进程在哪个态。有几个进程就有几个用户级页表,而内核级页表只有一个,不管进程怎么切换,每个进程看到的内核空间都是一样的。从进程的角度看,调用系统调用接口,就是在自己的进程地址空间调用。从操作系统的角度看,在任意时刻,只要有进程运行就可以随时调用系统调用接口。

怎么处理

当进程进入内核态(例如调用了系统调用接口),在执行系统调用操作后,会检查是否有可以递送的信号并进行处理然后返回用户态,如果是处理自定义的动作信号,就会先从内核进入用户态(因为用户态下,处理函数做非法操作会被操作系统拦截,保证了安全性),调用信号处理函数,再回到内核态,最后返回用户态,从主控制流程上次中断的地方继续执行

信号与进程等待

子进程退出会向父进程发送信号SIGCHLD,不过这个信号默认处理方式时忽略的,可以通过自定义捕捉对进程回收。

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

using namespace std;
void header(int sig)
{
    pid_t rid;
    while((rid = waitpid(-1,nullptr,WNOHANG)) > 0)
    {
        cout << "wait :" << rid << " success" << endl;
    }
}

int main()
{
    srand(time(nullptr));
    signal(SIGCHLD, header);
    // 创建10个子进程
    for (int i = 10; i > 0; i--)
    {
        pid_t id = fork();
        if (id == 0)
        {
            cout << "I am child:" << getpid() << endl;
            sleep(rand() % 2 + 1);
            cout << "child quit:" << getpid() << endl;
            sleep(rand() % 2 + 1);
            exit(0);
        }
        sleep(rand() % 3 + 3);
    }

    while(1)
    {
        cout << "I am father:" << getpid() << endl;
        sleep(1);
    }
}

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作 置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽 略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证 在其它UNIX系统上都可 用。请编写程序验证这样做不会产生僵尸进程。

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

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

相关文章

2002-2023年款别克君威 君威GS维修手册和电路图资料更新

经过整理&#xff0c;2002-2023年款别克君威 君威GS全系列已经更新至汽修帮手资料库内&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图…

Dorkish:一款针对OSINT和网络侦查任务的Chrome扩展

关于Dorkish Dorkish是一款功能强大的Chrome扩展工具&#xff0c;该工具可以为广大研究人员在执行OSINT和网络侦查任务期间提供强大帮助。 一般来说&#xff0c;广大研究人员在执行网络侦查或进行OSINT信息收集任务过程中&#xff0c;通常会使用到Google Dorking和Shodan&…

晶圆代工市占洗牌,中芯跃居第三名 | 百能云芯

市场研究机构集邦咨询&#xff08;TrendForce&#xff09;最新发布的调查显示&#xff0c;今年第1季前五大晶圆代工厂第1季排名出现明显变动&#xff0c;除了台积电&#xff08;TSMC&#xff09;继续蝉联第一名&#xff0c;中芯国际&#xff08;SMIC&#xff09;受惠消费性库存…

Flutter-使用MethodChannel 实现与iOS交互

前言 使用 MethodChannel 在 Flutter 与原生 Android 和 iOS 之间进行通信&#xff0c;可以让你在 Flutter 应用中调用设备的原生功能。 基础概念 MethodChannel&#xff1a;Flutter 提供的通信机制&#xff0c;允许消息以方法调用的形式在 Flutter 与原生代码之间传递。方法…

光储充一体化充电站:能源革新的绿色引擎

在这个科技日新月异的时代&#xff0c;一场绿色能源的革命正悄然兴起。 光储充一体化充电站&#xff0c;作为这场革命中的璀璨明星&#xff0c;正以其独特的魅力&#xff0c;引领我们走向更加环保、高效的未来。 光储充一体化充电站&#xff0c;顾名思义&#xff0c;将光伏发电…

Chromium源码阅读:Mojo实战:从浏览器JS API 到blink实现

​ 通过在前面几篇文章&#xff0c;我们粗略梳理了Mojo这套跨进程通信的设计思路和IDL细节。 实际上&#xff0c;Mojo不止是跨进程通信框架&#xff0c;而是跨语言的模块通信自动化系统。 在浏览器暴露的JS API&#xff0c;也是需要通过Mojo这个系统进行桥接&#xff0c;最终…

遥感图像地物覆盖分类,数据集制作-分类模型对比-分类保姆级教程

在遥感影像上人工制作分类数据集 1.新建shp文件 地理坐标系保持和影像一致&#xff0c;面类型 2.打开属性表 3.添加字段 这里分类6类&#xff0c;点击添加值添加 添加完毕 开始人工选地物类型&#xff0c;制作数据集 开始标注&#xff0c;标注的时候可以借助谷歌地图…

记录清除挖矿病毒 solrd 过程

1、发现solrd病毒 端午节期间&#xff0c;kafka 服务器被黑客攻击了&#xff0c;植入了挖矿病毒 solrd&#xff0c;这个病毒很聪明&#xff0c;内存&#xff0c;CPU并没有异常升高&#xff0c;以致于上班第一天完全没有察觉。 上班第一天 正常登录服务器查看 flink ,消费kafka…

Eclipse创建Spring项目

第一步&#xff1a;先用Eclipse创建一个tomcat项目 打开eclipse 配置tomcat 这里点击add去添加tomcat 创建项目 写好项目名字&#xff0c;点击next 将这个Deploy path修改一下 配置一下项目&#xff0c;将项目部署到tomcat上面去 写个html测试一下 <html><h1>Hel…

tensorboard终于不报错了!

之前tensorboard笔记,记录2种使用方法&#xff0c;可以参考&#xff01; 有图这也没有数据啊这也。实际是没点对&#xff0c;干了个大噶。选择上方scalars就能看见图了&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…

GitHub新手使用基本流程(保姆级教程)

GitHub&#xff0c; 一个聚合了海量开源免费的软件、电子书、视频课等资源的神仙网站&#xff0c;如果你还不会用GitHub&#xff0c;这条保姆级教程&#xff0c;您千万不要错过。 首先打开浏览器搜索GitHub, 点击进入官网 如果遇到无法加载的问题&#xff0c;可以试试这个名叫…

机器视觉:工业相机的主要参数

工业相机是将目标物体的表面特征信息转化为数字信号&#xff08;或者模拟信号&#xff09;的一种采集设备。 一、工业相机的成像原理 工业相机主要由光电传感器和转换电路组成。 光线照射到被检测物体的表面&#xff0c;反射光经过透镜&#xff0c;再进入相机的光电传感器&a…

从报名到领证:软考初级【信息系统运行管理员】报名考试全攻略

本文共计9991字&#xff0c;预计阅读33分钟。包括七个篇章&#xff1a;报名、准考证打印、备考、考试、成绩查询、证书领取及常见问题。 一、报名篇 报名条件要求&#xff1a; 1.凡遵守中华人民共和国宪法和各项法律&#xff0c;恪守职业道德&#xff0c;具有一定计算机技术…

flask基础3-蓝图-cookie-钩函数-flask上下文-异常处理

目录 一&#xff1a;蓝图 1.蓝图介绍 2.使用步骤 3.蓝图中的静态资源和模板 二.cookie和session 1.cookie 2.flask中操作cookie 3.session 4.session操作步骤 三.请求钩子 四.flask上下文 1.介绍 2.请求上下文&#xff1a; 3.应用上下文 3.g对象 五&#xff1a;…

STM32高级控制定时器(STM32F103):TIM1和TIM8介绍

目录 概述 1 认识TIM1和TIM8 2 TIM1和TIM8的特性 3 TIM1和TIM6时基和分频 3.1 时基单元 3.2 预分频 3.3 时基和分频相关寄存器 3.3.1TIMx_CR1 3.3.2 TIMx_PSC 概述 本文主要介绍STM32高级定时器TIM1和TIM8的功能&#xff0c;还介绍了与之相关的寄存器的配置参数。包括…

如何在3天内开发一个鸿蒙app

华为鸿蒙操作系统&#xff08;HarmonyOS&#xff09;自2.0版本正式上线以来&#xff0c;在短时间内就部署超过了2亿台设备&#xff0c;纵观全球操作系统的发展史&#xff0c;也是十分罕见的。与其他手机操作系统不同&#xff0c;HarmonyOS自诞生之日起&#xff0c;就是一款面向…

layui一个页面多个table显示时工具栏被下方的table遮挡

记录&#xff1a;layui一个页面多个table显示时工具栏被下方的table遮挡 css代码&#xff1a; [lay-idcurrentTableId] .layui-table-tool {position: relative;z-index: 9999;width: 100%;min-height: 50px;line-height: 30px;padding: 10px 15px;border-width: 0;border-bot…

c#中上传超过30mb的文件,接口一直报404,小于30mb的却可以上传成功

在一次前端实现上传视频文件时,超过30mb的文件上传,访问接口一直报404,但是在Swagger中直接访问接口确是正常的,且在后端控制器中添加了限制特性,如下 但是却仍然报404,在apifox中请求接口也是报404, 网上说: 在ASP.NET Core中,配置请求过来的文件上传的大小限制通常…

jfif格式怎么转换成jpg?关于将jfif转成jpg的几种方法

jfif格式怎么转换成jpg&#xff1f;JFIF格式是一种常见的图像文件格式&#xff0c;通常用于存储数字照片。然而&#xff0c;在某些情况下&#xff0c;你可能需要将JFIF格式转换为JPG格式。JPG格式是一种广泛使用的图像格式&#xff0c;它被支持和接受的程度比JFIF更高。PNG是一…

C++迈向精通:当我尝试修改虚函数表

尝试修改虚函数表 本期纯整活儿好吧&#xff01;&#xff01;&#xff01;&#xff01; 初衷 有一天我突然开始好奇虚函数表是否真的存在&#xff0c;于是我开始想是否能够从C中查看或者调用虚函数表中的内容。&#xff0c;于是有了下面的操作。 操作过程 起初我并没有思路…