当前位置: 首页 > news >正文

【系列文章】Linux系统中断的应用02-中断下文 tasklet

【系列文章】Linux系统中断的应用02-中断下文 tasklet

该文章为系列文章:Linux系统中断的应用中的第02篇
该系列的导航页连接:
【系列文章】Linux系统中断的应用-导航页


文章目录

  • 【系列文章】Linux系统中断的应用02-中断下文 tasklet
    • 一、什么是 tasklet
    • 二、tasklet 相关接口函数
      • 2.1静态初始化函数
      • 2.2动态初始化函数
      • 2.3关闭函数
      • 2.4使能函数
      • 2.5调度函数
      • 2.6销毁函数
    • 二、实验程序的编写
      • 2.1运行测试


在上一篇文章中,我们申请 GPIO 中断,使用的是 request_irq,但是 request_irq 绑定的中断服务程序指的是中断上文。在之前讲解了:中断分为俩个部分——中断上文和中断下文。本章节我们来学习中断下文的一种实现方式——tasklet。

一、什么是 tasklet

在 Linux 内核中,tasklet 是一种特殊的软中断机制,被广泛用于处理中断下文相关的任务。它是一种常见且有效的方法,在多核处理系统上可以避免并发问题。Tasklet 绑定的函数在同一时间只能在一个 CPU 上运行,因此不会出现并发冲突。然而,需要注意的是,tasklet 绑定的函数中不能调用可能导致休眠的函数,否则可能引起内核异常。

在 Linux 内核中,tasklet 结构体的定义位于 include/linux/interrupt.h 头文件中。其原型如下:

struct tasklet_struct {struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};
typedef struct tasklet_struct tasklet_t;

tasklet_struct 结构体包含以下成员:

next:指向下一个tasklet的指针,用于形成链表结构,以便内核中可以同时管理多个tasklet。
state:表示 tasklet 的当前状态。
count:用于引用计数,用于确保 tasklet 在多个地方调度或取消调度时的正确处理。
func:指向 tasklet 绑定的函数的指针,该函数将在 tasklet 执行时被调用。
data:传递给 tasklet 绑定函数的参数

此外,为了方便,还定义了 tasklet_t 类型作为 struct tasklet_struct 的别名。这样我们可以使用 tasklet_t 来声明 tasklet 变量,而不是直接使用 struct tasklet_struct。

二、tasklet 相关接口函数

2.1静态初始化函数

在 Linux 内核中,有一个用于静态初始化 tasklet 的宏函数:DECLARE_TASKLET。这个宏函数可以帮助我们更方便地进行 tasklet 的静态初始化。

宏函数的原型如下:

#define DECLARE_TASKLET(name,func,data) \
struct tasklet_struct name = { NULL,0,ATOMIC_INIT(0),func,data}

其中,name 是 tasklet 的名称,func 是 tasklet 的处理函数,data 是传递给处理函数的参数。初始化状态为使能状态。

如果 tasklet 初始化函数为非使能状态,使用以下宏定义:

#define DECLARE_TASKLET_DISABLED(name,func,data) \
struct tasklet_struct name = { NULL,0,ATOMIC_INIT(1),func,data}

其中,name 是 tasklet 的名称,func 是 tasklet 的处理函数,data 是传递给处理函数的参数。初始化状态为非使能状态。

下面是一个示例,展示了如何使用 DECLARE_TASKLET 宏函数进行 tasklet 的静态初始化:

#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{// Tasklet 处理逻辑// ... 
}// 静态初始化 taskletDECLARE_TASKLET(my_tasklet, my_tasklet_handler, 0);// 驱动程序的其他代码

在上述示例中,my_tasklet 是 tasklet 的名称,my_tasklet_handler 是 tasklet 的处理函数,0是传递给处理函数的参数。但是需要注意的是,使用 DECLARE_TASKLET 静态初始化的 tasklet无法在运行时动态销毁,因此在不需要 tasklet 时,应该避免使用此方法。如果需要在运行时销毁 tasklet,应使用 tasklet_init 和 tasklet_kill 函数进行动态初始化和销毁,接下来我们来学习动态初始化函数。

2.2动态初始化函数

在 Linux 内核中,可以使用 tasklet_init 函数对 tasklet 进行动态初始化。该函数原型为:

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

其中,t 是指向 tasklet 结构体的指针,func 是 tasklet 的处理函数,data 是传递给处理函数的参数

以下是一个示例,tasklet_init 函数进行动态初始化如下所示:

#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ... 
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 驱动程序的其他代码

在示例中,我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。然后,声明了一个名为 my_tasklet 的 tasklet 结构体。

接下来,通过调用 tasklet_init 函数,进行动态初始化。通过使用 tasklet_init 函数,我们可以在运行时动态创建和初始化 tasklet。这样,我们可以根据需要灵活地管理和控制 tasklet 的生命周期。在不再需要 tasklet 时,可以使用 tasklet_kill函数进行销毁,以释放相关资源。

2.3关闭函数

在 Linux 内核中,可以使用 tasklet_disabled 函数来关闭一个已经初始化的 tasklet。该函数的原型如下:

void tasklet_disable(struct tasklet_struct *t);

其中,t 是指向 tasklet 结构体的指针。
以下是一个示例,使用 tasklet_disable 函数来关闭 tasklet。

#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ... 
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 关闭 tasklet
tasklet_disable(&my_tasklet);
// 驱动程序的其他代码

在上述示例中,我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。然后,声明了一个名为 my_tasklet 的 tasklet 结构体,并使用 tasklet_init 函数对其进行初始化。最后,通过调用 tasklet_disable 函数,我们关闭了 my_tasklet。

关闭 tasklet 后,即使调用 tasklet_schedule 函数触发 tasklet,tasklet 的处理函数也不会再被执行。这可以用于临时暂停或停止 tasklet 的执行,直到再次启用(通过调用 tasklet_enable函数)。

需要注意的是,关闭 tasklet 并不会销毁 tasklet 结构体,因此可以随时通过调用tasklet_enable 函数重新启用 tasklet,或者调用 tasklet_kill 函数来销毁 tasklet。

2.4使能函数

在 Linux 内核中,可以使用 tasklet_enable 函数来使能(启用)一个已经初始化的 tasklet。该函数的原型如下:

void tasklet_disable(struct tasklet_struct *t);

其中,t 是指向 tasklet 结构体的指针。
以下是一个示例,展示如何使用 tasklet_enable 函数来使能 tasklet:

#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ... 
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 使能 tasklet
tasklet_enable(&my_tasklet);

在上述示例中,我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。然后,声明了一个名为 my_tasklet 的 tasklet 结构体,并使用 tasklet_init 函数对其进行初始化。最后,通过调tasklet_enable 函数,我们使能(启用)了 my_tasklet。

使能 tasklet 后,如果调用 tasklet_schedule 函数触发 tasklet,则 tasklet 的处理函数将会被执行。这样,tasklet 将开始按计划执行其处理逻辑。

需要注意的是,使能 tasklet 并不会自动触发 tasklet 的执行,而是通过调用 tasklet_schedule函数来触发。同时,可以使用 tasklet_disable 函数来临时暂停或停止 tasklet 的执行。如果需要永久停止 tasklet 的执行并释放相关资源,则应调用 tasklet_kill 函数来销毁 tasklet。

2.5调度函数

在 Linux 内核中,可以使用 tasklet_schedule 函数来调度(触发)一个已经初始化的 tasklet执行。该函数的原型如下:

void tasklet_schedule(struct tasklet_struct *t);

其中,t 是指向 tasklet 结构体的指针。
以下是一个示例,展示如何使用 tasklet_schedule 函数来调度 tasklet 执行:

#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ... 
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
// 调度 tasklet 执行
tasklet_schedule(&my_tasklet);
// 驱动程序的其他代码

在上述示例中,我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。然后,声明了一个名为 my_tasklet 的 tasklet 结构体,并使用 tasklet_init 函数对其进行初始化。最后,通过调用tasklet_schedule 函数,我们调度(触发)了 my_tasklet 的执行。需要注意的是,调度 tasklet 只是将 tasklet 标记为需要执行,并不会立即执行 tasklet 的处理函数。实际的执行时间取决于内核的调度和处理机制。

2.6销毁函数

在 Linux 内核中,可以使用 tasklet_kill 函数来销毁一个已经初始化的 tasklet,释放相关资源。该函数的原型如下:

void tasklet_kill(struct tasklet_struct *t);

其中,t 是指向 tasklet 结构体的指针。
以下是一个示例,展示如何使用 tasklet_kill 函数来销毁 tasklet:

#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ... 
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
tasklet_disable(&my_tasklet);
// 销毁 tasklet
tasklet_kill(&my_tasklet);
// 驱动程序的其他代码

在上述示例中,我们首先定义了 my_tasklet_handler 作为 tasklet 的处理函数。然后,声明了一个名为 my_tasklet 的 tasklet 结构体,并使用 tasklet_init 函数对其进行初始化。最后,通过调用 tasklet_kill 函数,我们销毁了 my_tasklet。

调用 tasklet_kill 函数会释放 tasklet 所占用的资源,并将 tasklet 标记为无效。因此,销毁后的 tasklet 不能再被使用。

需要注意的是,在销毁 tasklet 之前,应该确保该 tasklet 已经被停止(通过调用tasklet_disable 函数)。否则,销毁一个正在执行的 tasklet 可能导致内核崩溃或其他错误。

一旦销毁了 tasklet,如果需要再次使用 tasklet,需要重新进行初始化(通过调用 tasklet_init函数)。在下一小节中我们将使用上述 tasklet 函数相关接口函数进行相应的实验。

二、实验程序的编写

本实验将实现注册显示屏触摸中断,每按当触摸 LCD 显示屏就会触发中断服务函数,在中断服务函数中调度中断下文 tasklet 处理函数,打印“This id test_interrupt”和“data is 1”。

在驱动程序中的模块初始化函数中,我们将 GPIO 转换为中断号,并使用 request_irq 函数请求中断,然后对 tasklet 进行初始化。在中断处理函数中,我们调度 tasklet 执行,使得当中断触发时,tasklet 会被调度执行。在模块退出函数中,我们释放中断资源,并使能 tasklet 销毁tasklet。

