您的位置:首页 > 健康 > 养生 > 东莞市seo网络推广哪家好_十大耐玩手机单机游戏_百度搜索排行seo_百度投诉中心24人工 客服电话

东莞市seo网络推广哪家好_十大耐玩手机单机游戏_百度搜索排行seo_百度投诉中心24人工 客服电话

2025/3/26 6:14:31 来源:https://blog.csdn.net/m0_74186706/article/details/146514736  浏览:    关键词:东莞市seo网络推广哪家好_十大耐玩手机单机游戏_百度搜索排行seo_百度投诉中心24人工 客服电话
东莞市seo网络推广哪家好_十大耐玩手机单机游戏_百度搜索排行seo_百度投诉中心24人工 客服电话

一、tasklet

1.Tasklet概论

1. Tasklet 的定位与作用

Tasklet 是 Linux 内核中一种延迟执行中断处理任务的机制,属于“软中断”(SoftIRQ)的一种高级封装。它主要用于将**硬件中断处理程序(硬中断)**中的非紧急任务拆分出来,延迟到内核的“安全时间”执行,从而避免长时间占用硬中断上下文,影响系统实时性。


2. 硬件中断(HardIRQ)与软中断(SoftIRQ)的差异
  • 硬件中断

    • 由硬件设备触发(如网卡收到数据包、键盘输入)。

    • 需要立即响应,处理时间必须极短(通常以微秒计)。

    • 执行时会关闭本地 CPU 的中断响应(防止嵌套中断导致竞态条件)。

  • 软中断

    • 由内核触发,基于硬中断的后续处理需求。

    • 允许延迟执行,适合耗时较长的任务(如数据包处理、磁盘 I/O)。

    • 执行时不关闭中断,可被更高优先级的中断打断。


3. 为什么收到硬件中断后要设置软中断?

当硬件中断发生时,内核需要快速完成以下步骤:

  1. 硬中断处理

    • 立即响应硬件请求(如读取设备状态寄存器)。

    • 标记需要后续处理的任务(例如将网卡数据拷贝到内存缓冲区)。

  2. 触发软中断

    • 将耗时任务(如协议栈处理、数据解析)委托给软中断

    • 软中断会被内核调度,在**中断下半部(Bottom Half)**安全执行。

核心原因

  • 硬中断必须快速完成:长时间关闭中断会导致系统失去响应(例如鼠标卡顿)。

  • 软中断提供异步处理能力:允许内核在合适的时间调度任务,同时保证中断处理的实时性。


4. Tasklet 的特点与实现
  • 低优先级

    • Tasklet 的优先级低于硬中断和其他软中断,确保关键任务优先执行。

    • 例如:网络数据包处理的优先级可能低于磁盘 I/O。

  • 可中断性与自我恢复

    • Tasklet 执行时可以被硬中断或其他软中断打断。

    • 内核通过维护 Tasklet 的状态队列实现“断点续运行”(例如标记为 TASKLET_STATE_SCHED)。

  • 基于软中断的封装

    • Tasklet 内部依赖软中断框架(如 TASKLET_SOFTIRQ 类型)。

    • 提供更简单的 API(如 tasklet_schedule()),避免直接操作底层的软中断。


5. Tasklet 与工作队列(Workqueue)的区别
  • Tasklet

    • 运行在中断上下文(不可睡眠,不能调用可能阻塞的函数)。

    • 适合处理与硬件交互紧密的轻量级任务(如更新设备寄存器)。

  • 工作队列

    • 运行在进程上下文(可以睡眠,允许长时间操作)。

    • 适合需要复杂逻辑的任务(如文件系统操作、长时间计算)。

特性Tasklet工作队列(Workqueue)
执行上下文中断上下文(不可睡眠)进程上下文(可睡眠)
并发性同一 CPU 串行多线程并行
优先级高(接近软中断)低(普通线程)
适用任务短小、实时性要求高耗时、可能阻塞
调度延迟微秒级毫秒级
实现机制基于软中断基于内核线程
同步需求需自旋锁需互斥锁

