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

news2024/11/23 2:46:50

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_driver
    ret = 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_driver
    ret = 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.coloradmin.cn/o/1951669.html

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

相关文章

跨越至智慧水利新时代:以科技创新为核心引擎,全面构建智能化水资源管理体系,显著提升水资源治理的智能化水平与高效治理能力,共筑可持续水生态未来

目录 一、智慧水利的概念与重要性 &#xff08;一&#xff09;智慧水利的概念 &#xff08;二&#xff09;智慧水利的重要性 二、智慧水利的关键技术 &#xff08;一&#xff09;物联网技术 &#xff08;二&#xff09;大数据技术 &#xff08;三&#xff09;云计算技术 …

Tekion 选择 ClickHouse Cloud 提升应用性能和指标监控

本文字数&#xff1a;4187&#xff1b;估计阅读时间&#xff1a;11 分钟 作者&#xff1a;ClickHouse team 本文在公众号【ClickHouseInc】首发 Tekion 由前 Tesla CIO Jay Vijayan 于 2016 年创立&#xff0c;利用大数据、人工智能和物联网等技术&#xff0c;为其汽车客户解决…

X00078-基于深度强化学习图神经网络的云工作流调度python

强化学习&#xff08;RL&#xff09;技术通过蒙特卡洛树搜索&#xff08;MCTS&#xff09;的加速&#xff0c;成功解决了多资源需求下的DAG问题&#xff0c;优化目标为缩短makespan。根据当前的计算资源和任务状态&#xff0c;MCTS算法被用来探索并选择最佳的动作&#xff0c;在…

【Linux】远程连接Linux虚拟机(MobaXterm)

【Linux】远程连接Linux虚拟机&#xff08;MobaXterm&#xff09; 零、原因 有时候我们在虚拟机中操作Linux不太方便&#xff0c;比如不能复制粘贴&#xff0c;不能传文件等等&#xff0c;我们在主机上使用远程连接软件远程连接Linux虚拟机后可以解决上面的问题。 壹、软件下…

快速搞定分布式RabbitMQ---RabbitMQ进阶与实战

本篇内容是本人精心整理&#xff1b;主要讲述RabbitMQ的核心特性&#xff1b;RabbitMQ的环境搭建与控制台的详解&#xff1b;RabbitMQ的核心API&#xff1b;RabbitMQ的高级特性;RabbitMQ集群的搭建&#xff1b;还会做RabbitMQ和Springboot的整合&#xff1b;内容会比较多&#…

The resource type Sheet does not implement java.lang.AutoCloseable

The resource type Sheet does not implement java.lang.AutoCloseable 修改一下 应该是【高版本JDK】切换集成到我这个项目【低版本 JDK 8】出错了

Ubuntu设置网络

进入网络配置文件夹 cd /etc/netplan 使用 vim 打开下的配置文件 打开后的配置 配置说明&#xff1a; network:# 网络配置部分ethernets:# 配置名为ens33的以太网接口ens33:addresses:# 为ens33接口分配IP地址192.168.220.30&#xff0c;子网掩码为24位- 192.168.220.30/24n…

【MySQL】:表操作语法大全

表内容的操作 增删改查 CRUD (create、retrieve、update、delete) 新增 基本语法 语法为&#xff1a; insert into 表名 values (值&#xff0c;值&#xff0c;值...);这里的列数和类型&#xff0c;要和表结构匹配插入中文的话&#xff0c;要确保数据库创建的时候要设置字…

Python 高阶语法

前言&#xff1a; 我们通过上篇文章学习了Python的基础语法&#xff0c;接下来我们来学习Python的高阶语法 1.初识对象 在Python中我们可以做到和生活中那样&#xff0c;设计表格、生产表格、填写表格的组织形式的 面向对象包含 3 大主要特性&#xff1a;  封装  继承 …

NLP基础知识2【各种大模型的注意力】

