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

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










目录
相关文章
|
12天前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
44 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
|
2月前
|
数据采集 编解码 运维
一文讲完说懂 WowKey -- WowKey 是一款 Linux 类设备的命令行(CLT)运维工具
WowKey 是一款面向 Linux 类设备的命令行运维工具,支持自动登录、批量执行及标准化维护,适用于企业、团队或个人管理多台设备,显著提升运维效率与质量。
|
3月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
172 0
|
5月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
158 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
6月前
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化、简单化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权内容与执行内容,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行细节工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
6月前
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
127 1
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
存储 负载均衡 网络协议
X86 linux异常处理与Ipipe接管中断/异常
本文讲述了X86平台上Xenomai的ipipe如何接管中断处理。首先回顾了X86中断处理机制,包括IDT(中断描述符表)的工作原理和中断处理流程。接着详细介绍了Linux中中断门的初始化,包括门描述符的结构、中断门的定义和填充,以及IDT的加载。在异常处理部分,文章讲解了早期异常处理和start_kernel阶段的异常向量初始化。最后,讨论了APIC和SMP中断在IDT中的填充,以及剩余中断的统一处理。文章指出,ipipe通过在中断入口处插入`__ipipe_handle_irq()`函数,实现了对中断的拦截和优先处理,确保了实时性。
311 0
X86 linux异常处理与Ipipe接管中断/异常