发信人: arira(大天使)
整理人: qiaoqian(2001-12-31 00:01:56), 站内信件
|
主题:《Linux内核模块编程指南》(十二)
发信人: kevintz()
整理人: kevintz(2000-06-24 00:44:41), 站内信件
《Linux内核模块编程指南》
《Linux Kernel Module Programming Guide》
作者:Ori Pomerantz 中译者:谭志([email protected])
译者注:
1、LKMPG是一本免费的书,英文版的发行和修改遵从GPL version 2的许可。为了
节省时间,我只翻译了其中的大部分的大意,或者说这只是我学习中的一些中文
笔记吧,不能算是严格上的翻译,但我认为这已经足够了。本文也允许免费发布
,但发布前请和我联系,但不要把本文用于商业目的。鉴于本人的水平,文章中
难免有错误,请大家不吝指正。
2、本文中的例子在Linux(kernel version 2.2.10)上调试通过。你用的Linux必
须支持内核模块的加载,如果不支持,请在编译内核时选上内核模块的支持或升
级你的内核到一个支持内核模块的版本。
第十二章 中断处理
1、中断处理介绍和实现
除了最后一章,我们在内核模块里所做的只是响应进程的请求,既有处理特
定的文件,发送一个ioctl或发出一个系统调用。但内核模块能做的并不限于响应
进程的请求(它只是重要的一个功能),它能够和机器上的硬件交谈。
CPU和其它的硬件的通信有两种方式:一是CPU向硬件发指令,另一个是当硬
件需要告诉CPU一些东西,向CPU发信号。第二种方式叫中断,比第一种方式实现
上难度大很多。因为她的处理取决于硬件的状态,而不是CPU是否便利。硬件通常
都有很少数量的RAM,当你不及时读里面的可用信息,它会丢失。
在Linux里,硬件中断叫IRQs(Interrupt Requests的缩写,中断请求)。有两
种IRQs:短中断请求和长中断请求(kevintz注:应该是中断优先级的问题)。短中
断请求是指被认为是要在很短时间内处理完的的中断,处理这种中断时,机器的
其他部分将不会被服务,也没有其他的中断会被处理。长中断是指中断可以用较
长的时间处理完成,而且在处理期间可以允许其他的中断出现(但不会是相同设备
的中断。kevintz注:相同设备的中断优先级相同,只允许高优先级的中断出现)
。如果有可能,最好是把中断处理函数写成长中断请求。
当CPU收到一个中断时,它会停止正在做的东西(除非正在处理的是一个更重
要的中断),保存一些必要的堆栈参数然后调用中断处理函数。这意味着在中断处
理函数里某一些事情是不允许的,因为系统处于一个未知的状态。解决这个问题
的方法是中断处理函数做一些立刻要处理的事情,通常是从硬件读一些信息或发
一些信息给硬件,然后在以后的某个时间调度一个新的信息处理函数(这被称为"
bottom half"),并返回。内核将保证尽可能快地调用"bottom half"函数。这些
完成以后,内核模块又允许做任何事情了。
这里的实现方法是调用request_irq取得你的中断处理函数(收到相应的IRQ时
被调用,Intel平台有16个IRQ)。request_irq函数接收IRQ号、函数名、标志、一
个显示在/proc/interrupts中的名字和传送到中断处理函数的参数。标志包含SA
_SHIRQ表明你和其他中断处理函数共享中断号(通常是由于一个相同的IRQ号用于
多个设备)而SA_INTERRUPT表明这是一个快速的中断。函数只在没有中断处理函数
和IRQ相连时或你用SA_SHIRQ共享IRQ时才能注册成功。
然后在中断处理函数里,我们用queue_task_irq(kevintz注:2.2以上的版本
应该用queue_task代替)作用于tq_immediate任务队列和mark_bh(BH_IMMEDIATE)
来调度"bottom half"函数。我们不能使用标准的queue_task的原因是中断可能发
生在运行着的queue_task的中间(queue_task_irq用一个全局锁来保护)。我们需
要mark_bh是因为早期版本的Linux有一个32个bottom halves的函数数组,其中一
个(BH_IMMEDIATE)现在是用于驱动程序的bottom half连接列表,驱动程序是不能
通过赋值来取得bottom half的入口的。
2、例子:Intel体系下的键盘中断处理
警告:本章下面的例子完全是针对Intel平台体系的。如果你不是运行在Intel平
台,它将不会正常运作,不要尝试编译这里给出的代码。
我在写本章的例子代码时遇到一个问题。一方面,例子必须运行在每个人的
机器上,并得到有意义的结果,另一方面,内核已经包含了所有公共的设备的驱
动程序了,而这些设备驱动程序将不能和我写的程序共存(kevintz:这些驱动程序
已经对中断作了处理)。我找到的解决方法是写键盘的中断程序,并先禁止常规的
键盘中断处理函数。因为它在内核源代码中定义为一个静态的变量,所以没有方
法去重建它。在insmod这里的代码之前,在另外一个中断睡眠120秒;如果你重视
你的文件系统,你可以重新启动机器。
这里的代码把它自己绑定到IRQ 1,在Intel体系下是键盘控制器的中断请求
号。然后接收键盘中断,读键盘状态(inb(0x64)返回)和扫描码(键盘返回的值)。
一旦内核认为键盘输入已经准备好,就运行got_char取得所用按键的代码(扫描码
的前七位)和是否被按下或释放(第八位)。
/* intrpt.c - An interrupt handler. */
/* Copyright (C) 1998 by Ori Pomerantz */
/* The necessary header files */
/* Standard in kernel modules */
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/module.h> /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/sched.h>
#include <linux/tqueue.h>
/* We want an interrupt */
#include <linux/interrupt.h>
#include <asm/io.h>
/* Bottom Half - this will get called by the kernel
* as soon as it's safe to do everything normally
* allowed by kernel modules. */
/* Bottom Half函数*/
static void got_char(void *scancode)
{
printk("Scan Code %x %s.\n",
(int) *((char *) scancode) & 0x7F,
*((char *) scancode) & 0x80 ? "Released" : "Pressed");
}
/* This function services keyboard interrupts. It reads
* the relevant information from the keyboard and then
* scheduales the bottom half to run when the kernel
* considers it safe. */
void irq_handler(int irq,
void *dev_id,
struct pt_regs *regs)
{
/* This variables are static because they need to be
* accessible (through pointers) to the bottom
* half routine. */
static unsigned char scancode;
static struct tq_struct task =
{NULL, 0, got_char, &scancode};
unsigned char status;
/* Read keyboard status */
status = inb(0x64);
scancode = inb(0x60);
/* Scheduale bottom half to run */
queue_task(&task, &tq_immediate);
mark_bh(IMMEDIATE_BH);
}
/* Initialize the module - register the IRQ handler */
int init_module()
{
/* Since the keyboard handler won't co-exist with
* another handler, such as us, we have to disable
* it (free its IRQ) before we do anything. Since we
* don't know where it is, there's no way to
* reinstate it later - so the computer will have to
* be rebooted when we're done.
*/
free_irq(1, NULL);
/* Request IRQ 1, the keyboard IRQ, to go to our
* irq_handler. */
return request_irq(
1, /* The number of the keyboard IRQ on PCs */
irq_handler, /* our handler */
SA_SHIRQ,
/* SA_SHIRQ means we're willing to have othe
* handlers on this IRQ.
*
* SA_INTERRUPT can be used to make the
* handler into a fast interrupt.
*/
"test_keyboard_irq_handler", NULL);
}
/* Cleanup */
void cleanup_module()
{
/* This is only here for completeness. It's totally
* irrelevant, since we don't have a way to restore
* the normal keyboard interrupt so the computer
* is completely useless and has to be rebooted. */
free_irq(1, NULL);
}
测试:
警告:这个程序接管了原来的虚拟终端的键盘中断,而且是不能恢复到原来的键
盘中断处理函数的,要在通过网络登陆的伪终端上才能重起系统,否则只能rese
t或按power了!!!所以建议在另一台机上重起系统。网络的伪终端不是会用IRQ,
所以不会有问题。
编译后,insmod intrpt,然后到物理终端(虚拟终端)上敲入一些键,看发生了什
么。在网络伪终端上more /proc/interrupts看有什么变化。
--
那一刹那,我开始用心去看这个世界,所有的事物真的可以看得前
所未有的那么清楚……
|
|