中断处理

正如前面解释的那样,内核只要给引起异常的进程发送一个Unix信号就能处理大多数异常。因此,要采取的行动被廷迟,直到进程接收到这个信号。所以,内核能很快地处理异常。

这种方法并不适合中断,因为经常会出现一个进程被挂起好久后中断才到达的情况,因此,一个完全无关的进程可能正在运行。所以,给当前进程发送一个Unix信号是毫无意义的。

中断处理依赖于中断类型。就我们的目的而言,我们将讨论三种主要的中断类型:

I/O中断

某些I/O设备需要关注;相应的中断处理程序必须查询设备以确定适当的操作过程。我们在后面”I/O中断处理”一节将描述这种中断。

时钟中断

某种时钟产生一个中断;这种中断告诉内核一个固定的时间间隔已经过去。这些中断大部分是作为I/O中断来处理的;我们将在第六章讨论时钟中断的具体特征。

处理器间中断

多处理器系统中一个CPU对号一个CPU发出一个中断。我们在后面”处理器间中断处理”一节讨论这种中断。


I/O中断处理

一般而言,I/O中断处理程序必须足够灵活以给多个设备同时提供服务。例如在PCI总线的体系结构中,几个设备可以共享同一个IRQ线。这就意味着仅仅中断向量不能说明所有问题。在表4-3所示的例子中,同一个向量43既分配给USB端口,也分配给声卡。然而,在老式PC体系结构中发现的一些硬件设备,当它们的IRQ与其它设备共享是,就不能可靠地运转。

中断处理程序的灵活性是以两种不同的方式实现的,讨论如下:

IRQ共享

中断处理程序执行多个中断服务例程。每个ISR是一个与单独设备相关的函数。因为不可能预先知道哪个特定的设备产生IRQ,因此,每个ISR都被执行,以验证它的设备是否需要关注;如果是,当设备产生中断时,就执行需要执行的所有操作。

IRQ动态分配

一条IRQ线在可能的最后时刻与一个设备驱动程序相关联;例如,软盘设备的IRQ线只有在用户访问软盘设备时才被分配。这样,即使几个硬件设备并不共享IRQ线,同一个IRQ向量也可以由这几个设备在不同时刻使用。

当一个中断产生时,并不是所有的操作都具有相同的急迫性。事实上,把所有的操作都放时中断处理程序本身是不合适。需要时间长的、非重要的操作应该推后,因为当一个中断处理程序正在运行时,相应的IRQ线上发出的信号就被暂时忽略。更重要的是,中断处理程序是代表进程执行的,它所代表的进程必须总处于TASK_RUNNING状态,否则,就可能出现系统僵死情形。因此,中断处理程序不能执行任何阻塞过程,如磁盘I/O操作。因此,Linux把紧随中断要执行的操作分为三类:

紧急的

这样的操作诸如:对PIC应答中断,对PIC或设备控制器重编程,或者修改由设备和处理器同时访问的数据结构。这些都能被很快地执行,而之所以说它们是紧急的是因为它们必须尽快地执行。紧急操作要在一个中断处理程序内立即执行,而且是在禁止可屏蔽中断的情况下

非紧急的

这样的操作诸如:修改那些只有处理器才会访问的数据结构。这些操作也要很快地完成,因此,它们由中断处理程序立即执行,但必须是在开中断的情况下。

非紧急可廷迟的

这样的操作诸如:把缓冲区的内容拷贝到某个进程的地址空间。这些操作可能被廷迟较长的时间间隔而不影响内核操作。

不管引起中断的电路种类如何,所有的I/O中断处理程序都执行四个相同的基本操作:

  1. 在内核态堆栈中保存IRQ的值和寄存器的内容。

  2. 为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断。

  3. 执行共享这个IRQ的所有设备的中断服务例程。

  4. 跳到ret_from_intr()的地址终止。

当中断发生时,需要用几个描述符表示IRQ线的状态和需要执行的函数。图4-4以示意图的方式展示了处理一个中断的硬件电路和软件函数。下面几节会讨论这些函数。


