当前位置: 首页 > news >正文

Linux-进程信号

1.快速认识信号

1.1生活角度的信号

你在⽹上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临 时,你该怎么处理快递。也就是你能“识别快递”

当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。 那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的⾏为 并不是⼀定要⽴即执⾏,可以理解成“在合适的时候去取”。

在收到通知,再到你拿到快递期间,是有⼀个时间窗⼝的,在这段时间,你并没有拿到快递,但是 你知道有⼀个快递已经来了。本质上是你“记住了有⼀个快递要去取”

当你时间合适,顺利拿到快递之后,就要开始处理快递了。

⽽处理快递⼀般⽅式有三种:

1.执⾏默 认动作(幸福的打开快递,使⽤商品)

2.执⾏⾃定义动作(快递是零⻝,你要送给你你的⼥朋友)

3.忽略快递(快递拿上来之后,扔掉床头,继续开⼀把游戏)

快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

基本结论:

你怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。

信号产⽣之后,你知道怎么处理吗?知道。如果信号没有产⽣,你知道怎么处理信号吗? 知道。所以,信号的处理⽅法,在信号产⽣之前,已经准备好了。

处理信号,⽴即处理吗?我可能正在做优先级更⾼的事情,不会⽴即处理?什么时候?合 适的时候。

信号到来 | 信号保存 | 信号处理

怎么进⾏信号处理啊?a.默认b.忽略c.⾃定义,后续都叫做信号捕捉。

1.2技术应用的角度

1.2.1样例

1.2.2系统函数

1.3信号的概念

信号是进程之间事件异步通知的⼀种⽅式,属于软中断。

1.3.1信号处理

2.产生信号

2.1通过终端按键产生信号

Ctrl+C(SIGINT)已经验证过,这⾥不再重复

Ctrl+\(SIGQUIT)可以发送终⽌信号并⽣成core dump⽂件,⽤于事后调试(后⾯详谈)

Ctrl+Z(SIGTSTP)可以发送停⽌信号,将当前前台进程挂起到后台等。

2.2调用系统命令向进程发信号

2.3使用函数产生信号

kill 命令是调⽤ kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。

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

abort 函数使当前进程接收到信号⽽异常终⽌。

2.4由软条件产生信号

SIGPIPE 是⼀种由软件条件产⽣的信号,在“管道”中已经介绍过了。

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

调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个⽐⽅,某⼈要⼩睡⼀觉,设定闹 钟为30分钟之后响,20分钟后被⼈吵醒了,还想多睡⼀会⼉,于是重新设定闹钟为15分钟之后响,“以 前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表⽰取消以前设定的闹钟,函数 的返回值仍然是以前设定的闹钟时间还余下的秒数。

如何理解软件条件

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

2.5硬件异常产生信号

硬件异常被硬件以某种⽅式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。

例如当前 进程执⾏了除以0的指令,CPU的运算单元会产⽣异常,内核将这个异常解释为SIGFPE信号发送给进 程。再⽐如当前进程访问了⾮法内存地址,MMU会产⽣异常,内核将这个异常解释为SIGSEGV信号发送 给进程。

我们在C/C++当中除零,内存越界等异常,在系统层⾯上,是被当成信号处理的。

⼦进程退出core dump

3.保存信号

3.1信号相关概念

实际执⾏信号的处理动作称为信号递达(Delivery)

信号从产⽣到递达之间的状态,称为信号未决(Pending)。

进程可以选择阻塞(Block)某个信号。

被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才执⾏递达的动作.

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,⽽忽略是在递达之后可选的⼀种处理动 作。

3.2在内核中表示

3.3sigset_t

从上图来看,每个信号只有⼀个bit的未决标志,⾮0即1,不记录该信号产⽣了多少次,阻塞标志也是这样 表⽰的。因此,未决和阻塞标志可以⽤相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型 可以表⽰每个信号的“有效”或“⽆效”状态,在阻塞信号集中“有效”和“⽆效”的含义是该信号 是否被阻塞,⽽在未决信号集中“有效”和“⽆效”的含义是该信号是否处于未决状态。下⼀节将详 细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask), 这⾥的“屏蔽” 应该理解为阻塞⽽不是忽略。

4.捕捉信号

4.1流程

如果信号的处理动作是⽤⼾⾃定义函数,在信号递达时就调⽤这个函数,这称为捕捉信号。

 由于信号处理函数的代码是在⽤⼾空间的,处理过程⽐较复杂,举例如下:

• ⽤⼾程序注册了 SIGQUIT 信号的处理函数 sighandler 。

• 当前正在执⾏ main 函数,这时发⽣中断或异常切换到内核态。

• 在中断处理完毕后要返回⽤⼾态的 main 函数之前检查到有信号 SIGQUIT 递达。

• 内核决定返回⽤⼾态后不是恢复 main 函数的上下⽂继续执⾏,⽽是执⾏ sighandler 函 数, sighandler 和 main 函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个 独⽴的控制流程。

