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

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

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

时间: 2024-08-26 07:11:47

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

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

上节,我们讲到如何来实现tasklet小任务机制 http://blog.csdn.net/morixinguan/article/details/69666935 这节,我们来实现一下中断下半部的工作队列: 在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API. 需要包含的头文件: #include <linux/workqueue.h> 基本的数据结构: //工作队列结构 struct work_struct { atomic_long_t data; //链表处理 str

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

今天,我们要来实现一个基于tiny4412开发板上的最简本的按键中断驱动程序,那么,写这个程序之前,我们先来了解下Linux中断的基本知识. 在Linux内核中,每一个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线.所有现在存在的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连,我们可以来看下4412上与板子上相连的按键. 下面这张电路图,也就是4412板子上按键的电路图和CPU的连接关系图: 我们明显可以看到,4个按键分别接在GPX3这几个引脚上,对应着引脚,接下来我们

手把手教你写Linux设备驱动---input子系统(三)--电容屏事件坐标读取(基于友善之臂4412开发板)

前面我们学习了鼠标是如何如何通过应用程序来读取事件和坐标值的,后面也写了一个简单的input系统的按键驱动程序. 博文如下,讲的内容非常清楚,给小白来入手当然是非常容易的: http://blog.csdn.net/morixinguan/article/details/69808832 这节,我们来学习一下触摸屏事件获取,然后上一个基于4412开发板ft5x0x型号的x,y坐标值读取,后面我们将从零开始实现这款触摸屏的驱动程序: 首先,我们要明白一个概念,触摸屏在input系统中是一类什么事件

手把手教你写Linux设备驱动---input子系统(二)--按键驱动实现(一)(基于友善之臂4412开发板)

在上一节里,我们用一个应用程序实现了鼠标的控制,并控制鼠标用相对位移不断的画一个正方形,感觉非常有意思,这一节,我们将通过一个简单按键实例来真正的实现一个input设备驱动程序. http://blog.csdn.net/morixinguan/article/details/69808832 在写Input驱动之前,我们要了解下这个结构体,在此,我们要包含相应的头文件: #include <linux/input.h> 我们在这个头文件中找到了以下结构体,它就是input设备的核心: //用

手把手教你写Linux设备驱动---input子系统(四)--电容屏驱动ft5x06编写(一)(基于友善之臂4412开发板)

这一节,我们将从零开始写tiny4412的触摸屏驱动ft5x06,写这节博客之前,先了解下需要什么知识: 1.i2c驱动相关的知识 2.输入子系统 3.中断 4.工作队列 关于i2c驱动相关的知识,在后期的博文里会专门写几篇博文来进行总结,这里就不再说i2c相关的知识,我们先知道怎么用就行了. 首先,我在ts.h构造了一个ts_info_st结构体,用来存放触摸屏的中断线,x坐标,y坐标,压力值. 用ts_st构造了该触摸屏的设备结构体. 我们还是直接看点实际的东西,上代码: ts.h #ifn

手把手教你写Linux设备驱动---定时器(一)(基于友善之臂4412开发板)

这个专题我们来说下Linux中的定时器. 在Linux内核中,有这样的一个定时器,叫做内核定时器,内核定时器用于控制某个函数,也就是定时器将要处理的函数在未来的某个特定的时间内执行.内核定时器注册的处理函数只执行一次,即不是循环执行的. 如果对延迟的精度要求不高的话,最简单的实现方法如下---忙等待: Unsigned long j = jiffies + jit_delay * HZ; While(jiffies < j) { -- } 下面来说下具体的参数代表的含义: jiffies:全局变

手把手教你从零实现Linux misc设备驱动二(基于友善之臂4412开发板)

上一节,我教大家实现了一个最简单的MISC设备驱动,那么这节,我们将用一个实例来驱动蜂鸣器,这里为了方便,我就不再写应用程序进行测试,直接在驱动里调用open函数,这个程序是在Android系统里跑起来,后面我会教大家如何在Android下写应用测试程序. 我们参考以前写的蜂鸣器驱动程序,将它移植到我们这个程序里,让它成为一个MISC设备. 参考以前写的文章: http://blog.csdn.net/morixinguan/article/details/50628588 接下来,看看代码:

手把手教你从零实现Linux misc设备驱动一(基于友善之臂4412开发板)

关于如何来写一个misc设备,在前面有篇文章已经介绍了大致的流程,现在就让我们来实现一个最简单的misc设备驱动. http://blog.csdn.net/morixinguan/article/details/52700146 关于前面的字符设备有以下四篇文章,可以做参考: http://blog.csdn.net/morixinguan/article/details/55002774 http://blog.csdn.net/morixinguan/article/details/550

手把手教你写Linux设备驱动---input子系统(一)--input事件应用程序的读写实现(基于友善之臂4412开发板)

     这节,我们来说下input子系统,什么是input子系统? input子系统就是输入子系统.      输入子系统是 Linux内核用于管理各种输入设备 (键盘,鼠标,遥控杆,书写板等等 )的部分,用户通过输入子系统进行内核,命令行,图形接口之间的交换.输入子系统在内核里实现,因为设备经常要通过特定的硬件接口被访问 (例如串口, ps/2, usb等等 ),这些硬件接口由内核保护和管理.内核给用户导出一套固定的硬件无关的 input API,供用户空间程序使用. 输入子系统分为三块: