Linux操作系统之信号:信号的产生

前言:

上篇文章我们大致讲解了信号的有关概念,为大家引入了信号的知识点。但光知道那些是远远不够的。

本篇文章,我将会为大家自己的讲解一下信号的产生的五种方式,希望对大家有所帮助。

一、键盘(硬件)产生信号

回顾

我们上文曾经说过,当我们在前台运行的一个进程时,尤其是while控制的死循环程序,我们可以通过按下ctrl + c按键来终止进程。

这是因为我们通过系统按键给进程发送了二号信号SIGINT,并杀死了该进程:

其中从34-64的信号我们这里不予关注,也用不上。我们只谈论前面31中信号。 

我们当时通过signal系统调用接口,来自定义了对二号信号的处理方式handler。

其实忘记说的是,void handler(int signumber)中的参数signumber,其实就是该信号的编号。

而信号每一个信号如SIGINT,他其实都是一个宏定义:

 我们可以通过man 7 signal,来查看更多信号的内容:

2号信号的默认处理方式是Term,也就是终止进程,另外,Core也是默认终止进程。


如果我们把一个进程的所有信号的默认处理方式都变成handler,会发什么什么情况呢?


#include <iostream>
#include <unistd.h>
#include<sys/types.h>
#include<signal.h>void handler(int signumber)
{std::cout<<"捕获到信号"<<signumber<<",开始执行自定义处理方法"<<std::endl;
}int main()
{for(int i=1;i<=31;++i){signal(i,handler);}while(true){std::cout<<"I am "<<getpid()<<" , I am waiting signal!"<<std::endl;sleep(3);}return 0;
}

难不成,所有信号都干不掉这个进程了吗? 

这个问题,信号设计者自然也会想到,所以在信号中,就有几位信号的默认处理方式是无法改变的,九号信号就是这样。


信号位图

我们在按下键盘时,键盘会把信息交给操作系统,操作系统才向进程发送信号。

操作系统凭什么能收到键盘的信息啊?

:因为我们之前说过,操作系统是所有硬件的管理者,这一点我们在操作系统的那一章节说过。

同学们,我们说处理信号是在合适的时候而不是立即处理,那么,我们就必须把收到的信号保存起来。否则你不能及时处理,要等待,就会丢失信号。

那么进程是如何保管自己收到的信号的呢?

答案是:PCB

我们之前说每一个信号都有一个编号,并且编号刚好对应1-31,同学们,这你想到了什么?

:是不是位图啊!!

所以,在每一个进程的PCB中,存在一个位图,对应的比特位的0/1,就代表的是否收到对应的信号:

所以我们可以知道,发送信号,其实本质上不是发送,而是写入!!

写入信号!!:OS修改对应的进程的PCB中的信号位图:0->1。

同样的,我们说每一个信号都有一个对应的默认处理方法。这个方法又是怎么保存的呢?

参考一下我们的struct file中的操作表,在我们的task_struct中,也存在一个函数指针数组,分别存储对应下标的信号的默认处理方法:

 


 硬件中断初体验

那么,操作系统是怎么知道键盘上有数据的呢?
难不成要操作系统一直在询问这些硬件吗?要知道,操作系统可是很忙的,基本什么事情,都有它的参与。

自然不会由操作系统主动去询问,这就跟你平时在公司上班,上头直接把任务分配给你,你完成了要给别人说一样。操作系统就是这个老板,硬件就是员工。

当按下鼠标,鼠标就会产生硬件中断,在冯诺依曼体系的帮助下,告诉操作系统我已经准备好了。

 

至此,操作系统就不用主动去知道键盘是否有数据,他只需要等别人告诉他。

这样,就实现了硬件与操作系统的并行执行。

操作系统通过中断管理所有硬件。那他内部管理进程,想模拟硬件的行为,于是就有了信号


二、指令

我们之前讲过,当我们想要对一个进程发送信号,我们只需要知道这个进程的pid,于是我们可以通过kill的系统指令,来给这个进程发送指定编号的信号:

所以我们这里就不再过多复述了。

三、系统调用

那么kill指令是怎么实现的呢?

它是根据kill系统调用来实现的:
 

第一个参数就是对应进程pid,第二个参数就是发送信号的编号或者宏。

至此,我们可以模拟实现一个我们的mykill程序:

