[linux仓库]信号产生[进程信号·贰]

[linux仓库]信号产生[进程信号·贰]


🌟 各位看官好,我是!

🌍 Linux == Linux is not Unix !


🚀 今天来学习Linux的信号产生,从多种信号产生方式反推理解之前一直未解决的疑惑。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享更多人哦!

目录

信号产生

信号产生方式

键盘产生

kill命令产生

函数产生信号

kill系统调用

raise

abort 

软件条件

验证IO效率问题

理解闹钟

模拟OS行为

硬件异常

理解 /0

理解野指针

如何理解键盘产生信号?

总结


信号产生

对信号的概念进行一定的理解后,就可以从时间维度上讲解信号产生的话题

信号产生方式

键盘产生

  • Ctrl+C (SIGINT) 已经验证过,这⾥不再重复
  • Ctrl+\(SIGQUIT)可以发送终⽌信号并⽣成core dump⽂件,⽤于事后调试(后⾯详谈)
  • Ctrl+Z(SIGTSTP)可以发送停⽌信号,将当前前台进程挂起到后台等。

至于键盘是如何产生信号的话题需要到后面进行揭晓.

kill命令产生

我们之前演示过用Kill命令的9号信号可以杀死进程.这里让每一个信号都有自己的捕捉方式或者忽视该信号,验证每一个信号是否都会调用自定义函数.

void handler(int signo) { std::cout <<"我这个进程:" << getpid() << ",抓到了一个信号: " << signo << std::endl; // 我没有在这个函数里终止进程哦! } int main { for(int i = 1;i <= 31; i++) signal(i, handler); //signal(i, SIG_IGN); while (true) { std::cout<<"我是一个进程:"<<getpid()<<std::endl; sleep(1); } retrun 0; }
一 一 验证后,会发现有几个信号比较特别, 9号 和 19号 信号不可被捕捉,不可被忽略 --> 为什么这样做呢? --> 防止有人设置病毒,并且将每个信号都设置成自定义捕捉方法,导致你无法杀掉该进程.

函数产生信号

kill系统调用
kill 命令实际上底层一定调用了 kill函数。 kill 函数可以给⼀个指定的进程发送指定的信号。

int kill(pid_t pid, int sig);

pid : 目标进程

sig : 什么信号

自定义实现kill命令: 

int main(int argc, char *argv[]) { if (argc != 3) { std::cout << "Usage:" << argv[0] << "signumber pid" << std::endl; return 1; } //会把命令参数切到命令行参数表里,是一个一个的字符串,因此要stoi转成数字 int signumber = std::stoi(argv[1]); pid_t target = std::stoi(argv[2]); int n = kill(target, signumber); if (n < 0) { perror("kill"); return 2; } return 0; } 

  

raise
raise 函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号)。

int raise(int sig);
void handler(int signal) { std::cout << "收到了一个信号:" << signal << std::endl; } int main() { signal(2,handler); while(true) { sleep(1); raise(2); } return 0; }
abort 
abort 函数使当前进程接收到指定信号⽽异常终⽌。
void abort(void);

那么abort是给当前进程发送几号信号呢?给自己发送6号新号,如何证明呢?

软件条件

SIGPIPE 是⼀种由软件条件产⽣的信号,在“管道”中已经介绍过了。坏掉的管道,就是软件不满足 --> 也是OS给目标进程发送信号!

本节主要介绍 alarm 函数 和 SIGALRM 信号。

unsigned int alarm(unsigned int seconds);调用 alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发SIGALRM 信号,该信号的默认处理动作是终止当前进程。这个信号就是14号信号(默认动作为Term).

  函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。闹钟响一次,默认终止进程
alarm调用,一次只运行一个进程,设置一个闹钟,以最新的为准第二次设置闹钟的新时间,会取消上一次的闹钟,返回上一次闹钟的剩余时间!闹钟为0则为取消闹钟

验证IO效率问题
如下图所示可见printf函数这类IO操作会降低效率

理解软件条件:

在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知进程进⾏相应的处理。简而言之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。
理解闹钟

系统闹钟,其实本质是OS必须自身具有定时功能,并能让⽤⼾设置这种定时功能,才可能实现闹钟这样的技术。

如果每一个进程都有一个闹钟,有上百个进程就有上百个闹钟。那么这些闹钟是否需要被OS管理?如何管理?提出来一种对闹钟进行管理的方案 -->

先描述! 再组织!    

内核中定时器数据结构:定时器超时时间expires和处理⽅法function。

struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; struct tvec_t_base_s *base; };

