tasklet继续

软中断与Tasklet管理
本文介绍了Linux内核中软中断和Tasklet的工作原理及管理方法。包括如何使用tasklet_disable()、tasklet_enable()等函数控制Tasklet的启用与禁用;探讨了ksoftirqd线程如何协助处理软中断和Tasklet负载,确保系统稳定运行。
1.28学习记录


调度自己的taasklet
可以使用task_disable()来禁止某个指定的tasklet,若正在执行,那么就会等待其执行完毕再结束。
也可以使用tasklet_disable_nosync()强制禁止。


使用tasklet_enable()可以激活一个tasklet();
使用tasklet_kill()函数从挂起列队中去掉一个tasklet。


ksoftirqd
每个处理器都有一个辅助处理软中断(和tasklet)内核线程。这些内核线程会辅助处理他们。


在处理软中断的时候,会遇到处理器时间安排上的问题,特别是在高负载的情况下,越有可能遇到问题。


在设计软中断时,作为改进,当大量软中断出现的时候,内核会唤起一组内核线程来处理这些负载,这些
线程在最低的优先级上运行。以避免和其他重要的任务抢夺重要资源。
每个处理器都有一个一个这样的线程, 线程的名字叫做ksoftirqd/n n为处理器的编号,一旦线程被初始化
就会执行如下的死循环。
for(;;)
{
if(!softirq_pending(cpu))
schedule();


set_current_state(TASK_RUNNING);


while(softirq_pending(cpu)){
do_softirq();
if(need_resched())
schedule();
}
set_current_state(TASK_RUNNING);
}
/* * rx_scheduler.c - 基于优先级的接收包调度器(仅用于 CPU0) * * 功能: * - 将收到的数据包按 DSCP 优先级分类 * - 高优先级包尝试立即处理(fast path),降低延迟 * - 其他包或拥塞时进入队列,由 tasklet 批量处理(slow path) * - 使用 tasklet 替代 NAPI 实现软中断延迟处理 * - 不使用 per-CPU 变量,全局单实例绑定到 CPU0 * * 编译方式: * make -C /lib/modules/$(uname -r)/build M=$(pwd) modules * 加载: * insmod rx_scheduler.ko * 卸载: * rmmod rx_scheduler */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> // struct sk_buff 相关操作 #include <linux/netdevice.h> // netif_receive_skb() #include <linux/interrupt.h> // tasklet, softirq #include <linux/spinlock.h> // 自旋锁保护共享资源 #include <linux/ip.h> // ip_hdr(), ipv4_get_dsfield() /* ==================== 配置参数 ==================== */ #define MAX_PRIO_QUEUES 3 // 支持三种优先级:高、中、低 #define MAX_IMMEDIATE_RX 16 // 当高优先级队列少于该数量时,新包走 fast path /* ==================== 优先级枚举 ==================== */ enum rx_priority { PRIO_HIGH = 0, // EF ( Expedited Forwarding ) 或 CS7 等实时业务 PRIO_MEDIUM = 1, // AF 类业务,如视频流 PRIO_LOW = 2, // 默认 Best-Effort 流量 }; /* ==================== 数据结构定义 ==================== */ // 每个优先级对应一个 FIFO 队列 struct rx_prio_queue { struct sk_buff_head queue; // 内核提供的 skb 队列(双链表) spinlock_t lock; // 保护此队列的并发访问(中断安全) }; // 全局调度器状态(只运行在 CPU0 上) struct rx_scheduler { struct rx_prio_queue queues[MAX_PRIO_QUEUES]; // 三个优先级队列 bool processing; // 标志位:是否已有 tasklet 被调度(防重复唤醒) }; /* ==================== 全局变量 ==================== */ // 全局唯一的调度器实例,固定绑定到 CPU0 static struct rx_scheduler rx_sched_on_cpu0; // 延迟执行的 tasklet(将在 softirq 上下文中运行) static void rx_tasklet_handler(unsigned long data); static DECLARE_TASKLET(rx_tasklet, rx_tasklet_handler, 0); // 模块参数:是否启用调度功能(默认开启) static bool enable_rx_scheduling = true; module_param(enable_rx_scheduling, bool, 0644); MODULE_PARM_DESC(enable_rx_scheduling, "是否启用自定义 RX 调度器"); /* ==================== 函数声明 ==================== */ void receive_and_schedule(struct sk_buff *skb); EXPORT_SYMBOL(receive_and_schedule); // 导出给网卡驱动调用 /* ==================== 初始化与退出 ==================== */ /* * 模块初始化函数 * 在 insmod 时被调用,初始化所有数据结构 */ static int __init rx_scheduler_init(void) { int prio; pr_info("RX Scheduler (CPU0-only mode) loaded\n"); // 初始化每个优先级队列 for (prio = 0; prio < MAX_PRIO_QUEUES; prio++) { skb_queue_head_init(&rx_sched_on_cpu0.queues[prio].queue); // 初始化空队列 spin_lock_init(&rx_sched_on_cpu0.queues[prio].lock); // 初始化自旋锁 } // 初始状态:没有 tasklet 正在等待执行 rx_sched_on_cpu0.processing = false; // 启用 tasklet(虽然默认是启用的) tasklet_enable(&rx_tasklet); return 0; } /* * 模块卸载函数 * 在 rmmod 之前必须确保 tasklet 完全停止 */ static void __exit rx_scheduler_exit(void) { tasklet_disable(&rx_tasklet); // 禁止 tasklet 被调度 tasklet_kill(&rx_tasklet); // 等待正在运行的 tasklet 结束 pr_info("RX Scheduler unloaded\n"); } /* ==================== 包分类函数 ==================== */ /* * classify_packet - 根据 IP 头部 DSCP 字段对数据包进行分类 * @skb: 待分类的数据包 * * 返回值:对应的优先级(PRIO_HIGH / PRIO_MEDIUM / PRIO_LOW) * * 示例: * DSCP = 0x2E (EF) -> PRIO_HIGH * DSCP >= 0x20 -> PRIO_MEDIUM * 其他 -> PRIO_LOW */ static enum rx_priority classify_packet(struct sk_buff *skb) { // 如果 MAC 层信息未设置,无法解析上层协议 if (!skb_mac_header_was_set(skb)) goto low; // 只处理 IPv4 数据包 if (skb->protocol == htons(ETH_P_IP)) { struct iphdr *iph = ip_hdr(skb); // 获取 IP 头指针 u8 dscp = ipv4_get_dsfield(iph) >> 2; // 提取 DSCP (6 bits) if (dscp == 0x2E || dscp == 0x38) { // EF (VoIP) 或 CS7 (控制信令) return PRIO_HIGH; } else if (dscp >= 0x20) { // 如 AF41/AF42/AF43 视频流 return PRIO_MEDIUM; } } low: return PRIO_LOW; // 默认为低优先级 } /* ==================== 主入口函数 ==================== */ /** * receive_and_schedule - 网卡驱动调用的主入口函数 * @skb: 收到的数据包 * * 说明: * 这个函数通常由网卡中断上下文调用。 * 我们假设所有流量都被路由到 CPU0(通过 IRQ affinity 设置)。 * * 行为: * 1. 若禁用调度,则直接提交协议栈 * 2. 若为高优先级且队列不忙,则立即处理(fast path) * 3. 否则加入对应优先级队列,并触发 tasklet(slow path) */ void receive_and_schedule(struct sk_buff *skb) { enum rx_priority prio; // 分类后的优先级 bool defer = false; // 是否需要延迟处理(即是否要调度 tasklet) // 如果模块参数关闭了调度功能,直接交给协议栈处理 if (!enable_rx_scheduling) { netif_receive_skb(skb); return; } /* Step 1: 对数据包进行优先级分类 */ prio = classify_packet(skb); /* Step 2: 高优先级包尝试走快速路径(不进队列) */ if (prio == PRIO_HIGH) { struct rx_prio_queue *pq = &rx_sched_on_cpu0.queues[PRIO_HIGH]; unsigned long flags; // 获取锁以安全读取队列长度 spin_lock_irqsave(&pq->lock, flags); // 如果当前高优先级队列中的包少于阈值,认为系统不忙 // 直接在中断上下文中处理,减少延迟 if (skb_queue_len(&pq->queue) < MAX_IMMEDIATE_RX) { spin_unlock_irqrestore(&pq->lock, flags); netif_receive_skb(skb); // 直接提交协议栈(fast path) return; // 函数结束,不再入队 } // 否则解锁并继续走慢速路径(入队 + tasklet) spin_unlock_irqrestore(&pq->lock, flags); } /* Step 3: 将数据包加入对应优先级队列(slow path) */ // 获取目标队列的锁(注意:不同优先级使用不同的锁) spin_lock(&rx_sched_on_cpu0.queues[prio].lock); // 将 skb 添加到队列末尾 __skb_queue_tail(&rx_sched_on_cpu0.queues[prio].queue, skb); /* * 检查是否已经有一个 tasklet 在等待执行 * 如果没有(processing == false),我们就调度一个新的 tasklet * 防止重复调用 tasklet_schedule() 导致 softirq 开销过大 */ if (!rx_sched_on_cpu0.processing) { rx_sched_on_cpu0.processing = true; // 标记“已调度” defer = true; // 触发 tasklet 调度 } // 释放锁 spin_unlock(&rx_sched_on_cpu0.queues[prio].lock); /* Step 4: 触发 tasklet 进行批量处理 */ if (defer) { // 明确指定在 CPU0 上调度 tasklet(可选,但更清晰) tasklet_schedule_on_cpu(&rx_tasklet, 0); // 也可以直接用 tasklet_schedule(),它会在当前 CPU 触发 } } EXPORT_SYMBOL(receive_and_schedule); /* ==================== Tasklet 处理函数 ==================== */ /** * rx_tasklet_handler - 在 softirq 上下文中批量处理数据包 * @data: 传入的参数(此处未使用) * * 说明: * - 该函数运行在 softirq 上下文(原子上下文),不能睡眠 * - 按照 PRIO_HIGH → PRIO_MEDIUM → PRIO_LOW 的顺序出队 * - 每次最多处理 64 个包,避免长时间占用 CPU * - 若仍有积压,重新调度 tasklet;否则清除 processing 标志 */ static void rx_tasklet_handler(unsigned long data) { struct sk_buff_head output_list; // 临时链表,用于收集待提交的 skb bool has_more = false; // 标志位:是否还有更多包待处理 unsigned long flags; // 初始化临时输出链表(用于 netif_receive_skb_list) __skb_queue_head_init(&output_list); /* Step 1: 按优先级从高到低扫描队列 */ for (int prio = 0; prio < MAX_PRIO_QUEUES; prio++) { struct rx_prio_queue *pq = &rx_sched_on_cpu0.queues[prio]; // 获取该优先级队列的锁 spin_lock_irqsave(&pq->lock, flags); int count = 0; // 记录本次从该队列取出的包数 // 每个优先级最多取 32 个包,防止某个队列垄断处理时间 while (count < 32 && !skb_queue_empty(&pq->queue)) { struct sk_buff *skb = __skb_dequeue(&pq->queue); // 取出一个包 __skb_queue_tail(&output_list, skb); // 加入输出列表 count++; } // 如果该队列还有剩余包,则标记“仍有积压” if (!skb_queue_empty(&pq->queue)) { has_more = true; } // 释放锁 spin_unlock_irqrestore(&pq->lock, flags); // 总共最多处理 64 个包,提前退出 if (skb_queue_len(&output_list) >= 64) { break; } } /* Step 2: 决定是否需要再次调度 tasklet */ if (has_more) { // 仍有包未处理完,重新调度 tasklet 继续处理 tasklet_schedule_on_cpu(&rx_tasklet, 0); } else { // 所有队列均已清空,清除 processing 标志 // 注意:这里选择任意一个锁即可,因为 processing 是全局状态 spin_lock_irqsave(&rx_sched_on_cpu0.queues[0].lock, flags); rx_sched_on_cpu0.processing = false; spin_unlock_irqrestore(&rx_sched_on_cpu0.queues[0].lock, flags); } /* Step 3: 将收集的所有包提交给协议栈 */ if (!skb_queue_empty(&output_list)) { // 使用批量接口提升性能(比逐个调用更快) netif_receive_skb_list(&output_list); } } /* ==================== 模块元信息 ==================== */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("CPU0-only RX Scheduler with Priority Queues (no NAPI)"); // 注册模块初始化与退出函数 module_init(rx_scheduler_init); module_exit(rx_scheduler_exit); 会不会出现参数关闭了调度,但是task还没处理完包的情况
09-29
static void rx_tasklet_handler(unsigned long data) { struct sk_buff_head output_list; // 临时链表,用于收集待提交的 skb bool has_more = false; // 标志位:是否还有更多包待处理 unsigned long flags; // 初始化临时输出链表(用于 netif_receive_skb_list) __skb_queue_head_init(&output_list); /* Step 1: 按优先级从高到低扫描队列 */ for (int prio = 0; prio < MAX_PRIO_QUEUES; prio++) { struct rx_prio_queue *pq = &rx_sched_on_cpu0.queues[prio]; // 获取该优先级队列的锁 spin_lock_irqsave(&pq->lock, flags); int count = 0; // 记录本次从该队列取出的包数 // 每个优先级最多取 32 个包,防止某个队列垄断处理时间 while (count < 32 && !skb_queue_empty(&pq->queue)) { struct sk_buff *skb = __skb_dequeue(&pq->queue); // 取出一个包 __skb_queue_tail(&output_list, skb); // 加入输出列表 count++; } // 如果该队列还有剩余包,则标记“仍有积压” if (!skb_queue_empty(&pq->queue)) { has_more = true; } // 释放锁 spin_unlock_irqrestore(&pq->lock, flags); // 总共最多处理 64 个包,提前退出 if (skb_queue_len(&output_list) >= 64) { break; } } /* Step 2: 决定是否需要再次调度 tasklet */ if (has_more) { // 仍有包未处理完,重新调度 tasklet 继续处理 tasklet_schedule_on_cpu(&rx_tasklet, 0); } else { // 所有队列均已清空,清除 processing 标志 // 注意:这里选择任意一个锁即可,因为 processing 是全局状态 spin_lock_irqsave(&rx_sched_on_cpu0.queues[0].lock, flags); rx_sched_on_cpu0.processing = false; spin_unlock_irqrestore(&rx_sched_on_cpu0.queues[0].lock, flags); } /* Step 3: 将收集的所有包提交给协议栈 */ /* Step 3: 将收集的所有包提交给协议栈 */ if (!skb_queue_empty(&output_list)) { netif_receive_skb_list(&output_list); // 提交给协议栈 } }不使用netif_receive_skb_list
最新发布
09-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值