#include <iostream>
#include <unistd.h>
#include<sys/types.h>
#include<signal.h>int main(int argc,char* argv[])
{if(argc!=3){std::cerr<<"Usage:" << argv[0] << " -signumber pid" << std::endl;return 1;}int n=::kill(atoi(argv[2]),atoi(argv[1]));return n;
}


第二个系统调用就是raise:raise 函数可以给当前进程发送指定的信号(就是自己给自己发信号)。

还有一个系统调用时abort:abort 函数使当前进程接收到信号而异常终⽌。 

不难发现,后面两个调用的作用都是进行了特化,可以猜测他们的底层都调用了kill。


四、软件条件

我们当时在讲匿名管道的时候,提到过:

管道读端关闭,如果此时写端还想写入

操作系统就会直接终止该写端进程:这其实就是发送的13号信号

这歌案例就是软件条件不具备,你不具备写入的条件,于是要发送信号。

除了13信号外,还有一个14信号SIGALRM,这就涉及到了一个系统调用:alarm闹钟

 alarm() 是 Unix/Linux 系统提供的 定时器函数,用于在指定时间后向当前进程发送 SIGALRM 信号(默认行为是终止进程)。它属于 <unistd.h> 头文件,常用于实现超时控制或周期性任务。这个闹钟是一次性的,你设置一次alarm函数,就会设置一个闹钟 

我们运行程序:

#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{int count = 0;alarm(1);while (true){std::cout << "count : "<< count << std::endl;count++;}return 0;
}

就知道了一秒内的循环次数

 我们一般会搭配上signal,使用自己的处理方法,这样就能实现一下特殊的代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>int count = 0;
void handler(int signumber)
{std::cout << "count : " << count << std::endl;exit(0);
}
int main()
{signal(SIGALRM, handler);alarm(1);while (true){count++;}return 0;
}

诶,为什么后面这个代码while循环了这么多次呢?不都是一秒钟的闹钟吗 ?

这是因为:cout是阻塞式I/O操作,涉及用户态到内核态的切换,以及终端设备的输出

这些操作非常耗时,一秒钟的大部分时间花在 I/O 上,而非 count++

而后面的操作就只涉及了count++,最后才会打印输出。


如果我们想设置重复闹钟呢?

就需要循环调用了:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <vector>
#include <functional>using func_t = std::function<void()>;int gcount = 0;
std::vector<func_t> gfuncs;// 把信号 更换 成为 硬件中断
void hanlder(int signo)
{for (auto &f : gfuncs){f();}std::cout << "gcount : " << gcount << std::endl;alarm(1);
}int main()
{gfuncs.push_back([](){ std::cout << "我是一个内核刷新操作" << std::endl; });gfuncs.push_back([](){ std::cout << "我是一个检测进程时间片的操作,如果时间片到了,我会切换进程" << std::endl; });gfuncs.push_back([](){ std::cout << "我是一个内存管理操作,定期清理操作系统内部的内存碎片" << std::endl; });alarm(1); // 一次性的闹钟,超时alarm会自动被取消signal(SIGALRM, hanlder);while (true){pause();std::cout << "我醒来了..." << std::endl;gcount++;}
}

 

如果时间才间隔快点,把信号更换为硬件中断,就是我们操作系统的运行原理


 五、异常

我们都知道,当我们的代码出现除0或者使用野指针时,进程就会直接终止掉。

关于野指针我们在虚拟地址空间的时候曾经提到过:这是因为在页表上找该虚拟地址的映射关系时,找不到,或者权限不够,所以会被信号终止。

那么除零呢?

当我们出现除0异常时,会发送8信号(野指针是11)给我们的进程:

我们写以下代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <vector>
#include <functional>void handler(int num)
{std::cout<<"捕获到信号"<<num<<std::endl;
}int main()
{signal(11,handler);signal(8,handler);int a=10;a/=0;int*p=nullptr;*p=10;while(true){std::cout<<"hello world"<<std::endl;};return 0;
}

运行结果会无限打印:捕获到信号8.

这是为什么呢?

在我们的CPU上:

我们的操作系统需要知道CPU内部是否出错,(CPU也是一个硬件),就存在一个状态寄存器,负责判断此次的运算结果是否范围溢出等问题。(标记位为0表示正常,为1表示溢出)

