📄 (ldd) ch09-中断处理(下)(转载).txt
字号:
任务(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变量,
这段代码看起来有点象将sleep_on的内部实现展开了。显式地声明了wait变量,
因为需要用它来使进程进入睡眠;这一切是在第5章的“等待队列”一节中解释的,但这
个例子中引入了一些新的符号。
current->state
这个字段是给调度器用的提示。调度器被激活后,它将通过观察所有进程的state字段来
决定接着作些什么。所有进程都可以任意修改自己的state字段,但在调度器运行之前这
种改变还不会生效。
#include <linux/sched.h>
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
这些符号名代表了current->state最经常取的一些值。TASK_RUNNING表示进程正在运行
这些符号名代表了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的,因此不会丢失数据。
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参数,原型也相对简单些:
更早的内核版本并不需要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_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用的提示。
这些是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来实现的。
* PC机通常就已经有了两个中断控制芯片,叫做8259芯片(主从片)。而可编程的中断控
制器设备已经不存在了,但现代的芯片组中也实现了相同的功能。
* shortint设备是通过交替地向并口写入0x00和0xff来实现的。
* 随后就会介绍,使用自己的下半部的驱动程序可以调用disable_bh函数。
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -