深入理解中断

94 阅读5分钟

从故事的开始说起 —— 最开始”中断”解决了哪些问题

  • 计算机启动的过程

    • 按下电源后,主板开始给CPU通电,待电源稳定后,CPU把CS寄存器的值设置为0xFFFF,IP寄存器的值设置为0x0000;表示CPU要执行0xFFFF处的指令,这个地址指向主板的ROM中。

    • 0xFFFF指令一般为jmp指令,用以跳转到BIOS在ROM中真正的位置

    • BIOS就是一段启动代码。过程中,首先要开机自检(内存,显卡,声卡,网卡等有没有连接成功),之后去磁盘的第一个扇区(MBR master boot record 主引导记录)。将其加载到内存中,并且将系统的控制权交给MBR。

    • MBR首先搭建起文件系统来,有了文件系统后才能找到OS内核代码并负责从磁盘中加载OS内核到内存中,然后将系统控制器交给操作系统。

    以上并不是要说明的重点,但为了故事有头有尾就又记了一遍

    • 之前的系统全部处于实模式(real mode),内存访问直接根据物理地址进行;此时将实模式转化成保护模式(Protected mode 全称是虚拟地址保护模式);
    • 之后由操作系统负责加载各个程序,也就是各个进程,计算机开始全力工作。
  • 保护模式

    • 为什么要从实模式变成保护模式?

      内存中有内核和其他应用程序进程,假如仍然在实模式下,应用程序进程随意访问内核和其他程序的任意数据和指令,只需要提前知道目标数据所在的物理地址。更可怕的是可以随意修改,这就太危险了。

    • 保护模式

      不直接使用物理地址,而是使用虚拟地址,程序执行时CPU负责将虚拟地址转化成物理地址。

      • 由于每个进程内,可见的地址都是通过虚拟地址所指的(或者是在编码层面所有指针也都是虚拟地址),一个进程以为自己能访问到全部,但其实在地址转换中,对超出自身内存范围的指针是无法访问到的;以此来提供进程间安全的保证。
      • 于是可以得出结论:进程间是隔离的,进程和内核间也是隔离的。
      • 问题:
        1. 进程间隔离,那进程间通信怎么办?

        2. 进程和内核隔离,那系统调用怎么办?

          答案是:通过中断,安全的,绕路的 完成系统调用

中断

中断分为硬中断和软中断,解决上述问题的是软中断,硬中断用来解决进程切换问题

  • IDT 中断向量表

    CPU中记录有IDTR 中断描述符表寄存器,用来记录内存中中断描述符表的具体位置

    IDT类似一个map,key是0 - 255,value是所指内核程序的第一条指令的地址

    其中,0x80指向系统调用程序

  • 以一个简单代码为例说明中断的执行(软件中断)

    print("Hello World")
    

    编译器首先会编译成机器语言

    put "write()" eax
    put "hello" ebx
    int 0x80
    

    代码表示 将write放在eax寄存器上,hello放在ebx寄存器上,之后触发中断(interrupt)此时CPU从用户态切换到内核态,中断代码是IDT 0x80所指位置;而0x80就是指向System Call系统调用

    之后操作系统获取到寄存器上的系统调用方法名(或编号)和参数,从系统调用表中找到这个方法的具体位置,即可执行成功。也就是说,用户进程调用内核代码的方式是:通过系统调用表中共识的编号,完成函数调用。

    详细过程见博文:blog.csdn.net/u012503639/…

  • 硬件中断: 根据事件的发出者不同,可以将中断分为硬件中断和软件中断两种;软中断就是上文所述有CPU在处理某个特定指令时触发的吗。而硬中断则是由处理器外部的设备触发的电子信号。

    电子手表就像一个简单的计算机,中间有简单的类似CPU的累加器;手表核心有一块设备叫晶振,其作用是能够让手表的时间匀速增加;

    实现方式是:晶振的特性是:通入直流电,输出频率相同的信号,以此模拟时间匀速流动。

    而硬中断的实现方式也基于此,固定时间片后强制让CPU切换运行线程。

  • 中断的成本

    有研究测试表示:系统调用与函数调用相比(C语言),消耗的时间平均为十几倍。

    image.png

    1. 对于软中断而言,CPU由原本执行用户程序切换成执行内核程序,那势必要把原本用户进程的数据保存下来(保存现场);我觉得只需要把CPU内寄存器的数据保留即可,系统调用用不到三层缓存吧?
    2. 对于硬中断而言,CPU内寄存器和三级缓存的数据必然都要保护起来,所以成本会更高。

于是由于软中断的成本不低,系统调用又频繁的发生在任何操作中,这就导致程序的性能有所影响。 那么接下来的优化思路就是:如何减少系统调用的次数。

  • Java中我们最熟悉的优化:synchronized的锁升级过程就是为了避免频繁的加锁释放锁
  • IO中总是使用buffered...stream或者buffered...channel,是为了避免频繁的向socket中write和read
  • 操作系统IO多路复用模型中,epoll比select和poll高级的一点也在于减少了系统调用
  • redis使用 mset 代替 set 进行批量操作,也是为了避免过多文件IO
  • ......