中断向量

如表4-2所示,物理IRQ可以分配给32-128范围内的任何向量。不过,Linux使用向量128实现系统调用。

IBMPc兼容的体系结构要求,一些设备必须被表态地连接到指定的IRQ线。尤其是:

  1. 间隔定时设备必须连到IRQ0线。

  2. 8259APIC必须与IRQ2线相连。

  3. 必须把外部数学协处理器连接到IRQ13线。

  4. 一般而言,一个I/O设备可以连接到有限个IRQ线。

IRQ可配置设备选择一条线有三种方式:

  1. 设置一些硬件跳接器。

  2. 安装设备时执行一个实用程序。这样的程序可以让用户选择一个可用的IRQ号,或者探测系统自身以确定一个可用的IRQ号。

  3. 在系统启动时执行一个硬件协议。外设宣布它们准备使用哪些中断线,然后协商一个最终的值以尽可能减少冲突。该过程一旦完成,每个中断处理程序都能过访问设备某个I/O端口的函数。

4-3显示了设备和IRQ之间一种相当随意的安排,你或许能在某个PC中找到同样的排列。

内核必须在启用中断前发现IRQ号与I/O设备之间的对应,否则,内核不知道哪个向量对应哪个设备的情况下,怎么能处理来自这个设备的信号呢?IRQ号与I/O设备之间的对应是在初始化每个设备驱动程序时建立的。


IRQ数据结构


当讨论到涉及状态转换的复杂操作时,首先了解关键数据存放在什么地方总是有益的。因此,本节将解释支持中断处理的数据结构以及怎样把它们放各种描述符中。图4-5示意性地显示了几个描述符之间的关系,这些描述符表示IRQ线的状态。

每个中断向量都有它自己的irq_desc_t描述符,其字段在表4-4中列出。所有的这些描述符组织在一起形成irq_desc数组。

如果一个中断内核没有处理,那么这个中断就是个意外中断,也就是说,与某个IRQ线相关的中断处理例程不存在,或者与某个个中断级相关的所有例程都识别不出是否是自己的硬件设备发出的中断。通常,内核检查从IRQ线接收的意外中断的数量,当这条IRQ线连接的有故障设备没完没了地发中断时,就禁用这条IRQ线。由于几个设备可能共享IRQ线,内核不会在每检测到一个意外中断时就立刻禁用IRQ线,更合适的办法是:内核把中断和意外中断的总次数分别存放在irq_desc_t描述符的irq_countirqs_unhandled字段中,当第100000次中断是,如果意外中断的次数超过99900,内核才禁用这条IRQ线。

描述IRQ线状态的标志列在表4-5中。


irq_desc_t描述符的depth字段和IRQ_DISABLED标志表示IRQ线是否被禁用。每次调用disable_irq()disable_irq_nosync()函数,depth字段的值增加,如果depth等于0,函数禁用IRQ线并设置它的IRQ_DISABLED标志,相反,每当调用enable_irq()函数,depth字段的值减少,如果depth变为0,函数激活IRQ线并清除IRQ_DISABLED标志。

在系统初始化期间,init_IRQ()函数把每个IRQ主描述符的status字段设置成IRQ_DISABLED。此外,init_IRQ()调用替换由setup_idt()所建立的中断门来更新IDT。这是能过下列语句实现的:

for(i=0;i<NR_IRQS;i++)

if(i+32!=128)

set_intr_gate(i+32,interrupt[i]);

这段代码在interrupt数组中找到用于建立中断门的中断处理程序地址。interrupt数组中的第n项中存放IRQn的中断处理程序的地址。注意:这里不包括与128号中断向量相关的中断门,因为它用于系统调用的编程异常。

Linux除了支持本章前面已提到的8259A芯片外,也支持其它的几个PIC电路,如SMPIOAPICIntelPIIX4的内部8259PICSGIVisualWorkstation CobaltAPIC。为了统一的方式处理所有这样的设备,Linux用了一个”PIC对象”,由PIC名字和7PIC标准方法组成。这种面向对象方法的优点是,驱动程序不必关注安装在系统中的PIC种类。每个驱动程序可见的中断源透明地连接到适当的控制器。定义PIC对象的数据结构叫做hw_interrupt_type

