Embedded 版 (精华区)
发信人: Thinkpad (船长), 信区: Embedded_system
标 题: Ch08-中断处理(下)(zz)
发信站: 哈工大紫丁香 (Sun Jun 24 15:23:50 2001) , 转信
发信人: dot (爱吃萝卜和青菜), 信区: Embedded
标 题: (LDD) Ch08-中断处理(下)
发信站: 武汉白云黄鹤站 (2001年06月16日14:25:09 星期六), 站内信件
共享中断
PC机一个众所周知的“特性”就是不能将不同的设备挂到同一个中断信号线上。
但是,Linux打破了这一点。甚至我的ISA硬件手册―一本没提到Linux的书―也说“最多
只有一个设备”可以挂到中断信号线上,除非硬件设备设计的不好,电信号上并无这样
的限制。问题在于软件。
Linux软件对共享的支持是为PCI设备做的,但也可用于ISA卡。不必说,非PC平
台和总线也支持共享。
为了开发能处理共享中断信号的驱动程序,必须考虑一些细节。下面会讨论到,
使用共享中断的驱动程序不能使用本章描述的一些特性。但最好尽可能对共享中断提供
支持,因为这样对最终用户来说比较方便。
安装共享的处理程序
和已经拥有的中断一样,要与它共享的中断也是通过request_irq函数来安装的
,但它们有两处不同:
l 申请共享中断时,必须在flags参数中指定SA_SHIRQ位
l dev_id参数必须是唯一的。任何指向模块的地址空间的指针都可以,当然dev_
id一定不能设为NULL。
内核为每个中断维护了一张共享处理函数的列表,并且这些处理函数的dev_id各不相同
,就象是驱动程序的签名。如果两个驱动程序都将NULL注册为它们对同一个中断的签名
,那么在卸载时会混淆起来,当中断到达时内核就会出现oops消息。我第一次测试共享
中断时就发生过这种事情(当时我只是想着“一定要将SA_SHIRQ位加到这两个驱动程序上
”)。
满足这些条件之后,如果中断信号线空闲或者下面两个条件同时得到满足,那么request
_irq就会成功:
l 前面注册的处理函数的flags参数指定了SA_SHIRQ位。
l 新的和老的处理函数同为快速处理函数,或者同为慢速处理函数。
需要满足这些要求的原因很明显:快速和慢速处理函数处于不同的环境,不能互相混淆
。类似的,你也不能与已经安装为不共享的中断处理函数共享相同的中断。但关于快速
和慢速处理函数的限制对最近的2.1版的内核来说是不必要的,因为两种处理函数已经合
并了。
当两个或两个以上的驱动程序共享同一根中断信号线,而硬件又通过这根信号线中断了
处理器时,内核激活这个中断注册的所有处理函数,并将自己的dev_id传递给它们。因
此,共享处理函数必须能够识别出它对应于哪个中断。
如果你在申请中断信号之前需要探测你的设备的话,内核无法提供帮助。没有共享中断
的探测函数。仅当使用的中断信号线空闲时,标准的探测机制才能奏效;但如果被其它
的具有共享特性的驱动程序占用的话,那么即使你的程序已经可以正常工作了,探测也
会失败。
那么,唯一的可以用来探测共享中断信号的技术就是DIY探测。驱动程序必须为所有可能
的中断信号线申请共享处理函数,然后观察中断在何处报告。这里和前面介绍的DIY的探
测之间的差别在于,此时探测处理函数必须检查是否真的发生了中断,因为为响应共享
中断信号线上的其它设备的中断它可能已经被调用过了。
释放处理函数同样是通过执行release_irq来实现的。这里dev_id参数用于从该中断的共
享处理函数列表中正确地选出要释放的那个处理函数。这就是dev_id指针必须唯一的原
因。
使用共享处理程序的驱动程序时还要小心:不能使用enable_irq和disable_irq。如果它
使用了这两个函数,共享中断信号线的其它设备就无法正常工作了。一般地,程序员必
须牢记他的驱动程序并不独占这个中断,因此它的行为必须比独占中断信号线时更“社
会化”些。
运行处理函数
如上所述,当内核接收到中断时,所有注册过的处理函数都会被激活。共享中断
处理程序必须能将需要处理的中断和其它设备产生的中断区分开来。
装载short时指定shared=1将安装下面的处理程序而不是缺省的处理程序:
void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int value;
struct timeval tv;
/* 如果不是short,立即返回 */
value = inb(short_base);
if (!(value & 0x80)) return;
/* 清除中断位 */
outb(value & 0x7F, short_base);
/* 其余不变 */
do_gettimeofday(&tv);
short_head += sprintf((char *)short_head,"%08u.%06u\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); /* 唤醒所有读进程 */
}
解释如下。因为并口没有 “待处理的中断”位可供检查,为此处理函数使用了A
CK位。如果该位为高,报告的中断就是送给short的,并且处理函数将清除该位。
处理函数是通过将并口的数据端口的高位清零来清除中断位的-short假定并口
的9和10引脚是连在一起的。如果与short共享同一中断的设备产生了一个中断,short会
知道它的信号线并未激活,因此什么也不会做。
显然,真正的驱动程序做的工作会更多些;特别的,它要使用dev_id参数来得到
自己的硬件结构。
特性完全的驱动程序可能会将工作划分为上半部和下半部,但这很容易添加,对
实现共享的代码并无太大影响。
/proc接口
系统中安装的共享中断处理程序不会影响/proc/stat文件(该文件甚至并不知道
处理程序的存在)。但是,/proc/interrupts文件会有些变化。
为同一个中断号安装的处理程序会出现在/proc/interrupts文件的同一行上。下
面的快照取自我的计算机,是在我将short和我的帧捕捉卡装载为共享中断处理程序之后
:
0: 1153617 timer
1: 13637 keyboard
2: 0 cascade
3: 14697 + serial
5: 190762 NE2000
7: 2094 + short, + cx100
13: 0 math error
14: 47995 + ide0
15: 12207 + ide1
这里共享中断信号是IRQ7号中断;激活的处理程序列在同一行,用逗号隔开。显
然内核是无法区分short中断和捕捉卡(cx100)中断的。
中断驱动的I/O
如果和处理的硬件间的数据传输因为某些原因会被延迟的话,那么驱动程序的写
函数必须实现缓冲。数据缓冲可以将数据的发送和接收与write及read系统调用分离开来
,提高系统的整体性能。
一个好的缓冲机制是“中断驱动的I/O”,它在中断时间内填充一个输入缓冲区
并由读设备的进程将其取空;或由写设备的进程来填充一个输入缓冲区并在中断时间内
将其取空。
中断驱动的数据传输要正确进行,要求硬件必须安下面的语义产生中断:
l 对输入而言,当新数据到达,系统处理器准备读取它时,设备就中断处理器。
实际执行的动作取决于设备是否使用了I/O端口,内存映射或者DMA。
l 对输出而言,当设备准备好接收新数据或对成功的数据传输进行确认时都会发
出中断。内存映射和能进行DMA的设备通常是通过产生中断来通知系统它们的对缓冲区的
处理已经结束。
read或write调用时间和实际的数据到达时间之间的关系是在第5章“字符设备驱动程序
的扩展操作”的“阻塞型和非阻塞型操作”一节中介绍的。中断驱动的I/O引入了共享数
据项的并发进程间的同步问题,因此所有这些问题都与竞争条件有关。
竞争条件
当变量或其它数据项在中断时间内被修改时,由于竞争条件的存在,驱动程序的
操作就有可能造成它们的不一致。当操作不是原子地执行时,竞争条件就会发生,但在
执行时仍假定数据会保持一致性。因此“竞争”是在非原子性的操作和其它可能被同时
执行的代码之间发生的。典型的,竞争条件会在三种情况下发生:在函数内隐式地调用s
chedule,阻塞操作和由中断代码或系统调用访问共享数据。最后一种情况发生得最频繁
,因此我们在这一章处理竞争条件。
处理竞争条件是编程时最麻烦的一部分,因为相关的臭虫满足的条件很苛刻,不
容易再现,很难分辨出中断代码和驱动程序的方法间是否存在竞争条件。程序员必须极
为小心地避免数据或元数据的冲突。
一般用于避免竞争条件的技术是在驱动程序的方法中实现的,这些方法必须保证
当数据项受到没有预料到的修改时得到正确的处理。但另一方面,中断处理函数并不需
要特别的处理,因为相对设备的方法,它的操作是原子性的。
可以使用不同的技术来防止数据冲突,我下面将介绍最常用的一些技术。我不给
出完整的代码,因为各种情况下最好的实现代码取决于被驱动的设备的操作模式以及程
序员的不同爱好。
最常用的防止数据被并发地访问的方法有:
l 使用循环缓冲区和避免使用共享变量。
l 在访问共享变量的方法里暂时禁止中断。
l 使用锁变量,它是原子地增加和减少的。
当访问可能在中断时间内被修改了的变量时,不论你选用的是哪种方法,都必须决定如
何进行处理。这样的变量可以声明为volatile的,来阻止编译器对该值的访问进行优化(
例如,它阻止编译器在整个函数的运行期内将这个值放进一个寄存器中)。但是,使用vo
latile变量后,编译器产生的代码会很糟糕,因此你可能会转向使用cli和sti。Linux实
现这些函数时使用了gcc的制导来保证在中断标志位被修改之前处理器处于安全状态。
使用循环缓冲区
使用循环缓冲区是处理并发访问问题的一种有效方法:当然最好的处理方法还是
不允许并发访问。
循环缓冲区使用了一种被称为“生产者和消费者”的算法-一个进程将数据放进
缓冲区中,另一个则将它取出来。如果只有一个生产者和一个消费者,那就避免了并发
访问。在short模块中有两个生产者和消费者的例子。其中一个情形是,读进程等待消费
在中断时间里生产的数据;而另一个情形是,下半部消费上半部生产的数据。
共有两个指针用于对循环缓冲区进行寻址:head和tail。head是数据的写入位置
,由数据的生产者更新。数据从tail处读出,它是由消费者更新的。正如我上面提到的
,如果数据是在中断时间内写的,那么多次访问head多次时就必须小心。你必须将head
定义成volatile的或者在进入竞争条件前将中断禁止。
循环缓冲区在填满前工作的很好。如果缓冲区满了,就可能出问题,但你可以有
多种不同的解决方法可供选择。short中的实现就是简单地丢弃数据;并不检查溢出,如
果head超过了tail,那么整个缓冲区中的数据都丢失了。其它的实现还有丢弃最后那个
数据项;覆盖缓冲区的tail,printk是这么实现的(参见第4章的“消息是如何记录的”
一节);或者阻塞生产者,scullpipe是这么实现的;或者分配一个临时的附加的缓冲区
作为主力缓冲区的候补。最好的解决方案取决于数据的重要性和其它一些具体情况下的
问题,所以我就不在这讨论了。
虽然循环缓冲区看来解决了并发访问的问题,但当read函数进入睡眠时仍有出现
竞争条件的可能。下面的代码给出short中这个问题出现的位置:
while (short_head==short_tail) {
interruptible_sleep_on(&short_queue);
/* ... */
}
执行这个语句时,新数据有可能在while条件被测试是否为真后和进程进入睡眠
前到达。中断中携带的信息就无法被进程及时读取;因此即使此时head != tail进程也
将进入睡眠,直到下一项数据到达时它才会被唤醒。
我并没有为short实现正确的锁,因为short_read的源码在第8章的“驱动程序样
例”一节中就包括了,当时还没有讨论到这一点。而且,short处理的数据也不值得我们
为它这么做。
尽管short收集的数据并不重要,而且在连续的两条指令时间间隔内发生中断的
可能性小到可以忽略,但是有些时候你还是不能在还有待处理的数据时冒险地进入睡眠
。
但这个问题一般来说还是值得对它进行特别的处理的,我们将它留到本章后面的
“无竞争地进入睡眠”一节,那里我将会更详细地进行讨论。
值得注意的是,循环缓冲区只能处理生产者和消费者的情形。程序员必须经常地
通过更复杂的数据结构来解决并发访问的问题。生产者/消费者的情形实际上是这些问题
中最简单的一种;其它的数据结构,比如象链接表,就不能简单地使用循环缓冲区的实
现方案。
禁止中断
获得对共享数据独占访问的通用方法是调用cli来禁止处理器的中断报告。当数
据项(例如链接表)在中断时间内要被修改并且是被生存于正常的计算流中的函数修改时
,那么随后的函数在访问这些数据前就必须先禁止中断。
这种情况下,竞争条件会发生在读共享数据项的指令和使用刚获得与数据有关的
信息的指令之间。例如,如果链接表在中断时间内被修改过了,那么下面的循环在读这
个表时就可能会失败。
for (ptr=listHead; ptr; ptr=ptr->next)
/* do somthing */;
在ptr已经被读取后但在使用它之前,一个中断可能会改变了ptr的值。如果发生
了这种情况,你一使用ptr就会有问题,因为这个指针当前的值与链接表已经没有关系了
。
一个可能的解决的方法就是在整个关键循环期间都将中断禁止。虽然禁止中断的代码早
在第2章的“ISA内存”一节中就已经引入了,但仍值得在这里再重复一遍:
unsigned long flags;
save_flags(flags);
cli();
/* 临界区代码 */
restore_flags(flags);
实际上,在驱动程序的方法中,可以就用简单的cli/sti对来替代,因为你可以
认为当进程进入系统调用时中断会被打开。但是,在要被其它代码所调用的代码中,你
不得不使用更安全的save_flags/restore_flags解决方法,因为此时无法确定中断标志
位(IF) 当前的值。
使用锁变量
共享数据变量的第三种方法是使用使用原子指令进行访问的锁。当两个无关的实
体(比如象中断处理程序和read系统调用,或者是SMP对称多处理器计算机中的两个处理
器)需要并发地对共享的数据项进行访问时,它们必须先申请锁。如果得不到锁,它就必
须等待。
Linux内核开放了两套函数来对锁进行处理:位操作和对“原子性”数据类型的
访问。
位操作
经常的,我们要使有单个位的锁变量或者要在中断时间内更新设备状态位-而进
程可能正在访问它们。内核为此提供了一套原子地修改和测试位的函数。因为整个操作
是单步完成的,因此不会介入任何中断。
原子性的位操作运行的很快,因为它们通常不禁止中断,使用单条机器指令来完
成相应操作。这些函数与体系结构相关,在头文件<asm/bitops.h>中声明。即使在SMP机
器上它们也能保证是原子的,因此是推荐的保持处理器间一致性的方式。
不幸的是,这些函数的数据类型也是体系结构相关的。nr参数和返回值在Alpha
上是unsigned long类型,而在其它体系结构上是int类型。下面的列表描述了1.2到2.1.
37各版的位操作形式。但该列表在2.1.38版中有了改变,详情可参见第17章“近期发展
”的“位操作”一节。
set_bit(nr, void *addr);
这个函数用于设置addr指向的数据项的第nr个位。该函数作用在一个unsigned long上,
即使addr指向void。返回的是该位原先的取值-0或非零。
clear_bit(nr, void *addr);
这个函数用于清除addr指向的unsigned long数据中的指定位。它的语义和set_bit类似
。
change_bit(nr, void *addr);
这个函数用于切换指定位,其它方面和前面的set_bit和clear_bit函数类似。
test_bit(nr, void *addr);
这个函数是唯一一个不必是原子的为操作;它只是简单地返回该位当前的值。
当这些函数用于访问和修改共享的位时,你只要调用它们即可。而使用位操作来
管理控制共享变量访问的锁变量,则更复杂些,需要举一个例子。
要访问共享数据项的代码段可以使用set_bit或clear_bit来试着原子地获取锁。
通常是象下面的代码段这样实现的;假定锁位于地址addr的第nr位上。并且假定当锁空
闲时该位为0,锁忙时该位非零。
/* 试着设置锁 */
while (set_bit(nr,addr)!=0)
wait_for_a_while();
/* 做你的工作 */
/* 释放锁,并检查... */
if (clear_bit(nr,addr)==0)
something_wnt_wrong(); /* 已经被释放了:出错 */
这种访问共享数据的方式的毛病是竞争双方都必须要等待。如果其中一方是中断
处理程序,那么这一点就较难保证了。
原子性的整数操作
内核程序员经常需要在中断处理程序和其它函数间共享整数变量。我们刚才已经看到对
位的原子访问还不足以保证一切都能运行正常(对前面的例子来说,如果一方是一个中断
处理函数的话,那就必须使用cli)。
实际上,防止竞争条件的需要是如此迫切,以致于内核的开发者为这个问题专门实现了
一个头文件:<asm/atomic.h>。这个头文件比较新,Linux 1.2中就没有提供。因此,需
要向后兼容的驱动程序是不能使用的。
atomic.h中提供的函数比刚才介绍的那些位操作功能更强大。atomic.h中定义了一种新
的数据类型,atomic_t,只能通过原子操作来访问它。
atomic_t目前在所有支持的体系结构上都被定义为int。下面的操作是为这个数据类型所
定义的,能保证SMP机器上的所有处理器是原子地对它进行访问。这些操作都非常快,因
为它们都尽可能编译成单条的机器指令。
void atomic_add(atomic_t i, atomic_t *v);
将v指向的原子变量加上i。返回值是void类型,大部分时候没有必要知道新值。网络部
分的代码使用这个函数来更新套接字在内存使用上的统计信息。
void atomic_sub(atomic_t i, atomic_t *v);
从*v里减去i。在最新的2.1版的内核中这两个函数的参数i都声明成int类型,但这种改
变主要是出于美观的需要,并不对源代码造成影响。
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
对原子变量加减1。
int atomic_dec_and_test(atomic_t *v);
该函数是在1.3.84版的内核里加入的,用于跟踪引用计数。仅当变量*v在减1后取值为0
时返回值为0。
如上所述,只能使用上面这些函数来访问atomic_t类型的数据。如果你将原子数据项传
递给了一个要求参数类型为整型的函数,编译时就会得到警告。不用说,可以读取原子
数据项的当前值并将它强制转换成其它数据类型。
无竞争地进入睡眠
在讨论进入睡眠的问题中我们曾忽略了一个竞争条件。这个问题实际上要比中断
驱动的I/O问题更普遍,而有效的解决方案需要对sleep_on的实现内幕有些了解。
这种特别的竞争条件发生在检查进入睡眠的条件和对sleep_on的实际调用之间。
下面的测试代码和前面使用的代码是一样的,但我觉得还是值得再在这里列出:
while (short_head==short_tail){
interruptible_sleep_on(&short_queue);
/* ... */
}
如果要安全地进行比较和进入睡眠,你必须先禁止中断报告,然后测试条件并进
入睡眠。因此,比较中被测试的变量不会被修改。内核允许进程在发出cli指令后就进入
睡眠。而在将进程插入它的等待队列之后,在调用shcedule之前,内核只要简单地重新
打开中断报告就可以了。
这里给出的例子代码使用了while循环,由该循环来进行信号处理。如果有阻塞
的信号向进程发出报告,interruptible_sleep_on就返回,再次进行while语句中的测试
。
下面是一种可能的实现:
while (short_head==short_tail){
cli();
if (short_head==short_tail)
interuptible_sleep_on(&short_queue);
sti();
/* ... 信号解码 .... */
}
如果中断是在cli后发生的,那么这个中断在当前进程进入睡眠前都会处于待处
理状态。而当中断最终报告给处理器时,进程已经进入了睡眠,可以被安全地唤醒。
在这个例子中,我可以使用cli/sti,是因为设计的这段范例代码存在于read方
法内的;否则我们必须使用更为安全的save_flags,cli,和restore_flags函数。
如果在进入睡眠之前你不想禁止中断,那么还有另一种方法来完成与上面相同的
任务(Linus非常喜欢用这种方法)。但是,如果你愿意的话你可以跳过下面的讨论,因为
下面的讨论的确有点太细了。
该方法的基本想法是,进程可以把自己排进等待队列,声明自己的状态为睡眠状
态,然后执行它的测试代码。
典型的实现如下:
struct wait_queue wait = {current, NULL};
add_wait(&short_queue, &wait);
current->state=TASK_INTERRUPTIBLE;
while (short_head==short_tail){
schedule();
/* ... 信号解码 ... */
}
remove_wait_queue(&short_queue, &wait);
这段代码看起来有点象将sleep_on的内部实现展开了。显式地声明了wait变量,
因为需要用它来使进程进入睡眠;这一切是在第5章的“等待队列”一节中解释的,但这
个例子中引入了一些新的符号。
current->state
这个字段是给调度器用的提示。调度器被激活后,它将通过观察所有进程的state字段来
决定接着作些什么。所有进程都可以任意修改自己的state字段,但在调度器运行之前这
种改变还不会生效。
#include <linux/sched.h>
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
这些符号名代表了current->state最经常取的一些值。TASK_RUNNING表示进程正在运行
,其它两个表示进程正在睡眠。
void add_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
void remove_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
void __add_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
void __ remove_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
这些函数用于从等待队列中插入和删除进程。wait参数必须指向进程堆栈所在的页(临时
变量)。以下划线开头的函数运行的更快些,但它们在禁止中断后才能被调用(例如,在
快速中断处理程序内)。
有了这些背景知识,下面让我们看看当中断到达时会发生什么。此时处理程序将
调用wake_up_interruptible(&short_queue);对Linux而言,这意味着“将state置为TA
SK_RUNNING”。因此,如果在while条件和schedule调用间有中断报告的话,该任务的st
ate字段将会又被标记为TASK_RUNNING的,因此不会丢失数据。
而如果进程仍是“可中断的”(TASK_INTERRUPTIBLE),schedule将保持它的睡眠
状态。
值得注意的是,wake_up系统调用并不会将进程从等待队列中删去。是由sleep_o
n来对等待队列进行进程的添加和删除的。因此程序代码必须显式地调用add_wait_queue
和remove_wait_queue,因为这种情况下不再使用sleep_on了。
中断处理的版本相关性
不是所有本章引入的代码都能向后兼容地移植到Linux 1.2上的。在此我将列出
主要的差异并对如何处理这些差异提出建议。实际上,short在2.0.x和1.2.13版的内核
上都编译和运行得很好。
request_irq函数的不同原型
我在这一整章中使用的给request_irq函数传递参数的方式都是到1.3.70版的内
核才引入的,因为是到这个版本才出现了共享中断处理程序的。
更早的内核版本并不需要dev_id参数,原型也相对简单些:
int request_irq(unsigned int irq,
void (*handler)(int, struct pt_regs *),
unsigned long flags, const char *device);
只要使用下面的宏定义(注意早期的版本中free_irq也没有dev_id参数),新的语
义可以很容易地强加在旧原型上:
#if LINUX_VERSION_CODE < VERSION_CODE(1,3,70)
/* 预处理器必须能处理递归的定义 */
# define request_irq(irq,fun,fla,nam,dev) request_irq(irq,fun,fla,nam)
# define free_irq(irq,dev) free_irq(irq)
#endif
这些宏只是简单地丢弃额外的dev参数。
处理函数原型上的差异通过显式的#if/#else/#endif语句得到很好的处理。如果
你使用了dev_id指针,旧内核的条件分支可以将它申明为NULL变量,这样处理函数体就
可以对NULL设备指针进行处理了。
short模块中的一个例子可以作为这种想法的范例:
#if LINUX_VERSION_CODE < VERSION_CODE(1,3,70)
void short_sh_interrupt(int irq, struct pt_regs *regs)
{
void *dev_id = NULL;
#else
void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
#endif
探测中断信号线
内核到1.3.30版开始开放探测函数。如果你希望你的驱动程序能移植到旧的内核
上,你将不得不实现DIY检测。实际上早在1.2版的内核,这些函数就已经存在了,只是
模块化的驱动程序无法使用罢了。
这样,移植中断处理函数就没有什么其它的问题了。
快速参考
本章引入了下面这些与中断管理有关的符号:
#include <linux/sched.h>
int request_irq(unsigned int irq, void (*handler)());
unsigned long flags, const char *device, void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
这些系统调用用于注册和注销中断处理程序。低于2.0版的内核不提供dev_id参
数。
SA_INTERRUPT
SA_SHIRQ
SA_SAMPLE_RANDOM
这些是request_irq函数的各种选项。SA_INTERRUPT请求安装快速中断处理程序(相对于
慢速处理函数)。SA_SHIRQ安装共享中断处理函数,而第三种选项表明产生的中断的时间
戳对系统熵池(entropy pool)有贡献。
/proc/interrupts
/proc/stat
这些文件系统节点用于报告关于硬件中断和安装的处理函数的信息。
unsigned long probe_irq_on(void);
int probe_irq_off(unsigned long);
当驱动程序需要探测设备使用哪根中断信号线时,可以使用这些函数。在中断产生之后
,probe_irq_on的返回值必须传回给probe_irq_off。probe_irq_off的返回值就是检测
到的中断号。
void disable_irq(int irq);
void enable_irq(int irq);
驱动程序可以启动和禁止中断报告。禁止中断后,硬件产生的中断都将丢失。在上半部
处理程序中调用这些函数则没有任何效果。而使用共享中断处理程序的驱动程序决不能
使用这些函数。
#include <linux/interrupt.h>
void mark_bh(int nr);
这些函数用于标记要执行的下半部。
#include <asm/bitops.h>
set_bit(nr, void *addr);
clear_bit(nr, void *addr);
change_bit(nr, void *addr);
test_bit(nr, void *addr);
这些函数用于原子性地访问位的值;它们可作用于标志位和锁变量。使用这些函数避免
了所有与对位的并发访问有关的竞争条件。
#include <asm/atomic.h>
typedef int atomic_t;
void atomic_add(atomic_t i, atomic_t *v);
void atomic_sub(atomic_t i, atomic_t *v);
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
这些函数用于原子地访问整数变量。如果想让编译时不出现警告信息,必须只使用这些
函数来访问atomic_t类型的变量。
#include <linux/sched.h>
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
这些是current->state最经常取的一些值。它们是给schedule用的提示。
void add_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
void remove_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
void __add_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
void __ remove_wait_queue(struct wait_queue ** p, struct wait_queue *wait)
这些是使用等待队列的最底层的函数。打头的下划线标志该函数是底层的函数,使用后
两个函数时处理器必须已经禁止了中断报告。
-----------------------------------------------------------------------------
* PC机通常就已经有了两个中断控制芯片,叫做8259芯片(主从片)。而可编程的中断控
制器设备已经不存在了,但现代的芯片组中也实现了相同的功能。
* shortint设备是通过交替地向并口写入0x00和0xff来实现的。
* 随后就会介绍,使用自己的下半部的驱动程序可以调用disable_bh函数。
--
※ 来源:.哈工大紫丁香 http://bbs.hit.edu.cn [FROM: 202.118.239.147]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:223.353毫秒