关闭

Notes on Understanding the Linux Kernel

128人阅读 评论(0) 收藏 举报
分类:

第四章 中断和异常

中断通常被定义为改变处理器指令顺序的事件。这些事件对应于CPU芯片内部和外部的硬件电路产生的电信号。
中断通常被分为同步中断和异步中断


  • 同步中断是指指令执行时由cpu控制单元产生的,之所以称为同步,是因为只有在指令执行完成后,CPU才会发出中断。
  • 异步中断指的是由其他硬件设备根据cpu时钟信号随机产生。
  • 中断是由间隔定时器和IO设备发出的,异常是由程序错误产生的。

中断信号的作用

中断信号提供了一种方式,是处理器去处理运行控制流之外的代码。当中断信号来时,CPU停止当前的指令,切换到另外一条指令。
中断处理和进程切换之间的区别:由中断或者异常处理程序执行的代码不是一个代码。更正确的说,它是一个内核控制路径,代表中断发生时正在运行的进程执行。作为一个内核控制路径,中断处理程序比一个进程要轻量级(中断的上下文少,建立或终止的时间少)。


  • 为了应答中断,把内核需要执行的活动分为两个部分:上半部分和下半部分。上半部分内核立即执行,而下半部分留着稍后处理。内核维持着一个队列,这个队列指向表示下半部分,等待被执行的所有函数,并且内核在处理到某个特定的点时,把这些函数从队列中取出去执行。
  • 因为中断可能随时到来,正在处理一个中断时,另一个中断又发生了。应该尽可能的允许这种情况的发生,这能维持更多的IO设备处于忙的状态,因此中断处理程序必须被编写成使用相应的内核控制路径能以嵌套的方式执行。当最后一个内核控制路径终止时,内核必须能恢复中断进程的执行,或者,中断信号已经导致了重新调度的动作,内核只能切换到另外的进程。

    中断和异常

  • 中断
    • 可屏蔽中断
    • 不可屏蔽中断
  • 异常
    • 处理器探测异常
    • 故障
    • 陷阱
    • 异常结束
    • 编程异常

异常和中断向量

每个中断和异常都有0-255之间的一个数来标识。即一个无符号整数的8位向量。

IRQ和中断

每个能发出中断请求的硬件设备处理器都有一个指派为IRQ interrupt request的输出线。所有的现有的IRQ线都与一个叫做中断控制器的硬件电路输入引脚相连

中断描述符

中断描述符IDT是一个系统表,它与每一个中断或者异常向量联系,每一个向量有相应的中断或者异常处理程序的入口地址。在内核允许中断发生之前,必须适当的初始化IDT。
第二章中,我们介绍了GDT和LDT,IDT的格式与两种表的格式非常相似,表中的每一项对应一个中断或异常向量,每个向量由8个字节组成。因此,最多需要256*8=2048字节来存放IDT。

中断和异常处理程序的潜逃执行

一条内核控制路径由运行在内核态的指令序列组成,这些指令处理一个中断或者异常。当进程发出一个系统调用的请求时,相应的内被控制路径的第一部分指令就是那些把寄存器的内容保存在内核堆栈的指令,而最后一部分指令就是恢复寄存器内容并让CPU返回到用户态的那些指令。

假定内核没有bug,大多数异常就只会在CPU处于用户态时发生。事实上,异常要么是由编程错误引起的,要么是由调试程序引起的。
基于两个主要的原因,Linux交错执行内核控制路径:

  • 为了提高可编程中断控制器和设备控制器的吞吐量。假定设备控制器在一条IRQ线上产生了一个信号,PIC把这个信号转换成一个INTR请求,然后PIC和设备控制器保持阻塞,一直到PIC从CPU接收到一条应答信息。而正是因为内核控制路径交错执行,内核能在处理前一个中断的同时,发送应答。
  • 为了实现一种没有优先级的中断模型。在硬件设备之间没有必要建立预定义优先级。这就简化了内核代码,提高了内核的可移植性。

中断,陷阱以及系统门

intel提供了三种中断描述符:任务门,中断门以及陷阱门描述符。任务门描述符与Linux无关,但是它的中断描述符包含了几个中断和陷阱门的描述符。Linux使用与Intel不同的分类
中断门
用户态的进程不能访问的Intel的中断门(门DPL的域为0),所有的中断处理程序都由中断门激活,并全部限制在内核态。
系统门
用户态的进程可以访问Intel的陷阱门(门DPL的域为3)。通过系统门来激活四个Linux异常处理程序,它们的向量是3,4,5,128.int3,into,bound,int 0x80四条汇编语言。
陷阱门
用户态的进程不能访问Intel的陷阱门(门的DPL域为0)。除了前一段描述的四个Linux异常处理程序,其他所有的异常处理程序都由陷阱门来激活。

异常处理

Linux利用异常来达到两个差别很大的目标:

  • 向进程通报异常情况
  • 处理请求分页

异常处理程序有一个标准的结构,由以下三部分组成:
1. 在内核堆栈中保存大多数寄存器的内容(汇编实现)
2. 用高级的C语言函数处理异常
3. 通过ret_from_exception()函数从高级语言退出。

为了利用异常,必须对IDT进行适当的初始化,使得每一个被确认的异常都有一个异常处理程序。

为异常处理程序保存寄存器的值

当异常发生时,若是控制单元没有自动的把一个硬件错误插入代码中,相应的汇编语言片段会包含一个pushl $0 指令,在栈中垫上空值。然后,把高级C函数的地址压入栈中,它的名字由异常处理程序名与do_ 前缀组成。

中断处理

大多数异常的处理是仅仅给引起异常的进程发一个Unix信号,要采取的操作因此被延迟,直到进程接受这个信号。所以,内核能很快的处理异常。

这种方式并不适用于中断,因为中断的进程是被挂起,且一个不想关的进程正在运行,常常很久之后这些中断才到达。所以,给前进程发送一个Unix信号是毫无意义的。

IRQ数据结构

IRQ描述符

为中断处理器保存寄存器的值

保存寄存器是中断处理程序做的第一件事情。如前所述,IRQn中断处理程序的名字是IRQn_interrupt,它的地址包含在中断门中,而中断门存放在IDT相应的表项中。
保存寄存器后,调用do_IRQ()函数,并跳转到ret_from_intr()地址。

下半部分

下半部分是一个低优先级的函数,通常与中断处理相关。等待内核找到一个方便的时刻来运行它。

  • 内核处理完一个系统调用
  • 内核处理完一个异常
  • 内核终止do_IRQ函数
  • 内核执行schedule()函数以选择一个新进程在CPU上运行

从中断和异常返回

  • 内核控制路径并发执行的数量:如果仅仅只有一个,那么CPU必须切换到用户态。
  • 激活的,等待被执行的下半部分:如果有一些,必须执行它们
  • 挂起进程的切换请求:如果有任何请求,内核必须执行进程调度;否则把控制权还给当前进程。
  • 挂起的信号,如果信号发送给当前进程,就必须处理它。

技术上而言,完成这些事情的内核汇编代码并不能称作一个函数,因为控制权不返回调用它的函数。它只是一个代码片段,有三个不同的入口,叫做ret_from_intr,sys_call,exception。

从中断和异常返回

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15095次
    • 积分:790
    • 等级:
    • 排名:千里之外
    • 原创:47篇
    • 转载:34篇
    • 译文:11篇
    • 评论:3条