分类: LINUX
2009-05-07 00:46:13
5.1中断
中断本质上是一种特殊的电信号,由硬件设备发向处理器,处理器接收到中断后,会马上向操作系统反映此信号的到来,然后就由OS复杂处理这些新到来的数据。硬件设备生成中断的时候并不考虑与处理器的时钟同步,也就是说中断可以随时产生。
定义:
所谓中断是指CPU在执行程序的过程中,出现了某些突发事件时CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行。
分类:
根据中断的来源:分为内部中断和外部中断。内部中断的中断源来自CPU内部(软件中断指令,溢出等),外部中断的中断源来自CPU外部,由外设提出请求。
根据是否屏蔽:分为可屏蔽中断与不屏蔽中断(NMI),可屏蔽中断可通过屏蔽字屏蔽,屏蔽后该中断不再得到响应。
根据中断入口跳转方式的不同:分为向量中断和非向量中断。采用向量中断的CPU通常为不同的中断分配不同的中断号。非向量中断的多个中断共享一个入口地址。进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断。就是说,向量中断是由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。
5.2中断处理程序
中断处理程序与其他内和函数的真正区别在于:
中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。
前和后半部(上半部和下半部)
中断处理的一个主要问题是如何在处理中进行长时间的任务. 常常大量的工作必须响应一个设备中断来完成, 但是中断处理需要很快完成并且不使中断阻塞太长. 这 2 个需要(工作和速度)彼此冲突。
Linux (许多其他系统一起)解决这个问题通过将中断处理分为 2 半. 所谓的前半部是实际响应中断的函数 -- 你使用 request_irq 注册的那个. 后半部是由前半部调度来延后执行的函数, 在一个更安全的时间. 最大的不同在前半部处理和后半部之间是所有的中断在后半部执行时都使能——这就是为什么它在一个更安全时间运行. 在典型的场景中, 前半部保存设备数据到一个设备特定的缓存, 调度它的后半部, 并且退出: 这个操作非常快. 后半部接着进行任何其他需要的工作, 例如唤醒进程, 启动另一个 I/O 操作, 等等. 这种设置允许前半部来服务一个新中断而同时后半部仍然在工作。
5.3注册中断处理程序
中断处理程序是管理硬件的驱动程序的组成部分。每个设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。驱动程序可以通过下面的函数注册并激活一个中断处理程序,以便处理中断:
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long flags,
const char *dev_name,
void *dev_id);
(1)第一个参数irq表示要分配的中断号。对某些设备,这个值是先定的,对大多数设备来说,这个值是可以通过探测获取,也可以动态确定。
(2)第二个参数handler是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统一接收到中断,该函数就被调用。
(3)第三个参数irqflags可以为0,也可能是下列一个或多个标志的位掩码:SA_INTERRUPT:此标志表明给定的中断处理程序是一个快速中断处理程序。
SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献。
SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。
(4)第四个参数devname是与中断相关的设备的ASCII文本表示法。这些名字会被/proc/irq和proc/interrupt文件使用,以便与用户通信。
(5)第五个参数dev_id主要用于共享中断线。用于区分共享中断线上的各个中断处理程序。内核每次调用中断处理程序时,都会把这个指针传递给它。实践中往往会通过它传递驱动程序的设备结构:这个指针是唯一的,而且有可能在中断处理程序内及设备模式中被用到。
request_irq()成功执行会返回0,如果返回非0值,就表示有错误发生,在这种情况下,指定的中断处理程序不会被注册,最常见的错误是-EBUSY,它表示给定的中断线已经在使用(或者当前用户或者你没有指定SA_SHIRQ())。
注意:request_irq()函数可能会睡眠,因此,不能在中断上下文或其它不允许阻塞的代码中调用该函数,在睡眠不安全的上下文中可以安全的调用request_irq()函数,是一种常见错误。
为什么request_irq()会引起睡眠?
在注册的过程中,内核需要在/proc/irq文件创建一个与中断对应的项,函数proc_mkdir()就是用来创建这个新的procfs项的,proc_makedir()通过调用函数proc_create()对这个新的profs项进行设置,而proc_create()会调用函数kmalloc()来请求分配内存,而我们知道函数kmalloc()是可以睡眠的。
释放中断处理程序
释放中断线,可以调用:
void free_irq(unsigned int irq, void *dev_id);
如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线,如果中断线是共享的,则仅删除dec_id所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序才会被禁用,由此可以看出为什么唯一的dev_id如此重要,对于共享的中断线,需要一个唯一的信息来区分其上面的多个处理程序,并通过它来保证函数free_irq()能够正确的删除指定的处理程序,不管在哪种情况下(共享或不共享),如果dev_id非空,它都必须与需要的删除的处理程序相匹配。
注意:必须从进程上下文中调用free_irq()。
5.4编写中断处理程序
一个典型的中断处理程序声明:
static irqreturn_t intr_handle(int irq, void *dev_id, struct pt_regs *regs
注意,它的类型与request_irq()参数中handler所要求的参数类型相匹配(handler函数有一个类型为irqreturn_t的返回值)。第一个参数irq就是这个处理程序要响应的中断的中断线号。dev_id是一个通用的指针,它与在中断注册时传递给request_irq()的参数dev_id必须一致。dev_id也有可能指向中断处理程序使用的一个数据结构,对每个设备而言,设备结构都是唯一的,而且可能在中断处理程序中也用的到,因此,也通常会把设备结构传递给dev_id。
参数regs是一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态,除了调试以外,很少用到。
中断处理程序的返回值是一个特殊类型:irqreturn_t。它可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED。irqreturn_t这个返回类型实际上就是一个int型,之所以使用这些特殊值是为了与早期的内核保持兼容。
中断处理程序通常会标记static,因为它从来不会被别的文件中的代码直接调用。
共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有以下几点:
(1)request_irq()的参数flags必须设置SA_SHIRQ标志。
(2)对每个注册的中断处理程序来说,dev_id参数必须唯一。而指向任一设备结构体的指针就可以满足这一要求;通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。
(3)中断处理程序必须能够区分它的设备是否真的产生中断。这既需要硬件支持,也需要处理程序中有相关的处理逻辑。如果硬件不支持这一功能,它就没法知道到底是与它对应的设备发出了这个中断,还是共享这条中断线的其它设备发出了这个中断。
指定SA_SHIRQ标志以调用request_irq()时,只有在以下两种情况下才可能成功,一是中断线当前未被注册,二是在该线上的所有已注册处理程序都指定了SA_SHIRQ。
内核接收一个中断后,它将依次调用在该中断线上注册的每一个处理程序,因此,一个处理程序必须知道它是否应该为这个中断负责,如果与它相关的设备并没有产生中断,那么,处理程序应该立即退出。
当执行一个中断处理程序或下半部时,内核处于中断上下文(interrput context)中。让我们先回忆一下进程上下文,进程上下文是一种内核所处的操作模式,此时内核代表进程执行——例如,执行系统调用或运行内核线程,在进程上下文中,可以通过current宏去关联当前进程,此外,因为进程是以进程上下文的形式连接到内核中的,因此,在进程上下文可以睡眠,也可以调用调度程序。
与之相反,中断上下文和进程并没有什么瓜葛,与current宏也是不相干的(尽管它会指向被中断的进程),因为没有进程的背景,所以中断上下文不可以睡眠——否则又怎能对它重新调度呢?因此,不能从中断上下文中调用某些函数,如果一个函数睡眠,就不能在你的中断处理程序中使用它——这是对什么样的函数可以在中断处理程序中的使用的限制。
有一点非常重要:中断处理程序打断了其它代码(甚至可能是打断了在其他中断线上的另一个中断处理程序)。正是因为这种异步执行的特性,所以所有的中断处理程序必须尽可能的迅速,简洁,尽量把工作从中断处理程序中分离出来,放在下半部执行,因为下半部可以在更合适的时间运行。
最后,中断处理程序并不具有自己的栈,相反,它共享被中断进程的内核栈,如果没有正在运行的进程,它将使用idle进程的栈,因为中断处理程序共享别人的堆栈所以它们在栈中获取空间时必须非常节省,当然,内核栈本来就很有限(内核栈在32位体系结构上是8KB,在64位体系结构上是16KB,执行中的进程上下文和产生的所有中断都共享内核栈)。因此所有的内核代码都应该谨慎利用它。
5.5中断处理机制的实现
中断处理系统在Linux中的实现是非常依赖于体系结构的。实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。
对于中断从硬件到内核的路由,设备产生中断,通过总线把电信号发送给中断控制器。如果中断是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号,处理器会立即停止它正在做的事情,关闭中断系统,然后跳转到内存中预定义的位置开始执行那里的代码。这个预定以的位置是内核设置的,是中断处理程序的入口点。
在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。对于每一个中断线,处理器都会跳到对应的一个唯一的位置。至此,内核知道了所接受的中断的IRQ号。初始入口点只是在栈中保存这个号,并存放当前寄存器的值(这些值属于被中断的任务),然后,内核调用函数do_IRQ()。
5.6中断控制
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