为了简单起见,让我们假定我们的计算机是有两片8259APIC的单处理机,它提供16个标准的IRQ。在这种情况下,有16irq_desc_t描述符,其中每个描述符的handler字段指向描述8259APICi8259A_irq_type变量。这个变量被初始化:


这个结构中的第一个字段”XT-PIC”PIC的名字。接下来就是用于对PIC编程的六个不现的函数指针。前两个函数分别启动和关闭芯片的IRQ线。但是,在使用8259A芯片的情况下,这两个函数的作用与第三、四个函数都是一样的,每三,四函数是启用和禁用IRQ线。mask_and_ack_8259A()函数通过把适当的字节发往8259AI/O端口来应答所接收的IRQend_8259A_irq()函数在IRQ的中断处理程序终止时被调用。最后一个set_affinity()方法置为空:它用在多处理器系统中以声明特定IRQ所在CPU的”亲和力”----也就是说,那些CPU被启用来处理特定的IRQ

如前所述,多个设备能共享一个单独的IRQ。因此,内核要维护多个irqaction描述符,其中的每个描述符涉及一个特定的硬件设备和一个特定的中断。包含在这个描述符中的字段如表4-6所示,标志如表4-7所示。


最后,irq_start数组包含NR_CPUS个元素,系统中的每个CPU对应一个元素。每个元素的类型为irq_cpustat_t,该类型包含几个计数器和内核记录CPU正在做什么的标志。(见表4-8)

IRQ在多处理器系统上的分发

Linux遵循对称多处理模型;这就意味着,内核从本质上对任何一个CPU都不应该有偏爱。因而,内核试图以轮转的方式把来自硬件设备的IRQ信号在所有CPU之间分发。因此,所有CPU服务于I/O中断的执行时间片几乎相同。

在前面”高级可编程中断控制器”一节已提到,多APIC系统有复杂的机制在CPU之间动态分发IRQ信号。

在系统启动的过程中,引导CPU执行setup_IO_APIC_irqs()函数来初始化I/OAPIC芯片。芯片的中断重定向表的24项被填充,以便根据”最低优先级”模式把来自I/O硬件设备的所有信号都传递给系统中的每个CPU。此外,在系统启动期间,所有的CPU都执行setup_local_apic()函数,该函数处理本地APIC的初始化。特别是,每个芯片的任务优先级寄存器都初始化为一个固定的值,这就意味着CPU愿意处理任何类型的IRQ信号,而不是其优先级。Linux内核启动以后再也不修改这个值。

因为所有的任务优先级寄存器都包含相同的值,因此,所以CPU总线是具有相同的优先级。为了突破这种约束,正如前面所解释的那样,多APIC系统使用本地APIC又依次中断它的CPU。这个事件不通报给其它所有的CPU

所有这些都由硬件神奇地完成,因此多APIC系统初始化后无需内核费心。

内核线程为多APIC系统开发了一种优良特性,叫做CPUIRQ亲和力;通过修改I/OAPIC的中断重定向表表项,可以把中断信号发送到某个特定的CPU上。set_ioapic_affinity_irq()函数用来实现这一功能,该函数有两个参数;被重定向的IRQ向量和一个32位掩码。系统管理员通过文件/proc/irq/n/smp_affinity中写入新的CPU位图掩码也可以改变指定中断IRQ的亲和力。


多种类型的内核栈