操作系统管理定时器,采⽤的是时间轮的做法,但是我们为了简单理解,可以把它在组织成为"堆结构"。 

模拟OS行为

通过alarm设定闹钟,使得每1s都会向该进程发送14号新号,14号信号已经设置为自定义捕捉,去执行自定义函数,理解为硬件中断.在这个自定义函数中,可以做自己想做的事情,如进程调度、进程切换等等.就如同OS每固定一段时间都会去检测时间片是否到了,是否需要进行进程切换等等...

using func_t = std::function<void()>; std::vector<func_t> cb; void FlushDisk() { std::cout << "我是一个刷盘的操作" << std::endl; } void sched() { std::cout << "我是一个进程调度" << std::endl; } void handler(int signo) { for(auto &f : cb) { f(); } (void)signo; std::cout << "我是一个信号捕捉函数, 我被调用了" << std::endl; alarm(1); } // 写一段代码,理解OS int main() { cb.push_back(FlushDisk); cb.push_back(sched); signal(SIGALRM, handler); alarm(1); while(true) { pause(); // 等待信号到来,否则暂停 } }

硬件异常

常见的异常如 /0 错误 , 野指针 --> 会导致我们的进程崩溃了.

但是我们从未想过为什么这些异常会导致进程崩溃呢?

因为软件问题,被操作系统识别,给目标进程发送了信号然后进程处理信号,默认终止了进程!

下面写两段程序,分别是 /0 错误 以及 野指针 的异常,先是验证 /0 错误 和 野指针 是向目标进程发送几号信号,再来从深层进行理解.

 

 

产生信号都是有操作系统产生的.那么是谁让OS产生的?用户,OS内部的软件条件. 
理解 /0

首先,我们的代码和数据会加载到物理地址空间,再通过页表和虚拟地址空间建立映射关系,CPU执行代码的时候,一定是在调度某一个进程! 假设此时CPU内的一个寄存器eax存放着a为10的值,ebx存放着0,此时会有 /0 错误,而CPU里有一个寄存器EFLAGS(控制盒状态寄存器),简单理解为⼀个位图,对应着⼀些状态标记位、溢出标记位,记录CPU单次运算时所对应的状态.此时当CPU执行 /0 这一行代码时,由EFLAGS检测到是非法操作,CPU触发硬件异常,那么OS需不需要知道呢?OS是软硬件资源的管理者,因此告诉OS"我出错了,快来帮我" --> OS靠 struct task_struct *current(进程描述结构体),它永远指向 “当前正在被 CPU 调度的进程” , 知道了是这个进程的代码在搞事情,就需要对这个进程进行某种操作,即发送信号.CPU内部的寄存器本质是:当前进程的硬件上下文.一旦该进程被默认杀掉了,进程的硬件上下文就不存在了,从而使CPU的报错就没有了!

因此本质是:OS是为了恢复CPU的正常运行

那为什么当我对8号信号执行自定义捕捉时,会被OS一直调度啊?

当CPU告诉OS"我报错了",OS会向目标进程发送信号,又因为8号信号有了自定义捕捉方法,并没有清理内存,关闭进程打开的⽂件,切换进程等操作,所以CPU中还保留上下⽂数据以及寄存器内容
,当执行完这自定义方法时,此时又会恢复硬件上下文,让CPU继续执行这行数据,又检测到是非法操作,依次循环下去.看起来也是在被一直调度,但这其实是异常的情况,占用着CPU的资源!!!

 

理解野指针
int *p = nullptr; *p = 10;

OS是如何识别野指针问题,并终止进程的? 

野指针 --> 硬件报错 --> OS写信号 --> 进程终止!!! 

如何理解键盘产生信号?

 

那么键盘具体是如何让OS知道键盘外设有数据了?举ctrl + c 来说:

当我们键盘上按下 ctrl + c 时,会向CPU发送硬件中断,此时CPU通过里面内置的针脚识别发出硬件中断的是键盘(实际上就是高低电频) , 告诉CPU要键盘被按下了,此时OS会去识别是普通数据还是组合键(即将数据从外设读入内存),而OS内部内置了一套中断向量表(之后会扒出源码提供),执行键盘处理方法.

总结

