Linux中的异常和中断处理以及系统调用

Linux中的异常和中断处理

  1. Linux利用陷阱门来处理异常,利用中断门来处理中断。
  2. 异常和中断对应处理程序都属于内核代码段,所以,所有中断门和陷阱门的段选择符(0x60)都指向 GDT 中的“内核代码段”描述符。
  3. 通过中断门进入到一个中断服务程序时,CPU 会清除 EFLAGS 寄存器中的 IF 标志,即关中断;通过陷阱门进入一个异常处理程序时,CPU 不会修改 IF 标志。也就是说,外部中断不支持嵌套处理,而内部异常则支持嵌套处理
  4. 任务门描述符中不包含偏移地址,只包含 TSS 段选择符,这个段选择符指向 GDT 中的一个 TSS 段描述符,CPU 根据 TSS 段中的相关信息装载SS 和 ESP 等寄存器,从而执行相应的异常处理程序。
  5. Linux中,将类型号为8的双重故障(#DF)用任务门实现,而且是唯一通过任务门实现的异常。
  6. 双重故障 TSS 段描述符在 GDT 中位于索引值为 0x1f 的表项处,即13位索引为0 0000 0001 1111,且其TI=0(指向 GDT),RPL=00(内核级代码),即任务门描述符中的段选择符为00F8H。

Linux中的异常和中断处理以及系统调用Linux中的异常和中断处理以及系统调用

Linux中对异常的处理

  1. 异常处理程序发送相应的信号给发生异常的当前进程,或者进行故障恢复,然后返回到断点处执行。例如,若执行了非法操作,CPU就产生6号异常(#UD),在对应的异常处理程序中,向当前进程发送一个SIGILL信号,以通知当前进程中止运行。
  2. 采用向发生异常的进程发送信号的机制实现异常处理,可尽快完成在内核态的异常处理过程,因为异常处理过程越长,嵌套执行异常的可能性越大,而异常嵌套执行会付出较大的代价。
  3. 并不是所有异常处理都只是发送一个信号到发生异常的进程。例如,对于14号页故障异常(#PF),需要判断是否访问越级、越权或越界等,若发生了这些无法恢复的故障,则页故障处理程序发送SIGSEGV信号给发生页故障异常的进程;若只是缺页,则页故障处理程序负责把所缺失页面从磁盘装入主存,然后返回到发生缺页故障的指令继续执行。
所有异常处理程序的结构是一致的,都可划分成以下三个部分:
  1. 准备阶段:在内核栈保存通用寄存器内容(称为现场信息),这部分大多用汇编语言程序实现。
  2. 处理阶段:采用C函数进行具体处理。函数名由do_前缀和处理程序名组成,如 do_overflow 为溢出处理函数。大部分函数的处理方式:保存硬件出错码(如果有的话)和异常类型号,然后,向当前进程发送一个信号。当前进程接受到信号后,若有对应信号处理程序,则转信号处理程序执行;若没有,则调用内核abort例程执行,以终止当前进程。
  3. 恢复阶段:恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的断点处继续执行。
    Linux中的异常和中断处理以及系统调用
    Linux中的异常和中断处理以及系统调用
    Linux中的异常和中断处理以及系统调用Linux中的异常和中断处理以及系统调用

Linux中对中断的处理

  1. PIC需对所有外设来的 IRQ请求按优先级排队若至少有一个IRQ线有请求且未被屏蔽,则 PIC向 CPU的 INTR引脚发中断请求
  2. CPU每执行完一条指令都会查询 INTR,若发现有中断请求,则进入中断响应过程(关中断、保护断点和现场、发中断查询信号),调出中断服务程序执行。
  3. 所有中断服务程序的结构类似,都划分为以下三个阶段。
    1. 准备阶段:在内核栈中保存各通用寄存器的内容(称为现场信息)以及所请求 IRQi 的值等,并给PIC回送应答信息,允许其发送新的中断请求信号。
    2. 处理阶段:执行 IRQi 对应的中断服务例程 ISR (Interrupt Server Routine)。中断类型号为32+i
    3. 恢复阶段:恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的逻辑控制流的断点处继续执行。

IA-32/Linux的系统调用

  1. 系统调用(陷阱)是特殊异常事件,是OS为用户程序提供服务的手段
  2. Linux提供了几百种系统调用,主要分为以下几类:进程控制,文件操作,文件系统操作,系统控制,内存管理,网络管理,用户管理,进程通信等
  3. 系统调用号是系统调用跳转表索引值,跳转表给出系统调用服务例程首地址

Linux系统中printf()函数的执行过程
Linux中的异常和中断处理以及系统调用

软中断指令int $0x80的执行过程,它是陷阱类(编程异常)事件,因此它与异常响应过程一样。

  1. 将IDTi(i=128)中段选择符(0x60)所指GDT中的内核代码段描述符取出, 其DPL=0,此时CPL=3(因为int $0x80指令在用户进程中执行),因而CPL>DPL且IDTi 的 DPL=CPL,故未发生13号异常。
  2. 读 TR 寄存器,以访问TSS,从TSS中将内核栈的段寄存器内容和栈指针装入SS和ESP;
  3. 依次将执行完指令int $0x80时的SS、ESP、EFLAGS、CS、EIP的内容(即断点和程序状态)保存到内核栈中,即当前SS∶ESP所指之处
  4. 将IDTi(i=128)中段选择符(0x60)装入CS,偏移地址装入EIP。这里,CS:EIP即是系统调用处理程序system_call(所有系统调用的入口程序)第一条指令的逻辑地址

执行int $0x80需一连串的一致性和安全性检查,因而速度较慢。从Pentium II开始,Intel引入了指令sysenter和sysexit,分别用于从用户态到内核态、从用户态到内核态的快速切换。

以上总结,主要参考袁春风老师计算机系统系列课程。