linux tasklet

软中断、tasklet 以及工作队列,均是 linux 中将任务推后执行的机制。其中工作队列与用户态使用的线程池类似。

什么是任务推后执行呢 ?

可以借助于开发应用时经常使用的线程池来理解。任务推后执行,就是任务本该执行的时候没有立即执行,而是将任务放到任务容器(标志位或者任务队列)中,相当于生产者;任务容器还有消费者,消费者从容器中取出任务来执行。这就是推后执行,其中有 3 个组成元素,生产者,任务容器和消费者。

1 tasklet 通过软中断实现

tasklet 是通过软中断来实现的,相当于在软中断的基础上又扩展了一层。有一种场景的思路和 tasklet 与软中断的关系比较类似,在开发网络应用时,由于 tcp 端口是有限的,如果系统的规格下 tcp 端口号不够用的话,在应用层有时会做一层虚拟化,用户使用的连接是虚拟连接,并不是独占一个 tcp 端口,而是多个虚拟连接共享一个 tcp 端口。tasklet 对软中断的使用类似于使用 tcp 时的端口复用。

enum
{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};

tasklet 使用了两个软中断:HI_SOFTIRQ 和 TASKLET_SOFTIRQ。两者优先级不同,前者比后者优先级高。软中断的优先级是怎么实现的呢 ?不同的软中断触发,就是设置软中断的标志位,类似于一个 bitmap, 软中断数值越小,对应的优先级就越高,因为在函数 __do_softirq() 中遍历软中断时,是从小向大进行遍历的,这样保证了数值小的软中断被先处理。软中断的优先级类似于调度策略中的 rt 调度策略,rt 调度策略在内核中的优先级取值范围是 [0, 98],也使用了一个 bitmap 来标志这个优先级下有没有待执行的线程,优先级数值越小,越早被遍历,表示优先级越高。

这两个软中断在函数 softirq_init() 中进行注册。软中断处理函数分别是 tasklet_action 和 tasklet_hi_action。

void __init softirq_init(void)
{int cpu;for_each_possible_cpu(cpu) {per_cpu(tasklet_vec, cpu).tail =&per_cpu(tasklet_vec, cpu).head;per_cpu(tasklet_hi_vec, cpu).tail =&per_cpu(tasklet_hi_vec, cpu).head;}open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

2 tasklet 数据结构和 api

2.1 struct tasklet_struct

struct tasklet_struct 表示一个 tasklet。其中最主要的成员是这个 tasklet 的处理函数,处理函数有两个选择,一个是 func,func 的入参是用户自定义的参数,data 在 struct tasklet_struct 中的 data 成员中保存;一个是 callback,callback 的参数是 tasklet 本身,当使用 callback 的时候需要将 use_callback 设置为 true。

struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;bool use_callback;union {void (*func)(unsigned long data);void (*callback)(struct tasklet_struct *t);};unsigned long data;
};

state 字段表示 tasklet 当前的状态。状态可取如下两个值:

enum
{// 说明 tasklet 已经被提交了,等待被执行TASKLET_STATE_SCHED,	/* Tasklet is scheduled for execution */// 说明 tasklet 当前正在被执行// 该状态只在多处理器系统上才会生效// 单处理器系统上不需要对 tasklet 做是不是 running 的判断TASKLET_STATE_RUN	/* Tasklet is running (SMP only) */
};// 只用定义了 CONFIG_SMP,TASKLET_STATE_RUN 才会被使用
#ifdef CONFIG_SMP
static inline int tasklet_trylock(struct tasklet_struct *t)
{return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}static inline void tasklet_unlock(struct tasklet_struct *t)
{smp_mb__before_atomic();clear_bit(TASKLET_STATE_RUN, &(t)->state);
}static inline void tasklet_unlock_wait(struct tasklet_struct *t)
{while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { barrier(); }
}
#else
#define tasklet_trylock(t) 1
#define tasklet_unlock_wait(t) do { } while (0)
#define tasklet_unlock(t) do { } while (0)
#endif

