📄 (ldd) ch09-中断处理(上)(转载).htm
字号:
color=#ffffff size=3>
<P>定probe=1(内核帮助下的检测),此时可观察到中断计数器没有加1,而如果装载时指定p<BR>robe=2(DIY检测)则会将它们加1。<BR> <BR> <BR> <BR> 慢速中断的汇编入口点会将所有寄存器保存到堆栈中,并将数据段(DS和ES处理<BR>器寄存器)指向核心地址空间(处理器已经设置了CS寄存器)。然后代码将将中断转交给PI<BR>C,禁止在相同的中断信号线上触发新的中断,并发出一条sti指令(set interrupt<BR>flag,置中断标志位)。注意处理器在对中断进行服务时会自动清除该标志位。接着慢速<BR>中断处理程序就将中断号和指向处理器寄存器的一个指针传递给do_IRQ,这是一个C函数<BR>,由它来调用相应的C语言处理程序。驱动程序传递给中断处理程序参数struct<BR>pt_regs *是一个指向存放着各个寄存器的堆栈的指针。<BR> <BR> <BR> <BR> do_IRQ结束后,会发出cli指令,打开PIC中指定的中断,并调用ret_from_sys_c<BR>all。最后这个入口点(arch/i386/kernel/entry.S)从堆栈中恢复所有的寄存器,处理所<BR>有待处理的下半部处理程序(参见本章的“下半部”一节),并且,如果需要的话,重新<BR>调度处理器。<BR> <BR> <BR> <BR> 快入口点不同的是,在跳转到C代码之前并不调用sti指令,并且在调用do_fast_<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 快入口点不同的是,在跳转到C代码之前并不调用sti指令,并且在调用do_fast_<BR>IRQ前并不保存所有的机器寄存器。当驱动程序中的处理程序被调用时,regs参数是NULL<BR>(空指针,因为寄存器没有保存到堆栈中)并且中断仍被屏蔽。<BR> <BR> <BR> <BR> 最后,快速中断处理程序会重新打开8259芯片上的所有中断,恢复先前保存的所<BR>有寄存器,并且不经过ret_from_sys_call就返回了。待处理的下半部处理程序也不运行<BR>。<BR> <BR> <BR> <BR> 2.1.34前的所有内核版本中,这两种处理程序在将控制转移给C代码前都会将int<BR>r_count变量加1(参见第6章“时间流”的“任务队列的特性”一节)。<BR> <BR>实现中断处理程序<BR> 至此,我们学习了如何注册一个中断处理程序,但还并没有真正编写这样的一个<BR>处理程序。实际上,处理程序并没有什么特别的-就是普通的C代码。<BR> <BR> <BR> <BR> 唯一特别的地方就是处理程序是在中断时间内运行的,因此它的行为要受些限制<BR>。这些限制和我们在任务队列中看到的差不多。处理程序不能向用户空间发送或接受数<BR></P></FONT><FONT
color=#ffffff size=3>
<P>。这些限制和我们在任务队列中看到的差不多。处理程序不能向用户空间发送或接受数<BR>据,因为它不在任何进程的上下文中执行。快速中断处理程序,可以认为是原子地执行<BR>的,当访问共享的数据项时并不需要避免竞争条件。而慢速处理程序不是原子的,因为<BR>在运行慢速处理程序时也能为其它处理程序提供服务。<BR> <BR> <BR> <BR> 中断处理程序的功能就是将有关中断接收的信息反馈给设备,并根据要服务的中<BR>断的不同含义相应地对数据进行读写。第一步通常要先清除接口卡上的一个位;大部分<BR>硬件设备在它们的“中断待处理”位被清除前是不会产生任何中断的。一些设备就不需<BR>要这一步,因为它们没有“中断待处理”位;这样的设备比较少,但并口却是其中之一<BR>。因此,short不需要清除这样的位。<BR> <BR> <BR> <BR> 中断处理程序的典型任务是唤醒在设备上睡眠的那些进程——如果中断向这些进<BR>程发出了信号,指示它们等待的事件已经发生,比如,新数据到达了。<BR> <BR> <BR> <BR> 还举老的帧捕获卡的例子,进程可以通过连续地对设备读来获取一系列的图像;<BR>每读一帧后read调用都被阻塞,而新的帧一到达中断处理程序都会唤醒该进程。这假定<BR>了捕获卡会中断处理器来发出信号通知每一帧的成功到达。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>了捕获卡会中断处理器来发出信号通知每一帧的成功到达。<BR> <BR> <BR> <BR> 不论是快速还是慢速中断处理程序,程序员都要注意处理例程的执行时间必须尽<BR>可能短。如果要进行长时间的计算,最好的方法是使用任务队列,将计算调度到安全时<BR>间内进行(参见第6章的“任务队列”一节)。这也是需要下半部处理的一个原因(参见本<BR>章稍后的“下半部”)。<BR> <BR> <BR> <BR> short中的范例代码使用中断来调用do_gettimeofday并把当前时间打印到大小为<BR>一页的循环缓冲区。然后它唤醒所有的读进程(实际上由于short使用快速中断处理程序<BR>,这些读进程只会在下一个慢速中断处理程序结束时或下一个时钟滴答时醒来)。<BR> <BR> <BR> <BR>void short_interrupt(int irq, void *dev_id, struct pt_regs *regs)<BR> <BR>{<BR> <BR> struct timeval tv;<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> do_gettimeofday(&tv);<BR> <BR> <BR> <BR> /* 写一个16个字节的记录。假设 PAGE_SIZE是16的倍数 */<BR> <BR> short_head += sprintf((char *)short_head,"%08u.%06u\n",<BR> <BR> (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));<BR> <BR> if (short_head == short_buffer + PAGE_SIZE)<BR> <BR> short_head = short_buffer; /* 绕回来 */<BR> <BR> <BR> <BR> wake_up_interruptible(&short_queue); /* 唤醒所有的读进程 */<BR> <BR>}<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> 这段代码,尽管简单,却给出了一个中断处理程序的典型工作流程。<BR> <BR> <BR> <BR> 用来读取在中断时间里填满的缓冲区的节点是/dev/shortint。这是唯一的没有<BR>在第8章中介绍的short设备节点。/dev/shortint内部的实现为中断产生和报告作了特别<BR>的处理。每向设备写入一个字节都会产生一个中断;而读设备时则给出每次中断报告的<BR>时间。<BR> <BR> <BR> <BR> 如果你将并口插座的第9和第10引脚相连,那么拉高并行数据字节的最高位就可<BR>以产生中断。这可以通过向/dev/short0写二进制数据或者向/dv/shortint*写入任意数<BR>据来实现。<BR> <BR> <BR> <BR> 下面的代码是/dev/shortint的read和write的实现:<BR> <BR> <BR> <BR>read_write_t short_i_read (struct inode *inode, struct file *filp,<BR></P></FONT><FONT
color=#ffffff size=3>
<P>read_write_t short_i_read (struct inode *inode, struct file *filp,<BR> <BR> char *buf, count_t count)<BR> <BR>{<BR> <BR> int count0;<BR> <BR> <BR> <BR> while (short_head == short_tail) {<BR> <BR> interruptible_sleep_on(&short_queue);<BR> <BR> if (current->signal & ~current->blocked) /* 有信号到达 */<BR> <BR> return -ERESTARTSYS; /* 通知fs层去处理它 */<BR> <BR> /* 否则,再次循环 */<BR> <BR> }<BR> <BR> /* count0 是可以读进来的数据字节个数 */<BR></P></FONT><FONT color=#ffffff size=3>
<P> /* count0 是可以读进来的数据字节个数 */<BR> <BR> count0 = short_head - short_tail;<BR> <BR> if (count0 < 0) /* wrapped */<BR> <BR> count0 = short_buffer + PAGE_SIZE - short_tail;<BR> <BR> if (count0 < count) count = count0;<BR> <BR> <BR> <BR> memcpy_tofs(buf, (char *)short_tail, count);<BR> <BR> short_tail += count;<BR> <BR> if (short_tail == short_buffer + PAGE_SIZE)<BR> <BR> short_tail = short_buffer;<BR> <BR> return count;<BR> <BR>}<BR></P></FONT><FONT
color=#ffffff size=3>
<P>}<BR> <BR> <BR> <BR>read_write_t short_i_write (struct inode *inode, struct file *filp,<BR> <BR> const char *buf, count_t count)<BR> <BR>{<BR> <BR> int written = 0, odd = filp->f_pos & 1;<BR> <BR> unsigned port = short_base; /* 输出到并口数据锁存器 */<BR> <BR> <BR> <BR> while (written < count)<BR> <BR> outb(0xff * ((++written + odd) & 1), port);<BR> <BR> <BR> <BR> filp->f_pos += count;<BR></P></FONT><FONT
color=#ffffff size=3>
<P> filp->f_pos += count;<BR> <BR> return written;<BR> <BR>}<BR> <BR>使用参数<BR> 虽然short中不对参数进行处理,但还是有三个参数被传给了中断处理函数:irq<BR>,dev_id和regs。下面我们看看每个参数的意义。<BR> <BR> <BR> <BR> 当用一个处理程序来同时对若干个设备进行处理并且使用不同的中断信号线,那<BR>么中断号(int irq)就很有用了。例如,立体视频系统就使用了两个中断来支持两个帧捕<BR>捉卡。驱动程序必须能检测两个设备,并且安装一个处理程序来对两个中断进行处理。<BR>驱动程序就可以使用irq参数来通知处理程序是哪个设备发出了中断。<BR> <BR> <BR> <BR> 例如,如果驱动程序声明了一个设备结构的数组hwinfo,每个元素都有一个irq<BR>域,那么下面的代码可以在中断到达时选取出正确的设备。这段代码的设备前缀是cx。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR>static void cx_interrupt(int irq)<BR> <BR>{<BR> <BR> /* “Cxg_Board”是硬件信息的数据类型 */<BR> <BR> Cxg_Board *board; int i;<BR> <BR> <BR> <BR> for (i=0, board=hwinfo; i>cxg_boards; board++,i++)<BR> <BR> if (board->irq==irq)<BR> <BR> break;<BR> <BR> <BR> <BR> /* 现在'board' 指向了正确的硬件描述 */<BR> <BR> /* .... */<BR></P></FONT><FONT
color=#ffffff size=3>
<P> /* .... */<BR> <BR>}<BR> <BR> <BR> <BR> 第二个参数,void *dev_id,是一种ClientData;是传递给request_irq函数的<BR>一个void *类型的指针,并且当中断发生时这个设备ID还会作为参数传回给处理程序。<BR>参数dev_id是在1.3.70版的Linux中引入以处理共享中断,但即使不共享它也很有用。<BR> <BR> 假定我们例子中的设备是象下面这样注册它的中断的(这里board->irq是要申请<BR>的中断,board是ClientData)<BR> <BR> <BR> <BR>static void cx_open(struct inode *inode, struct file *filp)<BR> <BR>{<BR> <BR> Cxg_Board *board=hwinfo+MINOR(inode->i_rdev);<BR> <BR> Request_irq(board->irq, cx_interrupt, 0, "cx100", board /* dev_id<BR>*/);<BR></P></FONT><FONT
color=#ffffff size=3>
<P>*/);<BR> <BR> /* .... */<BR> <BR> return 0;<BR> <BR>}<BR> <BR> <BR> <BR> 这样处理程序的代码就可以缩减如下:<BR> <BR> <BR> <BR>static void cx_interrupt(int irq, void *dev_id, struct pt_regs *regs)<BR> <BR>{<BR> <BR> Cxg_Board *board=dev_id;<BR> <BR> <BR> <BR> /* 现在'board' 指向了正确的硬件项 */<BR></P></FONT><FONT
color=#ffffff size=3>
<P> /* 现在'board' 指向了正确的硬件项 */<BR> <BR> /* .... */<BR> <BR>}<BR> <BR> <BR> <BR> 最后一个参数,struct pt_regs *regs,很少使用。它存放着在处理器进入中断<BR>代码前的一个处理器上下文的快照。这些寄存器可用于监控和调试,实际上show_regs函<BR>数(它是按下RightAlt-PrScr键时由键盘中断启动的调试函数-第4章“调试技术”的“<BR>系统挂起”一节)就是使用它们来实现监控和调试的。<BR> <BR>打开和禁止中断<BR> 有时驱动程序要打开和禁止它相应IRQ信号的中断报告。内核为此提供了两个函<BR>数,都在头文件<asm/irq.h>中声明:<BR> <BR> <BR> <BR>void disable_irq(int irq);<BR> <BR>void enable_irq(int irq);<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR> 调用其中任一函数都会更新PIC中对指定的irq的掩码。<BR> <BR> <BR> <BR> 实际上,当中断被禁止后,那么即使硬件急需处理,处理器也得不到报告。例如<BR>,“x86上中断处理的内幕”一节中就介绍了“伪”处理程序在x86上的实现就禁止了它<BR>收到的所有中断。<BR> <BR> <BR> <BR> 但是,为什么我们要禁止中断呢?还是举并口的例子,我们看看plip(并行IP)网<BR>络接口。plip设备使用裸的(bare-bones)并口来传输数据。因为只能从并口读出5个位,<BR>它们就被解释为四个数据位和一个时钟/握手信号。当发起者(即发送数据包的那个接口)<BR>送出数据包的第一个位时,时钟信号会升高,接收方接口就会中断处理器。然后plip处<BR>理程序被调用来处理新到达的数据。<BR> <BR> <BR> <BR> 在设备被激活后,开始数据传输,使用握手信号将新数据按时钟周期传送给接收<BR>接口(这可能不是最好的实现方法,但只有这样才能和其它使用并口的数据包驱动程序兼<BR></P></FONT><FONT
color=#ffffff size=3>
<P>接口(这可能不是最好的实现方法,但只有这样才能和其它使用并口的数据包驱动程序兼<BR>容)。如果接收接口为接收每个字节(8个位)都要处理两次中断,那性能必然不可忍受。<BR>因此驱动程序在接收数据包时要禁止中断。<BR> <BR> <BR> <BR> 同样的,因为从接收方到发送方的握手信号用于确认数据的接收,发送接口也要<BR>在发送数据包时禁止它的中断信号。<BR> <BR> <BR> <BR> 但要注意的是,因为处理程序本身无法打开和禁止中断信号。存在这个限制是因<BR>为,如上所述,内核在调用处理程序前会禁止中断,而在处理程序结束后又会重新打开<BR>它。但打开和禁止中断仍可以做到,只要在下半部处理程序中作就可以了(参见下一节)<BR>。<BR> <BR> <BR> <BR> 最后值得注意的是,在Sparc实现中,disable_irq和enable_irq都被定义为指针<BR>而不是函数。这个小技巧允许内核在启动检测你是在运行哪种Sparc时对指针进行相应的<BR>赋值(Sun4c和Sun4m的中断硬件不相同)。而所有的Linux系统上,不管使不使用这种小技<BR>巧,函数在C语言中的语义都相同,这就避免了编写那些冗长无味的条件编译代码。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>下半部<BR>中断处理的一个主要问题是如何在处理程序中完成比较耗时的任务。Linux解决这个问题<BR>的方法是将中断处理程序划分成两个部分:所谓的“上半部”是你通过request_irq函数<BR>注册的处理例程,而“下半部”(bottom half,简称为“bh”)则是由上半部调度到以后<BR>在更安全的时间内执行的那部分例程。<BR> <BR> <BR> <BR>但是下半部有什么用呢?<BR> <BR> <BR> <BR>上半部和下半部处理程序最大的不同就在于在执行bh是所有的中断都是打开的---所以<BR>说它是在“更安全”时间内运行。典型的情况是,上半部处理程序将设备数据存放进一<BR>个设备指定的缓冲区,再标记它的下半部,然后退出;这样处理得就非常快。由bh将新<BR>到的数据再<BR>--<BR> <BR><FONT
color=#00ff00>※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]</FONT><BR>--<BR><FONT
color=#00ffff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>但是下半部有什么用呢?<BR> <BR> <BR> <BR>上半部和下半部处理程序最大的不同就在于在执行bh是所有的中断都是打开的---所以<BR>说它是在“更安全”时间内运行。典型的情况是,上半部处理程序将设备数据存放进一<BR>个设备指定的缓冲区,再标记它的下半部,然后退出;这样处理得就非常快。由bh将新<BR>到的数据再<BR>--<BR><FONT
color=#00ff00>※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]</FONT><BR>--<BR><FONT
color=#00ffff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#0000ff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#ffff00>※ 转载:.南京大学小百合站 bbs.nju.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -