【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十八章 中断下文之tasklet

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、


五十八章 中断下文tasklet

本章导读

上一章节我们已经写了一个简单的按键中断,我们是使用的中断上文,我们并没有使用中断下文。本章节我们来看一下,如果我们使用中断下文又如何来设计我们的程序呢?

58.1章节讲解了中断下文之tasklet的基础理论知识

58.2章节运用58.1章节的理论,在IMX8MM开发板上以按键中断为例,进行实验,实现按一下音量+按键,打印0-99。

本章内容对应视频讲解链接(在线观看):

中断下文之tasklet  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=37

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\015-中断下文之tasklet”路径下。

58.1 中断下文tasklet

中断的上下文与进程上下文并没有什么瓜葛,当执行一个中断处理函数时,内核处于中断上下文。由于中断相当于打断了当前执行的程序,而且中断也没有后备的进程,所以中断上下文不可以睡眠(注意某些函数会睡眠),中断处理也必须做到迅捷,有一定的时限要求。中断处理程序存在希望中断程序运行的尽量快以及希望中断处理程序完成的工作量多这一对矛盾。因此我们一般将中断分为上下两个部分,分为上半部,下半部。上半部完成有严格时限的工作(必须),例如回复硬件等,这些工作都是在禁止其他中断情况下进行的。能够延后执行的都放在下半部进行。上半部只能通过中断处理程序实现,下半部的实现目前有3种实现方式,分别为:1、软中断、2、tasklet 3、工作队列(work queues)我们主要讲tasklet。调用tasklet以后,tasklet绑定的函数并不会立马执行,而是有中断以后,经过一个很短的不确定时间在来执行,如下图所示:

58.1.1 tasklet的概念

tasklet 是通过软中断实现的,所以它本身也是软中断。软中断用轮询的方式处理,假如正好是最后一

种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。为了提高中断处理数量,顺道改

进处理效率,于是产生了 tasklet 机制。tasklet 采用无差别的队列机制,有中断时才执行,免去了循环查表之苦,tasklet 机制的优点:无类型数量限制,效率高,无需循环查表,支持 SMP 机制,一种特定类型的 tasklet只能运行在一个 CPU 上,不能并行,只能串行执行。多个不同类型的 tasklet 可以并行在多个 CPU 上。软中断是静态分配的,在内核编译好之后,就不能改变。但 tasklet 就灵活许多,可以在运行时改变(比如添加模块时)。

Linux 内核中的 tasklet 结构体:

struct tasklet_struct
{
struct tasklet_struct *next;    /* 下一个 tasklet */
unsigned long state;         /* tasklet 状态 */
atomic_t count;             /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long);   /* tasklet 执行的函数 */
unsigned long data;          /* 函数 func 的参数 */
};
  • next:链表中的下一个tasklet,方便管理和设置tasklet;
  • state: tasklet的状态。
  • count:表示tasklet是否处在激活状态,如果是0,就处在激活状态,如果非0,就处在非激活状态
  • void (*func)(unsigned long):结构体中的func成员是tasklet的绑定函数,data是它唯一的参数。
  • date:函数执行的时候传递的参数。

如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函

数原型如下:

函数

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);

t

要初始化的 tasklet

func

tasklet 的处理函数

data

要传递给 func 函数的参数

返回值

没有返回值。

功能

动态初始化tasklet

也可以使用宏 DECLARE_TASKLET 一次性完成 tasklet 的定义和初始化,DECLARE_TASKLET 定义在

include/linux/interrupt.h 文件中,定义如下:

DECLARE_TASKLET(name, func, data)

其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量,func 就是

tasklet 的处理函数,data 是传递给 func 函数的参数。

在需要调度 tasklet 的时候引用一个 tasklet_schedule()函数就能使系统在适当的时候进行调度运行,该函数原型为如下所示:

函数

void tasklet_schedule(struct tasklet_struct *t)

t

要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。

返回值

没有返回值

功能

调度tasklet

杀死tasklet使用tasklet_kill函数,函数原型如下表所示: 

函数

tasklet_kill(struct tasklet_struct *t)

t

要删除的 tasklet

功能

删除一个tasklet

注意

这个函数会等待tasklet执行完毕,然后再将它移除。该函数可能会引起休眠,所以要禁止在中断上下文中使用。

58.1.2 tasklet参考步骤

关于tasklet 的参考使用示例如下所示:

/* 定义 taselet */

struct tasklet_struct testtasklet;

/* tasklet 处理函数 */

void testtasklet_func(unsigned long data)

{

/* tasklet 具体处理内容 */

}

/* 中断处理函数 */

irqreturn_t test_handler(int irq, void *dev_id)

{

......

/* 调度 tasklet */

tasklet_schedule(&testtasklet);

......

}

/* 驱动入口函数 */

static int __init xxxx_init(void)

{

......

/* 初始化 tasklet */

tasklet_init(&testtasklet, testtasklet_func, data);

/* 注册中断处理函数 */

request_irq(xxx_irq, test_handler, 0"xxx", &xxx_dev);

......

}