• sighandler 函数返回后⾃动执⾏特殊的系统调⽤ sigreturn 再次进⼊内核态。

• 如果没有新的信号要递达,这次再返回⽤⼾态就是恢复 main 函数的上下⽂继续执⾏了。

4.2signaction

当某个信号的处理函数被调⽤时,内核⾃动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时⾃ 动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么它会被阻塞到 当前处理结束为⽌。

如果在调⽤信号处理函数时,除了当前信号被⾃动屏蔽之外,还希望⾃动屏蔽另外⼀ 些信号,则⽤sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。sa_flags字段包含⼀些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函 数,本章不详细解释这两个字段,有兴趣的同学可以在了解⼀下。

4.3操作系统的运行

4.3.1硬件中断

• 中断向量表就是操作系统的⼀部分,启动就加载到内存中了

• 通过外部硬件中断,操作系统就不需要对外设进⾏任何周期性的检测或者轮询

• 由外部设备触发的,中断系统运⾏流程,叫做硬件中断

4.3.2时钟中断

问题:

• 进程可以在操作系统的指挥下,被调度,被执⾏,那么操作系统⾃⼰被谁指挥,被谁推动执⾏呢?

• 外部设备可以触发硬件中断,但是这个是需要⽤⼾或者设备⾃⼰触发,有没有⾃⼰可以定期触发的 设备?

• 这样,操作系统不就在硬件的推动下,⾃动调度了么!!!

4.3.3死循环

如果是这样,操作系统不就可以躺平了吗?对,操作系统⾃⼰不做任何事情,需要什么功能,就向中 断向量表⾥⾯添加⽅法即可.操作系统的本质:就是⼀个死循环!

这样,操作系统,就可以在硬件时钟的推动下,⾃动调度了.

4.3.4软中断

• 上述外部硬件中断,需要硬件设备触发。

• 有没有可能,因为软件原因,也触发上⾯的逻辑?有!

• 为了让操作系统⽀持进⾏系统调⽤,CPU也设计了对应的汇编指令(int或者syscall),可以让CPU内 部触发中断逻辑。

问题:

• ⽤⼾层怎么把系统调⽤号给操作系统?-寄存器(⽐如EAX)

 • 操作系统怎么把返回值给⽤⼾?-寄存器或者⽤⼾传⼊的缓冲区地址

• 系统调⽤的过程,其实就是先int 0x80、syscall陷⼊内核,本质就是触发软中断,CPU就会⾃动执 ⾏系统调⽤的处理⽅法,⽽这个⽅法会根据系统调⽤号,⾃动查表,执⾏对应的⽅法

• 系统调⽤号的本质:数组下标!

• 可是为什么我们⽤的系统调⽤,从来没有⻅过什么 int 0x80 或者 syscall 呢?都是直接调⽤ 上层的函数的啊?

• 那是因为Linux的gnuC标准库,给我们把⼏乎所有的系统调⽤全部封装了。

缺⻚中断?内存碎⽚处理?除零野指针错误?这些问题,全部都会被转换成为CPU内部的软中断, 然后⾛中断处理例程,完成所有处理。有的是进⾏申请内存,填充⻚表,进⾏映射的。有的是⽤来 处理内存碎⽚的,有的是⽤来给⽬标进⾏发送信号,杀掉进程等等。

• 操作系统就是躺在中断处理例程上的代码块!

• CPU内部的软中断,⽐如int 0x80或者syscall,我们叫做陷阱

• CPU内部的软中断,⽐如除零/野指针等,我们叫做异常。

(所以,能理解“缺⻚异常” 为什么这么叫了吗?)

4.4如何理解内核态和用户态

结论:

• 操作系统⽆论怎么切换进程,都能找到同⼀个操作系统!换句话说操作系统系统调⽤⽅法的执⾏, 是在进程的地址空间中执⾏的!

• 关于特权级别,涉及到段,段描述符,段选择⼦,DPL,CPL,RPL等概念,⽽现在芯⽚为了保证 兼容性,已经⾮常复杂了,进⽽导致OS也必须得照顾它的复杂性,这块我们不做深究了。

• ⽤⼾态就是执⾏⽤⼾[0,3]GB时所处的状态

• 内核态就是执⾏内核[3,4]GB时所处的状态

• 区分就是按照CPU内的CPL决定,CPL的全称是CurrentPrivilegeLevel,即当前特权级别。

• ⼀般执⾏ int 0x80 或者 syscall 软中断,CPL会在校验之后⾃动变更(怎么校验看学⽣反映)

5.可重入函数

• main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的 时候,因为硬件中断使进程切换到内核,再次回⽤⼾态之前检查到有信号待处理,于是切换到 sighandler函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的两步都做完之后从sighandler返回内核态,再次回到⽤⼾态就从main函数调⽤的insert函数中继续往下执⾏,先前做第⼀步之后被打断,现在继续做完第⼆步。结果是,main函数和sighandler先后向 链表中插⼊两个节点,⽽最后只有⼀个节点真正插⼊链表中了。

