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

简介: 上节:http://blog.csdn.net/morixinguan/article/details/68958185在上一节博文中,教会了大家如何来写一个Linux设备的中断程序,实现也非常简单,我们来回顾一下具体的操作流程,只要遵循以下几个步骤即可实现最简单的中断处理程序:使用中断相...

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

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










目录
相关文章
|
4月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
54 6
|
4月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
58 5
|
4月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
50 3
|
4月前
|
缓存 安全 Linux
Linux 设备驱动程序(一)((下)
Linux 设备驱动程序(一)
47 3
|
4月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
31 1
|
4月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
49 1
|
4月前
|
存储 前端开发 大数据
Linux 设备驱动程序(二)(中)
Linux 设备驱动程序(二)
35 1
|
4月前
|
缓存 安全 Linux
Linux 设备驱动程序(二)(上)
Linux 设备驱动程序(二)
46 1
|
4月前
|
存储 缓存 安全
Linux 设备驱动程序(三)(下)
Linux 设备驱动程序(三)
39 0
|
4月前
|
安全 Linux 程序员
Linux 设备驱动程序(二)(下)
Linux 设备驱动程序(二)
32 0
下一篇
DataWorks