就像在第三章”标识一个进程”一节所提到的,每个进程的thread_info描述符与thread_union结构中的内核栈紧邻,而根据内核编译是的选项不同,thread_union结构可能占一个页框或两个页框。如果thread_union结构的大小为8KB,那么当前进程的内核栈被用于所有类型的内核控制路径:异常、中断和可廷迟的函数。相反,如果thread_union结构的大小为4KB,内核就使用三种类型的内核栈:

  1. 异常栈,用于处理异常。这个栈包含在每个进程的thread_union数据结构中,因此对系统中的每个进程,内核使用不同的异常栈。

  2. 硬中断请求栈,用于处理中断。系统中的每个CPU都有一个硬中断请求栈,而且每个栈占用一个单独的页框。

  3. 软中断请求栈,用于处理可廷迟的函数。系统中的每个CPU都有一个软中断请求栈,而且每个栈占用一个单独的页框。

所有的硬中断请求存放在handirq_stack数组中,而所有的软中断请求存在softirq_stack数组中,每个数组元素都是跨越一个单独页框的irq_ctx类型的联合体。thread_info结构存放在这个页的低部,栈使用其余的内存空间,注意每个栈向低地址方向增长。所以,硬中断请求栈和软中断请求栈都与第三章”标识一个进程”一节所描述的异常栈很相似,唯一的区别是与每个栈相连的thread_info结构不是与进程而是与CPU相关联的。

handirq_ctxsoftirq_ctx数组使内核能快速确定指定CPU的硬中断请求栈和软中断请求栈,它们包含的指针分别指向相应的irq_ctx元素。


为中断处理程序保存寄存器的值


CPU接收一个中断时,就开始执行相应的中断处理程序代码,该代码的地址存放在IDT的相应门中。与其它上下文切换一样,需要保存寄存器这一点给内核开发者留下有点杂乱的编码工作,因为寄存器的保存和恢复必须用汇编语言代码,但是,在这些操作内部,又期望处理器从C函数调用和返回。在这一节,我们将描述处理寄存器的汇编语言任务,而下一节,我们将讨论在随后调用的C函数中所需的一些技巧。

保存寄存器是中断处理程序做的第一件事情。如前所述,IRQn中断处理程序的地址开始存在interrupt[n]中,然后复制到IDT相应表项的中断门中。

通过文件arch/i386/kernel/entry.S中的几条汇编语言指令建立interrupt数组,数组包括NR_IRQS个元素,这里NR_IRQS宏产生的娄为22416,当内核支持新近的I/OAPIC芯片时,NR_IRQS宏产生的数为224,而当内核支持旧的8259A可编程控制器芯片是,NR_IRQS宏产生数是16。数组中索引为n的元素中存放下面两条汇编语言指令的地址

pushl$n-256

jmpcommon_interrup

结果是把中断号减256的结果保存在栈中。内核用负数表示所有的中断,因为正数用来表示系统调用。当引用这个数时,可以对所有的中断处理程序都执行相同的代码。这段代码开始于标签common_interrupt处,包括下面的汇编语言宏和指令。

common_interrupt:

SAVE_ALL

movl%esp,%eax

call do_IRQ

jmpret_from_intr

SAVE_ALL宏依次展开成下列片段:

cld

push%es

push%ds

push%eax

push%ebp

push%edi

push%esi

push%edx

push%ecx

push%ebx

movl$__USER_DS,%edx

movl%edx,%ds

movl%edx,%es

SAVE_ALL可以在栈中保存中断处理程序可能会使用的所有CPU寄存器,但eflagscseipssesp除外。因为这几个寄存器已经由控制单元自动保存了,然后,这个宏把用户数据段的选择符装到dses寄存器。然后,这个宏把用户数据段的选择符装到dsesp寄存器。

保存寄存器的值以后,栈顶的地址被存放到eax寄存器中,然后中断处理程序调用do_IRQ()函数。执行do_IRQ()ret指令时,控制转到ret_from_intr()


do_IRQ()函数

调用do_IRQ()函数执行与一个中断相关的所有中断服务例程。该函数声明为:

__attribut__((regparm(3)))unsigned int do_IRQ(struct pt_regs *regs)

关键字regparm表示函数到eax寄存器中去找到参数regs的值。如上所见,eax指向被SAVE_ALL最后压入栈的哪个寄存器在栈中的位置。

