linux 内核tasket机制

news/2025/1/12 18:53:31/

tasklet(tasklet,有的书中翻译为“任务蕾”)是基于软中断实现的。为什么要提供tasklet?因为tasklet相对软中断有以下优势。

(1)软中断的种类是编译时静态定义的,在运行时不能添加或删除;tasklet可以在运行时添加或删除。

(2)同一种软中断的处理函数可以在多个处理器上同时执行,处理函数必须是可以重入的,需要使用锁保护临界区;一个tasklet同一时刻只能在一个处理器上执行,不要求处理函数是可以重入的。

tasklet根据优先级分为两种:低优先级tasklet和高优先级tasklet。tasklet是通过软中断HI_SOFTIRQ和TASKLET_SOFTIRQ实现的,所以它们本身也是软中断,存放在两个单处理器数据结构:tasklet_vec和tasklet_hi_vec会在两个软中断中分别处理。

  数据结构


tasklet的数据结构如下:
include/linux/interrupt.h
struct tasklet_struct
{
    struct tasklet_struct *next;    //链表中的下一个tasklet
    unsigned long state;        //tasklet的状态
    atomic_t count;         //禁止计数器
    void (*func)(unsigned long);//tasklet处理函数
    unsigned long data;     //给tasklet处理函数的参数
};
成员 next 用来把tasklet添加到单向链表中。
成员 state 是tasklet的状态,取值如下。
(1) 0:tasklet没有被调度。
(2) (1 << TASKLET_STATE_SCHED):tasklet被调度,即将被执行。
(3) (1 << TASKLET_STATE_RUN):只在多处理器系统中使用,表示tasklet正在执行。成员 count 是计数, 0 表示允许tasklet被执行,非零值表示禁止tasklet被执行。成员 func 是处理函数,成员 data 是传给处理函数的参数。
每个处理器有两条单向链表:低优先级tasklet链表和高优先级tasklet链表。
kernel/softirq.c
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);


 编程接口


定义一个静态的tasklet,并且允许tasklet被执行,方法如下:
DECLARE_TASKLET(name, func, data)

定义一个静态的tasklet,并且禁止tasklet被执行,方法如下:
DECLARE_TASKLET_DISABLED(name, func, data)

在运行时动态初始化tasklet,并且允许tasklet被执行,方法如下:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

函数 tasklet_disable()用来禁止tasklet被执行,如果tasklet正在被执行,该函数等待tasklet执行完。
void tasklet_disable(struct tasklet_struct *t);

函数 tasklet_disable_nosync()用来禁止tasklet被执行,如果tasklet正在被执行,该函数不会等待tasklet执行完。
void tasklet_disable_nosync(struct tasklet_struct *t);

函数 tasklet_enable()用来允许tasklet被执行。
void tasklet_enable(struct tasklet_struct *t);

函数 tasklet_schedule()用来调度低优先级tasklet: 把tasklet添加到当前处理器的低优先级tasklet链表中,并且触发低优先级tasklet软中断。
void tasklet_schedule(struct tasklet_struct *t);

函数 tasklet_hi_schedule()用来调度高优先级tasklet:把tasklet添加到当前处理器的高优先级tasklet链表的尾部,并且触发高优先级tasklet软中断。
void tasklet_hi_schedule(struct tasklet_struct *t);

函数 tasklet_hi_schedule_first()用来调度高优先级tasklet:把tasklet添加到当前处理器的高优先级tasklet链表的首部,并且触发高优先级tasklet软中断。
void tasklet_hi_schedule_first(struct tasklet_struct *t);

函数 tasklet_kill()用来杀死tasklet,确保tasklet不会被调度和执行。如果tasklet正在被执行,该函数等待tasklet执行完。通常在卸载内核模块的时候调用该函数。
void tasklet_kill(struct tasklet_struct *t);


  技术原理


