操作系统中的信号神经 —— 中断与异常(2)
为何要有中断?
中断是操作系统中相当核心的功能,任何操作系统都包含了对于硬件设备的有效管理。处理器的素的和外围的硬件设备的速度不在一个数量级上,因此,如果让内核采用让处理器向硬件发出请求,然后专门去等回应显然是差强人意。那么内核就应该在此期间去完成其他的事情,等硬件完成了请求再回过头去处理。中断机制就让内核主动变为硬件主动。
那么此时就有两种处理方法,第一个就是轮询,内核定期的对硬件设备进行状态查询,作出处理。这样的话,也会导致内核做太多的无用功。更好的方法是另一个机制,让硬件在需要的时候向内核发出信号,这就是中断机制,对于中断的处理也同样软硬结合。
中断、终止、陷阱、故障区分
异常分为四类:中断,终止,陷阱,故障。
中断:他是有外部设备硬件发出的,是异步发生的,因为CPU也不知道这个中断什么时候发生,处理完中断之后,CPU会继续执行代码。
故障:CPU 执行当前指令内部产生,同步,执行某条指令的时候才触发。修复完故障之后,重新执行刚才出错的那条指令。例如:缺页异常。
陷阱:CPU 执行当前指令内部产生,同步。主动触发,故意触发,指令执行成功后才跳入处理。陷阱处理完毕,执行下一条指令,不会重复执行触发陷阱的那条指令。
终止:同步,严重硬件错误,无法修复。会直接杀死进程。
以上是根据英特尔CPU的具体异常分类。
内核态与用户态
内核态,用户态是对当前进程或者说是CPU的状态描述,而状态的转化也与中断有关联。同时还与虚拟地址空间有关(虚拟地址空间,页表是由虚拟到物理的桥梁)。内核态无非就是对内核地址空间的访问,用户态是对用户地址空间的访问,通过页表的权限认定后才可访问。
在CPU内部有寄存器存储了CPU的状态,例如CS寄存器。要想对于这两种状态的理解更加深入必须具体到实际场景当中。
硬件中断
当用户从键盘输入数据,网卡从网络当中读取数据的时候,CPU会从中断控制器获取相应的中断号,之后CPU由用户态转换到内核态,陷入内核当中,去中断向量表(IDT)当中查找对应的中断处理程序,一个设备的中断处理程序是它的驱动程序的一部分——设备驱动程序是用于对设备进行管理的内核代码。 CPU停止执行当前进程的代码指令,转而去执行操作系统的代码。在这之前,CPU需要对原有的进程的硬件上下文数据进行保护,即CPU自动进行压栈处理,将数据资源存入内核栈当中,即保护现场。
说到硬件中断就不得不提时钟中断,在我们电脑内部存在一个硬件——晶振。晶振可以向我们的CPU间接的,周期性的发送硬件中断。CPU就会按照以上的流程去处理当前的中断,这样也使得我们的操作系统得以周期性的执行。在这里,时钟中断和当前进程的时间片有着千丝万缕的关系。首先,CPU每次处理时钟中断,内核都会更新全局的时间,调整当前进程的时间片。如果当前进程的时间片耗尽,就会触发进程调度相关函数。所以进程的调度也是软硬件结合实现的,时间片只是一个计数器,对计数器的更改又要依靠时钟中断来配合。
在处理完中断之后,CPU会把在内核栈当中的数据恢复继续去执行进程代码,即恢复现场。
软件中断
软件中断是由CPU自己主动触发的,就比如用户在代码当中去调用系统调用函数。在C语言库当中会对系统调用做一些封装,里面会由汇编语言帮助我们CPU陷入内核态,比如:x86‑32位:int 0x80,x86‑64 现代:syscall 指令。这是我们用户自己有意图的去陷入内核当中的,之后CPU拿着系统调用号去内核当中去执行内核函数,处理完毕后也会恢复现场。
缺页异常(缺页中断)
很多教材称缺页为缺页中断,这是广义层面的叫法,代表 “打断程序执行,进入操作系统处理”;但从 x86CPU 硬件架构严格分类,它属于异常下的故障。最大特征:处理完成后,CPU 会重新执行那条触发缺页的内存访问指令。
那么在什么时候会发生缺页异常呢?
例如:我们在访问一块物理内存的时候,MMU在通过页表当中的标志位判断之后,发现无法映射此内存时,会发生缺页异常;
当我们的文件内容还未加载到物理内存又要访问的时候,会发生缺页异常。CPU从用户态陷入内核态。调用相应的中断处理函数。
缺页异常与Linux本身的懒加载(懒分配)机制,虚拟地址空间强相关。
在我们用户new或者malloc一块空间时,操作系统并不会立马就把物理内存给我们分配好,只是分配了一块虚拟地址编号范围,并没有实际的存储空间。只有等到CPU执行到此行代码的时候,真正去读取解析这一块虚拟地址的时候,CPU才会发生缺页异常,内核才去分配物理页,填充页表。此时,才有了实际的存储空间却写入数据,访问数据。
我们在与文件系统关联,在CPU发生缺页异常后,会先对该虚拟地址进行合法性判断,这也是缺页异常当中核心的步骤。首先,虚拟地址空间是由链表结构组织起来的,此时,会将该虚拟地址在所有的VMA当中去遍历查找,看该虚拟地址落在了哪一个VMA当中,如果没有,那么就是非法的操作。如果找到了,就会进入权限判断逻辑,比如:对于只读的文件进行写操作就是非法的,会通过信号的方式杀死进程等。通过这些检验之后,在vm_are_struct当中会有struct file*这个结构体指针,这是描述文件的结构体,如果该指针为NULL,则表明此地址只是需要去申请一块空间存储数据,不与文件关联,这也就是malloc的部分底层实现机制。反之,如果有明确的指向,代表与磁盘文件有关,此时,需要获取inode,去分区当中去查找,再将他的数据块以4KB为单位加载到物理内存当中,填充页表,构建映射。即可访问。
对于Swap机制的处理也是同样的,当内存资源紧张的时候,会把部分“冷”资源换出到磁盘当中,等到要访问的时候,再通过以上操作将数据再载入内存。
经过以上的操作之后,算是对缺页异常的处理结束了,CPU返回用户态,重复执行当前的代码,此时可以正常运行。从以上的解析当中,不难发现,有些缺页异常的情况是不需要I/O的,比如只是申请一块内存使用,对于他们的处理是极快的,没有磁盘的I/O操作。
总结
那么,对于中断的一些理解到这里就结束了。若有不足之处,敬请指出。感谢阅读!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)