count 是 tasklet 的计数器,当 count 不是 0,tasklet 会被禁止,不允许执行;只有当 count 为 0 的时候,tasklet 才可以被执行。与之对应的是 tasklet_disable() 和 tasklet_enable() 两个函数,分别是禁用 tasklet 和使能 tasklet,在 tasklet_disable() 中增加 count 的值,在 tasklet_enable() 中减小 count 的值。

static inline void tasklet_disable(struct tasklet_struct *t)
{tasklet_disable_nosync(t);tasklet_unlock_wait(t);smp_mb();
}static inline void tasklet_enable(struct tasklet_struct *t)
{smp_mb__before_atomic();atomic_dec(&t->count);
}

2.2 tasklet 初始化

初始化一个 tasklet,就是定义一个 struct tasklet_struct 结构体,然后初始化其中的成员。可以通过宏 DECLARE_TASKLET 来定义一个 tasklet,name 是 struct tasklet_struct 的名字,_callback 是 tasklet 的处理函数;tasklet_setup() 可以初始化一个 tasklet,处理函数是 callback,tasklet_init() 也可以初始化一个 tasklet,处理函数是 func。

#define DECLARE_TASKLET(name, _callback)		\
struct tasklet_struct name = {				\.count = ATOMIC_INIT(0),			\.callback = _callback,				\.use_callback = true,				\
}void tasklet_setup(struct tasklet_struct *t,void (*callback)(struct tasklet_struct *))
{t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->callback = callback;t->use_callback = true;t->data = 0;
}
EXPORT_SYMBOL(tasklet_setup);void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
{t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->func = func;t->use_callback = false;t->data = data;
}
EXPORT_SYMBOL(tasklet_init);

2.3 tasklet_schedule

使用 TASKLET_SOFTIRQ 的 tasklet 通过 task_schedule 来调度;使用 HI_SOFTIRQ 的 tasklet 通过 tasklet_hi_schedule 来调度。

tasklet_schedule() 这样的命名并不是太好理解,触发软中断的时候使用的关键字是 raise,而触发 tasklet 的时候使用的关键字是 schedule。意思都是将任务设置到待执行的状态,可以执行了。

① 判断 tasklet 是不是处于 SCHED 状态

test_and_set_bit() 将这一 bit 设置为 1,并返回设置之前的值,如果设置之前是 0 那么就会调用__tasklet_schedule() 将 tasklet 设置到待执行状态;否则的话,说明 tasklet 已经调度了,直接返回。

static inline void tasklet_schedule(struct tasklet_struct *t)
{if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))__tasklet_schedule(t);
}

② __tasklet_schedule() 最终调用到函数 __tasklet_schedule_common(),在这个函数中主要做了两件事,一个是将这个 tasklet 加入到 tasklet_vec 链表中,这样在函数 tasklet_action 中就能遍历到这个 tasklet 并进行处理;第二个工作是触发 tasklet 软中断。

struct tasklet_head {struct tasklet_struct *head;struct tasklet_struct **tail;
};static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);static void __tasklet_schedule_common(struct tasklet_struct *t,struct tasklet_head __percpu *headp,unsigned int softirq_nr)
{struct tasklet_head *head;unsigned long flags;local_irq_save(flags);head = this_cpu_ptr(headp);t->next = NULL;*head->tail = t;head->tail = &(t->next);raise_softirq_irqoff(softirq_nr);local_irq_restore(flags); tasklet_schedule
}

tasklet_vec 和 tasklet_hi_vec 是 per cpu 的变量,每个 cpu 都有一个对应的链表。这两个链表的维护非常绕,有两个成员,head 和 tail,head 是 struct tasklet_struct 指针,tail 是 struct task_struct 二级指针,在初始化的时候,使  tail 指向了 head。

在 tasklet_schedule() 中将 tasklet 加到链表中,只需要维护 tail 就可以了。

