当前位置: 首页 > Linux kernrl > 正文

Linux内核之中断初探

前言

上周去校外金工实习了,中间也看过一些书,但是没有写博客总结,今天回来先写篇博客压压惊。

中断初探

任何的内核都是用来管理硬件的,但是他们是如何管理硬件的呢?最早的时候,是使用轮询机制,但是这种机制很浪费资源的,会降低计算机的整体性能。所以诞生了一种更好的机制,就是让硬件在需要的时候再向内核发出信号。这就是中断机制。

中断

中断是硬件发出的,它给处理器发送信号,表示自己需要做某些事情,本质就是一种特殊的电信号,由硬件向处理器发送,处理器接收到信号后会根据信号的类型执行正确的中断处理程序。

可见中断是异步发生的,内核随时可能因为新到来的中断而被打断。

不同的设备对应的中断不同,而且每一种中断都有不同的中断信号,每一个中断都通过唯一的数字标志,每一种中断都是通过中断号先乎区别开的,所以中断发生了操作系统才能提供对应的中断处理程序。

举个例子,中断值通常被称为中断请求(IRQ)线,每个IRQ线被关联一个数值量,例如在经典的PC上,IRQ 1是键盘中断,IRQ 0 是时钟中断,但是并非所有的中断号都是这样严格定义的。例如对于IPC 中断其实就是动态分配的中断号。

异常

提到中断,就不能不提异常。这两个东西很相似但是并不是同一个东西。中断是硬件发生的,异常是软件发生的,这是他们两个的本质区别。异常还有一个名字,叫做同步中断。由此就能看到这两者的区别了。一个异步一个同步。

举一个实际的例子,再X86体系结构上,通过软中断实现系统调用,陷入内核就是一种特殊的异常,其实看起来效果类似,其差异就是中断是硬件引起的,异常是软件引起的。下来就来看看系统如何处理中断。内核会调用中断处理程序。

 

中断处理程序

在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或者中断处理程序。例如一个函数用来处理系统的时钟中断,另一个函数专门来处理键盘产生的中断。中断处理程序就是普普通通的C函数,只不过他们需要特殊的方式声明和调用一些API 罢了。

中断处理程序与其他内核函数的真正区别在于,中断处理程序是被内核调用来响应中断的,而他们运行在我们称之为中断上下文的特殊上下文当中。中断上下文也称作原子上下文,它是不可中断的,该上下文中的代码不可阻塞。

中断处理程序快速运行完成是很重要的。所以中断处理程序一定不能太大,所以最起码,中断处理程序要负责通知硬件设备中断已经被接收。将整个中断处理程序一分为二,我们就可以相对的保证快速响应,及时处理了。这就叫上半部与下半部。

上半部与下半部

我们需要中断处理程序运行的快,又想中断处理程序完成的工作量大,这两个目的显然是矛盾的。所以鉴于这种此消彼长的关系,我们一般把中断处理程序切为两个部分。一个上半部一个下半部。

上半部:

接收到一个中断,立即开始执行。但只做严格的时限工作。例如对接收的中断进行应答或者复位硬件。能够允许稍后完成的工作会被推迟到下半部去。

下半部:

以合理且及时的时机处理中断遗留的工作。

以网卡为例吧:

当网卡接收来自网络的数据包时,需要通知内核数据包到了。网卡需要立即完成这件事,从而优化网络的吞吐量和传输周期,以免超时。因此,网卡立即发出中断,告诉内核我这里有最新数据包了。内核通过执行网卡已经注册的中断处理程序来做应答。

中断开始执行,通知硬件,拷贝最新的网络数据包到内存,然后网卡获取更多的数据包。这些都是重要,紧迫而又与硬件相关动作。内核通常需要快速的从网卡上复制数据到内存上,因为网卡的缓存有限,它必须快速的获取下一个数据包。所以当网络数据被拷贝到内存系统的时候,中断任务算是完成了。处理与操作需要在随后的下半部分进行。

注册中断处理程序:每一个设备都有相关的驱动程序。如果设备使用中断,那么相应的驱动设备就注册一个中断处理程序。

驱动程序可以通过request_irq() 函数来注册一个中断处理程序,并且激活给定的中断线

这个函数:

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

参数说明:

@irq  表示要分配的中断号。这个值要么可以通过探测获取,有些设备可以通过动态编程获取

@handler 是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统一接收到中断,该函数就被调用

typedef irqreturn_t     (*irq_handler_t)(int ,void *);

@flags 中断标志

@name  中断的名字

@dev 设备结构体,一般用来传送一些数据。

中断处理程序标志

IRQF_DISABLED  该标志被设置后,意味这内核在处理中断程序本身期间,需要禁止所有其他中断。但是一般是不会设置这样的,这是一种野蛮的行为。这个标志是给轻量级的中断使用的。

IRQF_SAMPLE_RANDOM   这个标志表明这个设备产生的中断对内核熵池有贡献,内核熵池负责从各种随机的事件中导出真正的随机函数。如果指定了该标志,那么来自该设备的中断间隔时间。就会作为熵填充到熵池。如果你的设备以预知的速率产生中断,或者受了外部攻击,就不要设置这个标志。相反其他很多硬件产生中断是不可预知的,所以都能成为比较好的熵源。

IRQF_TIMER      该标志是特别为系统定时器的中断准备的

IRQF_SHARED    此标志表明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每一个处理函数必须指定这个标志。否则,在每一个中断线上只有一个处理程序。

但是这个函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中调用该函数。天真地在睡眠不安全的上下文中调用这个函数,是一种常见的错误。

释放中断处理程序

卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。

void  free_irq(unsigned int irq,void * dev)

如果指定的中断线步是共享的,那么,该函数删除处理程序的同时将禁用本条中断线。如果中断线是共享的,则仅删除dev所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序的时候才会被禁用。由此可以看到dev 唯一是很重要的。对于共享的中断线,需要一个唯一的信息来区分其上面的多个处理程序,并且让free_irq( )仅仅删除指定的处理程序。不管哪种情况下,如果dev非空,它都必须与需要删除的处理程序相配。必须从进程上下文中调用free_irq( ).

 编写中断处理程序

以下是一个中断处理程序的声明:

static irqreturn_t   intr_handler(int irq ,void *dev)

它的参数类型与request_irq( )参数类型相匹配的。第二个参数dev 是一个通用的指针。

中断处理程序的返回值是一个特殊类型,irqreturn_t  ,中断处理程序可能返回两个特殊的值。IRQ_NONE

IRQ_HANDLED  。当中断监测到一个中断的时候,但是该中断对应的设备并不是在注册处理函数期间产生源时,返回IRQ_NONE.当中断处理程序被正确调用,且确实是它所对应的中断时,返回IRQ_HANDLED.

外 可以使用宏:IRQ_RETVAL(val) 。如果val 为非0值。则宏返回IRQ_HANDLED .否则返回IRQ_NONE. 利用这些特殊值内核可以判定设备发生的是否是一种虚假的中断。

中断处理程序扮演什么样的角色取决于产生中断的设备和该设备为什么发送中断。但是绝大部分的中断程序一定要能够通知到内核有中断来了,或者设备必须能发送出中断信号。至于如何处理则就是下半部机制需要做的事情了。下半部机制我们会在下一篇博客写出来。

重入和中断处理程序

Linux 中的中断处理程序是无需重入的。当给定的一个中断处理程序正在执行时,相应的中断线在所有处理器上都被屏蔽掉了。以防止在同一个中断线上接收另一个新的中断。达到的效果就是,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。极大的简化了中断处理程序的编写。

中断上下文

当处理一个中断处理程序时,内核处于中断上下文当中。中断上下文,不同于进程上下文。这完全是两个东西。进程上下文是一种内核所处的操作模式,此时内核代表进程执行,例如执行系统调用或者运行内核线程。

进程上下文是可以通过宏current 宏关联当前进程,且可以睡眠。但是中断上下文和进程上下文,没有任何的关系。这里提示下,进程上下文可以睡眠,完全是因为进程上下文中调用了kmalloc ,因为kmalloc 是可以睡眠的,所以这个也可以睡眠。

中断上下文有非常高的时效性,中断上下文不允许睡眠。关于中断处理程序在哪里执行的问题,以前是在内核线程中执行的。但是因为效率不高的原因,所以给中断处理程序另行开辟了一个空间,就是中断栈。

关于中断控制的几个接口:

local_irq_disable( )           禁止本地中断

local_irq_enable( )           激活本地中断

local_irq_save(  )             保存本地中断传递状态,然后禁止本地中断传递

local_irq_restore( )           恢复本地中断传递到给定的状态

disable_irq( )                    禁止本地中断线,并确保该函数返回之前在该中断线上没有处理程序在运行

disable_irq_nosync( )        禁止给定中断线

enable_irq(  )                   激活给定中断线

irqs_disabled( )                 如果本地中断传递被禁止,则返回非0,否则返回0.

in_interrupt(  )                 如果在中断上下文中,则返回非0,如果在进程上下文中,则返回0

in_irq(  )                         如果当前正在执行中断处理程序,则返回非0,否则返回0.

 

 

本文固定链接: http://zmrlinux.com/2015/11/29/linux%e5%86%85%e6%a0%b8%e4%b9%8b%e4%b8%ad%e6%96%ad%e5%88%9d%e6%8e%a2/ | Kernel & Me

该日志由 root 于2015年11月29日发表在 Linux kernrl 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: Linux内核之中断初探 | Kernel & Me