一)、基本概念1、安装中断处理程序系统中中断信号线很有限,有时只有15或16根。内核维护了一个类似于I/O端口注册表的中断信号线的注册表。一个模块可以申请一个中断请求IRQ,处理完以后也可以释放掉它。相关函数:头文件 原型 1)int request_irq(unsigned int irq, void (*handler)(int, void*, struct pt_regs *), unsigned long flags, const char *device, void *dev_id);2)void free_irq(unsigned int irq, void *dev_id); 功能 1)申请中断2)释放中断 返回值 申请中断的函数的返回值为0时表示成功,或者返回一个负的错误码。函数返回-EBUSY通知另一个设备驱动程序已经使用了要申请的中断信号线,这种情况并不常见 参数表 unsigned int irq中断号。某些平台上Linux中断号到硬件中断号的映射并不是一对一的。void (*handler)(int,void *,struct pt_regs *)指向要安装的中断处理函数的指针。unsigned long flags与中断管理有关的各种选项的字节掩码。const char *device传递给request_irq的字符串,在/proc/interrupts中用于显示中断的拥有者。void *dev_id共享中断信号线时用于区别的唯一的标志符,类似于C++中的this指针。设备驱动程序可以自由地任意使用dev_id。除非强制使用中断共享,dev_id通常被置为NULL。unsigned long flags标志位,在flags中可以设置的位是:SA_INTERRUPT设置该位表示这是一个“快速”中断处理程序;否则就是一个“慢速”中断处理程序。SA_SHIRQ该位表明中断可以在设备间共享。SA_SAMPLE_RANDOM该位表明用于中断作于/dev/random和/dev/urandom设备使用熵池(entropy pool)的时候。可以读这些设备返回的真正的随机数,用来帮助应用软件选取用于加密的安全钥匙。这些随机数是从一个熵池中取得的,各种随机事件都会对系统的熵池(无序度)有贡献。需要设备真正随机地产生中断时才需要置上这个标志。
<!--[if !supportEmptyParas]--> <!--[endif]-->中断处理程序可以在驱动程序初始化时或者在设备第一次打开时安装。在init_module函数中申请了一个中断、安装了中断处理程序,会阻碍其它驱动程序使用这个中断,可能形成浪费。所以应该在打开设备时调用request_irq申请中断,在关闭设备时调用free_irq释放中断将允许资源有限的共享。该技术的缺点是你必须为每个设备维护一个记录其打开次数的计数器。如果在同一个模块中控制两个以上的设备,那么仅仅使用模块计数器那还不够。下面这段代码要申请的中断是short_irq。对这个变量的赋值将在后面再给出,因为它与现在的讨论无关。short_base是使用的并口的I/O基地址;写接口的2号寄存器打开中断报告。if (short_irq >=0 ) {result=request_irq(short_irq, short_interrupt, SA_INTERRUPT, "short", NULL);if (result) {printk(KERN_INFO "short: can't get assigned irq %i/n", short_irq);short_irq=-1;}else {outb(0x10, short_base+2);}}这段代码显示安装的处理程序是个快速中断处理程序(SA_INTERRUPT),不支持中断共享(没有设置SA_SHIRQ),并且对系统熵池无贡献(没有设置SA_SAMPLE_RANDOM)。然后调用outb打开并口的中断报告。2、自动检测中断号驱动程序初始化时需要确设备要使用哪条中断信号线。驱动程序需要以此来安装正确的处理程序。有时自动检测依赖于设备使用的缺省值。例如: if (short_irq<0) /* 尚未指定:强制为缺省的 */switch(short_base){case 0x378: short_irq=7; break;case 0x278: short_irq=2; break;case 0x3bc: short_irq=5; break;}这段代码根据选定的I/O地址来分配中断号,但也允许用户在装载驱动程序时通过调用insmod short short_irq=x来覆盖缺省值。short_base缺省为0x378,因此short_irq缺省为7。有时驱动程序可以通过读设备的某个I/O端口的一个状态字节来获得中断号。这时自动检测中断号就是探测设备,不需要额外工作来探测中断。PCI标准就要求外围设备声明要使用的中断信号线。有些设备需要自动检测:驱动程序使用设备产生中断,然后观察哪一条中断信号线被激活了。自动检测函数在实现时有两种方法:调用内核定义的帮助函数和实现我们自己的版本。1)核心帮助下的检测主流的内核版本都提供两个函数用于探测中断号。都在头文件 中声明:unsigned long probe_irq_on(void);函数返回未被分配的中断的位掩码。这个位掩码需要保留并需要它传递给probe_irq_off函数。int probe_irq_off(unsigned long);这个函数在申请了中断后使用,参数是上一函数获得的中断位掩码;函数将使设备产生中断,并返回“启动探测”后发出的中断次数。没有中断就返回0。产生了多次中断将返回一个负值。处理时要在调用probe_irq_on后启动设备,并在调用probe_irq_off后关闭。此外,在调用probe_irq_off之后,不要忘了处理你的设备尚未处理的那些中断。如下例:int count=0;do {unsigned long mask;mask=probe_irq_on();outb_p(0x10, short_base+2); /* 启动中断报告 */outb_p(0x00,short_base); /* 清位 */outb_p(0xFF, short_base); /* 置位:中断!*/outb_p(0x00, short_base+2); /* 关闭中断报告 */short_irq=probe_irq_off(mask);if (short_irq==0){ /* 没有探测到中断报告?*/printk(KERN_INFO "short: no irq reported by probe/n");short_irq=-1;}/** 如果激活了一个以上的中断,结果就是负的。我们将为中断提供服务(除非是lpt* 端口)并且再次进行循环。最多循环5次,然后放弃*/} while (short_irq<0 && count++<5);if (short_irq<0)printk("short: probe failed %i times, giving up/n",count);探测很耗时,最好就只在模块初始化时探测中断信号线一次。2)DIY(Do It Yourself自己做)检测探测也可以有驱动程序自己较容易地实现。实现机制和内核帮助下的检测是一样的,一般情况下,有些中断号已经被占用,只需要探测其它一些中断号。例:下面的代码实现自动测试。trials数组列出所有要尝试的中断号,0是该列表的结束标志;trials数组用于记录实际上哪个处理程序被驱动程序注册了。int trials[]={3,5,7,9,0};int tried[]={0,0,0,0,0};int i,count=0;/**为所有可能的中断信号线安装探测处理程序。记录下结果(0表示成功,-EBUSY*表示失败)以便只释放申请的中断*/for (i=0; trials[i]; i++)tried[i]=request_irq(trials[i], short_probing, SA_INTERRUPT, "short probe", NULL);do {short_irq=0; /*尚末取得中断号 */outb_p(0x10, short_base+2); /* 启动 */outb_p(0x00, short_base);outb_p(0xFF, short_base); /* 置位 */outb_p(0x10, short_base+2); /* 关闭 *//* 处理程序已经设置了这个值 */if (short_irq==0) { /*printk(KERN_INFO "short: no irq reported by probe/n");}/** 如果激活了一个以上的中断,结果就是负的。我们将为中断提供服务(除非是lpt* 端口)并且再次进行循环。最多这样做5次*/} while(short_irq<=0 && count++<5);/* 循环结束,卸载处理程序 */for (i=0; trials[i]; i++)if (tried[i]==0)free_irq(trials[i],NULL);if (short_irq<0)printk("short: probe failed %i times, giving up/n",count);在事先不知道哪些中断号已经被占用时。就需要探测所有空闲的中断,即从0号中断到NR_IRQS-1号中断,NR_IRQS是在头文件中定义的与平台无关的常数。例:处理程序的功能是根据实际接收到的中断号来更新short_irq变量。0表示无,负数表示二义检测。void short_probing(int irq, void *dev_id, struct pt_regs *regs){if (short_irq == 0) short_irq = irq; /* 找到 */if (short_irq != irq) short_irq = -irq; /* 有二义性 */}3、快速和慢速中断处理快速中断处理程序在处理时设置了处理器标志位(IF),表示不允许被中断,这保证了中断的原子处理,而调用慢速中断处理时,其它中断仍可以得到服务。但在中断处理前,不管是快速还是慢速中断处理程序,内核都要关闭刚才发出报告的那个中断信号线。当处理程序还在处理上一个中断时,如果设备又发出新的中断,新的中断将会丢失。中断控制器并不缓存被屏蔽的中断,但是处理器会进行缓存,快速中断处理程序运行时会关闭微处理器的中断报告,中断控制器禁止了被服务这个中断。中断处理程序在处理后可以通过调用sti来启动处理器的中断报告,微处理器就会处理被缓存的中断。sti函数是“置中断标志位”处理器指令。慢速处理程序运行时是启动了处理器的中断报告的,但中断控制器也禁止了正在被服务这个中断。两种中断处理给内核带来的额外开销也不同。慢速中断处理程序会给内核带来的一些管理开销。因此此较频繁(每秒大于100次)的中断最好由快速中断处理程序为之提供服务。4、x86平台上中断处理的内幕最底层的中断处理是在头文件irq.h中的声明为宏的一些汇编代码,这些宏在文件irq.h中被扩展。为每个中断声明了三种处理函数:慢速,快速和伪处理函数。“伪”处理程序最小,是在没有为中断安装C语言的处理程序时的汇编入口点。它将中断转交给PIC(可编程的中断控制器)设备的同时禁止它。在驱动程序处理完中断信号后调用free_irq时又会重新安装伪处理程序。伪处理程序不会将/proc/stat中的计数器加1。在x86上的自动探测依赖于伪处理程序的这种行为。probe_irq_on启动所有的伪中断,而不安装处理程序;probe_irq_off只是简单地检查自调用probe_irq_on以来那些中断被禁止了。慢速中断的汇编入口点会将所有寄存器保存到堆栈中,并将数据段(DS和ES处理器寄存器)指向核心地址空间(处理器已经设置了CS寄存器)。然后代码将将中断转交给PIC,禁止在相同的中断信号线上触发新的中断,并发出一条sti指令(set interrupt flag,置中断标志位)。处理器在对中断进行服务时会自动清除该标志位。接着慢速中断处理程序就将中断号和指向处理器寄存器的一个指针传递给do_IRQ,这是一个C函数,由它来调用相应的C语言处理程序。驱动程序传递给中断处理程序参数struct pt_regs *是一个指向存放着各个寄存器的堆栈的指针。do_IRQ结束后,会发出cli指令,打开PIC中指定的中断,并调用ret_from_sys_call。最后这个入口点(arch/i386/kernel/entry.S)从堆栈中恢复所有的寄存器,处理所有待处理的下半部处理程序,如果需要的话,重新调度处理器。快入口点不同的是,在跳转到C代码之前并不调用sti指令,并且在调用do_fast_IRQ前并不保存所有的机器寄存器。当驱动程序中的处理程序被调用时,regs参数是NULL(空指针,因为寄存器没有保存到堆栈中)并且中断仍被屏蔽。最后,快速中断处理程序会重新打开8259芯片上的所有中断,恢复先前保存的所有寄存器,并且不经过ret_from_sys_call就返回了。待处理的下半部处理程序也不运行。5、实现中断处理程序处理程序是在中断时间内运行的,它不在任何进程的上下文中执行,就不能向用户空间发送或接受数据。快速中断处理程序,是原子地执行的,当访问共享的数据项时并不需要避免竞争条件。而慢速处理程序不是原子的,在运行慢速处理程序时也能为其它处理程序提供服务。中断处理程序的功能就是将有关中断接收的信息反馈给设备,并根据要服务的中断的不同含义相应地对数据进行读写。对于大部分硬件设备第一步通常要先清除接口卡上“中断待处理”位,这样硬件在该位被清除前就不会产生任何中断。而没有“中断待处理”位的设备不需要这一步,如并口。典型中断处理程序是唤醒在设备上睡眠的那些进程,比如,新数据到达了。老式的帧捕获卡,进程可以通过连续地对设备读来获取一系列的图像;每读一帧后read调用都被阻塞,而新的帧一到达后中断处理程序都会唤醒该进程。。不论是快速还是慢速中断处理程序,处理例程的执行时间必须尽可能短。如果要进行长时间的计算,最好使用任务队列。short范例使用中断来调用do_gettimeofday并把当前时间打印到大小为一页的循环缓冲区。然后它唤醒所有的读进程。void short_interrupt(int irq, void *dev_id, struct pt_regs *regs){struct timeval tv;do_gettimeofday(&tv);/* 写一个16个字节的记录。假设 PAGE_SIZE是16的倍数 */short_head += sprintf((char *)short_head,"u.u/n",(int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));if (short_head == short_buffer + PAGE_SIZE)short_head = short_buffer; /* 绕回来 */wake_up_interruptible(&short_queue); /* 唤醒所有的读进程 */}用来读取在中断时间里填满的缓冲区的节点是/dev/shortint,它内部的实现为中断产生和报告作了特别的处理。每向设备写入一个字节都会产生一个中断;而读设备时则给出每次中断报告的时间。如果你将并口插座的第9和第10引脚相连,那么拉高并行数据字节的最高位就可以产生中断。这可以通过向/dev/short0写二进制数据或者向/dv/shortint写入任意数据来实现。下面的代码是/dev/shortint的read和write的实现:read_write_t short_i_read (struct inode *inode, struct file *filp,char *buf, count_t count){int count0;while (short_head == short_tail) {interruptible_sleep_on(&short_queue);if (current->signal & ~current->blocked) /* 有信号到达 */return -ERESTARTSYS; /* 通知fs层去处理它 *//* 否则,再次循环 */}/* count0 是可以读进来的数据字节个数 */count0 = short_head - short_tail;if (count0 < 0) /* wrapped */count0 = short_buffer + PAGE_SIZE - short_tail;if (count0 < count) count = count0;memcpy_tofs(buf, (char *)short_tail, count);short_tail += count;if (short_tail == short_buffer + PAGE_SIZE)short_tail = short_buffer;return count;}read_write_t short_i_write (struct inode *inode, struct file *filp, const char *buf, count_t count){int written = 0, odd = filp->f_pos & 1;unsigned port = short_base; /* 输出到并口数据锁存器 */while (written < count)outb(0xff * ((++written + odd) & 1), port);filp->f_pos += count;return written;}上面的函数中有三个参数被传给了中断处理函数:irq,dev_id和regs。当用一个处理程序来同时对若干个设备进行处理并且使用不同的中断信号线,那么中断号(int irq)就可以用来通知处理程序是哪个设备发出了中断。例如,如果驱动程序声明了一个设备结构的数组hwinfo,每个元素都有一个irq域,那么下面的代码可以在中断到达时选取出正确的设备。这段代码的设备前缀是cx。static void cx_interrupt(int irq){/* “Cxg_Board”是硬件信息的数据类型 */Cxg_Board *board; int i;for (i=0, board=hwinfo; i>cxg_boards; board++,i++)if (board->irq==irq)break;/* 现在'board' 指向了正确的硬件描述 *//* .... */}第二个参数,void *dev_id,是一种ClientData;是传递给request_irq函数的一个void *类型的指针,并且当中断发生时这个设备ID还会作为参数传回给处理程序。参数dev_id是可以用来处理共享中断,但即使不共享它也很有用。假定我们例子中的设备是象下面这样注册它的中断的(这里board->irq是要申请的中断,board是ClientData)static void cx_open(struct inode *inode, struct file *filp){Cxg_Board *board=hwinfo+MINOR(inode->i_rdev);Request_irq(board->irq, cx_interrupt, 0, "cx100", board /* dev_id */);/* .... */return 0;}这样处理程序的代码就可以缩减如下:static void cx_interrupt(int irq, void *dev_id, struct pt_regs *regs){Cxg_Board *board=dev_id;/* 现在'board' 指向了正确的硬件项 *//* .... */}参数struct pt_regs *regs,很少使用。它存放着在处理器进入中断代码前的一个处理器上下文的快照。这些寄存器可用于监控和调试,show_regs函数(按下RightAlt-PrScr键时由键盘中断启动的调试函数就是使用它们来实现监控和调试的。6、打开和禁止中断有时驱动程序要打开和禁止它相应IRQ信号的中断报告。内核为此提供了两个函数,都在头文件中声明:void disable_irq(int irq);void enable_irq(int irq);调用其中任一函数都会更新PIC中对指定的irq的掩码。当中断被禁止后,处理器将得不到中断报告。但要注意的是,因为处理程序本身无法打开和禁止中断信号。内核在调用处理程序前会禁止中断,而在处理程序结束后又会重新打开它。但打开和禁止中断仍可以做到,只要在下半部处理程序中作就可以了。7、使用/proc查看当处理器被硬件中断时,一个内部计数器会被加1,这为检查设备是否正常工作提供了一个方法。报告的中断显示在文件/proc/interrupts中。下面是该文件的一个快照:0: 537598 timer1: 23070 keyboard2: 0 cascade3: 7930 + serial5: 4568 NE20007: 15920 + short13: 0 math error14: 48163 + ide015: 1278 + ide1第一列是IRQ中断号。该文件只显示已经安装了驱动程序的那些中断。出现在各记录中的加号标志该行中断采用了快速中断处理程序。/proc树中另一个与中断有关的文件是/proc/stat;这个文件记录了系统活动的一些底层的统计信息,包括系统启动以来接收到的中断次数。stat文件的每一行都以一个字符串表示的关键字开始;其中intr标记表示中断相关记录,例如:intr 947102 540971 23346 0 8795 4907 4568 0 15920 0 0 0 0 0 0 48317 1278第一个数(947102)是总的中断次数,后面16个数字表示0~15共16个中断各自的使用次数。interrupts文件与体系结构无关,而stat文件则与体系结构有关:其字段的个数取决于内核之下的硬件。比如在Atari(M68k处理器)上则中断号可以多达72个。<!--[if !supportEmptyParas]--> <!--[endif]-->(二)关于下半部Linux将中断处理程序分成两部分:“上半部”即request_irq函数注册的处理例程,“下半部”则是由上半部调度到以后在更安全的时间内执行的那部分例程,这种机制有助于处理程序中比较耗时的任务。两部分处理程序最大的不同就在于在执行bh时所有的中断都是打开的。典型的情况是,上半部处理程序将设备数据存放进一个设备指定的缓冲区,再标记它的下半部,然后退出;这样处理得就非常快。由bh将新到的数据再分派给各个进程,必要时再唤醒它们。这种设置允许上半部处理程序在下半部还在运行时就能为新的中断提供服务。但是,如果在上半部处理程序结束前有新的数据到了,由于中断控制器禁止了中断信号,这些数据仍会丢失。所有实际的中断处理程序都作了这样的划分。如,当网络接口卡报告新的数据包到达了,处理程序只是取得数据并将它推进协议层中;对数据包的实际处理是在下半部中完成的。实际上,任务队列就是从下半部的一个较老的实现演变而来的。与动态的任务队列不同,下半部的个数有限,并由内核预定义了;下半部的静态特性并不是个问题,因为有些下半部可以通过运行任务队列演变为动态对象。在头文件 中,你可以看到下半部的一张列表; 1、下半部的设计下半部由一个函数指针数组和一个位掩码组成,它们不超过32个。当内核准备处理异步事件时,它就调用do_bottom_half,如从系统调用返回和退出慢速处理程序时;而这两类事件都发生得很频繁。而使用掩码主要出于性能的考虑。当代码需要调度运行下半部处理时,只要调用mark_bh,该函数设置了掩码变量的一个位以将相应的函数进入执行队列。下半部可以由中断处理程序或其它函数来调度。执行下半部时,它会自动去除标记。标记下半部的函数是在头文件 中定义的:void mark_bh(int nr);nr是激活的bh的“数目”。这个数是在头文件 中定义的一个符号常数,它标记位掩码中要设置哪个位。每个下半部bh相应的处理函数由拥有它的那个驱动程序提供。例如,当调用mark_bh(KEYBOARD_BH)时,要调度执行的函数是kbd_bh,它是键盘驱动程序的一部分。因为下半部是静态对象,模块化的驱动程序无法注册自己的下半部。但此时可以使用立即队列。一些比较常见的下半部:IMMEDIATE_BH对设备驱动程序来说这是最重要的bh。被调度执行的函数处理任务队列tq_immediate。没有下半部的驱动程序可以通过使用立即队列来取得和tq_immediate同样的效果。将任务等记到队列中后,驱动程序必须标记bh以使得它的代码真正得到执行; TQUEUE_BH如果任务登记在tq_timer队列中,每次时钟都会激活这个bh。驱动程序也可以使用tq_timer来实现自己的下半部;但不必为定时器队列调用mark_bh。TQUEUE_BH总是在IMMEDIATE_BH后执行的。NET_BH网络驱动程序通过标记这个队列来将事件通知上面的网络层。bh本身是网络层的一部分,模块无法访问。CONSOLE_BH控制台是在下半部中进行终端tty切换的。这个操作要包含进程控制。例如,在X Window系统和字符模式间切换就是由X 服务器控制的。而且,如果键盘驱动程序请求控制台的切换,那么控制台切换不能在中断时进行。也不能在进程向控制台写的时候进行。使用bh就能满足这些要求,因为驱动程序可以任意禁止下半部;如果发生了前面情况,在写控制台时禁止console_bh即可。TIMER_BH这个bh由do_timer函数标记;do_timer函数管理着时钟滴答。这个bh要执行的函数正是驱动内核定时器的那个函数。不使用add_timer的驱动程序是无法使用这种功能的。<!--[if !supportEmptyParas]--> <!--[endif]-->其余的下半部是有特定的内核驱动程序使用的。bh一旦被激活,当在return_from_sys_call中调用do_bottom_half(kernel/softirq.c)时它就会得到执行。当进程退出系统调用或慢速中断处理程序退出时都会执行return_from_sys_call过程。快速中断处理程序退出时就不会执行下半部; 时钟滴答总要执行ret_from_sys_call的;如果快速中断处理程序标记了一个bh,实际的bh处理函数最多10ms后就会被执行。下半部运行后,如果设置了need_resched变量,就会调用调度器;各种wake_up函数都会设置这个变量。因此,上半部可以将任何与被唤醒的进程有关的任务放到下半部去做,并通过设置need_resched调度这些任务。2、编写下半部下半部代码是在安全时间内运行的。但是,bh还是在“中断时间”内处理的。intr_count不为0,因为下半部是在进程上下文之外执行的。因此,针对“任务队列”的各种限制也适用于在下半部中执行的代码。下半部通过暂时禁止中断或使用锁来与上半部中断处理程序共享数据结构,且避免竞争条件。新编写的实现了下半部的驱动程序应该通过使用立即队列来将它的代码挂在IMMEDIATE_BH上。共有三个函数可用于管理自己私有的下半部:init_bh,enable_bh和disable_bh。实际上,立即队列也是一种下半部。当标记了IMMEDIATE_BH后,处理下半部的函数实际上就是去处理立即队列。如果你的中断处理函数将它的bh处理函数排进tq_immediate队列并且标记了下半部,那么队列中的这个任务会正确地被执行。内核都可以将相同的任务多次排队而不破坏任务队列,每次运行上半部处理函数时都可以将下半部排队。一些需要特殊配置的驱动程序需要多个下半部或不能简单地用tq_immediate来设置,就可以使用定制的任务队列。中断处理函数将任务排进自己的队列中,并准备运行这些任务时,就将一个简单的对队列进行处理的函数插入立即队列。例子:装载时如果指定bh=1,那么模块就会安装一个使用了下半部的中断处理函数。short是这样对中断处理进行划分的:上半部(中断处理函数)将当前时间保存到一个循环缓冲区中并调度下半部。而bh将累积的各个时间值打印到一个字符缓冲区,然后唤醒所有的读进程。最后上半部非常简单:void short_bh_interrupt(int irq, void *dev_id, struct pt_regs *regs){do_gettimeofday(tv_head);tv_head++;if (tv_head == (tv_data + NR_TIMEVAL) )tv_head = tv_data; /* wrap *//* 将bh排队。即使被多次排队也没有关系 */queue_task_irq_off(&short_task, &tq_immediate);mark_bh(IMMEDIATE_BH);short_bh_count++; /* 记录一个新的中断到了 */}这段代码调用queue_task却不会检查任务是否已被排进队列。然后,下半部记录下在调度下半部前上半部被激活的次数(savecount)。如果上半部是一个“慢速”处理函数,那么这个数总为1,当慢速处理函数退出时,总会运行待处理的下半部。void short_bottom_half(void *unused){int savecount = short_bh_count;short_bh_count = 0; /* 我们已经从队列中删去*//** 下半部读入由上半部填充的tv数组,并将它打印入循环的字符缓冲区,该缓冲区是* 由读进程处理的*//* 首先写入在这个bh 前发生的中断的次数*/short_head += sprintf((char *)short_head,"bh after %6i/n",savecount);if (short_head == short_buffer + PAGE_SIZE)short_head = short_buffer; /* 绕回来 *//**然后,写入时间值。每次写16个字节。因此与PAGE_SIZE是对齐的*/do {short_head += sprintf((char *)short_head,"u.u/n",(int)(tv_tail->tv_sec % 100000000),(int)(tv_tail->tv_usec));if (short_head == short_buffer + PAGE_SIZE)short_head = short_buffer; /* 绕回来 */tv_tail++;if (tv_tail == (tv_data + NR_TIMEVAL) )tv_tail = tv_data; /* 绕回来 */} while (tv_tail != tv_head);wake_up_interruptible(&short_queue); /* 唤醒所有读进程 */}使用下半部,两个中断间的时间间将减少,但处理中断的总的工作量不变,更快的上半部的优点是禁止中断的时间较短,但对真正的硬件中断来说,这个时间还是很有关系的。下面是当装载short时指定bh=1你可能看到的输出结果:morgana