深入理解Linux内核(学习笔记)_第四章中断和异常

     中断通常被定义为一个事件,该事件改变处理器执行的指令顺序,这样的事件与CPU芯片内外硬件电路产生的电信号相对应。中断通常分为同步中断和异步中断:1)同步中断是当指令执行时由CPU控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后CPU才会发出中断;2)异步中断是由其他硬件设备依照CPU时钟信号随机产生的。

    中断是由间隔定时器和I/O设备产生的。异常是由程序错误产生的,或者是由内核必须处理的异常条件产生的。

一.中断信号的作用

     中断信号提供了一种特殊的方式,使处理器转而去运行正常控制流之外的代码,当一个中断信号达到时,cpu必须停止它当前正在做的事情,并且切换到一个新的活动。中断处理是由内核执行的最敏感的任务之一,因为它必须满足下列约束:

  • 当内核正打算去完成一些别的事情时,中断随时会到来。因此内核的目标是让中断尽可能快地处理完,尽其所能把更多的处理向后推迟。因此,内核响应中断后需要进行的操作分为两部分:关键而紧急的部分,内核立即执行;其余推迟的部分,内核随后执行。
  • 因为中断随时会到来,所以内核可能正在处理其中的一个中断时,另一个中断(不同类型)又发生了。
  • 尽管内核在处理前一个中断时可以接受一个新的中断,但在内核代码中还是存在一个临界区,在临界区中,中断必须被禁止。

二.中断和异常

    Intel文档把中断和异常分为以下几类:(1)中断:可屏蔽中断,非屏蔽中断;(2)异常:处理器探测异常(故障、陷阱、异常终止),编程异常。

  • IRQ和中断:每个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线。所有现有的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连,可编程中断控制器执行下列动作:(1)监视IRQ线,检查产生的信号;(2)如果一个引发信号出现在IRQ线上:a.把接受到的引发信号转换成对应的向量;b.把这个向量存放在中断控制器的一个I/O端口,从而允许CPU通过数据总线读此向量;c.把引发信号发送到处理器的INRT引脚,即产生一个中断;d.等待,直到CPU通过把这个中断信号写进可编程中断控制器的一个I/O端口来确认它;当这种情况发生时,清INTR线。(3)返回到第一步。
  • 高级可编程中断控制器:来着外部硬件设备的中断请求以两种方式在可用CPU之间分发:静态分发,动态分发。目前大部分单处理器系统都包含一个I/O APIC芯片,可以用以下两种方式对这种芯片进行配置:作为一种标准8259A方式的外部PIC连接到CPU,本地APIC被禁止,两条LINT0和LINT1本地IRQ线分别配置为INTR和NMI引脚;作为一种标准外部I/O APIC,本地APIC被激活,且所有的外部中断都通过I/O APIC接收。
  • 异常:80x86微处理器发布了大约20种不同的异常,内核必须为每种异常提供一个专门的异常处理程序。对于某些异常,CPU控制单元在开始执行异常处理程序前会产生一个硬件出错码,并且压入内核态堆栈。
  • 中断描述符表:中断描述符表是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址。内核在允许中断发生前,必须适当地初始化IDT。IDT包含三种类型的描述符:任务门,中断门,陷阱门。
  • 中断和异常的硬件处理:当执行了一条指令后,cs和eip这对寄存器包含下一条将要执行的指令的逻辑地址。在处理那条指令之前,控制单元会检查在运行前一条指令时是否发生了一个中断或异常。

三.中断和异常处理程序的嵌套执行

     每个中断或异常都会引起一个内核控制路径,或者说代表当前进程在内核态执行单独的指令序列。内核控制路径可以任意嵌套,一个中断处理程序可以被另一个中断处理程序“中断”,因此引起内核控制路径的嵌套执行。一个中断处理程序既可以抢占其他的中断处理程序,也可以抢占异常处理程序。相反,异常处理程序从不抢占中断处理程序。在内核态能触发的唯一异常就是刚刚描述的缺页异常。基于以下两个主要原因,Linux交错执行内核控制路径:(1)为了提高可编程中断控制器和设备控制器的吞吐量;(2)为了实现一种没有优先级的中断模型。

四.初始化中断描述符表

  •  中断门、陷阱门及系统门:(1)中断门:用户态的进程不能访问的一个Intel中断门(门的DPL字段为0);(2)系统门:用户态的进程可以访问的一个Intel陷阱门(门的DPL字段为3);(3)系统中断门:能够被用户态进程访问的中断门(门的DPL字段为3);(4)陷阱门:用户态的进程不能访问的一个Intel陷阱门(门的DPL字段为0);(5)任务门:不能被用户态进程访问的Intel任务门(门的DPL字段为0)。
  • IDT的初步初始化:当计算机还运行在实模式时,IDT被初始化并由BIOS例程使用,然而一旦Linux接管,IDT就被移到RAM的另一个区域,并进行第二次初始化。IDT存放在idt_table表中,有256个表项,6字节的itd_descr变量指定了IDT的大小和它的地址,只有当内核用lidt汇编指令初始化itdr寄存器时才用到这个变量。

