1、信号其他相关常见概念
1、实际执行信号的处理动作称为信递达。(Delivery)
2、信号从产生到递达之间的状态,称为信号未决(Pending)
3、进程可以选择阻塞(Block)某个信号。
4、被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
5、阻塞和忽略是不同的,只要信号被阻塞就不会被递达,而忽略是在递达之后可选的一种处理动作。(忽略是处理信号的方式,属于开始处理信号了。阻塞不是信号处理的方式,属于不让信号抵达的做法之一。)
2、信号在内核中的表示

问题:signal(2,myhandler)底层会做什么?
内核找到handler表,找到下表为2的位置,把原来的值改为你传进去的myhandler地址,当被处理时,就会调用你写的myhandler。
在当前进程的 task_struct 里,把 handler 表中第 2 号信号的处理函数,改成你自己写的 myhandler。
内核里有block,pending、handler表。
3、sigset_t
os需要让用户控制信号,本质上就是访问和操作上面的三张表。-->内核数据结构,需要提供系统调用。(获取或者设置当前进程的pending表或者block)
sigset_t 是 Linux/Unix 里的「信号集」类型(被叫做信号集),本质是一个位图,用来记录一组信号的 "有 / 无" 状态,主要用于信号阻塞、未决信号管理。也就是说,可以表示每一个信号的"有效"或"无效"状态,在阻塞信号集"有效和无效"用来表示该信号是否被阻塞,而在未决信号中,有效和无效是该信号是否处于未决状态。
阻塞信号集也被叫做当前进程的信号屏蔽字 ,这里的"屏蔽"应该理解为阻塞 而不是忽略。
4、信号集操作函数
(1)sigprocmask

第一个参数:how(做什么操作?)
决定怎么修改信号屏蔽字,只有 3 种选择:
- SIG_BLOCK :把 set 里的信号 加到 阻塞列表(追加屏蔽)
- SIG_UNBLOCK :把 set 里的信号 从 阻塞列表 移除(解除屏蔽)
- SIG_SETMASK :直接替换 整个阻塞列表(覆盖)
第二个参数:set(新的信号集合)
- 类型:
const sigset_t * - 作用:你要操作的那一组信号
- 如果你不想改信号,可以传 NULL
第三个参数:oldset(保存旧的屏蔽字)
- 类型:
sigset_t * - 作用:把修改前的信号屏蔽字保存下来
- 方便你之后恢复原来的信号状态
- 不需要保存就传 NULL
(2)sigpending

sigpending = 获取当前进程的「未决信号集」
- 把内核里的 pending 信号集,拷贝到你定义的 sigset_t 变量里
- 它只读 ,不修改任何东西
修改操作时通过操作系统内核修改。
一旦我们解除对某个信号的阻塞,该信号,就会被立即递达。
5、信号处理(捕捉信号)
(1)信号什么时候处理?怎么处理?
答:当进程调度的时候,从内核态返回到用户态的时候,会进行(用do_signal()进行检测)信号的检测和处理!


(2)用户态、内核态
答:用户态:进程执行代码,访问数据,都在访问[0,3GB]地址空间的时候,就是访问用户自己的代码,自己的数据。
内核态:都在访问[3GB,4GB]地址空间的时候,就是访问OS的过程。
内核态的权限级别更高,


(3)os是如何工作的?系统调用如何工作?异常了os怎么知道?中断,时钟中断,软中断....
答:ELF---->加载--->进程--->scanf阻塞住,等待用户输入(或者进程阻塞)。
进程怎么知道键盘被摁下了?操作系统怎么知道键盘被摁下了?
(1)OS自己主动轮询间的(效率低下)。(2)中断
cpu本身支持中断,
IDT 是 x86/x86-64 架构 CPU 里,由操作系统内核维护的一张内存表 ,作用是:当 CPU 收到中断或异常信号时,根据中断号,从这张表里找到对应的处理程序入口地址,然后跳过去执行。它是连接 "中断号" 和 "中断服务程序" 的桥梁。Interrupt Descriptor Table,中断描述符表

中断向量表--->函数指针数组
-
外设就绪,发起中断请求当外部设备(如键盘、网卡、磁盘)完成任务(如键盘按键、网卡收到数据)后,会向中断控制器发送中断信号,这是中断流程的起点。
-
中断控制器转发并传递中断号中断控制器会对多个外设的中断请求进行仲裁(优先级判断),然后向 CPU 发送中断信号,并附带该设备对应的中断号(唯一标识)。
-
CPU 响应中断,保护现场CPU 在当前指令执行完毕后,响应中断:自动将当前寄存器(程序计数器 PC、通用寄存器等)的值压入栈中保存,确保中断结束后能恢复到原来的执行状态,这一步叫保护现场。
-
查询中断向量表,跳转中断服务程序CPU 根据中断号,查询内存中的中断向量表(IDT),找到对应中断的服务程序入口地址,跳转到操作系统内核中对应的中断处理程序执行。比如键盘中断会执行 "读取按键数据" 的处理程序,网卡中断会执行 "读取数据包" 的处理程序。
-
执行中断服务程序,处理完毕后恢复现场内核执行对应的中断处理逻辑,处理完成后,CPU 从栈中弹出之前保存的寄存器值,恢复到中断前的状态,这一步叫恢复现场。
-
中断返回,继续执行原程序CPU 回到被打断的程序,继续执行之前的指令,整个中断流程结束。
注意:
暂停CPU正在执行的任务,处理硬件突发事件,结合中断号和中断向量表(中断向量表由软件完成)。--->硬件中断
计算机世界,现有硬件中断,由硬件完成处理,后来,发现了,进程也需要类似的机制,发明信号机制。
信号机制是用纯软件的方式,模拟中断完成特定的任务处理。
信号 、硬件中断原理类似,但是本质完全不同。
中断向量表就是操作系统的一部分,启动就加载到内存中了。
通过外部硬件中断,操作系统就不需要对外设进行任何周期性的检测或者轮询

