转自:http://blog.csdn.net/tigerjb/article/details/6069516
在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程
一.内核中断程序 :
我们还是来看一看成程序:
在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法。如果不熟悉的话请大家看,module_param()的学习 和Linux内核模块编程,在此不作解释。
1.程序interrupt.c
- 1 /*
- 2 *file name :interrupt.c
- 3 *atuthor : john
- 4 */
- 5 #include<linux/init.h>
- 6 #include<linux/module.h>
- 7 #include<linux/kernel.h>
- 8 #include<linux/interrupt.h>
- 9
- 10 MODULE_LICENSE("GPL");
- 11 static int irq;
- 12 char *interface;
- 13 static irqreturn_t myirq_handler(int irq,void *dev);
- 14
- 15 static int __init myirq_init(void)
- 16 {
- 17 printk("the module is working!/n");
- 18 printk("the irq is ready for working!/n");
- 19 if(request_irq(irq,myirq_handler,IRQF_SHARED,interface,&irq)){
- 20 printk(KERN_ERR "%s interrrupt can't register %d IRQ /n",interface,irq);
- 21 return -EIO;
- 22 }
- 23 printk("%s request %d IRQ/n",interface,irq);
- 24 return 0;
- 25 }
- 26 static irqreturn_t myirq_handler(int irq,void *dev)
- 27 {
- 28 printk("%d IRQ is working/n",irq);
- 29 return IRQ_NONE;
- 30 }
- 31 static void __exit myirq_exit(void)
- 32 {
- 33 printk("the module is leaving!/n");
- 34 printk("the irq is bye bye!/n");
- 35 free_irq(irq,&irq);
- 36 printk("%s interrupt free %d IRQ/n",interface,irq);
- 37
- 38 }
- 39 module_init(myirq_init);
- 0 module_exit(myirq_exit);
- 41 module_param(interface,charp,0644);
- 42 module_param(irq,int,0644);
- 43
2.Makefile的编写
- 1 obj-m:=tiger.o
- 2
- 3 CURRENT_PATH:=$(shell pwd)
- 4 VERSION_NUM:=$(shell uname -r)
- 5 LINUX_PATH:=/usr/src/linux-headers-$(VERSION_NUM)
- 6
- 7
- 8 all :
- 9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules
- 10 clean:
- 11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean
(程序的调试,加载和运行,在此不进行说明)
3.首先我们来分析下内核加载模块
在内核加载模块中最重要的的action就是注册中断处理程序。很明显,这一动作是通过request_irq()函数来完成的。
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long flags, const char *devname, void *dev_id)
A.先来分析形参:
第一个参数irq: 表示要分配的中断号。对于一些设备(系统时钟或键盘)它的值是预先固定的,而对于大多数设备来说,这个值是动态确定的。
第二个参数 handler: 表示要挂入到中断请求对列中的中断服务例程, 这个中断服务函数的原型是static
irqreturn_t handler(int , void *);
中断处理程序的前缀为static,因为它从来不会被别的文件中的代码直接调用。
第三个参数flags:为标志位。可以取IRQF_DISABLED、IRQF_SHARED和IRQF_SAMPLE_RANDOM之一。在本实例程序中取 IRQF_SHARED,该标志表示多个中断处理程序共享irq中断线。一般某个中断线上的中断服务程序在执行时会屏蔽请求该线的其他中断,如果取 IRQF_DISABLED标志,则在执行该中断服务程序时会屏蔽所有其他的中断。取IRQF_SAMPLE_RANDOM则表示设备可以被看做是事件随见的发生源。
以下是官方解释:
- /*
- * These flags used only by the kernel as part of the
- * irq handling routines.
- *
- * IRQF_DISABLED - keep irqs disabled when calling the action handler
- * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
- * IRQF_SHARED - allow sharing the irq among several devices
- * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
- * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
- * IRQF_PERCPU - Interrupt is per cpu
- * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
- * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
- * registered first in an shared interrupt is considered for
- * performance reasons)
- */
- #define IRQF_DISABLED 0x00000020
- #define IRQF_SAMPLE_RANDOM 0x00000040
- #define IRQF_SHARED 0x00000080
- #define IRQF_PROBE_SHARED 0x00000100
- #define IRQF_TIMER 0x00000200
- #define IRQF_PERCPU 0x00000400
- #define IRQF_NOBALANCING 0x00000800
- #define IRQF_IRQPOLL 0x00001000
第四个参数devname:是请求中断的设备的名称。当你加载模块成功后可以在/proc/interrupts中查看到具体设备的名称,与此同时也可以看到这个设备对应的中断号以及请求次数。
第五个参数dev_id:为一个指针型变量。注意该参数为void型,也就是说通过强制转换可以转换为任意类型。dev_id主要用于共享中断线,对每个注册的中断处理程序来说,( Dev_id must be globally unique. Normally the address of the device data structure is used
as the cookie.)dev_id参数必须唯一(指向任一设备结构的指针就可以满足此要求,选择设备结构因为它是唯一的,而且中断处理程序可能会用到它)如果无需共享中断线,则将该参数赋值为NULL。
B:函数返回值
requset_irq()函数成功执行后返回0。如果返回非0值,就表示错误发生。此时,指定的中断处理程序不会被注册。
这里面有几个疑问:
为什么要注册中断函数
共享中断线的概念,参数dev_id的作用是什么
看一个图进行说明 :
1>由图可知:有16个中断线。要使用中断线,就要进行中断线的 申请 ,也常把申请一条中断线称为申请一个中断号,这就 与request_irq()函数中的第一个形参 irq 有关系 。
2>Linux有256个中断向量,而外部中中断向量只有16个(32~47)。由于硬件上的限制,很多外部设备不得不共享中断线。
(例如:一些PC机所用的网卡和图形卡可以把它们分配到一条中断线上)
让每个中断源独自占用一条中断线是不实现的。
3>共享中断线的话虽然解决了中断资源的问题,但是,此时引出了另一个问题( 任何事物都有其两面性 ),此时仅仅用中断描述符并不能提供中断产生的所有信息。为了解决这个问题,内核必须对中断线给出近一步的描述,所以在Linux设计中,为每个中断请求IRQ设置了一个专用队列(中断请求队列)。
4>中断服例程序和中断处理程序的区别:
a.中断服务例程(interrupt service routine):
Linux中,15条中断线对应15个中断处理程序,依次命名是IRQ0x00_interrupt(),IRQ0x01_interrupt()..... IRQ0X1f_interrupt().
中断处理程序相当于某个中断向量的总处理程序。
eg:IRQ0X05_interupt()是5号中断(向量为37)的总处理程序。
b.中断服务例程是针对一个具体设备的中断。
5>.注册中断服务例程:
在IDT表完成初始化时,每个中断服务队列还为空。此时即使打开中断且某个外设的中断真的发生了,也得不到实际的服务。因为CPU虽然通过中断门进入了某个中断向量的总处理程序。但是,具体的中断服务例程还没有挂入中断请求队列。所以,在设备驱动程序的初始化阶段,必须通过request_irq()函数将响应的中断服务例程挂入中断请求队列,也就是进行注册。
6>分析一下中断服务程序,即request_irq()函数中第二个参数所对应的函数
static irqreturn_t myirq_handler(int irq,void *dev_id)
{
printk("ISR is Working/n");
return IRQ_HANDLED;
}
中断服务例程的形参:
a.int irq :中断号。
b.void *dev_id :与request_irq()的参数dev_id一致,可以根据这个设备id号得到相应设备的数据结构,进而得到相应设备的信息和相关数据。
c.返回值:中断程序的返回值是一个特殊类型 rqreturn_t。但是中断程序的返回值却只有两个值IRQ_NONE和IRQ_HANDLED。
IRQ_NONE:中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号。
IRQ_HANDLED:接收到了准确的中断信号,并且作了相应正确的处理。
一般 中断处理程序要做什么service,主要取决于产生的设备和该设备为什么要发送中断。
John哥说明:
1.当一个给定的中断处理程序正在执行时,这条中断线上的其它中断都会被屏蔽。but,所有其他中断线上的中断都是打开的。因此这些不同中断线上的其他中断都能被处理。