Chinaunix首页 | 论坛 | 博客
  • 博客访问: 37509
  • 博文数量: 6
  • 博客积分: 511
  • 博客等级: 下士
  • 技术积分: 72
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-19 08:49
文章分类
文章存档

2011年(3)

2010年(3)

分类: LINUX

2011-01-05 09:08:25

中断及中断处理

 

操作系统的核心任务是管理与之相连的任何硬件设备,为了完成这个任务,内核有必要与每个硬件进行通讯。假定处理器可以比这些硬件设备工作快数倍,那么处理器发送一个请求且等待硬件的响应,将不是一个好的方法,而且影响系统的性。硬件的工作是相当的慢,所以这时候内核应该去完成其它的工作,直到硬件确实处理完成请求。那么硬件和处理器如何协同工作才能提供系统的性能呢。一个方法是周期的查询,既轮询,显然,这也是相当浪费cpu资源的,而且这个轮询时间的设定也比较难确定。另外一个比较好的方法那就是中断机制,也就是本文的重点。

 

中断

中断使硬件可以发出一个信号,以通知cpu,要进行处理相应的事务。这个信号被称为中断,处理器接收中断并通知操作系统去响应新的数据。与处理器定时器相比,硬件产生的信号是异步的,这意味着它可能在任何时刻都会产生中断。系统调用中产生的异常,也可以看成中断,不过它的产生是同步的,也有时被称为同步中断。

中断是由硬件产生的物理电信号,直接传达至中断控制器输入引脚,这个中断控制器可能复用很多中断线为一个中断线,最后传给处理器。中断控制器接收到信号后会通知处理器,处理器会中断当前的执行(当前中断进程直到中断完成才有可能执行)来完成中断的处理。处理器通知系统一个中断发生了,操作系统要合适的处理这个中断。

每个不同的设备都可以有一个不同的中断号,这竟味着,对于每个设备操作系统均可以作相应的处理,当然,也有很多设备共享一个中断号的情况。

 

中断处理

       内核所运行的相应的中断处理程序又被称为中断服务程序(ISR)。每个产生中断的设备均应有一个中断服务程序。如,一个ISR来完成系统定时器产生的中断,而另外的一个ISR来完成键盘产生的中断。对于一个设备来说,ISR是设备驱动程序的一部分。

       linuxISR也是一般的c函数,它们也有原型要匹配,这是内核可以用标准的形式来传递参数。中断处理的不同于其它内核函数的是:内核调用它们来响应中断,而且它们运行在一个比较特殊的环境中中断上下文。这个特殊的环境又被称为原子上下文,在这个环境下它不能被阻塞。

       因为中断可能发生于任何时刻,那么一个中断处理程序可能在任何时刻被执行。那么毫无疑问,它应该尽可能的快,以恢复那个被它中断的进程。虽然说操作系统应尽可能的快的执行中断处理程序是非常重要的,同时,这个中断处理程序也应该以最短的时间来执行。否则那个被它中断的进程将受到影响。最少工作的情况下,ISR的任务仅是响应来自硬件的中断。如,网络设备的中断处理程序,最快的响应硬件,中断处理程序需要把网络数据包从硬件拷至内存,然后处理它们:把它们传入适当的应用或协议栈。显然,这些工作量很大,尤其对于那些万兆速率的网卡。

 

上半部与下半部

       中断处理最快的执行及完成大量的工作,明显这是有冲突的。因为这种冲突的目标,中断处理就被分为两部分。中断处理为上半部分,它在接收到中断时立既运行,只完成与时间相关性较强的处理,如接受中断并复位中断。那些可以被推迟的工作将会在下半部完成,既下半部运行于未来的某个时刻,某个方便的时刻,而且中断是打开的内核提供了很多关于下半部分执行的机制。

       举个例子,如网卡,当它从网络收到数据时,它会通知内核。这项工作要求而且应该以最快的速度来处理,以优化网络吞吐量,低的延迟,避免超时。因此立即发出一个中断告知内核,内核以执行注册的网卡中断处理程序来完成响应。中断处理程序运行:确认硬件中断,拷贝新的网络数据包至主存,使网卡处于准备状态,以接收更多的数据。这些工作是非常重要的,时间相关的,硬件工作标准。内核通常需要快速把网络数据包拷贝至主存,因为网卡中的数据缓存区是固定的,而且与主存相比较小。当有大量数据包到来时,数据包的拷贝延迟可能导致缓存溢出,这些数据将会被丢失。当网络数据包位于主存中时,中断的工作才算是真正的完成,这样它就可以返回那个被他中断的代码了。余下的关于包的处理过程发生在之后,在下半部分。

 

注册中断处理

中断处理也是驱动管理硬件的一个方面,每个硬件均会有一个驱动与之对应,如果硬件使用了中断那么那个驱动必须注册一个中断处理程序。以下为注册中断处理程序接口:

 