当我们不终止进程,使用自己的处理方法,由于进程会轮循调度的原因,保存上下文信息,不终止进程就会一直调度该进程,发现溢出->发送信号,不断重复该过程

 

 野指针越界访问也是类似的形式,当我们虚拟地址转化为物理地址成功时,会把地址存储在CR3中,如果失败,就会存储在CR2中,这样我们就知道出错了。


六、Core与Term

Core与Term都是终止进程,但是Core还做了一些特殊的处理。

如果是core的终止进程,在终止后会帮我们形成一个debug文件,通常是corecore.pid文件。

我们可以通过一些命令来看到出错的信息来调试。(或者gdb)

这里我就不在赘述,感兴趣的可以了解一下。

总结:

今天我们详细讲解的信号产生的五种方式,希望对大家有所帮助!!

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

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

相关文章

pdf拆分

文章目录 背景目标实现下载 背景 好不容易下载的1000页行业报告&#xff0c;领导非要按章节拆分成20份&#xff01;学术论文合集需要按作者拆分投稿&#xff0c;手动分页到怀疑人生…客户发来加密合同&#xff0c;要求每5页生成独立文档&#xff0c;格式还不能乱&#xff01; …

vue3使用mermaid生成图表,并可编辑

效果图实际代码<template><div class"mermaid-container" style"z-index: 99999" ref"wrapperRef"><!-- 控制栏 --><div class"control-bar"><div class"control-bar-flex control-bar-tab-wrap"…

tcp/quic 的滑动窗口

一、滑动窗口 rwnd&#xff1a; 接收端窗口&#xff0c;接收方在每次发送ACK确认报文时&#xff0c;会包含一个 rwnd (Receive Window Size) 字段&#xff0c;指明自己当前剩余的接收缓冲区大小&#xff08;即可用窗口&#xff09;&#xff0c;这里是否是socket的接收缓冲区&am…

JVM监控及诊断工具-命令行篇

18.1. 概述 性能诊断是软件工程师在日常工作中需要经常面对和解决的问题&#xff0c;在用户体验至上的今天&#xff0c;解决好应用的性能问题能带来非常大的收益。 Java 作为最流行的编程语言之一&#xff0c;其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能…

Jenkins 版本升级与插件问题深度复盘:从 2.443 到 2.504.3 及功能恢复全解析

前言&#xff1a;问题溯源与升级必要性 在 Jenkins 持续集成体系中&#xff0c;插件生态是其强大功能的核心驱动力。然而&#xff0c;某次例行维护中&#xff0c;团队对 Jenkins 2.443 环境的插件进行批量升级后&#xff0c;意外触发连锁反应 &#xff1a; SSH Server 插件功能…

Ribbon实战

一、前置知识 1.1 负载均衡定义 负载均衡指的是将网络请求通过不同的算法分配到不同的服务器上的技术&#xff0c;从而提升系统的性能。 1.2 负载均衡工具 负载均衡工具可以分分为客户端负载均衡工具和服务端负载均衡工具&#xff0c;它们的区别如下。 表1-1 负载均衡工具…

cs285学习笔记(一):课程总览

根据 Fall 2023 学期的官方课程日程&#xff0c;这里是 CS 285 全课程的 Lecture 大纲及内容摘要&#xff0c;详细对应周次和主题&#xff0c;方便你快速定位每节课要点、相关作业与视频资源 &#x1f3af; 官方课程地址 YouTobe 视频地址 blibli视频(带中文字幕) &#x…

OkHttp SSE 完整总结(最终版)

1. SSE 基础概念 什么是 SSE&#xff1f; SSE&#xff08;Server-Sent Events&#xff09;是一种 Web 标准&#xff0c;允许服务器向客户端推送实时数据。 核心特点 单向通信&#xff1a;服务器 → 客户端 基于 HTTP 协议&#xff1a;使用 GET 请求 长连接&#xff1a;连…

聚宽sql数据库传递

自建数据库从聚宽到Q-MT自动化交易实战 从接触聚宽以来一直都是手动跟单&#xff0c;在网上看到许多大佬的自动交易文章&#xff0c;心里也不禁十分痒痒。百说不如一练&#xff0c;千讲不如实干。经过一番努力&#xff0c;终于成功实盘了&#xff0c;效果还可以&#xff0c;几…

es里为什么node和shard不是一对一的关系

