Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)

最是人间留不住,朱颜辞镜花辞树。这篇文章主要讲述Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)相关的知识,希望能为你提供帮助。
一、中断控制的概述

  • Linux内核提供了一组接口用于操作机器上的中断状态
  • 这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些例程都是与体系结构相关的,可以在< asm/system.h> 和< asm/irq.h> 中找到
Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)

文章图片

  • 一般来说,控制中断系统的原因归根结底是需要提供同步:
    • 通过禁止中断,可以确保某个中断处理程序不会抢当前的代码。此外,禁止中断还可以禁止内核抢占。然而,不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问
    • Linux支持多处理器,因此,内核代码一般都需要获取某种锁,防止来自其他处理器对共享数据的并发访问。 获取这些锁的同时也伴随着禁止本地中断。锁提供保护机制,防止来自其他处理器的并发访问, 而禁止中断提供保护机制,则是防止来自其他中断处理程序的并发访问
    • 后面会着重讨论同步的各种问题及其对策。因此,必须理解内核中断的控制接口
二、禁止和激活中断(local_irq_disable、local_irq_enable)
  • 下面是禁止和激活当前处理器(仅仅是当前处理器)上中断的接口(一般而言,都是两个配合使用(先中断再开启))
Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)

文章图片

函数的实现
  • 这两个函数通常以单个汇编指令来实现(当然,这依赖于体系结构)
  • 实际上,在x86中,loacl_irq_disable()仅仅是cli指令,而 local_irq_enable()只不过是sti指令。cli 和 sti分别是对clear和set允许中断(allow interrupt)标志的汇编调用。换句话说,在发出中断的处理器上,它们将禁止和激活中断的传递
禁止或激活中断的注意事项
  • 如果在调用loacl_irq_disable()例程之前已经禁止了中断,那么该例程往往会带来潜在的危险 ;同样相应的local_irq_enable()例程也存在潜在危险,因为它将无条件地激活中断,尽管这些中断可能在开始时就是关闭的
  • 所以我们需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活。内核普遍关心这点是因为,内核中一个给定的代码路劲既可以在中断激活的情况下达 到,也可以在中断禁止的情况下达到,这取决于具体的调用链。例如,想象一下前面的代码片段是一个大函数的组成部分。这个函数被另外两个函数调用:其中一个函数禁止中断,而另一个函 数不禁止中断。因为随着内核的不断增长,要想知道到达这个函数的所有代码路劲将变得越来越困难,因此,在禁止中断之前保存中断系统的状态会更加安全一些。相反,在准备激活中断时, 只需把中断恢复到它们原来的状态
Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)

文章图片

  • 这些方法至少部分要以宏的形式实现,因此表面上flags参数(这些参必须定义为unsigned long类型)是以值传递的。该参数包含具体体系结构的数据,也就是包含中断系统的状 态。至少有一种体系结构把栈信息与值相结合(SPARC),因此flags不能传 给另一个函数(特 别是它必须驻留在同一找帧中)。基于这个原因,对local_irq_save()核对local_irq_restore()的调用必须在同一个函数中进行
不再使用全局的cli()函数
  • 以前的内核中提供了一种“能够禁止系统中所有处理器上的中断”方法。而且,如果另一个处理器调用这个方法,那么它就不得不等待,直到中断重新被激活才能继续执行。这个函数就是cli(),相应的激活中断函数为sti()——虽然适用于所有体系结构,但完全以x86为中 心 。这些接口在2.5版本开发期间被取消了,相应地,所有的中断同步现在必须结合使用本地中断控制和自旋锁。这就意味着,为了确保对共享数据的互斥访问,以前代码仅仅需要通过全局禁止中断达到互斥,而现在则需要多做些工作了
  • 以前,驱动程序编写者可能假定在他们的中断处理程序中,任何访问共享数据地方都可以使用cli()提供互斥访问。cli()调用将确保没有其他的中断处理程序(因而只有它们特定的处理程序)会运行。此外,如果另一个处理器进入了cli()保护区,那么它不可能继续运行,直到原来的处理器退出它们的cli()保护区,并调用了 sti()后才能继续运行
  • 取消全局cli()有不少优点:
    • 首先,强制驱动程序编写者实现真正的加锁。要知道具有特定目的细粒度锁比全局锁要快许多,而且也完全吻合cli()的使用初衷
    • 其次,这也使得很多代码更具流线型,避免了代码的成簇布局。所以由此得到的中断系统更简单也更易于理解
三、禁止指定中断线
  • 在前面的内容中,我们看到了禁止整个处理器上所有中断的函数。在某些情况下,只禁止整个系统中一条特定的中断线就够了。这就是所谓的屏蔽掉一条中断线。作为例子,你可能想在对中断的状态操作之前禁止设备中断的传递
  • 为此,Linux提供了四个接口:
【Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)】
Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)

文章图片

前两个函数
  • 前两个函数禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。 另外,函数只有在当前正在执行的所有处理程序完成后,disable_irq()才能返回。因此,调用者不仅要确保不在指定线上传递新的中断,同时还要确保所有已经开始执行的处理程序已全部退出。函数disable_irq_nosync()不会等待当前中断处理程序执行完毕
synchronize_irq函数
  • 该函数等待一个特定的中断处理程序的退出。如果该处理程序正在执行,那么该函数必须退出后才能返回
enable_irq函数
  • 对这些函数的调用可以嵌套。但要记住在一条指定的中断线上,对disable_irq()或 disable_irq_nosync()的每次调用,都需要相应 调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正重新激活了中断线。例如,如果disablejrq()被调用了两次,那么直到第二次调用enable_irq()后,才能真正地激活中断线
  • 所有这三个函数可以从中或 进程上下文中调用,而且不会睡眠。但如果从中断上下文中调用,就要特别小心!例如,当你正在处理一条中断线时,并不想激活它(回想当某个处理程序的中断线正在被处理时,它被屏蔽掉)
  • 禁止多个中断处理程序共享的中断线是不合适的。禁止中断线也就禁止了这条线上所有设备的中断传递。因此,用于新设备的驱动程序应该倾向于不使用这些接口。根据规范,PCI设备必须支持中断线共享,因此,它们根本不应该使用这些接口。所以,disable_irq()及其相关函数在老式传统设备(如PC并口)的 驱动程序中更容易被找到
四、中断系统状态
  • 通常有必要了解中断系统的状态(如中断是禁止的还是激活的),或者你当前是否正处于中断上下文的执行状态中
  • 宏irqs_disable()定义在 < asm/system.h> 。如果本地处理器上的中断系统被禁止,则它返回非0;否则返回0
  • 在< linux/hardirq.h> 中定义的两个宏提供一个用来检査内核的当前上下文的接口,它们是:
    • 第一个宏最有用:如果内核处于任何类型的中断处理中,它返回非0,说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序
    • 宏in_irq()只有在内核确实正在执行中断处理 程序时才返回非0
Linux(内核剖析):22---中断之中断控制接口(禁止/激活/屏蔽中断)

文章图片

  • 通常情况下,你要检査自己是否处于进程上下文中。也就是说,你希望确保自己不在中断上 下文中。这种情况很常见,因为代码要做一些像睡眠这样只能从进程上下文中做的事。如果in_ interrupt()返 回 0 , 则此刻内核处于进程上下文中

    推荐阅读