6. 示例流程:硬件中断触发 Tasklet
  1. 网卡收到数据包,触发硬中断。

  2. 硬中断处理程序:

    • 快速读取数据到内核缓冲区。

    • 调用 tasklet_schedule() 触发关联的 Tasklet。

  3. 内核退出硬中断上下文后,调度软中断。

  4. Tasklet 执行实际的数据包处理(如解析 TCP/IP 协议)。

         tasklet实际上就是一个内核定时器,在一个“软中断”的上下文执行(以原子模式),在使能硬件中断执行异步任务的一个内核机制。软中断是将操作推迟到未来执行的最有效的办法。但该延期机制处理起来非常复杂,因为多个处理器可以同时且独立处理软中断,同一个软中断的处理程序例程可以在几个CPU上运行。

2.创建tasklet

        各个tasklet中的数据结构称作tasklet_struct,内核源码如下:

1. tasklet_struct 的作用

tasklet_struct 是 Linux 内核中用于实现 Tasklet 机制的核心数据结构。
每个 tasklet_struct 实例代表一个 延迟执行的任务,通常用于处理硬件中断的“下半部”(Bottom Half),将耗时操作从硬中断上下文中剥离,延迟到内核的安全时间异步执行。


2. 核心字段详解

以下是 tasklet_struct 中最重要的字段及其作用:

字段类型说明
nextstruct tasklet_struct *链表指针,将多个 Tasklet 连接成队列,允许多个任务排队等待执行。
stateunsigned long状态标志
TASKLET_STATE_SCHED:Tasklet 已被调度,等待执行。
TASKLET_STATE_RUN:Tasklet 正在执行。
countatomic_t原子计数器
- 值为 0 时,Tasklet 可被激活和执行。
- 值非 0 时,Tasklet 被禁用(通常用于同步或资源保护)。
funcvoid (*)(unsigned long)任务处理函数:指向实际要执行的函数,该函数会在 Tasklet 被调度后调用。
dataunsigned long传递给 func 的参数:可以是任意用户定义的数据(如设备指针、状态标志等)。

3. 关键机制说明
  • 链表管理
    通过 next 指针,内核将多个 Tasklet 组织成链表,实现任务排队。例如,当多个硬件中断触发时,它们的 Tasklet 可以依次加入队列,按顺序执行。

  • 状态切换

    • 调用 tasklet_schedule() 时,设置 state 为 TASKLET_STATE_SCHED,表示 Tasklet 已加入调度队列。

    • 执行前,内核将状态设为 TASKLET_STATE_RUN,防止同一 Tasklet 在多个 CPU 上并发执行。

  • 原子计数器 count

    • atomic_inc(&tasklet->count):禁用 Tasklet(使其不可调度)。

    • atomic_dec(&tasklet->count):启用 Tasklet(若计数器为 0,则允许调度)。


4. 具体案例:网卡驱动中的 Tasklet 使用

场景描述
网卡收到数据包后触发硬中断,中断处理程序(上半部)快速将数据拷贝到内存缓冲区,然后通过 Tasklet(下半部)异步处理协议栈(如解析 TCP/IP 头)。

代码实现步骤

  1. 定义 Tasklet 和回调函数

    void process_packet(unsigned long data) {struct net_device *dev = (struct net_device *)data;// 解析数据包协议栈、提交给上层网络栈
    }DECLARE_TASKLET(nic_tasklet, process_packet, (unsigned long)dev);

  2. 硬中断中调度 Tasklet

    irqreturn_t nic_interrupt(int irq, void *dev_id) {// 1. 读取网卡寄存器,确认数据到达// 2. 将数据拷贝到内存缓冲区// 3. 调度 Tasklet 处理协议栈tasklet_schedule(&nic_tasklet);return IRQ_HANDLED;
    }

  3. Tasklet 执行流程

    • 硬中断退出后,内核触发软中断(TASKLET_SOFTIRQ)。

    • 软中断处理程序遍历 Tasklet 链表,依次执行 nic_tasklet->func(dev)


5. 关键过程图示
硬件中断触发│↓
执行硬中断上半部(快速拷贝数据)│↓
调用 tasklet_schedule() 将 Tasklet 加入队列│↓
硬中断退出,内核触发软中断│↓
软中断处理程序执行 Tasklet 的 func()│↓
协议栈处理完成,数据提交给上层应用

3.注册tasklet

        tasklet_schedule将一个tasklet的任务注册到系统中:

1. tasklet_schedule 的核心作用

tasklet_schedule() 是 Linux 内核中用于将 Tasklet 注册到系统并触发异步执行的关键函数。其核心目的是:

  • 将 Tasklet 加入 CPU 本地队列,确保任务被内核后续的软中断处理程序执行。

  • 避免重复调度:通过状态标志 TASKLET_STATE_SCHED 防止同一 Tasklet 被多次加入队列。

  • 分离中断上下文:允许硬件中断处理程序(上半部)快速完成,耗时操作延迟到 Tasklet(下半部)执行。


