手把手教你写Linux设备驱动---中断(二)--tasklet实现(基于友善之臂4412开发板)

来源:互联网 发布:巴蜀中学教师待遇知乎 编辑:程序博客网 时间:2024/04/28 19:04

上节: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){//调度tasklettasklet_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 ;//初始化tasklettasklet_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){//调度tasklettasklet_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的函数没有去尝试,就留给大家去尝试了,道理是类似的,多写代码,实践出真知。

下一节,我们将实现中断下半部的另外一种---->工作队列。










0 0