tasklet是基于软中断实现的,根据优先级分为两种:低优先级tasklet和高优先级tasklet。软中断 HI_SOFTIRQ 执行高优先级tasklet,软中断 TASKLET_SOFTIRQ 执行低优先级tasklet。
(1)调度tasklet。
函数 tasklet_schedule()用来调度低优先级tasklet,函数 tasklet_hi_schedule()用来调度高优先级tasklet。以函数 tasklet_schedule()为例说明,其代码如下:
include/linux/interrupt.h
static inline void tasklet_schedule(struct tasklet_struct *t)
{
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
    __tasklet_schedule(t);
}
kernel/softirq.c
void __tasklet_schedule(struct tasklet_struct *t)
{
    unsigned long flags;
    local_irq_save(flags);
    t->next = NULL;
    *__this_cpu_read(tasklet_vec.tail) = t;
    __this_cpu_write(tasklet_vec.tail, &(t->next));
    raise_softirq_irqoff(TASKLET_SOFTIRQ);
    local_irq_restore(flags);
}
如果tasklet没有被调度过,那么首先设置调度标志位,然后把tasklet添加到当前处理器的低优先级tasklet链表的尾部,最后触发软中断 TASKLET_SOFTIRQ。
(2)执行tasklet。
初始化的时候,把软中断 TASKLET_SOFTIRQ 的处理函数注册为函数 tasklet_action,把软中断 HI_SOFTIRQ 的处理函数注册为函数 tasklet_hi_action。
kernel/softirq.c
void __init softirq_init(void)
{
    …
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
以函数 tasklet_action()为例说明,其代码如下:
kernel/softirq.c
1 static __latent_entropy void tasklet_action(struct softirq_action *a)
2 {
3   struct tasklet_struct *list;
4   
5   local_irq_disable();
6   list = __this_cpu_read(tasklet_vec.head);
7   __this_cpu_write(tasklet_vec.head, NULL);
8   __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
9   local_irq_enable();
10  
11   while (list) {
12      struct tasklet_struct *t = list;
13      
14      list = list->next;
15      
16      if (tasklet_trylock(t)) {
17          if (!atomic_read(&t->count)) {
18              if (!test_and_clear_bit(TASKLET_STATE_SCHED,
19                  &t->state))
20                  BUG();
21                  t->func(t->data);
22                  tasklet_unlock(t);
23                  continue;
24          }
25          tasklet_unlock(t);
26      }
27      
28      local_irq_disable();
29      t->next = NULL;
30      *__this_cpu_read(tasklet_vec.tail) = t;
31      __this_cpu_write(tasklet_vec.tail, &(t->next));
32      __raise_softirq_irqoff(TASKLET_SOFTIRQ);
33      local_irq_enable();
34      }
35 }

第 6~8 行代码,把当前处理器的低优先级tasklet链表中的所有tasklet移到临时链表list 中。
第 11 行代码,遍历临时链表 list,依次处理每个tasklet,如下。
1)第 16 行代码,尝试锁住tasklet,确保一个tasklet同一时刻只在一个处理器上执行。
2)第 17 行代码,如果tasklet的计数为 0,表示允许tasklet被执行。
3)第 18 行代码,清除tasklet的调度标志位,其他处理器可以调度这个tasklet,但是不能执行这个tasklet。
4)第 21 行代码,执行tasklet的处理函数。
5)第 22 行代码,释放tasklet的锁,其他处理器就可以执行这个tasklet了。
6)第 29~32 行代码,如果尝试锁住tasklet失败(表示tasklet正在其他处理器上执行),或者禁止tasklet被执行,那么把tasklet重新添加到当前处理器的低优先级tasklet链表的尾部,然后触发软中断 TASKLET_SOFTIRQ。


  tasklet的简单用法


 下面是tasklet的一个简单应用, 以模块的形成加载。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>

static struct tasklet_struct my_tasklet;
static void tasklet_handler (unsigned long data)
{
    printk(KERN_ALERT "tasklet_handler is running.\n");
}

