当前位置:文档之家› linux中断实例

linux中断实例

你的第一个中断程序:本文通过一个简单的中断程序来描述一般中断程序的基本框架。

完整代码在这里。

中断程序一般会包含在某个设备的驱动程序中,因此,接下来的程序本质上还是一个内核模块。

说到内核模块,你应该知道首先去看什么了吧?对了,就是内核模块加载函数。

view sourceprint?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:%dfailed..\n",devname,irq);07 return -1;08 }09 printk("%s rquest IRQ:%d success..\n",devname,irq);10 return 0;11 }在内核加载函数中,我们除了显示一些信息外,最重要的工作就是申请一根中断请求线,也就是注册中断处理程序。

很明显,这一动作是通过 request_irq函数来完成的。

这个函数的原型如下:view sourceprint?1 static int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);第一个参数是中断号,这个中断号对应的就是中断控制器上IRQ线的编号。

第二个参数是一个irq_handler_t类型个函数指针:view sourceprint?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函数,它会注销相应的中断处理程序,并释放中断线。

这个函数一般被在内核模块卸载函数中被调用。

view sourceprint?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的重要性。

下面具体分析中断处理函数。

该函数的功能很简单,只是显示一些提示信息。

view sourceprint?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宏来接受参数。

view sourceprint?1 static int irq;2 static char* devname;34 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=myirq3.再次查看/proc/interrupts文件,可以发现1号中断线对应的的设备名处多了myirq设备名;4.dmesg查看内核日志文件,可看到在中断处理程序中所显示的信息;5.卸载内核模块;可以看到,内核模块加载后,我们所写中断处理程序是被自动调用的,主要是因为该中断线上有键盘所发出的中断请求,因此内核会执行该中断线上的所有中断处理程序,当然就包括我们上述所写的那个中断处理程序。

关于中断处理程序的执行,可参考这里的文章。

这样,一个最基本的中断程序就编写完成了!try!后记:这个程序调试起来并不难,但是我们并不能仅仅局限在这个程序本身。

以它为入口点深入学习中断的基本原理再好不过。

下面给出几个学习的入口点。

1.为何我们的中断程序和其他设备共享了一个中断线后会被执行?或者说,共享中断线上的所有中断服务例程是怎么执行的?2.中断涉及到那些基本的数据结构?这些数据结构之间有什么关系?3.do_IRQ()函数的大体执行流程是什么?亲们,要学习的东西还很多,让我们一起加油吧!中断下半部-tasklet:tasklet的实现tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。

正如在前文中你所知道的那样,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。

可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。

对应到我们此刻所说的tasklet就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用 tasklet,如下图示。

tasklet由tasklet_struct结构体来表示,每一个这样的结构体就表示一个tasklet。

在<linux/interrupt.h>中可以看到如下的定义:view sourceprint?1 tasklet_struct2 {3 struct tasklet_struct *next;4 unsigned long state;5 atomic_t count;6 void (*func)(unsigned long);7 unsigned long data;8 };在这个结构体中,第一个成员代表链表中的下一个tasklet。

第二个变量代表此刻tasklet的状态,一般为 TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。

count成员是一个引用计数器,只有当其值为0时候,tasklet 才会被激活;否则被禁止,不能被执行。

而接下来的 func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。

使用tasklet在使用tasklet前,必须首先创建一个tasklet_struct类型的变量。

通常有两种方法:静态创建和动态创建。

这样官方的说法仍然使我们不能理解这两种创建到底是怎么一回事。

不够透过源码来分析倒是可以搞明白。

在<linux/interrupt.h>中的两个宏:view sourceprint?1 464#define DECLARE_TASKLET(name, func, data) \2 465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }3 4664 467#define DECLARE_TASKLET_DISABLED(name, func, data) \5 468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }就是我们进行静态创建tasklet的两种方法。

通过第一个宏创建的tasklet处于激活状态,再通过调度函数被挂起尽而被内核执行;而通过第二个宏创建的tasklet处于禁止状态。

从两个宏的定义可以看到,所谓的静态创建就是直接定义个一个名为name的tasklet_struct类型的变量,并将宏中各个参数相应的赋值给这个name变量的各个成员。

注意,两个宏在功能上差异就在于对name变量count成员的赋值上,具体原因在第一部分已经说明。

也许你对ATOMIC_INIT这样的初始化方式感到疑惑,那么看完定义后,你就会一目了然:view sourceprint?1 //在arch/x86/include/asm/atomic.h中2 15#define ATOMIC_INIT(i) { (i) }3 //在linux/types.h中4 190typedef struct {5 191 int counter;6 192} atomic_t;与静态创建相对的是动态创建,通过给tasklet_init函数传递一个事先定义的指针,来动态创建一个tasklet。

相关主题