总结一下基本步骤为:

步骤一:定义一个tasklet结构体

步骤二:动态初始化tasklet

步骤三:编写tasklet绑定的函数

步骤四:在中断上文调用tasklet

步骤五:卸载模块的时候删除tasklet

58.2 实验程序编写

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\015-中断下文之tasklet\001”路径下。

我们以IMX8MM开发板为例,实现按一下音量+按键,打印0-99。编写驱动代码如下所示:

/** @Author:topeet* @Description: 中断下文之tasklet,实现按键打印0-99*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;//定义tasklet结构体
struct tasklet_struct key_test;/*** @description: tasklet 的处理函数* @param {unsignedlong} data:要传递给 func 函数的参数* @return {*}无*/
void test(unsigned long data)
{int i = 100;while (i--)printk("test_key is %d \n", i);
}
/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk("start\n");tasklet_schedule(&key_test);printk("end\n");return IRQ_HANDLED;
}
/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号irq = gpio_to_irq(gpio_nu);// irq =irq_of_parse_and_map(test_device_node,0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}/* 初始化 tasklet */tasklet_init(&key_test,test,0 );return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("gooodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

58.3 运行测试

编译驱动代码为驱动模块,如下图所示:

编译成功加载驱动模块,如下图所示: 

我们按一下开发板上面的音量+键,打印信息如下图所示(部分): 

如上图所示,和我们预期结果是一样的,先打印start,再打印end,再打印0-99。

在上面的代码中,我们在代码中是直接赋值i是100,我们也可以将100传参进去,完整代码如下图所示;

/** @Author:topeet* @Description: 中断下文之tasklet,实现按键打印0-99*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;//定义tasklet结构体
struct tasklet_struct key_test;/*** @description: tasklet 的处理函数* @param {unsignedlong} data:要传递给 func 函数的参数* @return {*}无*/
void test(unsigned long data)
{int i = data;printk("i is %d \n", i);while (i--)printk("test_key is %d \n", i);
}
/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk("start\n");tasklet_schedule(&key_test);printk("end\n");return IRQ_HANDLED;
}
/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号//irq = gpio_to_irq(gpio_nu);irq =irq_of_parse_and_map(test_device_node,0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}/* 初始化 tasklet */tasklet_init(&key_test,test,100);return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

我们重新编译下驱动文件,将原来加载的驱动模块卸载掉,再加载新编译好的驱动模块,如下图所示:

我们按一下开发板上面的音量+键,打印信息如下图所示,可以看到i的值是100。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/876362.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全球性“微软蓝屏”事件及其对网络安全和系统稳定性的深远影响

近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件源于美国电脑安全技术公司“众击”提供的一个带有“缺陷”的软件更新&#xff0c;它如…

女人内裤怎么洗才是最干净?内衣裤洗衣机怎么样?哪个牌子更好?

最近刚好用到一款比较好用的洗内衣裤洗衣机&#xff01;如果你也和我一样有洗内衣裤烦恼的&#xff0c;或者可以看看&#xff01; 内衣裤作为贴身穿的衣服&#xff0c;我是不会把它和外衣一起清洗的&#xff0c;而家里面的大洗衣机已经担起了清洗外衣的工作&#xff01; 朋友们…

.NetCore定时刷新第三方Token

在.NET Core中实现定时刷新第三方Token的功能&#xff0c;你可以使用多种方法&#xff0c;包括使用System.Threading.Timer、IHostedService&#xff08;特别是用于ASP.NET Core应用&#xff09;&#xff0c;或者结合Quartz.NET等定时任务框架。以下我将介绍如何使用IHostedSer…

发票识别接口图片调用格式与python集成方式

企业员工在工作的过程中会接触到很多的发票&#xff0c;在报销前&#xff0c;需要将发票上的如发票代码、号码、日期、金额、校验码等全票面信息录入到报账系统中&#xff0c;如果一张一张手动去复制粘贴&#xff0c;将会耗费大量的人力、无力、时间成本&#xff0c;且人工手动…

JVM 内存分析工具 Memory Analyzer Tool(MAT)入门(一)

一、打开 jvisualvm &#xff08;VisualVM 是一款集成了 JDK 命令行工具和轻量级剖析功能的可视化工具。 设计用于开发和生产。&#xff09; 打开 jvisualvm.exe 工具会出现如下一些监控指标 二、VisualVM可以根据需要安装不同的插件&#xff0c;每个插件的关注点都不同&#x…

uniapp vue3 使用画布分享或者收藏功能

使用HBuilder X 开发小程序&#xff0c;大多数的画布插件很多都是vue2的写法&#xff0c;vue3的很少 我自己也试了很多个插件&#xff0c;但是有一些还是有问题&#xff0c;不好用 海报画板 - DCloud 插件市场 先将插件导入项目中 自己项目亲自用过&#xff0c;功能基本是完善…

谷粒商城-性能压测

