Linux 内核学习(6) - 中断和异常处理

中断的硬件背景

  • IOAPIC:所有外设都会连接到IOAPIC引脚上面

    • 基本的配置方法
      • IOREGSEL: 用来选择配置的功能
      • IOWIN: 用来读取更改具体的配置项
    • 配置功能选项
      • IOAPICID: 唯一标识
      • IOAPICVER: 版本识别
      • IOAPICARB: 用于发送信息的仲裁,按优先级轮转自动转换
      • IOREDTBL: 0~23条中断线的配置
        • 中断向量号 bit 0~7
        • 传送中断的模式
        • 指定目标CPU
        • 禁用启用中断线
        • 几个状态位
    • 中断流程
      1. 连接IOAPIC引脚的设备触发中断
      2. 检测Delivery Status是否为0,不是0则等待,否则设其为1 (sent pending)
      3. 将中断发送至对应的CPU
      4. CPU响应中断
      5. 对于level触发的中断,设置IRR为1,并等待CPU触发EOI,再将IRR复位0
      6. Delivery Status置位0。到此时,方可继续响应此条线上的中断
  • 每个CPU都有Local APIC,接收IOAPIC的数据

    • 用来响应本地中断和发送至本CPU的中断,或者用来相应其它CPU发送的中断(IPI)
    • CPU里面有Performance Counter,专门用来提供CPU数据的
    • 几个状态标识
      • IRR: interrupt request register 中断请求
      • ISR: in-service register 正在服务的中断
      • TMR: trigger mode register 记录中断模式
    • 相应中断的流程
      1. 判断接收到的中断目的是否为本CPU,如果不是则丢弃
      2. 如果非fixed类型的中断则直接传给CPU
      3. IRR->ISR->CPU
      4. CPU触发EOI,Local APIC处理此EOI并将之传给IOAPIC,完成此次中断的处理
  • CPU对中断和异常的处理

    • CPU LINTIN0, LINTIN1:用于CPU本地设备中断,其设备直接连在CPU上,不经过IOAPIC,通常用于单CPU的8259A兼容模式(经常用于watch dog等检测CPU数据的软件)

    • SMI:system management interrupt,用于进入SMI模式

    • MSI:message signalled interrupts,往特定地址写特定格式的消息来产生中断

    • 中断/异常分类

      • 异常:在指令执行过程中发生的错误,包括单步调试,为同步事件
      • 软件中断:由INT指令产生的事件,为同步事件
      • 外部中断:由外部设备产生的事件,为异步事件
      • 同步事件在可预测的指令流中发生,异步事件不可预测
    • 异常的分类

      • Fault:可以重试触发异常的指令, 例如 page fault
      • Trap: 处理完trap后,直接执行下一条指令, 例如 INT
      • Abort: 程序不能可靠地执行下去,例如#MC
    • 屏蔽中断/异常

      • 处理NMI外的外部中断都是可屏蔽的
        • RFLAGS.IF = 0
      • 可在特权层有条件地屏蔽掉某些异常
        • #AC alignment check
        • #NM device-not-available
        • #MC machine check
    • 中断处理流程(以x86-64为例)

      1. 找到中断处理程序
      • 中断描述符表中有中断号和中断处理程序的位置
      • 通过中断门会关闭中断,而陷阱门不会
      1. 切换堆栈
      • 如果有特权级转变,新的SS被强制设为NULL
      • 如果 IDT-entry.IST!=0, 则以此为序号,到IST表中找到对应的堆栈(无条件切换堆栈)
      • 如果 IDT-entry.IST==0,且特权级发生改变,则从当前TSS表中找到新的堆栈(无条件切换堆栈)。只要在特权级发生改变时才会切换堆栈
      1. 保存环境
      • 无条件推入SS和ESP
      1. $iret

Linux Kernel 中断子系统

  • 中断/异常初始化

    • trap_init(void)

    • 几点需要注意的地方

      • x86_64全部使用的是中断门(interrupt gate)

        • 在进入到处理函数后需要快速地打开中断
      • set_intr_gate() VS set_intr_gate_ist()

        • 前者没有使用IST, 后者可以自行指定IST
      • set_intr_gate() VS set_system_intr_gate()

        • 前者将门的权限设为0, 即用户空间不能访问。后者将门的权限设为3,即所有特权层上都可以访问

        • 只有在INT指令等触发的软件中断才需要检查,由程序指令执行产生的异常则不需要做此检查,例如除0操作

        • 触发INT异常

          #include <stdio.h>
          #include <stdlib.h>
          #define INTNUM "0x0"
          
          static void intfun(void) {
              asm volatile(".byte 0xcd, "INTNUM);
          }
          
          int main(int argc, char* argv[]) {
              int i, x;
              x = 0;
              i = 3 / x;
              intfun();
              return i;
          }
          

          运行后,从dmesg中能看到 general protection 表示权限检查没有通过

  • 异常处理

    • 所有的异常处理最终都会转到do_xxx()中,例如divide_error=>do_divide_error()

      zeroentry divide_error do_divide_error
      /*
       * Exception entry points.
       */
      .macro zeroentry sym do_sym
      ENTRY(\sym)
      	INTR_FRAME
      	PARAVIRT_ADJUST_EXCEPTION_FRAME
      	pushq_cfi $-1		/* ORIG_RAX: no syscall to restart */
      	subq $15*8,%rsp
      	CFI_ADJUST_CFA_OFFSET 15*8
      	call error_entry
      	DEFAULT_FRAME 0
      	movq %rsp,%rdi		/* pt_regs pointer */
      	xorl %esi,%esi		/* no error code */
      	call \do_sym
      	jmp error_exit		/* %ebx: no swapgs flag */
      	CFI_ENDPROC
      END(\sym)
      .endm
      
    • do_xxx()的实现

      #define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr)		\
      dotraplinkage void do_##name(struct pt_regs *regs, long error_code)	\
      {									\
      	siginfo_t info;							\
      	info.si_signo = signr;						\
      	info.si_errno = 0;						\
      	info.si_code = sicode;						\
      	info.si_addr = (void __user *)siaddr;				\
      	if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr)	\
      							== NOTIFY_STOP)	\
      		return;							\
      	conditional_sti(regs);						\
      	do_trap(trapnr, signr, str, regs, error_code, &info);		\
      }
      
  • 中断初始化

    • trap_init()中可以看到,处理中断使用的向量是从0x20开始的
    • 再将其它的向量号注册为外部中断
    • 所以,对于所有外部中断而言,首先会经过interrupt[]中对应的函数
    • interrupt[]的初始化 (arch/x86/kernel/entry_64.S)
  • 中断处理

    • 向量(vector)与中断号(IRQ)的对应关系:对中断号取反再加0x80

      unsigned vector = ~regs->orig_ax;
      unsigned irq;
      irq_enter();
      exit_idle();
      irq = __this_cpu_read(vector_irq[vector]);
      
    • 最终的处理 do_irq()

      • vector_irq,由ioapic来配置,用来控制中断分发到不同的CPU。
    • 设备中断的处理

      • 中段描述符表的每一项都指向一个struct irqaction链表
      • 通常多个设备共享一个中断号
      • 发生中断后kernel会检查每个设备的状态寄存器是否产生了中断
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值