编写完成的 interrupt.c 代码如下所示,添加的代码已加粗表示。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
// #include <linux/delay.h>int irq;
struct tasklet_struct mytasklet;
// 定义 tasklet 处理函数
void mytasklet_func(unsigned long data)
{printk("data is %ld\n", data);// msleep(3000);
}// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{printk("This id test_interrupt\n");tasklet_schedule(&mytasklet); // 调度 tasklet 执行return IRQ_RETVAL(IRQ_HANDLED);
}// 模块初始化函数
static int interrupt_irq_init(void)
{int ret;irq = gpio_to_irq(101); // 将 GPIO 转换为中断号printk("irq is %d\n", irq);// 请求中断ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);if (ret < 0){printk("request_irq is error\n");return -1;}// 初始化 tasklettasklet_init(&mytasklet, mytasklet_func, 1);return 0;
}// 模块退出函数
static void interrupt_irq_exit(void)
{free_irq(irq, NULL);tasklet_enable(&mytasklet); // 使能 tasklet(可选)tasklet_kill(&mytasklet); // 销毁 taskletprintk("bye bye\n");
}module_init(interrupt_irq_init); // 指定模块的初始化函数
module_exit(interrupt_irq_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("XXX"); // 模块的作者

2.1运行测试

看到驱动加载之后,可以看到申请的中断号(113)被打印了出来,然后用手触摸连接的屏幕,触发中断服务程序,打印如下图所示:
在这里插入图片描述
在上图中,可以看到打印中断处理函数中添加的打印“This is test_interrupt”和 tasklet 处理函数中添加的打印“data is 1”,说明成功执行了中断下文 tasklet 处理函数。


文章转载自:

http://rn9X1Na2.bfLws.cn
http://Y00HTWLM.bfLws.cn
http://0ktwKcLa.bfLws.cn
http://3isecOuU.bfLws.cn
http://gr4XNH66.bfLws.cn
http://JGjS4mUl.bfLws.cn
http://DB1wW6Sw.bfLws.cn
http://FYNdYrz4.bfLws.cn
http://jMLj45eN.bfLws.cn
http://pnRykWFV.bfLws.cn
http://D9cWUKvK.bfLws.cn
http://PNHJR4QN.bfLws.cn
http://yoDwzrAf.bfLws.cn
http://yUxLXzvJ.bfLws.cn
http://KWwWoWQi.bfLws.cn
http://f3ILoo59.bfLws.cn
http://cbZUr7zc.bfLws.cn
http://sm93qdyg.bfLws.cn
http://3PriRM4h.bfLws.cn
http://u91yhyV8.bfLws.cn
http://HS1R3lvg.bfLws.cn
http://VMATySJE.bfLws.cn
http://gtSXTKXI.bfLws.cn
http://BoiQEziz.bfLws.cn
http://X0fmGd6L.bfLws.cn
http://GpXnby5q.bfLws.cn
http://GYsIz9Ih.bfLws.cn
http://qyEB8kPj.bfLws.cn
http://bNXxV7uG.bfLws.cn
http://zK08IZ3g.bfLws.cn
http://www.dtcms.com/a/386243.html

相关文章:

  • GPT-5-Codex 模型评测报告
  • MAZANOKE+cpolar让照片存储无上限
  • (笔记)Linux系统设置虚拟内存
  • Kotlin-基础语法练习三
  • windows上Redis Desktop Manager链接服务器docker内Redis方法
  • jMeter小记-数组数据X_id集合获取及循环控制器使用调用数组数据X_id
  • 迁移指南:从旧版 Electron 升级
  • Node.js中的 http 模块详解
  • 设置powershell每次打开自动启动anaconda中自设环境
  • keil5和arm编译器安装
  • 【初阶数据结构】顺序表
  • 外媒称Switch2手柄鼠标功能 将彻底改变玩游戏的方式
  • 【Spring Cloud】微服务
  • 设计模式(Java实现)----建造者模式
  • C++设计模式_创建型模式_建造者模式Builder
  • Dell PowerEdge R620 服务器内存和硬盘罢工了
  • 儿童无屏幕对讲机 Bunny 融资百万美元;腾讯会议推出 AI 托管:先行听会、代听多会、全程记录丨日报
  • linux系统命令学习
  • Java 大视界 -- 基于 Java 的大数据可视化在企业供应链风险管理与应急响应中的应用(412)
  • 【C++游记】Map与Set的封装
  • Infoseek舆情监测系统:AI驱动的一站式舆情管理解决方案
  • IDEA 连接MySQL数据库
  • Electron的IPC通讯 send/on 和 invoke/handle 的区别
  • 扩展开发:创建 Electron 插件
  • windows下ffmpeg的编译安装(支持硬件加速)--2025最新
  • JAVA后端面试笔记(二)
  • 每日前端宝藏库 | fullPage.js [特殊字符]✨
  • c语言 实现每条指令之间都会无阻塞间隔指定ms数
  • 需求:如何高效的推荐产品
  • java21学习笔记-序列集合