上节:http://blog.csdn.net/morixinguan/article/details/68958185
在上一节博文中,教会了大家如何来写一个Linux设备的中断程序,实现也非常简单,我们来回顾一下具体的操作流程,只要遵循以下几个步骤即可实现最简单的中断处理程序:
使用中断相关的API和定义时要包含以下头文件:
#include <linux/interrupt.h>
然后写中断需要以下步骤
1、申请中断号
使用gpio_to_irq函数,可以从返回值获取到对应的中断号
2、请求中断 , 在里面实现中断服务函数handler,这个中断服务函数会在中断触发的时候被调用,我们上一节写的是一个按键的外部中断,所以当我按下按键的时候,就会调用handler相关的代码。
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
释放中断
void free_irq ( unsigned int irq, void * dev_id);
释放匹配irq和dev_id的中断, 如果irq有多个相同的dev_id, 将释放第一个
So, 共享中断的dev_id不是唯一时, 可能会释放到其它设备的中断
中断共享部分我们等以后再写。
这节,我们来实现一下中断底半部------俗称小任务机制
那么什么是中断底半部?有底是不是就是有上半部,分别代表什么含义呢?
有一篇文章讲得非常详细,可以拿来参考参考:
http://blog.csdn.net/morixinguan/article/details/69666642
在中断上半部执行中断处理函数期间,中断是关闭的,而下半部分在执行中断时是允许中断请求的,所以既然允许请求,那么可以被打断。
我们这节主要用一段代码是来实现一下tasklet小任务机制,顺便说说写的时候要注意的一些基本问题:
那么怎么实现tasklet?我们先来看看这个结构体,一样的,在#include <linux/interrput.h>中可以找到:
//下半部实现机制---->通过软中断实现tasklet struct 小任务机制 struct tasklet_struct { //指向下一个tasklet的指针,其实就是一条链表 struct tasklet_struct *next; //定义tasklet当前的状态,用两个位来进行表示,0或者1 //bit[1]=1 表示这个tasklet当前正在某个CPU上被执行, //它仅对SMP系统才有意义, //其作用就是为了防止多个CPU同时执行一个tasklet的情形出现; //bit[0]=1表示这个tasklet已经被调度去等待执行了? unsigned long state; //tasklet的引用计数? //注:只有当count等于0时, // tasklet代码段才能执行,也即此时tasklet是被使能的? //如果count非零,则这个tasklet是被禁止的。任何想要执行一个tasklet代码段的人都首先必须先检查其count成员是否为0。? atomic_t count; //处理函数---->指向以函数形式表现的可执行tasklet代码段 void (*func)(unsigned long); //函数func的参数 unsigned long data; };上面这个结构体所描述的state,其实就是下面这个枚举:
//以下这个枚举表示的就是tasklet_struct结构体中的state //在这里,tasklet状态指两个方面: // 1) state:成员所表示的运行状态; // 2) count:成员决定的使能/禁止状态。 enum { TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */ TASKLET_STATE_RUN /* Tasklet is running (SMP only) */ };那么,我们要实现一个基本的tasklet需要以下函数:
初始化: void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long),unsigned long data); 定义并初始化: DECLARE_TASKLET(t, void (*func)(unsigned long),unsigned long data); 调度Tasklet: void tasklet_schedule(struct tasklet_struct *t); void tasklet_hi_schedule(struct tasklet_struct *t); 高优先级 同一个Tasklet不会同时被多个CPU执行 禁止Tasklet: void tasklet_disable(struct tasklet_struct *t); 开启Tasklet: void tasklet_enable(struct tasklet_struct *t);接下来,说一说实现的步骤:
1、定义一个tasklet struct
struct tasklet_struct t;
2、初始化
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),unsigned long data);
其中t是tasklet struct,func是tasklet处理函数,data是处理函数的参数
3、调度Tasklet:
void tasklet_schedule(struct tasklet_struct *t);
高优先级
void tasklet_hi_schedule(struct tasklet_struct *t);
调度这个过程是在中断服务函数中进行的,接下来我们来看看代码:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/fb.h> #include <linux/backlight.h> #include <linux/err.h> #include <linux/pwm.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/timer.h> /*timer*/ #include <asm/uaccess.h> /*jiffies*/ #include <linux/delay.h> #include <linux/interrupt.h> //定义tasklet结构体变量 struct tasklet_struct task_t ; //tasklet处理函数 static void task_fuc(unsigned long data) { //判断此刻是位于进程上下文还是中断上下文 if(in_interrupt()){ printk("%s in interrupt handle!\n",__FUNCTION__); } } //中断处理函数 static irqreturn_t irq_fuction(int irq, void *dev_id) { //调度tasklet tasklet_schedule(&task_t); //判断此刻是位于进程上下文还是中断上下文 if(in_interrupt()){ printk("%s in interrupt handle!\n",__FUNCTION__); } printk("key_irq:%d\n",irq); //返回中断句柄 return IRQ_HANDLED ; } static int __init tiny4412_Key_irq_test_init(void) { int err = 0 ; int irq_num1 ; //DECLARE_TASKLET(t, void (*func)(unsigned long),unsigned long data); int data_t = 100 ; //初始化tasklet tasklet_init(&task_t,task_fuc,data_t); printk("irq_key init\n"); //申请中断号 irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); //请求中断 err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1"); if(err != 0){ free_irq(irq_num1,(void *)"key1"); return -1 ; } return 0 ; } static void __exit tiny4412_Key_irq_test_exit(void) { int irq_num1 ; printk("irq_key exit\n"); irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); free_irq(irq_num1,(void *)"key1"); } module_init(tiny4412_Key_irq_test_init); module_exit(tiny4412_Key_irq_test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("YYX"); MODULE_DESCRIPTION("Exynos4 KEY Driver");将编译好的zImage下载到开发板上:
当我们按下按键的时候,我们看到以下信息:
从而我们可以知道,中断服务程序和tasklet程序是位于中断上下文的,那么在中断上下文能否休眠或者延时?
我们只需要把上面其中一个处理函数中加一个msleep(2),然后重新编译烧写到开发板:
//tasklet处理函数 static void task_fuc(unsigned long data) { //判断此刻是位于进程上下文还是中断上下文 if(in_interrupt()){ printk("%s in interrupt handle!\n",__FUNCTION__); } msleep(2); } //中断处理函数 static irqreturn_t irq_fuction(int irq, void *dev_id) { //调度tasklet tasklet_schedule(&task_t); //判断此刻是位于进程上下文还是中断上下文 if(in_interrupt()){ printk("%s in interrupt handle!\n",__FUNCTION__); } printk("key_irq:%d\n",irq); msleep(2); //返回中断句柄 return IRQ_HANDLED ; }此时我们会发现,当我们按下按键的时候会看到下面的信息,内核崩溃,出现段错误,然后开发板重启了,所以在任何情况下,有休眠的,睡眠的函数一定不要放在中断上下文的代码中执行除了delay.h里面的接口,比如zmalloc,或者vmalloc,当申请内存足够大的情况下,也是会引起睡眠的,当然,我们在init函数中,就处于进程上下文,进程上下文是可以睡眠或者延时的。
我们可以来试试,在init函数中加一个in_interrupt()函数用来判断处于哪种上下文,再加一个延时试试,看看会不会。
修改代码,将tasklet处理函数和中断处理函数中的msleep(2)去掉,在init中修改代码如下:
static int __init tiny4412_Key_irq_test_init(void) { int err = 0 ; int irq_num1 ; int data_t = 100 ; if(in_interrupt()){ printk("%s is interrupt handle\n",__FUNCTION__); }else{ printk("%s is proccess handle\n",__FUNCTION__); msleep(2); printk("%s delay success!\n",__FUNCTION__); } tasklet_init(&task_t,task_fuc,data_t); printk("irq_key init\n"); irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1"); if(err != 0){ free_irq(irq_num1,(void *)"key1"); return -1 ; } return 0 ; }下到板子上,我们看到以下信息:
开发板内核启动时打印了下面,我们看到了init函数在当前处于进程上下文,而不是中断上下文,所以允许休眠和延时。
这时候,我无论怎么去按按键,也不会段错误了,因为我已经把运行在中断上下文的msleep(2)给去掉了。
本节到此为止,当然上面还有一些tasklet的函数没有去尝试,就留给大家去尝试了,道理是类似的,多写代码,实践出真知。
下一节,我们将实现中断下半部的另外一种---->工作队列。