📄 (ldd) ch09-中断处理(上)(转载).txt
字号:
下面的代码是/dev/shortint的read和write的实现:
read_write_t short_i_read (struct inode *inode, struct file *filp,
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 是可以读进来的数据字节个数 */
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;
filp->f_pos += count;
return written;
}
使用参数
虽然short中不对参数进行处理,但还是有三个参数被传给了中断处理函数:irq
,dev_id和regs。下面我们看看每个参数的意义。
当用一个处理程序来同时对若干个设备进行处理并且使用不同的中断信号线,那
么中断号(int irq)就很有用了。例如,立体视频系统就使用了两个中断来支持两个帧捕
捉卡。驱动程序必须能检测两个设备,并且安装一个处理程序来对两个中断进行处理。
驱动程序就可以使用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是在1.3.70版的Linux中引入以处理共享中断,但即使不共享它也很有用。
假定我们例子中的设备是象下面这样注册它的中断的(这里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' 指向了正确的硬件项 */
/* 现在'board' 指向了正确的硬件项 */
/* .... */
}
最后一个参数,struct pt_regs *regs,很少使用。它存放着在处理器进入中断
代码前的一个处理器上下文的快照。这些寄存器可用于监控和调试,实际上show_regs函
数(它是按下RightAlt-PrScr键时由键盘中断启动的调试函数-第4章“调试技术”的“
系统挂起”一节)就是使用它们来实现监控和调试的。
打开和禁止中断
有时驱动程序要打开和禁止它相应IRQ信号的中断报告。内核为此提供了两个函
数,都在头文件<asm/irq.h>中声明:
void disable_irq(int irq);
void enable_irq(int irq);
调用其中任一函数都会更新PIC中对指定的irq的掩码。
实际上,当中断被禁止后,那么即使硬件急需处理,处理器也得不到报告。例如
,“x86上中断处理的内幕”一节中就介绍了“伪”处理程序在x86上的实现就禁止了它
收到的所有中断。
但是,为什么我们要禁止中断呢?还是举并口的例子,我们看看plip(并行IP)网
络接口。plip设备使用裸的(bare-bones)并口来传输数据。因为只能从并口读出5个位,
它们就被解释为四个数据位和一个时钟/握手信号。当发起者(即发送数据包的那个接口)
送出数据包的第一个位时,时钟信号会升高,接收方接口就会中断处理器。然后plip处
理程序被调用来处理新到达的数据。
在设备被激活后,开始数据传输,使用握手信号将新数据按时钟周期传送给接收
接口(这可能不是最好的实现方法,但只有这样才能和其它使用并口的数据包驱动程序兼
接口(这可能不是最好的实现方法,但只有这样才能和其它使用并口的数据包驱动程序兼
容)。如果接收接口为接收每个字节(8个位)都要处理两次中断,那性能必然不可忍受。
因此驱动程序在接收数据包时要禁止中断。
同样的,因为从接收方到发送方的握手信号用于确认数据的接收,发送接口也要
在发送数据包时禁止它的中断信号。
但要注意的是,因为处理程序本身无法打开和禁止中断信号。存在这个限制是因
为,如上所述,内核在调用处理程序前会禁止中断,而在处理程序结束后又会重新打开
它。但打开和禁止中断仍可以做到,只要在下半部处理程序中作就可以了(参见下一节)
。
最后值得注意的是,在Sparc实现中,disable_irq和enable_irq都被定义为指针
而不是函数。这个小技巧允许内核在启动检测你是在运行哪种Sparc时对指针进行相应的
赋值(Sun4c和Sun4m的中断硬件不相同)。而所有的Linux系统上,不管使不使用这种小技
巧,函数在C语言中的语义都相同,这就避免了编写那些冗长无味的条件编译代码。
下半部
中断处理的一个主要问题是如何在处理程序中完成比较耗时的任务。Linux解决这个问题
的方法是将中断处理程序划分成两个部分:所谓的“上半部”是你通过request_irq函数
注册的处理例程,而“下半部”(bottom half,简称为“bh”)则是由上半部调度到以后
在更安全的时间内执行的那部分例程。
但是下半部有什么用呢?
上半部和下半部处理程序最大的不同就在于在执行bh是所有的中断都是打开的---所以
说它是在“更安全”时间内运行。典型的情况是,上半部处理程序将设备数据存放进一
个设备指定的缓冲区,再标记它的下半部,然后退出;这样处理得就非常快。由bh将新
到的数据再
--
※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]
--
※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]
--
但是下半部有什么用呢?
上半部和下半部处理程序最大的不同就在于在执行bh是所有的中断都是打开的---所以
说它是在“更安全”时间内运行。典型的情况是,上半部处理程序将设备数据存放进一
个设备指定的缓冲区,再标记它的下半部,然后退出;这样处理得就非常快。由bh将新
到的数据再
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -