本文通过一个简单的中断程序来描述一般中断程序的基本框架。
中断程序一般会包含在某个设备的驱动程序中,因此,接下来的程序本质上还是一个内核模块。说到内核模块,你应该知道首先去看什么了吧,就是内核模块加载函数。
01 static int __init myirq_init()
02 {
03 printk("Module is working..\n");
04 if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
05 {
06 printk("%s request IRQ:%d failed..\n",devname,irq);
07 return -1;
08 }
09 printk("%s rquest IRQ:%d success..\n",devname,irq);
10 return 0;
11 }
在内核加载函数中,我们除了显示一些信息外,最重要的工作就是申请一根中断请求线,也就是注册中断处理程序。很明显,这一动作是通过request_irq函数来完成的。这个函数的原型如下:
1 static int request_irq(unsigned int irq, irq_handler_t handler, unsignedlong flags,const char *name, void *dev);
第一个参数是中断号,这个中断号对应的就是中断控制器上IRQ线的编号。
第二个参数是一个irq_handler_t类型个函数指针:1 typedef irqreturn_t (*irq_handler_t)(int, void *);
handler所指向的函数即为中断处理程序,需要具体来实现。
第三个参数为标志位,可以取IRQF_DISABLED、IRQF_SHARED和IRQF_SAMPLE_RANDOM之一。在本实例程序中取IRQF_SHARED,该标志表示多个设备共享一条IRQ线,因此相应的每个设备都需要各自的中断服务例程。一般某个中断线上的中断服务程序在执行时会屏蔽请求该线的其他中断,如果取IRQF_DISABLED标志,则在执行该中断服务程序时会屏蔽所有其他的中断。取IRQF_SAMPLE_RANDOM则表示设备可以被看做是事件随见的发生源。
第四个参数是请求中断的设备的名称。可以在/proc/interface中查看到具体设备的名称,与此同时也可以查看到这个设备对应的中断号以及请求次数,甚至中断控制器的名称。
第五个参数为一个指针型变量。注意此参数为void型,也就是说通过强制转换可以转换为任意类型。这个变量在IRQF_SHARED标志时使用,目的是为即将要释放中断处理程序提供唯一标志。因为多个设备共享一条中断线,因此要释放某个中断处理程序时,必须通过此标志来唯一指定这个中断处理程序。习惯上,会给这个参数传递一个与设备驱动程序对应的设备结构体指针。关于中断程序,可参考这里的文章。
以上就是request_irq函数各个参数的意义。
与中断处理程序的注册相对应的是free_irq函数,它会注销相应的中断处理程序,并释放中断线。这个函数一般被在内核模块卸载函数中被调用。
1 static void __exit myirq_exit()
2 {
3 printk("Module is leaving..\n");
4 free_irq(irq,&mydev);
5 printk("%s request IRQ:%d success..\n",devname,irq);
6 }
如果该中断线不是共享的,那么该函数在释放中断处理程序的同时也将禁用此条中断线。如果是共享中断线,只是释放与mydev对应的中断处理程序。除非该中断处理程序恰好为该中断线上的最后一员,此条中断线才会被禁用。在此处,你也可以感受到mydev的重要性。
下面具体分析中断处理函数。该函数的功能很简单,只是显示一些提示信息。
01 static irqreturn_t myirq_handler(int irq,void* dev)
02 {
03 struct myirq mydev;
04 static int count=1;
05 mydev=*(struct myirq*)dev;
06 printk("key: %d..\n",count);
07 printk("devid:%d ISR is working..\n",mydev.devid);
08 printk("ISR is leaving..\n");
09 count++;
10 return IRQ_HANDLED;
11 }
另外,本内核模块在插入时还需要附带参数,下面的语句首先定义两个参数,然后利用宏module_param宏来接受参数。1 static int irq;
2 static char* devname;
3
4 module_param(devname,charp,0644);
5 module_param(irq,int,0644);
使用方法:
1.通过cat /proc/interrupts查看中断号,以确定一个即将要共享的中断号。本程序因为是与键盘共享1号中断线,因此irq=1;
2.使用如下命令就可以插入内核:
sudo insmod filename.ko irq=1 devname=myirq
3.再次查看/proc/interrupts文件,可以发现1号中断线对应的的设备名处多了myirq设备名;
4.dmesg查看内核日志文件,可看到在中断处理程序中所显示的信息;
5.卸载内核模块;
可以看到,内核模块加载后,我们所写中断处理程序是被自动调用的,主要是因为该中断线上有键盘所发出的中断请求,因此内核会执行该中断线上的所有中断处理程序,当然就包括我们上述所写的那个中断处理程序。关于中断处理程序的执行,可参考这里的文章。
这样,一个最基本的中断程序就编写完成了
具体程序 可编译通过
interrupt.c
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/interrupt.h>
static int irq;
static char * devname;
module_param(irq,int,0644);
module_param(devname,charp,0644);
struct myirq
{
int devid;
};
struct myirq mydev={1119};
//中断处理函数
static irqreturn_t myirq_handler(int irq,void * dev)
{
struct myirq mydev;
static int count=1;
mydev = *(struct myirq*)dev;
printk("key: %d..\n",count);
printk("devid:%d ISR is working..\n",mydev.devid);
printk("ISR is leaving......\n");
count++;
return IRQ_HANDLED;
}
//内核模块初始化函数
static int __init myirq_init(void)
{
printk("Module is working...\n");
if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
{
printk("%s request IRQ:%d failed..\n",devname,irq);
return -1;
}
printk("%s request IRQ:%d success...\n",devname,irq);
return 0;
}
//内核模块退出函数
static void __exit myirq_exit(void)
{
printk("Module is leaving...\n");
free_irq(irq,&mydev);
printk("Free the irq:%d..\n",irq);
}
MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);
Makefile
#Makefile文件注意:假如前面的.c文件起名为first.c,那么这里的Makefile文件中的.o文件就要起名为first.o 只有root用户才能加载和卸载模块
obj-m:=interrupt.o #产生interrupt模块的目标文件
#目标文件 文件 要与模块名字相同
CURRENT_PATH:=$(shell pwd) #模块所在的当前路径
LINUX_KERNEL:=$(shell uname -r) #linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
#[Tab] 内核的路径 当前目录编译完放哪 表明编译的是内核模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理模块
sudo insmod interrupt.ko irq=1 devname=myirq