static int __init test_init(void)
{
    tasklet_init(&my_tasklet, tasklet_handler, 0);
    tasklet_schedule(&my_tasklet);
    return 0;
}

static void __exit test_exit(void)
{
    tasklet_kill(&my_tasklet);
    printk(KERN_ALERT "test_exit running.\n");
}
MODULE_LICENSE("GPL");

module_init(test_init);
module_exit(test_exit);

从这个例子可以看出,所谓的tasklet机制是为下半部函数的执行提供了一种执行机制,也就是说,推迟处理的事情是由tasklet_handler实现,何时执行,经由tasklet机制封装后交给内核去处理。
 


http://www.ppmy.cn/news/1246784.html

相关文章

删除链表的倒数第N个节点,剑指offerII(21),力扣

目录 题目地址&#xff1a; 题目&#xff1a; 相似类型题&#xff1a; 我们直接看本题题解吧&#xff1a; 解题方法&#xff1a; 难度分析&#xff1a; 解题分析&#xff1a; 解题思路&#xff08;双指针&#xff09;&#xff1a; 代码实现&#xff1a; 代码说明&#xff1a; 代…

算法训练 第九周

二、删除排序链表中的重复元素 II 1.遍历 由于给定的链表是排好序的&#xff0c;因此重复的元素在链表中出现的位置是连续的&#xff0c;因此我们只需要对链表进行一次遍历&#xff0c;就可以删除重复的元素。由于链表的头节点可能会被删除&#xff0c;因此我们需要额外设定一…

TiDB 7.x 源码编译之 TiDB Server 篇,及新特性详解

本文将介绍如何编译 TiDB Server 源码。以及阐释 TiDB Server 7.x 的部分新特性。 TiDB v7.5.0 LTS 计划于 2023 年 11 月正式 Release&#xff0c;目前代码虽未冻结&#xff0c;但已经可以看到 Alpha 版本的 Code 了&#xff0c;本文代码将以 v7.5.0-alpha 为基准。 TiDB Se…

大数据平台/大数据技术与原理-实验报告--实战HDFS

实验名称 实战HDFS 实验性质 &#xff08;必修、选修&#xff09; 必修 实验类型&#xff08;验证、设计、创新、综合&#xff09; 综合 实验课时 2 实验日期 2023.10.23-2023.10.27 实验仪器设备以及实验软硬件要求 专业实验室&#xff08;配有centos7.5系统的linu…

机器人算法——costmap膨胀层InflationLaye

如下图是更新地图膨胀 void InflationLayer::updateCosts(costmap_2d::Costmap2D& master_grid, int min_i, int min_j, int max_i, int max_j) {//用指针master_array指向主地图&#xff0c;并获取主地图的尺寸&#xff0c;确认seen_数组被正确设置。boost::unique_lock …

【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件

思路 JS-DLL-WINCC-PLC之间进行交互&#xff0c;思路&#xff0c;先用Visual Studio创建一个C#的DLL控件&#xff0c;然后这个控件里面嵌入浏览器组件&#xff0c;实现JS与DLL通信&#xff0c;然后DLL放入到WINCC里面的图形编辑器中&#xff0c;实现DLL与WINCC的通信。然后PLC与…

Vue 双向数据绑定

之前通过v-bind来完成的数据绑定&#xff0c;属性值和表达式进行绑定&#xff0c;表达式的值发生变化了属性值也跟着发生变化。 单向数据绑定&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>首页</titl…

高质量短效SOCKS5代理IP是什么意思?作为技术你了解吗

小张是一位网络安全技术测试员&#xff0c;最近他接到了一个头疼的任务&#xff0c;那就是评估公司系统的安全性&#xff0c;因此他前来咨询&#xff0c;在得知SOCKS5代理IP可以帮他之后&#xff0c;他不禁产生疑问&#xff0c;这是什么原理&#xff1f;其实和小张一样的朋友不…