分类:
2010-07-28 12:25:09
6.1 中断
硬件设备生成中断的时候并不考虑与处理器的时钟同步——也就是说中断随时可以产生。内核随时可能因为新到来的中断而被打断。从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚。然后由中断控制器向处理器发送相应的信号。
异常
在操作系统中,讨论中断就不得不提及异常。异常与中断不同,它在产生时必须考虑与处理器的同步。实际上 异常也常常被称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,处 理器就会产生一个异常。因为许多处理器体系结构处理异常与处理中断的方式类似,因此,内核对它们的处理也很类似。我们已经熟悉一种特殊的异常:系统调用处理程序异常。
6.2 中断处理程序
在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程(interrupt service routine,ISR)。产生中断的每一个设备都有一个(中断处理程序通常不是和特定设备相关联,而是和特定中断相关联,也就是说,若一个设备可以产生 多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动也就需要准备多个这样的函数。)相应的中断处理程序。一个设备的中断程序是它设备驱动程序的一部分——设备驱动程序是用于对设备进行管理的内核代码。
在Linux中,中断处理程序看起来就是普普通通的C函数。只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递处理程序的信息。中断处理程序与其它内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。
上半部与下半部的对比
通常我们把中断处理切为两个部分或两半。中断处理程序是上半部——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,这些工作都是在所有中断被禁止的情况下完成。能够被允许稍后完成的工作会推迟到下半部去。在合适的时机,下半部会被开中断执行。
6.3 注册中断处理程序
中断处理程序是管理硬件的驱动程序的组成部分。每个设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。驱动程序可以通过下面的函数注册并激活一个中断处理程序。 int request_irq( unsigned int irq,
irqrequest_t(*handler)( int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
(1)第一个参数irq表示要分配的中断号。(2)第二个参数handler是一个指针,指向处理这 个中断的实际中断处理程序。(3)第三个参数irqflags可以为0,也可能是下列一个或多个标志的位掩码:SA_INTERRUPT:此标志表明给定 的中断处理程序是一个快速中断处理程序。在ARM中是否和FRQ向对应呢???????????SA_SAMPLE_RANDOM: 此标志表明这个设备产生的中断对内核熵池有贡献。SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。(4)第四个参数devname是 与中断相关的设备的ASCII文本表示法。这些名字会被/proc/irq和proc/interrupt文件使用,以便与用户通信。(5)第五个参数 dev_id主要用于共享中断线。用于区分共享中断线上的各个中断处理程序。内核每次调用中断处理程序时,都会把这个指针传递给它。实践中往往会通过它传 递驱动程序的设备结构:这个指针是唯一的,而且有可能在中断处理程序内及设备模式中被用到。注意,request_irq()函数可能会睡眠,因此,不能 在中断上下文或其他不允许阻塞的代码中调用该函数。一个例子:
if(request_irq(irqn, my_interrupt, SA_SHIRQ, "my_device", dev)) { printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn); return -EIO; } 该例子中我们用dev_id传递dev结构体。
释放中断处理程序
卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。调用void free_irq(unsigned int irq, void *dev_id)来释放中断线。如果指定的中断线不是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才 会被禁用。不管在那种情况下,如果dev_id非空,它都必须与需要删除的处理程序相匹配。
6.4 编写中断处理程序
一个典型的中断处理程序声 明: static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs) 注意,它的类型与request_irq()参数中handler所要求的参数类型相匹配(在前面我没已经看到,handler函数有一个类型为 irqreturn_t的返回值)。dev_id是一个通用的指针,它与在中断注册时传递给request_irq()的参数dev_id必须一致。 dev_id也有可能指向中断处理程序使用的一个数据结构,对每个设备而言,设备结构都是唯一的,而且可能在中断处理程序中也用的到,因此,也通常会把设备结构传递给dev_id。 中断处理程序的返回值是一个特殊类型:irqreturn_t。它可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED。
6.4.1 共享的中断处理程序
共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有以下几点: (1)request_irq()的参数flags必须设置SA_SHIRQ标志。(2)对每个注册的中断处理程序来说,dev_id参数必须唯一。而指 向任一设备结构体的指针就可以满足这一要求;通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。(3)中断处理程序必须能够区分它的设 备是否真的产生中断。这既需要硬件支持,也需要处理程序中有相关的处理逻辑。
6.5 中断上下文
当执行一个中断处理程序或下半部时,内核处于中断上下文中。让我们先回忆一下进程上下文。进程上下文 是一种内核所处的模式,此时内核代表进程执行——例如,执行系统调用或运行内核线程。在进程上下文中,可以通过current宏关联当前进程。中断上下文 和进程并没有什么瓜葛。与current宏也是不相干的(它此时指向被中断的进程)。中断上下文不可以睡眠。如果一个函数睡眠,就不能在中断处理程序中使 用——这是对什么样的函数可以在中断处理程序中使用的限 制。
中断处理程序栈的设置是一个配置选项。
6.6 中断处理机制的实现
中断处理系统在Linux中的实现是非常依赖于体系结构的。实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。设备产生中断,通过 总线把电信号发送给中断控制器。如果中断是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚 发送一个信号,处理器会立即停止它正在做的事情,关闭中断系统,然后跳转到内存中预定义的位置开始执行那里的代码。这个预定以的位置是内核设置的,是中断 处理程序的入口点。 在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。对于每一个中断线,处理器都会跳到对应的一个唯一的位置。至 此,内核知道了所接受的中断的IRQ号。初始入口点只是在栈中保存这个号,并存放当前寄存器的值(这些值属于被中断的任务),然后,内核调用函数 do_IRQ()。此处尚不太明白,回头要参考一下ARM相关的源代码。
6.7 中断控制
Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的
中断系统,或屏蔽掉整个机器的一条中断线的能力,这些例程都是与体系结构相关的,可以在
local_irq_disable() 禁止本地中断传送
local_irq_enable() 激活本地中断传送
local_irq_save() 保存本地中断传递的当前状态,然后禁止本地中断传递
local_irq_restore() 恢复本地中断传递到给定的状态
disable_irq() 禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行
disable_irq_nosynoc() 禁止给定中断线
enable_irq() 激活给定中断线
irqs_disabled() 如果本地中断传递被禁止,则返回非0,否则返回0
in_interrupt() 如果在中断上下文中,则返回非0,如果在进程上下文中,则返回0
in_irq() 如果当前正在执行中断处理程序,则返回非0,否则,返回0