1.压力测试 在项目上线前对其进行压力测试(以每个微服务为单元) 目的:找到系统能承载的最大负荷,找到其他测试方法更难发现的错误(两种类型:内存泄漏,并发与同步). 1.性能指标 响应时间(Response Time (RT)): 响应时间 指用户从客户端发起一个请求开始,到客户端接收到从服务…

python实现和mysql一起实现数据库的增删改查

要在 Python 中使用 MySQL 数据库进行增删改查&#xff08;CRUD&#xff09;操作&#xff0c;你可以使用 pymysql 库。以下是一些示例代码&#xff0c;展示如何实现这些操作。 首先&#xff0c;确保你已经安装了 pymysql。如果没有安装&#xff0c;可以通过以下命令安装&#…

543.二叉树的直径

给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 class Solution{// 全局变量int ans 0;public int diameterOf…

【第五天】HTTPS和HTTP有哪些区别,HTTPS的工作原理

HTTPS和HTTP的区别&#xff1a; 1.安全性&#xff1a; HTTP是明文传输协议&#xff0c;数据在传输的过程中不加密&#xff0c;容易被窃听和篡改。HTTPS通过使用SSL或TLS协议对数据进行加密&#xff0c;确保传输的数据在网络上是安全的&#xff0c;不容易被窃取和篡改。 2.加…

leetcode-104. 二叉树的最大深度

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,n…

PyQt5之PyQt5 vs PySide6: 选择Python GUI框架的对比分析

PyQt5 vs PySide6: 选择Python GUI框架的对比分析 在Python世界中,当谈到创建图形用户界面(GUI)应用程序时,PyQt5和PySide6经常被提及。这两个框架都是基于强大的Qt库的Python绑定,但它们有着微妙的差异。本文将深入比较这两个框架,帮助您做出明智的选择。 背景简介 PyQt5: …

自动化测试 pytest 中 scope 限制 fixture使用范围!

导读 fixture 是 pytest 中一个非常重要的模块&#xff0c;可以让代码更加简洁。 fixture 的 autouse 为 True 可以自动化加载 fixture。 如果不想每条用例执行前都运行初始化方法(可能多个fixture)怎么办&#xff1f;可不可以只运行一次初始化方法&#xff1f; 答&#xf…

一招就能轻松解决猫咪浮毛?最新值得买的浮毛空气净化器汇总分享

那次逛街后去朋友家&#xff0c;她家猫哈基米特别热情&#xff0c;一开门就扑过来&#xff0c;朋友直接给了个大拥抱加亲亲。汗水和猫毛全粘身上了&#xff0c;看着都让人头皮痒。好多铲屎官都抱怨&#xff0c;就算天天梳毛&#xff0c;家里还是到处都是毛&#xff0c;毕竟家里…

简说是什么虚拟DOM (Virtual DOM )

前言 虚拟 DOM &#xff08;Virtual DOM &#xff09;这个概念相信大家都不陌生&#xff0c;从 React 到 Vue &#xff0c;虚拟 DOM 为这两个框架都带来了跨平台的能力&#xff08;React-Native 和 Weex&#xff09;。因为很多人是在学习 React 的过程中接触到的虚拟 DOM &…

理解文件系统(上)

模拟实现文件库 创建文件以便理解 自己想实现的文件接口&#xff0c;进行模拟实现 模拟的头文件要准备的头文件 open接口的实现 write接口的实现fflush接口的实现 flose接口的实现 文件实现 stdio.h stdio.c test.c makefile 创建makefile 编译运行 执行后输出log.txt,看…

Jenkins中使用环境变量

直接使用环境变量 pipeline {agent {label "${28}"}stages {stage("git clone"){steps{script{sh """pwdls"""// 环境变量的使用// 输出所有环境变量 echo "All environment variables: ${env}" // 输出单个环境…

【虚拟机】 VMware截图版详细安装教程

VMware-workstation-full-17.5.1-23298084 的安装&#xff0c;详细安装过程。 1.以管理员身份运行安装包 点击文件&#xff0c;右键打开&#xff0c;以管理员身份运行&#xff1b; 2.根据安装提示&#xff0c;重启电脑&#xff1b; &#xff08;重启与否看自己电脑情况&…

企业邮件系统管理(七)全面解析企业邮件系统的配置与管理:从安全到高可用性的深入探讨

文章目录 全面解析企业邮件系统的配置与管理&#xff1a;从安全到高可用性的深入探讨引言第一章&#xff1a;邮件系统安全配置一、SMTP连接器及其配置配置步骤&#xff1a; 二、接受域与DNS中的MX记录配置步骤&#xff1a; 三、邮件加密和签名配置步骤&#xff1a; 第二章&…

【深入理解SpringCloud微服务】深入理解Ribbon原理并手写一个微服务负载均衡器

深入理解Ribbon原理并手写一个微服务负载均衡器 负载均衡器理解Ribbon原理手写一个微服务负载均衡器总体设计LoadBalanceClientHttpRequestFactorySimpleLoadBalanceClientSimpleLoadBalancerLoadBalanceRulespring.factories与LoadBalanceConfig 负载均衡器 在微服务架构里面…