/* request_irq: allocate a given interrupt line */

int request_irq(unsigned int irq,irq_handler_t handler,\

unsigned long flags,const char *name,void *dev)

第一个参数为分配的irq号,一般此号为硬件决定的,有时此号可以由软件编程决定。

第二个参数为处理程序,它是一个函数指针,指向真正的中断服务处理程序。当操作系统收到中断后,它就会掉用这个中断处理程序。

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

它接受两个参数,返回值为irqreturn_t

第三个参数为中断处理标志,其中几个比较重要的标志如下:

1,  IRQF_DISABLED,当这个标志位被设置时,内核在执行此中断处理程序时关闭所有中断。当不设置时,除了它本身中断外,其它中断均打开。现在多数使用另外一个标志SA_INTERRUPT以区别“快”,“慢”中断。

2,  IRQF_SAMPLE_RANDOM 此标志位指定硬件所产生的中断可用于内核熵池。

3,  IRQF_TIMER 指定中断处理过程处理系统定时器所产生的中断。

4,  IRQF_SHARED,指定中断号在多个中断处理程序中共用。每个注册的中断处理程序必须设定此标志,否则每个中断号上仅能存在一个中断处理程序

第四个参数,名称,以ASSCII文本表示中断处理程序相关的设备。

第五个参数,设备,为了共享中断号的设备而使用的。在共享中断号的中断处理情况下,当释放一个中断处理程序,此参数提供了一个特别的标识,可以移除期望的中断处理程序。如果中断号未被共享,此处可传递参数NULL即可。

 

此函数执行成功时返回零,非零值意味着一个错误的发生,中断未被注册至内核中。此函数可以睡眠,因此不能在中断上下文或其它不能阻塞的情况下使用。因为在注册时,它在内核中创建了/proc/irq文件,它使用proc_mkdir来完成这个功能的,这个函数执行时需要调用到kmalloc函数来分配内存一个可能睡眠的函数。

 

释放中断处理函数

                     Void free_irq(unsigned int irq,void *dev)

这个函数完成中断处理程序的释放,当然如果共享中断号的情况下它只是释放相应的中断处理函数。这个函数只能在进程上下文中被调用。

写一个中断处理

       中断处理函数的声明如下:

              static irqreturn_t intr_handler(int irq, void *dev)

此函数必须匹配申请中断处理函数中的原型。第一个参数为中断号,它只在打印日志信息时被使用。第二个参数为设备指针,与申请中断处理函数中的设备指针相同。中断处理函数的返回值为irqreturn_t类型,一般中断处理只能返回两个值,IRQ_NONE,IRQ_HANDLED。当前者被返回进,意味着中断处理函数检测到指定的设备并未发生中断,后者返回时表明一个设备确实发生了中断,而且中断处理函数被正确调用。一般中断处理函数多定义为static类型。中断处理程序没有必要是可重入的,当一个中断发生时,相应的中断号在所有的cpu均会被屏蔽,避免了同一中断再次被接收,这也就简化了中断处理程序的设计。

 

中断上下文

       中断执行时,它位于中断上下文,内核代表硬件运行于内核空间。进程上下文运行于内核态,比如内核线程或一个系统调用进入内核。在进程上下文中,current宏指向当前相关的任务。因为一个进程总是在进程上下文中与内核相关,因此,进程上下文中可以睡眠或者执行调度。

       中断上下文时,一方面它不与任何进程相关,尽管它当前仍然指向那个被它中断的进程。没有任何备份过程,中断上下文中不可睡眠,否则它又如何调度呢?

       中断上下文在时间上是十分严格的,因为它中断了其它正在执行的代码,所以中断程序本身应该快速执行,程序本身应该简单。尽管忙等待是可行的,但这是不鼓励的。另外一方面也是十分重要的:中断打断了其它正在执行的代码。因为这种异步性,所以它应该尽快执行。

中断处理程序的栈本身是可以设置的,在历史上中断处理程序本身没有自己的栈,它们本来是共享那个被它们中断的进程的栈。现在在2.6内核中,一个新增加的选项被加入,以允许减小栈的大小从两而至一页,也就是在32位的系统上,有4KB32KB的栈。这也就减小的内存压力,因为系统上的每个进程先前需要两页的非交换的内核连续内存。为了处理内存栈的减小,中断处理程序本身就有了自己的栈,每个处理器一个栈,被称为中断栈。尽管中断栈的大小仅是共享栈的一半,但由于中断处理程序本身可以获得全部的页,平均获得的空间也就变大了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

中断处理实例

       中断处理系统在linux中是体系相关的,它的应用依靠cpu,使用的中断控制器,体系架构及机器的设计。以下为基本过程

 

 

 

 

 

 

 

 