本文详细介绍了Linux系统中的信号产生方式及其原理。主要内容包括:

  1. 键盘组合键产生信号(如Ctrl+C、Ctrl+\、Ctrl+Z);
  2. 通过kill命令和函数产生信号,重点分析了9号和19号信号的不可捕捉特性;
  3. 软件条件产生的信号(如alarm函数和SIGALRM信号);
  4. 硬件异常产生的信号(如除零错误和野指针访问)。

文章深入探讨了信号产生的底层机制,包括CPU寄存器状态、中断处理等硬件层面原理,并解释了操作系统如何管理这些信号。最后通过代码示例展示了信号处理的实际应用场景,如模拟操作系统调度等。

Read more

【数据结构与算法】汉诺塔问题(C++)

【数据结构与算法】汉诺塔问题(C++)

目录 一、汉诺塔问题基础定义 1. 问题描述 2. 核心规律 3. 问题分解思想(递归核心) 二、递归实现汉诺塔(简洁优雅,易理解) 1. 递归思路 2. 完整递归代码(带步骤打印 ) 三、非递归实现汉诺塔 非递归思路: 1. 非递归思路 2.代码实现: 四、写在最后 汉诺塔(Hanoi Tower)是经典的递归算法入门问题,源于古印度的数学传说,其核心是通过有限次移动,将一根柱子上的所有圆盘按 “小圆盘在上、大圆盘在下” 的规则移到另一根柱子,且移动过程中始终遵守 “一次仅移一个圆盘、大圆盘不能压小圆盘” 的规则。本文将从问题原理出发,详细讲解汉诺塔的递归实现(核心思路,简洁优雅)和非递归实现(基于栈模拟,深入理解递归本质),并提供完整可运行的

By Ne0inhk
备战蓝桥杯----C/C++组 (一)所需C++基础知识(上)

备战蓝桥杯----C/C++组 (一)所需C++基础知识(上)

个人主页: wengqidaifeng ✨永远在路上,永远向前走 个人专栏: 数据结构 C语言 嵌入式小白启动! 重要OJ算法题详解 文章目录 * 前言 * 一. 分析大纲,了解所需 * 1. 大纲显示内容 * 2、组别划分与难度关系 * 3、知识点结构分析(按组别) * 3.1 大学C组:基础入门阶段 * 3.2 大学B组:中级提高阶段 * 3.3 大学A组 / 研究生组:高级挑战阶段 * 4.难度系数说明 * 二. C++基础语法(上):从零开始的编程基石 * 1.前言 * 2.开发环境搭建 - DevC++的安装与使用 * 2.1

By Ne0inhk

初识c++;format函数;cout输出

一.程序 在c语言中输入和输出的头文件是#include <stdio.h>,而在c++中标准输入输出头文件则是#include <iostream>,它的作用和stdio类似,不过更为强大 #include <iostream> int main(){ std::cout<<"Hello World!"<<std::endl; } 1.cout   这是iostream文件里的内容 2.std::  c++中有个命名空间的概念,在这里std::cout的作用是在告诉程序我们在使用标准库里的cout 3.cout  代表标准输出流对象,作用是将数据以字符的形式打印到终端 4.<<

By Ne0inhk
用飞算JavaAI轻松完成高校宿舍管理系统

用飞算JavaAI轻松完成高校宿舍管理系统

今天我们使用飞算来完成高校宿舍管理系统。 一、需求分析与规划 1.1 功能需求与核心模块 高校宿舍管理系统主要服务于宿舍管理员、学生和学校管理部门,实现宿舍资源的数字化管理。系统核心功能包括:用户管理(登录认证、角色权限分配)、宿舍管理(楼栋房间信息、床位分配状态)、学生住宿管理(入住登记、宿舍分配调换、退宿处理)、日常管理(考勤记录、访客登记、违纪管理、卫生检查)、维修管理(故障申报、工单派发、进度跟踪)以及统计报表(入住率、费用统计、数据分析)等功能模块。 系统采用分层架构设计,包含八个核心模块:用户认证授权模块负责JWT令牌管理和权限控制;用户管理模块处理用户CRUD和角色分配;宿舍管理模块管理楼栋房间和床位状态;学生住宿模块处理入住分配和调宿业务;日常管理模块记录考勤访客和违纪信息;维修管理模块处理维修申请和工单流转;统计报表模块提供数据分析和图表展示;系统管理模块负责配置管理和日志监控。 1.2 技术选型 后端采用Spring Boot 2.

By Ne0inhk