2. 注册流程详解

以下是 tasklet_schedule() 的具体执行步骤:

(1) 检查 Tasklet 状态
  • 标志位 TASKLET_STATE_SCHED
    每个 Tasklet 的 state 字段中有一个比特位表示是否已被调度。

    • 如果该标志已设置(值为 1),说明 Tasklet 已被加入队列,无需重复操作,直接退出。

    • 如果未设置(值为 0),继续执行后续操作。

代码逻辑

if (test_and_set_bit(TASKLET_STATE_SCHED, &t->state))return; // 已注册,直接退出
(2) 将 Tasklet 加入 CPU 本地队列
  • 目标队列:每个 CPU 维护一个 tasklet_vec 链表(普通优先级)或 tasklet_hi_vec(高优先级)。

  • 插入操作

    1. 获取当前 CPU 的队列头(struct tasklet_head)。

    2. 将 Tasklet 的 next 指针设为 NULL(表示链表末尾)。

    3. 通过二级指针 tail 快速插入到链表尾部(时间复杂度 O(1)O(1))。

代码逻辑

struct tasklet_head *head = this_cpu_ptr(&tasklet_vec); // 获取当前 CPU 的队列
t->next = NULL; // 新节点 next 置空
*head->tail = t; // 将 t 写入链表尾部节点的 next 字段
head->tail = &t->next; // 更新 tail 指针到新节点的 next 地址
(3) 触发软中断
  • 软中断类型

    • 普通优先级 Tasklet 触发 TASKLET_SOFTIRQ

    • 高优先级 Tasklet 触发 HI_SOFTIRQ

  • 内核响应:软中断处理程序(如 tasklet_action())将在后续的软中断处理阶段执行队列中的 Tasklet。

代码逻辑

raise_softirq_irqoff(TASKLET_SOFTIRQ); // 触发软中断

3. 关键设计思想
  • 状态标志 (TASKLET_STATE_SCHED)
    避免同一 Tasklet 被多次加入队列,防止重复执行和竞态条件。例如,在网卡中断频繁触发时,即使多次调用 tasklet_schedule(),实际仅第一次有效。

  • 每 CPU 队列 (tasklet_vec)
    每个 CPU 维护独立的队列,避免多核竞争(无锁设计),提升性能。

  • 二级指针尾部插入
    通过直接操作链表尾部节点的 next 字段地址,实现 O(1)O(1) 时间复杂度的插入,无需遍历链表。

4.执行tasklet

        tasklet的生命周期中最重要的部分就是将其执行。因为tasklet是基于软中断实现的,它们总是在处理软中断的时候被执行。

        tasklet关联到TASKLET_SOFTIRQ软中断。因而,调用raise_softirq(TASKLET_SOFTIRQ)

,即可在下一个适当的时机执行当前处理器的tasklet。内核使用task_action作为该软中断的action函数。

1. tasklet_action 的核心作用

tasklet_action 是 Linux 内核中处理 TASKLET_SOFTIRQ 软中断的核心函数,其核心作用为:

  • 遍历并执行当前 CPU 的 Tasklet 队列tasklet_vec)中注册的所有 Tasklet。

  • 协调 Tasklet 的状态切换:在 Tasklet 执行前标记为运行状态(TASKLET_STATE_RUN),执行后清除调度状态(TASKLET_STATE_SCHED)。

  • 确保 Tasklet 的串行执行:同一 Tasklet 不会在多个 CPU 上并发执行。


2. 函数调用流程

以下是 tasklet_action 的执行流程及关键代码逻辑:

(1) 软中断触发与入口

当内核触发 TASKLET_SOFTIRQ 软中断时(例如通过 raise_softirq(TASKLET_SOFTIRQ)),软中断子系统会调用注册的 tasklet_action 函数:

static __latent_entropy void tasklet_action(struct softirq_action *a) {// 调用通用处理函数,传入当前 CPU 的 Tasklet 队列(tasklet_vec)tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
(2) 通用处理函数 tasklet_action_common

tasklet_action_common 是实际处理 Tasklet 队列的函数,其核心步骤如下:

  1. 禁用本地中断:防止并发操作导致队列状态不一致。

  2. 获取当前 CPU 的 Tasklet 队列(例如 tasklet_vec)。

  3. 清空队列头尾指针:将队列重置为空,后续新调度的 Tasklet 会加入新队列。

  4. 遍历链表并逐个执行 Tasklet

    • 检查 Tasklet 的 count 原子计数器(若为 0 才执行)。

    • 设置状态为 TASKLET_STATE_RUN,防止多核并发执行。

    • 调用 Tasklet 的 func 函数执行实际任务。

    • 清除 TASKLET_STATE_SCHED 和 TASKLET_STATE_RUN 状态。


(3)新增任务

1. 队列已有任务:全部执行

        当 tasklet_action 被软中断触发时,会遍历当前 CPU 的 tasklet_vec 链表,逐个执行队列中已挂载的所有 tasklet_struct 任务。例如,若队列中有 A、B 两个 tasklet,tasklet_action 会先执行 A,再执行 B,直到队列清空。

2. 执行中新增任务:下次处理

        如果在某个 tasklet 执行过程中,又通过 tasklet_schedule 调度了新的 tasklet(包括当前正在执行的 tasklet 再次调度自己),新调度的 tasklet 会被加入队列,但 不会在本次 tasklet_action 中立即执行,而是等待下一次 TASKLET_SOFTIRQ 软中断触发时,由 tasklet_action 处理。

5.tasklet队列

        在 Linux 内核中,Tasklet 队列(存储所有待处理任务的结构)并不是一个独立的全局结构体,而是通过 每 CPU(Per-CPU)的链表 来管理的。具体来说,每个 CPU 核心维护两个 Tasklet 队列:

  • 普通优先级队列tasklet_vec):用于普通 Tasklet。

  • 高优先级队列tasklet_hi_vec):用于高优先级 Tasklet(如定时器相关任务)。

1. Tasklet 队列的结构体

队列头定义

Tasklet 队列的链表头通过 struct tasklet_head 表示:

struct tasklet_head {struct tasklet_struct *head;  // 指向链表中的第一个 Taskletstruct tasklet_struct **tail; // 指向链表尾部的指针(用于快速插入)
};
每 CPU 的队列实例

内核为每个 CPU 定义了两个 tasklet_head 实例:

// 普通优先级队列(每个 CPU 一个)
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
// 高优先级队列(每个 CPU 一个)
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

2. Tasklet 队列的工作原理

(1) 队列组织方式

每个 Tasklet(tasklet_struct)通过其 next 字段链接成单链表,链表的头尾由 tasklet_head 管理:

struct tasklet_struct {struct tasklet_struct *next; // 链表指针// ... 其他字段(state, func, data 等)
};
(2) 任务调度流程

当调用 tasklet_schedule() 注册一个 Tasklet 时:

  1. 根据 Tasklet 的优先级(普通或高),将其添加到当前 CPU 的 tasklet_vec 或 tasklet_hi_vec 链表中。

  2. 触发软中断(TASKLET_SOFTIRQ 或 HI_SOFTIRQ),通知内核在适当时间处理队列。

(3) 任务执行流程

内核在软中断处理函数(如 tasklet_action())中:

  1. 遍历当前 CPU 的 Tasklet 链表。

  2. 依次执行每个 Tasklet 的 func 函数。

  3. 执行完成后,清除 TASKLET_STATE_SCHED 状态。


3. 关键代码示例

(1) 队列初始化

内核初始化时,为每个 CPU 的队列设置空链表:

// 初始化普通优先级队列
for_each_possible_cpu(cpu) {per_cpu(tasklet_vec, cpu).head = NULL;per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head;
}
// 高优先级队列同理
(2) Tasklet 入队操作

以 tasklet_schedule() 为例,将 Tasklet 加入当前 CPU 的队列:

void tasklet_schedule(struct tasklet_struct *t) {// 1. 检查 Tasklet 是否已被调度if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {// 2. 获取当前 CPU 的队列指针struct tasklet_head *head = this_cpu_ptr(&tasklet_vec);// 3. 将 Tasklet 插入链表尾部t->next = NULL;*head->tail = t;head->tail = &t->next;// 4. 触发软中断raise_softirq_irqoff(TASKLET_SOFTIRQ);}
}

4. 实际案例:网卡驱动中的 Tasklet 队列使用

场景描述