设备通过电信号发出中断,通知中断控制器,如果中断控制器被使能,那么中断控制器将会把信号送到cpucpu的中断未被禁止,它将会立即中断当前任务,禁止系统的中断,跳转至预先定义在内存中指定位置,执行代码。预先定义点由内核设置,它是中断处理程序的入口点。

中断处理过程的处理开始于预先定义的入口点,正如系统调用通过异常进入内核一样。对于每个中断号,处理器跳转至内存中唯一的位置执行那里的代码。以这种方式,内核得知到来中断的中断处理程序。初始入口简单的保存着这个值,保存当前寄存器的值(这个值属于被中断进程)于栈上;然后调用do_IRQ(),此后多数中断处理程序用c完成,但它依然是体系相关的。这个函数定义如下:

                     unsigned int do_IRQ(struct pt_regs regs)

因为c语言的惯例是把函数的参数放入栈顶,pt_regs结构体中包含了先前汇编入口保存的寄存器初始值。因为中断号也被保存了,此刻可以提取出来这个中断号,然后此函数响应接收到中断,禁止当前中断号中断。接下来,它确保此中断被合理的注册而且被使能,且当前没有正在执行。然后调用handle_IRQ_event函数来运行中断处理程序。它完成了中断处理程序的运行,返回时它清除了初始入口,跳回至ret_from_intr,这个函数使用汇编完成,它判断重新调度是否被挂起,既是否需要重新调度。如果确实发生重新调度,内核返回至用户空间(意味着中断打断了用户空间的进程),schedule()将被调用。如果内核返回至内核空间(意味着中断打断了内核空间进程),如果preempt_count0,则schedule将会被调用。否则这是不安全的内核抢占。在调度发生后,若没有挂起的工作,初始值被恢复,内核将会恢复至它被中断位置处。

 

中断控制

内核提供了一系统的接口来控制机器上的内核中断,你可以通过这些接口禁止或使能当前处理器上的中断系统,也可以屏蔽整个机器的中断。这些接口是体系相关的。之所以要提供这些接口主要是为了提供同步化。通过禁止中断,你可以保证中断处理程序不会抢占你当前的代码,而且禁止中断同时也禁止了内核的抢占。

 

 

禁止及打开中断

为了禁止当前(仅当前)cpu中断,然后再打开它们可以按以下步骤进行:

              Local_irq_disable();

              /*中断被禁止*/

              Local_irq_enable();

这些操作是体系相关的,且使用汇编完成。在x86中,前者仅是一个简单的cli,后者为sti.换句话说,它们禁止和使能相应cpu上的中断的发送。在调用前者时,如果系统中断已经禁止了,这种情况存在一定的风险。Local_irq_enable()无条件的使能中断。

local_irq_save(unsigned long flags)  //保存本地中断状态,然后禁止本地中断
local_irq_restore(unsigned lond flags)  //
恢复本地中断到给定状态  通过保存状态解决    local_irq_disable()的潜在危险,但saverestore必须在同一个函数中执行,flags不能传递给另一个函数)

以上函数均可以在中断上下文或进程上下文进行调用

 

禁止指定中断号

disable_irq(unsigned int irq)  //禁止给定中断线(全cpu),并确保该函数返回之前在该中断线上没有处理程序运行
disalbe_irq_nosync(unsigned int irq)  //
禁止给定中断线
enable_irq(unsigned int irq)
(每执行一次disable_irqdisable_irq_nosync都要执行一次enable_irq(),要匹配出现,直到最后一个enable_irq才真正使能相应的中断)

同样以上函数也可以在中断或进程上下文中使用,如果使用时,一定要小心,例如使能一个你正在处理的中断的中断号。禁止中断号对于那些共享中断号的中断是一种比较粗鲁的方式,那样它将会禁止此中断号上所有设备发送的中断。因此新的设备驱动趋向于不再使用这些接口。因为pci设备必须支持中断共享的标准,它们应该完全不再使用这些接口。

 

系统中断状态

       经常我们要知道系统的中断状态,例如:中断是否使能,或者你当前是否在中断上下文的环境中执行代码。

irqs_disbaled() //如果本地中断被禁止,则返回非0
in_interrupt()  //
如果在中断上下文中,则返回非
0
in_irq()  //
如果当前正在执行中断处理程序,则返回非0

第一个函数判断中断是否被禁止,如被禁止返回非零。

第二个函数判断当前执行环境是否处理中断上下文,若处于则返回非零。这包括执行中断处理或下半部分处理函数。

第三个函数返回非零,若在执行指定的中断处理函数中。

 

       通常你想确定当前的执行是否处于进程上下文,以确定不是在中断上下文中。因为我们知道想做的一些事情仅能在进程上下文中执行,例如,睡眠。如果in_interrupt返回零,那么当前内核处于进程上下文。

 

阅读(4442) | 评论(0) | 转发(0) |
0

上一篇:系统调用

下一篇:定时器及时间管理

给主人留下些什么吧!~~