do_IRQ()函数执行下面的操作:

  1. 执行irq_enter()宏,它使表示中断处理程序嵌套数量的计数器递增。计数器保存在当前进程thread_info结构的preempt_count字段中。

  2. 如果thread_union结构的大小为4KB,函数切换到硬中断请求栈,并执行下面这些特殊步骤:

    a.执行current_thread_info()函数以获取与内核栈相连的thread_info描述符的地址。

b.把上一步获取的thread_info描述符的地址与存放在 harding_ctx[smp_processor_id()]中的地址相比较,如果两个地址相等,说明 内核已经在使用硬中断请求栈,因此跳转到第3步,这种情况发生在内核 处理另外一个中断是又产生了中断请求的时候。

c.这一步必须切换内核栈。保存当前进程描述符指针,该指针在本地CPU irq_ctx联合体中的thread_info描述符的task字段中。完成这一步操作就能在 内核使用硬件中断请求栈时使当前宏预先的期望工作。

d.esp栈指针寄存器的当前值存入本地CPUirq_ctx联合体的 thread_info描述符的previosu_esp字段中。

e.把本地CPU硬中断请求栈的栈顶装入esp寄存器;以前esp的值存入ebx 寄存器。

  1. 调用__do_IRQ()函数,把指针regsregs->orig_eax字段中的中断号传递给该函数

  2. 如果在上面的第2e步已经成功地切换到硬中断请求栈,函数把ebx寄存器中的原始栈指针拷贝到esp寄存器,从而回到以前在用的异常栈或软中断请求栈。

  3. 执行宏irq_exit(),该宏递减中断计数器并检查是否有可廷迟函数正等待执行。

  4. 结束;控制转向ret_from_intr()函数。


__do_IRQ()函数


__do_IRQ()函数接受IRQ号和指向pt_regs结构的指针作为它的参数。

函数相当于下面的代码段;


在访问主IRQ描述符之前,内核获得相应的自旋锁。在第五章我们会看到,自旋锁保护不同CPU的并发访问。在多处理器系统上,这个锁是必要的,因为同类型的其它中断可能产生,其它CPU可能关注新中断的出现。没有自旋锁,主IRQ描述符会被几个CPU同时访问。正如我们会看到的那样,这种情况必须绝对避免。

获得自旋锁后,函数就调用主IRQ描述符的ack方法。如果使用旧的8259APIC,相应的mask_and_ack_8259A()函数应答PIC上的中断,并禁用这条IRQ线。屏蔽IRQ线是为了确保在这个中断处理程序结束前,CPU不进一步接受这种中断的出现。请记住,__do_IRQ()函数是以禁止本地中断运行的;事实上,CPU控制单元自动清eflags寄存器IF标志,因为中断处理程序是通过IDT中断门调用的。不过,我们立即会看到,内核在执行这个中断服务例程之前可能会重新激活本地中断。

然而,在使用I/O高级可编程中断控制器时,事情更为复杂。应答中断依赖于中断类型,可能是由ack方法做,也可能廷迟到中断处理程序结束。在任何一种情况下,我们都认为中断处理程序结束前,本地APIC不进一步接收这种中断,尽管这种中断的进一步出现可能被其它的CPU接受。

现在,__do_IRQ()函数检查是否必须真正地处理中断。在三种情况下什么也不做,这在下面给予讨论:

IRQ_DISABLED被设置

即使相应的IRQ线被禁止,CPU也可能执行__do_IRQ()函数;在后面”挽救丢失的中断”一节你会找到对这种非直觉情况的解释。

IRQ_INPROGRESS被设置

在多处理器系统中,另一个CPU可能处理同一个中断的前一次出现。为什么不把这次出现的中断推迟到哪个CPU上去处理呢?这正是Linux所做的事情。这就导致了较简单的内核结构,因为设备驱动程序的中断服务例程不必是可重入的。此外,释放的CPU很快又返回到它正在做的事上而没有弄脏它的硬件高速缓存;这对系统性能是益的。

irq_desc[irq].actionNULL

当中断没有相关的中断服务例程时出现这种情况下,通常情况下,只有在内核正在探测一个硬件设备时这才会发生。