网卡收到数据包后触发硬中断,中断处理程序(上半部)快速将数据存入缓冲区,然后通过 Tasklet(下半部)处理协议栈。

代码实现
  1. 定义 Tasklet 和队列

    // 定义 Tasklet 处理函数
    void process_packet(unsigned long data) {struct net_device *dev = (struct net_device *)data;// 解析数据包并提交给网络协议栈
    }// 初始化 Tasklet(关联到当前 CPU 的队列)
    DECLARE_TASKLET(nic_tasklet, process_packet, (unsigned long)dev);
  2. 硬中断中调度 Tasklet

    irqreturn_t nic_interrupt(int irq, void *dev_id) {// 1. 读取网卡状态寄存器// 2. 拷贝数据到内存缓冲区// 3. 将 Tasklet 加入当前 CPU 的队列tasklet_schedule(&nic_tasklet);return IRQ_HANDLED;
    }
  3. 内核处理队列

    • 硬中断退出后,触发 TASKLET_SOFTIRQ 软中断。

    • 内核调用 tasklet_action(),遍历当前 CPU 的 tasklet_vec 队列,依次执行所有 Tasklet 的 func

二 、等待队列

        等待队列(wait queue)用于使进程等待某一特定事件发生,而无需频繁轮询。进程在等待期间睡眠,在事件发生时有内核自动唤醒。完成量(completion)机制基于等待队列,内核利用该机制等待某一操作结束。

       在内核里面,等待队列有很多用处,尤其是在中断处理、进程同步、定时等等场合。可以使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等等。 

1.等待队列头数据结构

        每一个等待队列都有一个等待队列头,由一下源码表示:

2.等待队列中的有效成员wait_queue_entry

        每一个wait_queue_entry关联着一个等待特定事件的进程。它记录了等待队列中具体等待进程的相关信息,是进程和等待队列之间的桥梁。

 

 3.使进程睡眠

        add_wait_queue函数用于将一个进程增加到等待队列,该函数在获得必要的自旋锁后,将工作委托给__add_wait_queue:

 4.唤醒进程

        内核定义了一系列宏,可以用于唤醒等待队列中的进程。它们基于同一个函数:

三、工作队列

        每个工作队列都有一个数组,数组项的数目与系统中处理器的数目相同。每个数组项都列出了将延期执行的任务。对每个工作队列来说,内核都会创建一个新的内核守护进程,延期任务使用上文描述的等待队列机制,在该守护进程的上下文中执行。

        所有推送到工作队列上的任务,都必须打包为 work_struct 结构的实例,内核源码如下:

        workqueue_struct,这个结构是用来描述内核队列的数据结构,源码如下:

1. 内核工作队列的分类

Linux 内核中的工作队列(Workqueue)分为两类:

  • 共享工作队列(Shared Workqueue):由内核在启动时自动创建,供所有驱动和子系统共享使用。

  • 自定义工作队列(Custom Workqueue):由开发者自行创建和管理,适用于需要独立调度或优先级控制的场景。


2. 共享工作队列的核心特点
  • 全局性:系统仅维护一个共享队列(如 system_wq),所有用户共用。

  • 无需手动创建队列:开发者只需定义任务(Work),无需关心队列的创建和销毁。

  • 简单易用:适用于大多数不需要特殊控制的异步任务。


3. 共享工作队列的使用步骤

使用共享工作队列需要以下步骤:

(1) 静态定义工作结构

通过 DECLARE_WORK 宏静态定义一个工作结构体(work_struct),并关联处理函数。

DECLARE_WORK(my_work, my_work_handler); // 静态定义,初始化时绑定处理函数
(2) 动态初始化工作结构(可选)

如果需要在运行时动态初始化工作结构,可使用 INIT_WORK 宏。

struct work_struct my_work;
INIT_WORK(&my_work, my_work_handler); // 动态初始化
(3) 调度工作到共享队列

通过 schedule_work() 将工作项添加到共享队列,由内核异步执行。

schedule_work(&my_work); // 将工作项加入共享队列

4. 关键函数与宏解析
函数/宏作用使用场景
DECLARE_WORK静态声明并初始化一个 work_struct 结构体,绑定处理函数。编译时已知任务处理函数。
INIT_WORK动态初始化一个已定义的 work_struct 结构体,绑定处理函数。运行时动态创建任务。
schedule_work将工作项提交到共享工作队列(system_wq),由内核调度执行。触发异步任务执行。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com