• 像上例这样,insert函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函 数,这称为重⼊,insert函数访问⼀个全局链表,有可能因为重⼊⽽造成错乱,像这样的函数称为不可 重⼊函数,反之,如果⼀个函数只访问⾃⼰的局部变量或参数,则称为可重⼊(Reentrant)函数。想⼀ 下,为什么两个不同的控制流程调⽤同⼀个函数,访问它的同⼀个局部变量或参数就不会造成错乱?

如果⼀个函数符合以下条件之⼀则是不可重⼊的:

• 调⽤了malloc或free,因为malloc也是⽤全局链表来管理堆的。

• 调⽤了标准I/O库函数。标准I/O库的很多实现都以不可重⼊的⽅式使⽤全局数据结构。

volatile作⽤:保持内存的可⻅性,告知编译器,被该关键字修饰的变量,不允许被优化,对该 变量的任何操作,都必须在真实的内存中进⾏操作

6.SIGCHLD信号

进程⼀章讲过⽤wait和waitpid函数清理僵⼫进程,⽗进程可以阻塞等待⼦进程结束,也可以⾮阻塞地查 询是否有⼦进程结束等待清理(也就是轮询的⽅式)。采⽤第⼀种⽅式,⽗进程阻塞了就不能处理⾃⼰的 ⼯作了;采⽤第⼆种⽅式,⽗进程在处理⾃⼰的⼯作的同时还要记得时不时地轮询⼀下,程序实现复杂。

其实,⼦进程在终⽌时会给⽗进程发SIGCHLD信号,该信号的默认处理动作是忽略,⽗进程可以⾃定义 SIGCHLD信号的处理函数,这样⽗进程只需专⼼处理⾃⼰的⼯作,不必关⼼⼦进程了,⼦进程终⽌时会通 知⽗进程,⽗进程在信号处理函数中调⽤wait清理⼦进程即可。

请编写⼀个程序完成以下功能:⽗进程fork出⼦进程,⼦进程调⽤exit(2)终⽌,⽗进程⾃定义SIGCHLD信 号的处理函数,在其中调⽤wait获得⼦进程的退出状态并打印。

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

7.用户态和内核态

CPU中有⼀个标志字段,标志着线程的运 ⾏状态,⽤⼾态为3,内核态为0。

低权限的资源范围较⼩,⾼权限的资源范围更⼤,所以⽤⼾态与内核态的概念就是CPU指令集权限的 区别。

• ⽤⼾态:只能操作0-3G 范围的低位虚拟空间地址

• 内核态: 0-4G 范围的虚拟空间地址都可以操作,尤其是对3-4G 范围的⾼位虚拟空间地址必 须由内核态去操作

 ◦ 3G-4G 部分⼤家是共享的(指所有进程的内核态逻辑地址是共享同⼀块内存地址),是内 核态的地址空间,这⾥存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。

◦ 在内核运⾏的过程中,会涉及内核栈的分配,内核的进程管理的代码会将内核栈创建在内核 空间中,当然相应的⻚表也会被创建。

⽤⼾态与内核态的切换

什么情况会导致⽤⼾态到内核态切换??

切换时CPU需要做什么??

切换流程

从上述流程可以看出⽤⼾态切换到内核态的时候,会牵扯到⽤⼾态现场信息的保存以及恢复,还要进 ⾏⼀系列的安全检查,还是⽐较耗费资源的。

相关文章:

  • 存储系统03——数据缓冲evBuffer
  • ebpf程序入门编写
  • frida 配置
  • OCframework编译Swift
  • 【C++]string模拟实现
  • C++编程this指针练习
  • 【科研项目】大三保研人科研经历提升
  • Python元组全面解析:从入门到精通
  • 【基础】Windows开发设置入门8:Windows 子系统 (WSL)操作入门
  • 深入解析Java四大引用类型:从强引用到虚引用的内存管理艺术
  • 软件设计师E-R模型考点分析——求三连
  • STM32实战指南:DHT11温湿度传感器驱动开发与避坑指南
  • 关于ECMAScript的相关知识点!
  • 认识常规贴片电阻
  • 数学实验(方程和微分方程求解)
  • 11.4/Q1,GBD数据库最新文章解读
  • 第二十一次博客打卡
  • Prompt、Agent、MCP关系
  • Mergekit——高频合并算法 TIES解析
  • 嵌入式(C语言篇)Day10
  • 中国旅马大熊猫“福娃”和“凤仪”启程回国
  • 广西北流出现强降雨,1人被洪水冲走已无生命体征
  • 广西鹿寨一水文站“倒刺扶手”存安全隐患,官方通报处理情况
  • 竞彩湃|欧联杯决赛前,曼联、热刺继续划水?
  • 马上评|训斥打骂女儿致死,无暴力应是“管教”底线
  • 在本轮印巴冲突的舆论场上也胜印度一筹,巴基斯坦靠什么?