五.异常处理

     CPU产生的大部分异常都是由Linux解释为出错条件。当其中一个异常发生时,内核就向引起异常的进程发送一个信号向它通知一个反常条件。异常处理程序有一个标准的结构,由以下三部分组成:(1)在内核堆栈中保存大多数寄存器的内核(这部分用汇编语言实现);(2)用高级的C函数处理异常;(3)通过ret_from_exception()函数从异常处理程序退出。为了利用异常,必须对IDT进行适当的初始化,使得每个被确认的异常都有一个异常处理程序。

  • 为异常处理程序保存寄存器的值:使用handler_name来表示一个通用的异常处理程序的名字,每一个异常处理程序都以下列的汇编指令开始:
    handler_name:
        pushl $0 /*only for some exception*/
        pushl $do_handler_name
        jmp error_code

    被调用的函数从eax和edx寄存器而不是从栈中接收参数。

  • 进入和离开异常处理程序:异常处理刚一终止,当前进程就关注这个信号。该信号要么在用户态进程自己的信号处理程序(如果存在的话)来处理,要么由内核来处理。在后面这种情况下,内核一般会杀死这个进程。异常处理程序总是检查异常是发生在用户态还是内核态,在后一种情况下,还要检查是否由系统调用的无效参数引起。

六.中断处理

      中断处理依赖于中断类型,就我们目前而言,将讨论三种主要的中断类型:I/O中断、时钟中断、处理器间中断。

  • I/O中断处理:I/O中断处理程序必须足够灵活以给多个设备同时提供服务。中断处理程序的灵活性是以两种不同的方式实现的:IRQ共享,IRQ动态分配。Linux把紧随中断要执行的操作分为三类:紧急的、非紧急的、非紧急可延迟的。中断向量:物理IRQ可以分配给32~238范围内的任何向量。IRQ数据结构:描述符、标志位。IRQ在多处理器系统的分发:Linux遵循对称多处理器模型(SMP),内核本质上对任何一个CPU都不应该有偏爱。多种类型的内核栈:异常栈、硬中断请求栈、软中断请求栈。为中断处理程序保存寄存器的值:当CPU接收一个中断时,就开始执行相应的中断处理程序代码,该代码的地址存放在IDT的相应门中。do_IRQ()函数:调用该函数执行与一个中断相关的所有中断服务例程。__do_IRQ()函数:该函数接受IRQ(通过eax寄存器)和指向pt_regs结构的指针寄存器(通过edx寄存器,用户态寄存器的值已经存在其中)作为它的参数。挽救丢失的中断:内核用来激活IRQ线的enable_irq()函数先检查是否发生了中断丢失,如果是,该函数就强迫硬件让丢失的中断再产生一次。中断服务例程:一个中断服务例程(ISR)实现一种特定设备的操作,当中断处理程序必须执行ISR时,它就调用handle_IRQ_event()函数,所有的中断服务例程都作用于相同的参数(irq号,dev_id设备标识符,regs栈寄存器)。IRQ线的动态分配:动态new。
  • 处理器间中断处理:处理器间中断允许一个CPU向系统中的其它CPU发生中断信号。

七.软中断及tasklet

      软中断的分配是静态的(即在编译时定义),而tasklet的分配和初始化可以在运行时进行。一般而言,在可延迟函数上可以执行四种操作:初始化、激活、屏蔽、执行。

  • 软中断:一个软中断的下标决定了它的优先级:低下标意味着高优先级,因为软中断函数将从0下标开始执行。软中断所使用的数据结构:表示软中断的主要数据结构是softirq_vec数组。处理软中断:open_softirq()函数处理软中断的初始化,raise_softirq()函数用来激活软中断。do_softirq()函数:如果在这样的一个检查点(local_softirq_pending()不为0)检测到挂起的软中断,内核就调用该函数处理它们。__do_softirq()函数:__do_softirq()函数读取本地CPU的软中断掩码并执行与每个设置位相关的可延迟函数。ksoftirqd内核线程:每个CPU都有自己的ksoftirqd()函数。
  • tasklet:tasklet是I/O驱动程序中实现可延迟函数的首选方法。tasklet和高优先级的tasklet分别存放在tasklet_vec和tasklet_hi_vec数组中。

八.工作队列

      尽管可延迟函数和工作队列非常相似,但它们的区别还是很大的。主要区别在于:可延迟函数运行在中断上下文中,而工作队列中的函数运行在进程上下文中。

  • 工作队列的数据结构:与工作队列相关的主要数据结构是名为orkqueque_struct的描述符,它包括一个有NR_CPUS个元素的数组,NR_CPUS是系统中CPU的最大数量。工作队列函数,预定义工作队列。

九.从中断和异常返回

     恢复某个程序的执行,在这之前需要考虑几个问题:内核控制路径并发执行的数量、挂起进程的切换请求、挂起的信号、单步执行模式、virtual-8086模式。

  • 入口点:ret_from_intr()和ret_from_exception()入口本质上相当于下面这段汇编代码。
  • 恢复内核控制路径:如果被恢复的程序运行在内核态,就执行resume_kernel处的汇编语言代码。
  • 检查内核抢占:执行检查内核抢占的代码时,所有没执行完的内核控制路径都不是中断处理程序,否则preempt_count字段值就会是大于0的。
  • 恢复用户态程序:如果要恢复的程序运行在用户态,就跳转到resume_userspace处。
  • 检测重调度标志:thread_info描述符的flags标志表示在恢复被中断的程序之前,需要完成额外的工作。
  • 处理挂起信号、虚拟8086模式和单步执行:除了处理进程切换请求,还有其他的工作需要处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值