外部的、以固定频率给CPU触发硬件中断的叫外部晶振。
时钟源+时钟中断=硬件中断!==>让os运行的。
什么是时间片?
答: 时间片本质是一个计数器,
为什么OS能计算时间?
答:系统启动时,会初始化一个全局变量,long long ticks =0;累计的滴答次数,每发生一次中断
ticks就会加1,已知中断周期(比如10ms),就能通过ticks*周期换算出系统运行的时间。
初始时间的获取,开机时,(BIOS/UEFI阶段),系统会读取RTC实时时钟(主板上的电池供电时钟),获取当前的真实时间,作为时间起点。之后就完全依靠ticks的累计来计算当前时间,不需要频繁读取RTC。
中断处理和进程运行,它两是串行运行关系,每次中断发生时,CPU会立刻停下当前进程,进入中断处理,
保存当前进程上下文 (寄存器、程序计数器、程序状态字),切换到内核态 ,执行中断服务程序(中断处理),中断处理结束 → 恢复被打断进程的上下文,回到原进程,从打断的位置继续执行。
时间片被耗尽,表示的是当前PCB的Counter计数器就变为0.
do_timer 是时钟中断的核心处理函数 系统每一次时钟中断 (硬件定时器触发),内核都会调用 do_timer。
晶振震荡
外部设备可以触发硬件中断,但是这个是需要用户或者设备自己触发,有没有自己可以定期触发的
设备?
1、晶振是什么?
晶体振荡器 ,主板 / 单片机 / CPU 上一个小硬件元件,里面石英晶体,通电就会规律、不停震荡。
2、晶振震荡 = 产生固定频率脉冲
石英物理特性:震荡频率绝对稳定、固定不变
每秒震荡几万 / 几百万 / 上亿次
震荡一下,输出一个电脉冲信号
3. 核心作用(两条)
-
给 CPU 定节拍 CPU 每识别一个晶振脉冲,执行一条 / 一段指令👉 CPU 主频,本质就是晶振震荡频率
-
生成系统时钟 → 产生时钟中断 电脑里专门有一个实时时钟晶振 / 定时晶振 固定频率震荡 → 定时电路计数 →每攒够固定次数 → 发一次时钟中断
芯片拿晶振的震荡脉冲来计数 攒够一定数量脉冲 → 才发一次时钟中断
在cpu内部,触发时钟中断的硬件单元是由两部分组成的,一部分叫做时钟发生器,但是在cpu内部不能以它的频率生成中断,若以它的频率生成中断太快了,一部分是时钟计数器,eg.3.2G次,(每触发一次,计数器就会减减),当计数器减到0时,才会触发一次时钟中断。

OS本身是什么?---是一个死循环。
软中断
由软件主动触发的中断,叫软中断。它不是硬件设备(比如键盘、硬盘)发出的信号,而是 CPU 执行指令时,由指令本身主动发起的中断请求。

一、软中断(以 int 0x80 为例)之前,内核要先做好这些准备(开机时一次性完成)
-
搭好 "中断地图" 内核先建一张叫 IDT(中断描述符表) 的表,告诉 CPU:"每个中断号,对应哪个内核函数。"
-
给系统调用开个 "后门" 用
set_system_gate(0x80, &system_call);这行代码,在表里给0x80号中断设置一个入口:- 用户程序一喊
int 0x80,CPU 就直接跳去内核的system_call函数。
- 用户程序一喊
-
准备好 "系统调用菜单" 内核建一张
sys_call_table表,把所有系统调用(比如read、write、exit)的地址按号排好,这样system_call收到请求,就能立刻找到对应的处理函数。
二、用户程序触发软中断前,要先做这些准备
-
选好要执行的 "菜" 把系统调用号放进
eax寄存器。- 比如
eax=1代表退出程序,eax=3代表读文件。
- 比如
-
备好参数 把要传给系统调用的数据,放进约定好的寄存器(比如
ebx、ecx、edx)。- 比如调用
write(fd, buf, len),就把文件号、数据地址、长度分别放进这几个寄存器。
- 比如调用
-
执行软中断指令 最后执行
int 0x80,触发中断,CPU 就会自动切换到内核态,处理你的请求。
一句话总结
内核先把路修好,用户程序按规则备好参数,再喊一声 int 0x80,就能通过软中断进内核干活了。
问题:为什么我们用的系统调用,从来没有见过int 0x80、syscall呢?
答:因为Linux的gun标准库,给我们把所有的系统调用全部封装了。
缺页中断,内存碎片处理?除零野指针错误?
硬件中断----eg.时钟中断、外设中断
软中断--系统调用的实现
异常中断处理
缺页中断
(你要用的数据 / 代码,现在在硬盘虚拟内存里,没加载进内存。)
程序要访问的内存,不在物理内存里,CPU 立马触发缺页中断。
内存碎片
(内存剩下很多空闲小块,但都太小,拼不起来,用不了。)
分两种:
- 外部碎片:空闲内存零零散散,分散各处,单个都很小,放不下大程序。
- 内部碎片:给程序分配的内存比它实际要用的多,多出来的浪费了。处理办法:内存紧缩、分页管理、伙伴系统、slab 分配 整理碎片。
除零中断
代码里写了 数 / 0,CPU 不允许,直接触发异常中断。数学上非法,硬件直接报错,操作系统捕获这个异常,直接杀掉这个进程。
操作系统是一个软件块--
操作系统是一个基于中断处理的软件集合。

CPU内部的软中断,比如除零/野指针等,我们叫做 异常。(所以,能理解"缺页异
常"为什么这么叫了吗?)