当前位置:操作系统 > Unix/Linux >>

Linux内核模块编程--中断处理程序

中断处理程序

  除了上一章外,迄今为止,我们在内核中所做的每件事都是作为对一个进程请求的回应,要么通过处理特殊的文件,发送 ioctl,要么发出系统调用。但是内核的工作并不仅仅是回应进程请求。另一个每个字节都很重要的工作是和连接到机器的硬件对话。

  在CPU和计算机的其他设备之间有两种交互作用。第一种是当CPU对硬件发布命令时,另一种是当硬件要告诉CPU什么事情时。第二种,被称为中断,实现起来是很困难的,因为它不得不处理什么时间硬件是方便的而不是CPU。典型的硬件设备只有很少的内存,如果当信息可见的时候你不读取的话它就会消失。

  在 Linux 下,硬件中断被称为 IRQs [Interrupt Requests (这是Linux起源的Intel 架构上的标准术语。 )的缩写]。有两种 IRQs,短的和长的。一个短的 IRQ 预期占用 非常短的一段时间,在那期间,机器的剩余部分被阻塞,没有其他的中断将被处理。长的 IRQ 占用的时间长些,在那期间其他中断有可能发生(但不能是来自同一设备)。只要是可能的,声明一个长中断是较好的。

  当 CPU 接收到一个中断,它停止它正在做的任何事情(除非它正在处理一个更重要的中断,在那种情况下,它将处理完那个中断后才来处理现在的这个),在堆栈中保存某些参数并调用中断处理程序。这意味着在中断处理程序自身中有些东西是不能允许的,因为系统处于一种未知的状态。解决的办法是中断处理程序马上做完需要做的,通常是从硬件里面读什么或向硬件发送什么然后安排处理稍后的新信息(这被称为‘bottom half’)并返回。然后内核保证只要可能就调用bottom half --当这在运行,内核模块中允许做的所有事情将被允许。

  实现这个办法是当接收到相关的IRQ(在 Intel 平台下有16个)时去调用 request_irq 以使中断处理程序被调用。 这个函数接收IRQ 号,函数名,标志, /proc/interrupts 中的名字及一个传送给中断处理程序的参数作为其参数。标志可以包括 SA_SHIRQ 以指明你愿意和其他的中断处理程序分享那个IRQ(通常因为几个硬件设备在同一IRQ)以及 SA_INTERRUPT 以指明这是一个快速中断。这个函数只在那个IRQ上没有处理程序的情况下成功,或者你愿意两者共享。

  然后从中断处理程序中我们和硬件通信,联合tq_immediate使用 queue_task_irq 和 mark_bh(BH_IMMEDIATE) 调度 bottom half。我们在 2.0 版中不使用标准的queue_task 的原因是中断有可能在其他人的 queue_task(queue_task_irq 从这被一个全局锁保护 -- 在 2.2 版中没有queue_task_irq 而 queue_task 被一个锁保护。 )中发生。我们需要 mark_bh 是因为Linux 的早期版本只能有32个 bottom half,而现在它们中的一个(BH_IMMEDIATE) 用于还没有得到bottom half入口的驱动程序的bottom half连接表。

  

  Intel 架构键盘

  警告: 这章剩下的内容都特别指定为完全的 基于Intel 架构。如果你不是在这个平台下运行,它没有用。甚至不要试图去编译这里的代码。

  在为这章写范例代码的时候我有一个问题。一方面,对于一个有用的范例,它应该运行于每个人的计算机上且有意味深长的结果。另一方面,内核已经包含了所有的通用设备的驱动程序,并且那些设备驱动程序不能和我将要写的共存。我发现的结果是写一些键盘中断的东西并且先关闭通常的键盘的中断句柄。因为在内核源文件(明确的, drivers/char/keyboard.c)中它被定义为静态符号,所以没有办法恢复它。如果你重视你的文件系统,在 insmod 这些代码前,在另一个终端上sleep 120 ; reboot 。

  这个代码将自己绑定为 IRQ 1,这是Intel 架构下的键盘控制器的IRQ(中断请求)。然后当它收到键盘中断时它就读键盘的状态( 那就是inb(0x64)的目的)和扫描代码,该代码即是键盘的返回值。然后,内核一认为它是可行的它就运行给出键所使用的代码(扫描代码的前7位)和它是被按下(第8位为0)还是被释放(第8位为1)的got_char函数。

  范例 intrpt.c

  /* intrpt.c - 中断句柄 *//* Copyright (C) 1998 by Ori Pomerantz *//* 必要头文件 *//* 标准头文件 */#include /* 内核工作 */#include /* 明确指定是模块 *//* 处理 CONFIG_MODVERSIONS */#if CONFIG_MODVERSIONS==1#define MODVERSIONS#include#endif#include#include/* 我们想中断 */#include#include/* 在 2.2.3 版/usr/include/linux/version.h 中包含这个宏,* 但 2.0.35 版不包含-因此在这加入以被需要 */#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))#endif/* Bottom Half - 一旦内核模块认为它做任何事都是安全的时候这将被内核调用。 */static void got_char(void *scancode){printk("Scan Code %x %s.

  ",(int) *((char *) scancode) & 0x7F,*((char *) scancode) & 0x80 ? "Released" : "Pressed");}/* 这个函数为键盘中断服务。它读取来自键盘的相关信息然后安排当内核认为bottom half安全的时候让它运行 */void irq_handler(int irq,void *dev_id,struct pt_regs *regs){/* 这些变量是静态的,因为它们需要对 bottom half 可见(通过指针)。 */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);/* 安排 bottom half 运行 */#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)queue_task(&task, &tq_immediate);#elsequeue_task_irq(&task, &tq_immediate);#endifmark_bh(IMMEDIATE_BH);}/* 初始化模块--登记 IRQ 句柄 */int init_module(){/* 既然键盘的句柄不能和我们的共存,在我们做事情前我们不得不关闭它(释放它的 IRQ)。* 因为我们不知道它在哪儿,所以以后没有办法恢复它--因此当我们做完时计算机将被重新启动。*/free_irq(1, NULL);/* 请求 IRQ 1,键盘的 IRQ,指向我们的 irq_handler。 */return request_irq(1, /* PC上的键盘的 IRQ 号 */irq_handler, /* 我们的句柄 */SA_SHIRQ,/* SA_SHIRQ 意味着我们将另一个句柄用于这个 IRQ。** SA_INTERRUPT 能使句柄为一个快速中断。*/"test_keyboard_irq_handler", NULL);}/* 清除 */void cleanup_module(){/* 它在这儿只是为了完全。它是完全不相关的,因为我们没有办法恢复通常的键盘中断因此计算机完全没用 * 了,需要被重新启动。 */free_irq(1, NULL);}
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,