原文:你的第一个中断程序!
作者: edsionte
地址:http://edsionte.com/techblog/archives/1521
之前在学习中断知识时,都是概念,理解的不是很好,虽然使用时钟中断写了一个例程,但是终究不是主要关于中断的程序,所以在就在网上找啊找啊,终于发现了这个大神的博文,很是经典啊,博客里还有其他的关于linux内核的文章,大家有学习兴趣的都可以去看看,
本文通过一个简单的中断程序来描述一般中断程序的基本框架。完整代码在此处。
代码需要修改一下,init()函数和exit()的传入参数需要追加void,
修改后为
static int __init myirq_init(void)
static void __exit myirq_exit(void)
中断程序一般会包含在某个设备的驱动程序中,因此,接下来的程序本质上还是一个内核模块。说到内核模块,你应该知道首先去看什么了吧?对了,就是内核模块加载函数。
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, unsigned long 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;
说明:这个键盘中断有待研究,我的台式机使用USB键盘,死活不能出现key:xxx的情报,问了一下其他大神,说什么软中断/硬中断的原因。
后来拿到我的笔记本虚拟机上运行,OK,没有问题,准确的出现log,证明中断函数被正确执行了。
2.使用如下命令就可以插入内核:
sudo insmod filename.ko irq=1 devname=myirq
3.再次查看/proc/interrupts文件,可以发现1号中断线对应的的设备名处多了myirq设备名;
4.dmesg查看内核日志文件,可看到在中断处理程序中所显示的信息;
[ 683.910314] tasklet is wroking..
[ 684.012115] key:157..
[ 684.012126] devid:1119 ISR is working..
[ 684.012128] Bottom half will be working..
[ 684.012131] ISR is leaving..
[ 684.012148] tasklet is wroking..
[ 684.176827] key:158..
[ 684.176841] devid:1119 ISR is working..
[ 684.176843] Bottom half will be working..
[ 684.176845] ISR is leaving..
[ 684.176865] tasklet is wroking..
[ 684.265099] key:159..
[ 684.265102] devid:1119 ISR is working..
[ 684.265103] Bottom half will be working..
[ 684.265103] ISR is leaving..
[ 684.265106] tasklet is wroking..
[ 686.885017] key:160..
[ 686.885024] devid:1119 ISR is working..
[ 686.885025] Bottom half will be working..
[ 686.885027] ISR is leaving..
5.卸载内核模块;
可以看到,内核模块加载后,我们所写中断处理程序是被自动调用的,主要是因为该中断线上有键盘所发出的中断请求,因此内核会执行该中断线上的所有中断处理程序,当然就包括我们上述所写的那个中断处理程序。关于中断处理程序的执行,可参考这里的文章。
这样,一个最基本的中断程序就编写完成了!try!