提问&#xff1a; 既然多个shard会被分配到同一个node上&#xff0c;那么为什么不把多个shard合并成一个然后存在当前node上呢&#xff0c;简而言之也就是让node和shard形成一对一的关系呢 &#xff1f;非常好的问题&#xff0c;这正是理解Elasticsearch分片&#xff08;shard…

浅谈npm,cnpm,pnpm,npx,nvm,yarn之间的区别

首先做一个基本的分类 名称描述npm,cnpm,yarn,pnpm都是Javascript包管理器nvm是Node.js版本控制器npx命令行工具 I.npm,cnpm,yarn,pnpm npm (Node Package Manager) npm是Node.js默认的包管理器&#xff0c;随Node.js的安装会一起安装。使用npm可以安装&#xff0c;发布&…

滑动窗口-76.最小覆盖子串-力扣(LeetCode)

一、题目解析1.不符合要求则返回空串("")2.子串中重复字符的数量要不少于t中该字符的数量二、算法原理解法1&#xff1a;暴力枚举哈希表这里的暴力枚举也可以优化&#xff0c;即在包含t中元素处枚举&#xff0c;如在A、B和C处开始枚举&#xff0c;减少不必要的枚举 解…

从零构建搜索引擎 build demo search engine from scratch

从零构建搜索引擎 build demo search engine from scratch 我们每天都会使用搜索引擎&#xff1a;打开google等搜索引擎&#xff0c;输入关键词&#xff0c;检索出结果&#xff0c;这是一次搜索&#xff1b;当打开历史记录旁边的&#x1f50d;按钮&#xff0c;输入关键词&#…

pytorch小记(二十九):深入解析 PyTorch 中的 `torch.clip`(及其别名 `torch.clamp`)

pytorch小记&#xff08;二十九&#xff09;&#xff1a;深入解析 PyTorch 中的 torch.clip&#xff08;及其别名 torch.clamp&#xff09;深入解析 PyTorch 中的 torch.clip&#xff08;及其别名 torch.clamp&#xff09;一、函数签名二、简单示例三、广播支持四、与 Autograd…

快速分页wpf

/*没有在xaml设置上下文window.context是因为 命名空间一直对应不上 所以在xaml.cs 里面绑定*/ <Window x:Class"DataGrid.views.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft…

如何彻底禁用 Chrome 自动更新

如何彻底禁用 Chrome 自动更新 随着谷歌将 Chrome 浏览器版本升级至 138&#xff0c;它即将彻底抛弃对 Manifest V2 扩展的支持。许多用户希望将浏览器版本锁定在 138&#xff0c;以继续使用 uBlock Origin、Tampermonkey 等常用扩展。 本文总结了四种有效方法&#xff0c;帮助…

流批一体的“奥卡姆剃刀”:Apache Cloudberry 增量物化视图应用解析

引言&#xff1a;流批一体&#xff0c;理想与现实的鸿沟 在数据驱动的今天&#xff0c;“实时”二字仿佛拥有魔力&#xff0c;驱使着无数企业投身于流批一体架构的建设浪潮中。我们渴望实时洞察业务变化&#xff0c;实时响应用户需求。以 Apache Flink 为代表的流处理引擎&…

C# 入门教程(三):详解字段、属性、索引器及各类参数与扩展方法

文章目录一、字段、属性、索引器、常量1.字段2.属性2.1 什么是属性2.2 属性的声明2.3 属性与字段的关系3 索引器4. 常量二、传值 输出 引用 数组 具名 可选参数&#xff0c;扩展方法2.1 传值参数2.1.1 值类型 传参2.1.2 引用类型 传参2.2 引用参数2.2.1 引用参数-值类型 传参2.…

《美术教育研究》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答&#xff1a;问&#xff1a;《美术教育研究》是不是核心期刊&#xff1f;答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。问&#xff1a;《美术教育研究》级别&#xff1f;答&#xff1a;省级。主管单位&#xff1a; 安徽出版集团有限责任公司 主办…

每日算法刷题Day47:7.13:leetcode 复习完滑动窗口一章,用时2h30min

思考: 遇到子数组/子字符串可以考虑能不能用滑动窗口&#xff0c; 定长:逆向思维,答案不定 最大长度/最小长度:一般求长度 越长越合法/越短越合法/恰好:一般求数量 主要思考窗口条件成立&#xff0c; 判断条件是符合窗口条件(最小长度/越长越合法还是不符合(最大长度/越短越合法…