注意力 传统Attention存在的问题优化方向变体有哪些现在的主要变体集中在KVMulti-Query AttentionGrouped-query AttentionFlashAttention 传统Attention存在的问题 上下文约束速度慢&#xff0c;显存占用大&#xff08;因为注意力考虑整体信息&#xff0c;所以每一个位置都要…

mysql之触发器的使用

cr一&#xff1a;创建goods表和orders表&#xff1b; mysql> use mydb16_tirgeer Database changed mysql> create table goods(-> gid char(8) primary key,-> name varchar(10),-> price decimal(8,2),->-> num int); Query OK, 0 rows affected (0.0…

18 Python常用内置函数——排序与逆序

sorted() 对列表、元组、字典、集合或其他可迭代对象进行排序并返回新列表&#xff0c;reversed() 对可迭代对象&#xff08;生成器对象和具有惰性求值特性的 zip、map、filter、enumerate 等类似对象除外&#xff09;进行翻转&#xff08;首尾交换&#xff09;并返回可迭代的 …

《GPT-4o mini:开启开发与创新的新纪元》

在科技发展的快速进程中&#xff0c;OpenAI 推出的 GPT-4o mini 模型如同一阵春风&#xff0c;给开发者们带来了新的希望和机遇。它以其卓越的性能和极具吸引力的价格&#xff0c;成为了行业内热议的焦点。 当我首次听闻 GPT-4o mini 的消息时&#xff0c;内心充满了好奇与期待…

深度学习目标检测入门实战

深度学习目标检测入门实战 一、什么是目标检测二、目标检测常用的数据集&#xff08;开源&#xff09;&#xff08;一&#xff09;VOC数据集&#xff08;1&#xff09;背景知识&#xff08;2&#xff09;数据集的下载&#xff08;3&#xff09;VOC2007 数据集的标注&#xff08…

vue3前端架构---打包配置

最近看到几篇vue3配置项的文章&#xff0c;转载记录一下 Vue3.2 vue/cli-service 打包 chunk-vendors.js 文件过大导致页面加载缓慢解决方案-CSDN博客文章浏览阅读2k次&#xff0c;点赞8次&#xff0c;收藏9次。Vue3.2 vue/cli-service 打包 chunk-vendors.js 文件过大导致页…

苦学Opencv的第九天:模板匹配

Python OpenCV入门到精通学习日记&#xff1a;模板匹配 前言 模板匹配是一种最原始、最基本的识别方法&#xff0c;可以在原始图像中寻找特定图像的位置。模板匹配经常应用于简单的图像查找场景中&#xff0c;例如&#xff0c;在集体合照中找到某个人的位置。 #mermaid-svg-N…

Linux中,MySQL索引、事物与存储引擎

MySQL索引介绍 索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址。在数据十分庞大的时候&#xff0c;索引可以大大加快查询的速度。这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访…

tinyxml2的入门教程

tinyxml2的入门教程 前言一、tinyxml2 创建xml 文件二、tinyxml2 添加数据三、tinyxml2 更改数据四、tinyxml2 删除数据五、tinyxml2 打印总结 前言 xml 是一种标记型文档&#xff0c;有两种基本解析方式&#xff1a;DOM(Document Object Model&#xff0c;文档对象模型)和SAX…

C++ 数字和数组解析

文章目录 1. 定义数字 2. 数学运算 3. 随机数 4. 数组 声明数组 初始化数组 5. 访问数组元素 6. 数组类型 7. 多维数组 二维数组 初始化二维数组 访问二维数组元素 8. 指向数组的指针 9. 传递数组给函数 10. 从函数返回数组 1. 定义数字 通常&#xff0c;当需要…

嵌入式人工智能(23-基于树莓派4B的温湿度传感器DHT11)

1、湿度传感器 目前市面上&#xff0c;仅测量湿度的传感器很少&#xff0c;普遍使用的都是温/湿度传感器&#xff0c;即以温/湿度一体式的探 头作为测温元件&#xff0c;将温度和湿度信号采集出来&#xff0c;再经过稳压滤波、运算放大、非线性校正、V转换、恒流及反向保护等电…