让我们假定三种情况没有一种成立,因此中断终须被处理。__do_IRQ()设置IRQ_INPROGRESS标志并开始一个循环。在每次循环中,函数清IRQ_PENDING标志,释放中断自旋锁,并调用handle_IRQ_event()执行中断服务例程。当handle_IRQ_event()终止时,__do_IRQ()再次获得自旋锁,并检查IRQ_PENDING标志的值。如果该标志清0,那么,中断的进一步出现不传递给另一个CPU,因此,循环结束。相反,如果IRQ_PENDING被设置,当这个CPU正在执行handle_IRQ_event()时,另一个CPU已经在为这种中断执行do_IRQ()函数。因此,do_IRQ()执行循环的另一次反复,为新出现中断提供服务。

我们的__do_IRQ()函数现在准备终止,或者是因为已经执行了中断服务例程,或者是因为无事可做。函数调用主IRQ描述符的end方法。当使用旧的8259APIC时,相应的end_8259A_irq()函数重新激活IRQ线。当使用I/OAPIC时,end方法应答中断。

最后,__do_IRQ()释放自旋锁;艰难的工作已经完成。


挽救丢失的中断


__do_IRQ()函数小而简单,但在大多数情况下它都能正常工作。的确,IRQ_PENDINGIRQ_INPROGRESSIRQ_DISABLED标志确保中断能被正确地处理,即使硬件失常也不例外。然而,在多处理器系统上事情可能不会这么顺利。

假定CPU有一条激活的IRQ线。一个硬件设备出现在这条IRQ线程上,且多APIC系统选择我们的CPU处理中断。在CPU应答中断前,这条IRQ线被另一个CPU屏蔽掉;结果,IRQ_DISABLED标志被设置。随后,我们的CPU开始处理挂起的断;因此,do_IRQ()函数应答这个中断,然后返回,但没有执行中断服务例程,因为它发现IRQ_DISABLED标志被设置了。因此,在IRQ线禁用之前出现的中断丢失了。

为了应付这种局面,内核用来激活IRQ线的enable_irq()函数先检查是否发生了中断丢失,如果是,该函数就强迫硬件让丢失的中断再产生一次:

函数通过检查IRQ_PENDING标志的值检测一个中断被丢失了。当离开中断处理程序时,这个标志总置为0;因此,如果IRQ线被禁止且该标志被设置,那么,中断的一个出现已经被应答但还没有处理。在这种情况下,hw_resend_irq()函数产生一个新中断。这可以通过强制本地APIC产生一个自我中断来达到。IRQ_REPLAY标志的作用是确保只产生一个自我中断。


中断服务例程

 

如前所述,一个中断服务例程实现一种特定设备的操作。当中断处理程序必须执行ISR时,它就调用handle_IRQ_event()函数。这个函数本质上执行如下步骤:

(1)    如果SA_INTERRUPT标志清0,就用sti汇编语言指令激活本地中断。

(2)    通过下列代码执行每个中断的中断服务例程:

retval =0;

do{

retval != action->handler(irq,action->dev_id,regs);

action = action->next;

}while(action);

在循环的开始,action指向irqaction数据结构链表的开始,而irqaction表示接受中断后要采取的操作

(3)    用cli汇编语言指令禁止本地中断。

(4)    通过返回局部变量retval的值而终止,也就是说,如果没有与中断对应的中断服务例程,返回0;否则返回1.

所有的中断服务例程都作用于相同的参数:

irq

       IRQ号

dev_id

       设备标识符

regs

       指向内核栈的pt_regs结构的指针,栈中含有中断发生后随即保存的寄存器。pt_regs结构包括15个字段。

(1)    开始的9个字段是被SAVE_ALL压入栈中的寄存器的值。

(2)    第10个字段为IRQ号编码,通过orig_eax字段被引用。

(3)    其余的字段对应由控制单元自动压入栈中寄存器的值。

