Linux中断原理

引入

什么是中断? 为什么需要中断?

中断是操作系统中一种重要的机制,可以让处理器中断当前的执行程序,转向另外一个特定的程序,并在处理完成后返回当前执行程序。

1
2
3
while(true){
dosomething();
}

如果没有中断,处理器只能完全处理完一个任务后,才能处理下一个任务,无法实现任务切换和多任务并发(我如果键盘按下一个按钮,系统可能很久后才能反应)

中断根据实现的方式可以分为硬中断和软中断,注意是os实现的方式,而不是触发的方式

  • 硬中断
    • 异常 (同步事件,执行指令过程中)
      • 故障 faults 缺页
      • 陷阱 traps INT n
      • 中止 abort
    • 中断 (异步事件,io设备 敲击键盘)
  • 软中断 执行复杂逻辑

硬中断

具体来说是如何中断的执行包含两部分:

  1. 如何让cpu知道中断了呢?并且是哪个中断?也就是如何让cpu知道中断号
  2. 中断号和中断处理函数的对应关系如何知道

告知中断

中断

在硬中断的中断中,cpu通过可编程中断控制器来管理中断

  • 控制器提前设置好了IRQ引脚和中断号的对应关系,IRQ引脚连接外部设备
  • 当外部设备触发中断时,中断控制器将对应中断号保存到端口上,并给CPU的INTR引脚一个信号(有中断来咯快来读取)。
  • CPU在执行完当前指令后发现INTR有信号,说明产生了中断,就去端口读取中断号

image-20240412172932052

异常
  • faults :在faults场景中,就更简单了,cpu当前就在执行指令,如果检测到了一些异常,直接自己给自己一个中断号就好了(异常对应的中断号是提前写死的)
  • traps :CPU在指令中给自己一个中断号 INT n , INT 0x80 执行系统调用(执行前会设置系统调用号)
1
2
3
4
5
mov eax, 3      ; 系统调用号,3是read的调用号
mov ebx, 0 ; 第一个参数,文件描述符
mov ecx, buffer ; 第二个参数,缓冲区地址
mov edx, 128 ; 第三个参数,读取的字节数
int 0x80 ; 执行系统调用 在x86-64 中等价于syscall

处理中断

在知道了中断号后,如何去响应并处理中断呢,其实就是一个中断向量表管理这个对应关系

实际上就是一个数组:中断描述符表IDT,主要就包含处理函数地址以及中断函数类型(中断门(禁止中断嵌套)、陷阱门)

1
2
3
4
5
6
7
8
9
10
// IDT条目的结构
struct idt_entry {
unsigned short offset_low;
unsigned short selector;
unsigned char zero;
unsigned char type_attr;
unsigned short offset_high;
} __attribute__((packed));

struct idt_entry idt[256];

中断描述符表在哪CPU如何知道:IDTR寄存器保存该地址

IDT谁负责写?

也就是初始化工作谁做的,操作系统! traps.c

1
2
3
4
5
6
7
8
9
// 初始化异常处理函数
void __init trap_init(void) {
set_idt_entry(0, divide_error); // 除法错误
set_idt_entry(6, invalid_op) // 非法操作码
set_idt_entry(14, page_fault); // 页面错误

set_idt_entry(0x80, system_call, .., ..); // 系统调用

}

中断处理流程

  1. push保存当前现场:EFLAGS、CS IP等
  2. CS IP设置为IDT对应的处理函数地址,实现跳转
  3. 开始执行中断处理函数
  4. pop恢复现场

总结

  1. 如何触发中断?
    1. 外部设备INTR引脚触发
    2. CPU执行过程中触发
    3. INT n强制触发(包括系统调用)
  2. 如何找到中断处理函数:一个数组,保存函数入口,加载时初始化
  3. 找到入口后,保存状态跳转执行,执行完成后pop状态

软中断

前面的都是硬中断,当CPU发现中断时,就会立即执行中断向量表中的处理函数(硬件实现);

软中断(Linux-0.11中并没有实现),纯粹由软件实现的一种类似中断的机制,模仿硬件,在内存中某个地方存储中断标志为,然后由内核中一个守护进程不断轮询标志,存在中断就查询软中断向量数组并执行

软中断可以配和硬中断一起实现复杂需求

  • 宏观上:CPU不再执行当前程序,转向另外的程序

  • 微观上:CPU每次执行完指令,都去检测一下内存标记是否有中断的发生

  • ksoftirqd负责轮询并执行中断,在操作系统启动时创建并开始执行kernel/smpboot.c

  • 标志位为1代表触发中断,执行softirq_vec[i]中对应的函数

1
2
3
4
5
6
7
8
9
10
11
void __softirq_entry __do_softirq(void)
{
struct softirq_action *h = softirq_vec;

while ((softirq_bit = ffs(pending))) { // 找到最右边的1的位置
h += softirq_bit - 1;
h->action(h); // 执行中断处理程序
h++;
pending >>= softirq_bit;
...
}

softirq_vec在初始化时被设置,例如想要网络收发,就需要注册网络的中断处理函数 net_dev_init

1
2
3
4
5
// file: kernel/softirg
void open_softirg(int nr, void (*action)(struct softirg_action *))
{
softirq_vec[nr].action = action;
}

image-20240412164734608

结合使用

  • ⽹卡收到数据以后,以DMA的⽅式把⽹卡收到的帧写到内存⾥,再向CPU发起⼀ 个中断,以通知CPU有数据到达。
  • 当CPU收到中断请求后,会去调⽤⽹络设备驱动注册 的中断处理函数。⽹卡的中断处理函数并 不做过多⼯作(查数据包完整性),发出软中断请求,然后尽快释 放CPU资源。
  • ksoftirad内核线程检测到有软中断请求到达,调⽤poll开始轮询收包,收到后交由各级协议栈处理。对于TCP包来说,会被放到⽤户socker的接收队列中。

image-20240412164216556

总结

  • 硬中断是通过给CPU物理引脚施加电压变化实现的,而软中断是通过给内存中的⼀个变量赋予⼆进制值以标记有软中断发⽣。
  • 硬中断表:IDT,软中断表:softirq_vec
  1. Linux的中断处理机制
  2. 深入理解Linux网络
  3. Linux源码趣读