void __init softirq_init(void)
{int cpu;for_each_possible_cpu(cpu) {per_cpu(tasklet_vec, cpu).tail =&per_cpu(tasklet_vec, cpu).head;per_cpu(tasklet_hi_vec, cpu).tail =&per_cpu(tasklet_hi_vec, cpu).head;}open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

2.4 tasklet 执行

tasklet 的执行在函数 tasklet_action() 中进行。最终调用到函数 tasklet_action_common()。

(1) tl_head 是 tasklet_vec 链表,首先从 tasklet_vec 中把所有的 tasklet 取下来,然后将 tasklet_vec 链表设置为空

(2) 处理 tasklet 链表中的任务

① tasklet_trylock() 会判断当前 tasklet 是不是出于 RUN 状态,如果是的话那么不处理这个 tasklet

② atomic_read 读取 count 的值,来判断这个 tasklet 是不是被禁用,如果被禁用,则不处理

③ 清除 SCHED 标志,清除之后,可以再次调度这个 tasklet 了

④ 如果这个 tasklet 没有被处理,那么会被加回到 tasklet_vec 链表中

static void tasklet_action_common(struct softirq_action *a,struct tasklet_head *tl_head,unsigned int softirq_nr)
{struct tasklet_struct *list;local_irq_disable();list = tl_head->head;tl_head->head = NULL;tl_head->tail = &tl_head->head;local_irq_enable();while (list) {struct tasklet_struct *t = list;list = list->next;if (tasklet_trylock(t)) {if (!atomic_read(&t->count)) {if (!test_and_clear_bit(TASKLET_STATE_SCHED,&t->state))BUG();if (t->use_callback)t->callback(t);elset->func(t->data);tasklet_unlock(t);continue;}tasklet_unlock(t);}local_irq_disable();t->next = NULL;*tl_head->tail = t;tl_head->tail = &t->next;__raise_softirq_irqoff(softirq_nr);local_irq_enable();}
}

3 tasklet 使用示例

如下是在一个内核模块中使用 tasklet。

① 定义了一个 tasklet my_tasklet,在模块初始化函数中通过 tasklet_init() 进行初始化,

tasklet 的处理函数是 tasklet_handler() 在该函数中打印了一条日志 "Tasklet executed successfully!\n"。

② 在内核模块中创建了一个线程,在该线程中每隔一秒通过 tasklet_schedule() 调度一次。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/interrupt.h>// 定义 Tasklet 处理函数
void tasklet_handler(unsigned long data);// 声明一个 Tasklet 结构体
static struct tasklet_struct my_tasklet;// 内核线程执行函数
static int thread_func(void *data)
{while (!kthread_should_stop()) {// 调度 Tasklet 执行tasklet_schedule(&my_tasklet);// 等待 1smsleep(1000);}return 0;
}// 模拟的 Tasklet 处理函数
void tasklet_handler(unsigned long data)
{printk(KERN_INFO "Tasklet executed successfully!\n");
}// 模块初始化函数
static int __init tasklet_example_init(void)
{// 初始化 Tasklet 结构体tasklet_init(&my_tasklet, tasklet_handler, 0);// 创建内核线程kthread_run(thread_func, NULL, "tasklet_thread");printk(KERN_INFO "Tasklet example module initialized\n");return 0;
}// 模块清理函数
static void __exit tasklet_example_exit(void)
{// 停止 Tasklet 的调度tasklet_kill(&my_tasklet);printk(KERN_INFO "Tasklet example module exited\n");
}// 注册模块初始化和清理函数
module_init(tasklet_example_init);
module_exit(tasklet_example_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyl");

4 软中断,tasklet 的区别

软中断和 tasklet 的最主要区别,同一个软中断可以在多个 cpu 上并发处理,而对于一个 tasklet 来说,虽然也可以调度到不同的 cpu 上,但是对于同一个 tasklet 不会并发处理。

从 tasklet 的处理函数 tasklet_action_common() 中也可以看出来,如果当前这个 tasklet 处于 RUN 状态,那么就不会处理它。

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

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

相关文章

文档转换失败如何排查?魔众文库系统

如何排查 文档转换失败时&#xff0c;请查看最新的日志文件 storage/logs/laravel-*.log 日志文件中会将执行的命令和命令执行后返回的结果存储到日志中&#xff0c;请参考日志报错信息自行解决。

西圣、漫步者、万魔开放式耳机怎么样?无广真实测评对比推荐

开放式耳机因其独特的音质体验和佩戴舒适度&#xff0c;受到了越来越多消费者的青睐。西圣、漫步者、万魔作为国内知名的耳机品牌&#xff0c;各自都推出了自家的开放式耳机产品&#xff0c;那么&#xff0c;这三款耳机究竟如何呢&#xff1f;身为开放式耳机党的我&#xff0c;…

Datacom HCIP笔记-MPLS协议 之一

MPLS标签放在二层头和IP头之间可以称之为2.5层的位置 LSP&#xff08;Label Switched Path&#xff09;&#xff1a;标签交换路径&#xff0c;艮即到达同一目的地址的报文在MPLS网络中经过的路径。 FEC&#xff08;Forwarding Equivalent Class&#xff09;&#xff1a;一般指具…

编辑脚本 shell中的符号

shell中的符号 ~&#xff1a;家目录 !&#xff1a;执行历史命令 $&#xff1a;取变量内容 - * / %:数学运算符 &&#xff1a;后台执行 *&#xff1a;通配符 ?&#xff1a;匹配除回车以外的一个字符 ;&#xff1a;命令分隔符 |&#xff1a;管道符&#xff0c;该符号的上一…

【科研】SCI同行评审-审稿指南与意见撰写

【科研】SCI同行评审-审稿指南与意见撰写 文章目录 1、审稿指南 By Elsevier2、审稿指南 By Nature3、审稿意见撰写 1、审稿指南 By Elsevier Elsevier审稿人注意事项 Elsevier如何进行同行评审 how-to-review 完整的研究文章 检查手稿中解决的研究问题的重要性&#xff08;例…

uniapp:Hbuilder没有检测到设备请插入设备或启动模拟器的问题解决

问题 使用模拟器调试运行项目时&#xff0c;出现以下提示&#xff0c;“没有检测到设备&#xff0c;请插入设备或启动模拟器后点击刷新再试”。排查了一天最终找到原因。 解决 已确认模拟器是已经正常启动&#xff0c;并且Hbuilder设置中的adb路径和端口都配置没有问题&#…

一文搞懂从爬楼梯到最小花费(力扣70,746)

文章目录 题目前知动态规划简介动态规划模版 爬楼梯一、思路二、解题方法三、Code 使用最小花费爬楼梯一、思路二、解题方法三、Code 总结 在计算机科学中&#xff0c;动态规划是一种强大的算法范例&#xff0c;用于解决多种优化问题。本文将介绍动态规划的核心思想&#xff0c…

主从复制、数据持久化 、Redis主从集群、哨兵机制 、Redis分片集群

数据持久化 Redis、主从集群、哨兵机制 Redis分片集群 1、单点 redis 的问题2、主从复制2.1 命令传播 3、Redis的持久化3.1 AOF3.2 RDB&#xff08;默认方式&#xff09;RDB 方式&#xff1a;执行快照时&#xff0c;数据能被修改吗&#xff1f;RDB 方式总结 3.3 RDB 和 AOF 组合…

电路基础-电容-电感

电路基础 电容 通交流阻直流&#xff0c;滤波&#xff0c;旁路&#xff0c;退耦&#xff0c;作驱动电源&#xff08;洗衣机电机启动时需要一个强电启动&#xff09; 电容选型的工程值&#xff1b;参考以往开发板的选型&#xff1b;抄作业。 电源并连多个电容的作用 保证单…

30万奖金谁能瓜分?OurBMC开源大赛决赛入围名单公示

首届开放原子开源大赛基础软件赛道自今年 1 月开启报名以来&#xff0c;吸引了全国各地 BMC 技术爱好者的广泛关注和踊跃报名。该赛事由开放原子开源基金会牵头&#xff0c; OurBMC 社区及理事长单位飞腾信息技术有限公司联合承办&#xff0c;以 “基于 BMC 技术的服务器故障诊…

go的orm框架-Gorm

官网文档 特点 全功能 ORM 关联 (拥有一个&#xff0c;拥有多个&#xff0c;属于&#xff0c;多对多&#xff0c;多态&#xff0c;单表继承) Create&#xff0c;Save&#xff0c;Update&#xff0c;Delete&#xff0c;Find 中钩子方法 支持 Preload、Joins 的预加载 事务&…

基于Vue3 中后台管理系统框架

基于Vue3 中后台管理系统框架 文章目录 基于Vue3 中后台管理系统框架一、特点二、源码下载地址 一款开箱即用的 Vue 中后台管理系统框架&#xff0c;支持多款 UI 组件库&#xff0c;兼容PC、移动端。vue-admin, vue-element-admin, vue后台, 后台系统, 后台框架, 管理后台, 管理…

Leetcode - 2009. 使数组连续的最少操作数

文章目录 解析排序 原地去重 滑动窗口AC CODE 题目链接&#xff1a;Leetcode - 2009. 使数组连续的最少操作数 解析 题中所述的连续数组就是一串连续的自然数&#xff0c;想问需要多少次操作能将原数组变为连续的数。 我们排序去重&#xff0c;用逆向思维想能保留的数字数目…

Qt | Q_PROPERTY属性和QVariant 类

一、属性基础 1、属性与数据成员相似,但是属性可使用 Qt 元对象系统的功能。他们的主要差别在于存取方式不相同,比如属性值通常使用读取函数(即函数名通常以 get 开始的函数)和设置函数(即函数名通常以 set 开始的函数)来存取其值,除此种方法外,Qt 还有其他方式存取属性值…

JavaScript学习 BOM操作

Browser Object Model (BOM) 是一套专为与浏览器交互而设计的API&#xff0c;它允许JavaScript访问和操纵浏览器窗口以及浏览器本身的相关特性。以下是对BOM主要对象及其操作的详细讲解&#xff1a; 1. Window对象 Window对象 是BOM的核心&#xff0c;它代表浏览器窗口&#…

20240325-2-K-means面试题

K-means面试题 1. 聚类算法&#xff08;clustering Algorithms&#xff09;介绍 聚类是一种无监督学习—对大量未知标注的数据集&#xff0c;按数据的内在相似性将数据集划分为多个类别&#xff0c;使类别内的数据相似度较大而类别间的数据相似度较小。 聚类算法可以分为原型…

构建高效网络:深入理解正向与反向代理的作用与配置

正向代理 如果把局域网外的互联网环境想象成一个巨大的资源库&#xff0c;则局域网中的客户端要访问互联网则需要通过代理服务器来访问&#xff0c;这种代理成为正向代理。 示例&#xff1a; 用户想要访问 https://chensir.ink &#xff08;目标服务器&#xff09;&#xff0…

DataLoader 的 collate_fn 解释与示例教程

文章目录 导包数据Dataloadercollate_fn 导包 import torch from torch.utils.data import Dataset from typing import Any数据 class CustomDataset(Dataset):def __init__(self, length) -> None:super().__init__()self.length lengthdef __getitem__(self, indexNon…

请简述Vaadin框架的主要特点和优势。如何在项目中引入和使用Vaadin框架?

请简述Vaadin框架的主要特点和优势。 Vaadin框架的主要特点和优势主要体现在以下几个方面&#xff1a; 首先&#xff0c;Vaadin是一个基于GWT&#xff08;Google Web Toolkit&#xff09;的Java UI框架&#xff0c;这使得它能够快速构建高性能的Web应用程序。它使用Java语言编…

基于SSE长连接的智能客服问答系统技术方案及完整项目源码

文章目录 一、项目背景二、项目演示三、项目介绍B系统主要功能1. 注册登录重置密码2. 权限管理3. 项目管理4.客服管理 C系统主要功能1. 问答组件2. 主题色定制3. 类微信时间显示控件及智能tip提示4. 无障碍阅读4. 丰富的输入框组件5. 人工客服6. 聊天记录分表记录与查询 四、项…