第一个参数允许一个单独的ISR处理几条IRQ线,第二个参数允许一个单独的ISR照顾几个同类型的设备,第三个参数允许ISR访问被中断的内核控制路径的执行上下文,实际上,大多数ISR不使用这些参数。

每个中断服务例程在成功处理完中断后都返回1,也就是说,当中断服务例程所处理的硬件设备发出信号时;否则返回0。这个返回码使内核可以更新在本章前面“IRQ数据结构”一节描述过的伪中断计数器。

当do_IRQ()函数调用一个ISR时,主IRQ描述符的SA_INTERRUPT标志决定是开中断还是关中断,通过中断调用的ISR可以由一种状态转换成相反的状态。在单处理器系统上,这是通过cli和sti。

ISR的结构依赖于所处理设备的特点。我们将在第六章和第十三章给出几个ISR的例子。

 

IRQ线的动态分配

在前面”中断向量”一切已经看到,有几个向量留给特定的设备,而其余的向量都被动态地处理。因此有一种方式,在该方式下同一条IRQ线可以让几个硬件设备使用,即使这些设备不允许IRQ共享。技巧就在于使这些硬件设备的活动串行化,以便一次只能有一上设备拥有这个IRQ线。

在激活一个准备利用IRQ线的设备之前,其相应的驱动程序调用request_irq()。这个函数建立一个新的irqaction描述符,并用参数值初始化它。然后调用setup_irq()函数把这个描述符插入到合适的IRQ链表。如果setup_irq()返回一个出错码,设备驱动程序中止操作,这意味着IRQ线已由另一个设备所使用,而这个设备不允许中断共享。当设备操作结束时,驱动程序调用free_irq()函数从IRQ链表删除这个描述符,并释放相应的内存区。

让我们用一个简单的例子看一下这种方案是怎么工作的。假定一个程序想访问/dev/fd0设备文件对应于系统中的每一个软盘。程序要做到这点,可能通过直接访问/dev/fd0,也可以通过系统上安装一个文件系统。通常将IRQ6分配给软盘控制器,给定这个号,软盘驱动程序发出下列请求:

request_irq(6,floppy_interrupt,SA_INTERRUPT|SA_SAMPLE_RANDOM,”floppy”,NULL);

我们可以观赛到,floppy_interrup()中断服务例程必须以关中断的方式来执行,并且不共享这个IRQ。设备SA_SAMPLE_RANDOM标志意味对软盘的访问是内核用于产生随机数的一个较好的随机事件源。当软盘的操作被终止时,驱动程序就释放IRQ6;

free_irq(6,NULL);

为了把一个irqaction描述符插入到适当的链表中,内核调用setup_irq()函数,传递给这个函数的参数为irq_nr(即IRQ号)和new。这个函数将:

(1)     检查一个设备是否已经在用irq_nr这个IRQ,如果是,检查两个设备的irqaction描述符中的SA_SHIRQ标志是否都指定了IRQ线能被共享。如果不能使用这个IRQ线,则返回一个出错码。

(2)     把new加到由irq_desc[irq_nr]->action指向的链表的末尾。

(3)     如果没有其它设备共享同一个IRA,清new的flags字段的IRQ_DISABLED、IRQ_AUTODETECT、IRQ_WAITING和IRQ_INPROGRESS标志,并调用irq_desc[irq_nr]->handler PIC对象的startup方法以确保IRQ信号被激活。

举一个如何使用setup_irq()的例子,它是从系统初始化的代码中抽出的。内核通过执行time_init()函数中的下列指令,初始化间隔定时器设备的irq0描述符。

struct irqaction irq0=(timer_interrupt,SA_INTERRUPT,0,”timer”,NULL,NULL);

setup_irq(0,&irq0);

首先,类型irqaction的irq0变量被初始化;把handler字段设置成timer_interrupt()函数的地址,flags字段设置成SA_INTERRUPT,name字段设置成”timer”,最后一个字段设备成NULL以表示没有用dev_id值。接下来,内核调用setup_irq()把irq0插入到与IRQ0相关的irqaction描述符链表中。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值