Linux: softirq 简介

文章目录

  • 1. 前言
  • 2. softirq 实现
    • 2.1 softirq 初始化
      • 2.1.1 注册各类 softirq 处理接口
      • 2.1.2 创建 softirq 处理线程
    • 2.2 softirq 的 触发 和 处理
      • 2.1.1 softirq 触发
      • 2.1.2 softirq 处理
        • 2.1.2.1 在 中断上下文 处理 softirq
        • 2.1.2.2 在 ksoftirqd 内核线程上下文 处理 softirq
  • 3. softirq 之 tasklet
    • 3.1 定义初始化 tasklet
    • 3.2 使能调度 tasklet
    • 3.3 执行 tasklet
  • 4. softirq 同步
  • 5. softirq 观测
  • 6. softirq 的未来

1. 前言

2. softirq 实现

2.1 softirq 初始化

2.1.1 注册各类 softirq 处理接口

start_kernel() /* init/main.c */...sched_init(); /* kernel/sched/core.c */...init_sched_fair_class(); /* kernel/sched/fair.c */#ifdef CONFIG_SMPopen_softirq(SCHED_SOFTIRQ, run_rebalance_domains); /* 调度均衡处理 软中断 */...#endif....../* 注册 RCU 软中断 处理接口  */rcu_init(); /* kernel/rcu/tree.c */...open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);....../** 所有 CPU 的 软件 timer 管理数据初始化, * 以及 软件 timer 软中断处理接口注册.*/init_timers(); /* kernel/time/timer.c */init_timer_cpus();/* 注册软件 timer 处理接口: 在 softirq 中处理 每个 CPU 上的 软件 timer */open_softirq(TIMER_SOFTIRQ, run_timer_softirq);.../* tasklet 软中断 初始化 */softirq_init(); /* kernel/softirq.c */int cpu;/* 初始每 CPU 的 tasklet, tasklet hi 队列为空 */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;}/* 注册 taslet(TASKLET_SOFTIRQ), tasklet hi(HI_SOFTIRQ) 软中断 处理接口 */open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);
start_kernel()...rest_init();/* 在 BOOT CPU 上启动初始化线程, 处理剩下的初始化工作 */pid = kernel_thread(kernel_init, NULL, CLONE_FS);/* 初始化线程入口 */
kernel_init()kernel_init_freeable();do_basic_setup();do_initcalls();do_initcall_level(level);do_one_initcall(*fn);/** block/blk-softirq.c, blk_softirq_init()* lib/irq_poll.c, irq_poll_setup()* net/core/dev.c, net_dev_init()*/fn()blk_softirq_init() /* block/blk-softirq.c */...open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);...irq_poll_setup() /* lib/irq_poll.c */...open_softirq(IRQ_POLL_SOFTIRQ, irq_poll_softirq);...net_dev_init() /* net/core/dev.c */.../* 注册 网络设备 收、发 软中断 处理接口 */open_softirq(NET_TX_SOFTIRQ, net_tx_action);open_softirq(NET_RX_SOFTIRQ, net_rx_action);...

从上面的代码分析中,我们看到了如下列表中、各类型软中断处理接口的注册:

/* include/linux/interrupt.h */enum
{HI_SOFTIRQ=0, TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ, SCHED_SOFTIRQ,HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};

注册软中端处理接口的函数 open_softirq() 实现如下:

/* kernel/softirq.c */static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;void open_softirq(int nr, void (*action)(struct softirq_action *))
{softirq_vec[nr].action = action;
}

2.1.2 创建 softirq 处理线程

open_softirq() 注册的各类软中断处理接口,可能运行于两种上下文:

1. 中断上下文,软中断处理接口在中断处理过程退出时被 irq_exit() 调用。
2. 每 CPU 的软中断线程 ksoftirqd 上下文。

本小节描述软中断接口运行的第2种上下文建立的过程,即 每 CPU 的软中断线程 ksoftirqd 的建立过程。ksoftirqd 的建立,是在内核初始化线程中完成:

kernel_init()kernel_init_freeable();do_pre_smp_initcalls();for (fn = __initcall_start; fn < __initcall0_start; fn++)do_one_initcall(*fn);/* 调用 early_initcall(spawn_ksoftirqd); */spawn_ksoftirqd() /* kernel/softirq.c */
static struct smp_hotplug_thread softirq_threads = {.store			= &ksoftirqd,.thread_should_run	= ksoftirqd_should_run,.thread_fn		= run_ksoftirqd,.thread_comm		= "ksoftirqd/%u",
};static __init int spawn_ksoftirqd(void)
{.../* 注册每 CPU 软中断线程 ksoftirqd */BUG_ON(smpboot_register_percpu_thread(&softirq_threads));return 0;
}

上面的代码,为每个 CPU 创建了一个名为 ksoftirqd 的内核线程,内核线程的入口函数为 run_ksoftirqd() 。我们可以用 ps 命令观察到它们:

# ps -ef | grep ksoftirqd10 root     [ksoftirqd/0]16 root     [ksoftirqd/1]21 root     [ksoftirqd/2]26 root     [ksoftirqd/3]

我们看到,在这个带 4 核 CPU 的硬件上,Linux 内核创建了 4 个 ksoftirqd 内核线程。

2.2 softirq 的 触发 和 处理

2.1.1 softirq 触发

Linux 系统提供下列接口 抛出 或 生成 softirq

/* include/linux/interrupt.h */extern void raise_softirq_irqoff(unsigned int nr);
extern void raise_softirq(unsigned int nr);extern void __raise_softirq_irqoff(unsigned int nr);

来看下它们的实现:

/* kernel/softirq.c */#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned; /* 每 CPU 的 softirq 挂起状态 */
EXPORT_SYMBOL(irq_stat);
#endifvoid raise_softirq(unsigned int nr)
{unsigned long flags;/* ARMv7: 读取 CPSR 寄存器的值到 @flags, 同时关闭 CPU IRQ 中断 */local_irq_save(flags);raise_softirq_irqoff(nr);/* ARMv7: CPSR = flags */local_irq_restore(flags);
}/** This function must run with irqs disabled!*/
inline void raise_softirq_irqoff(unsigned int nr)
{__raise_softirq_irqoff(nr);/** If we're in an interrupt or softirq, we're done* (this also catches softirq-disabled code). We will* actually run the softirq once we return from* the irq or softirq.** Otherwise we wake up ksoftirqd to make sure we* schedule the softirq soon.*/if (!in_interrupt())wakeup_softirqd(); /* 唤醒 当前 CPU 的 ksoftirq 线程, 处理 softirq */ 
}void __raise_softirq_irqoff(unsigned int nr)
{trace_softirq_raise(nr);or_softirq_pending(1UL << nr); /* 标记 [当前 CPU] 有挂起的、@x 类型的 softirq */
}/* include/linux/interrupt.h */
#ifndef __ARCH_SET_SOFTIRQ_PENDING
#define set_softirq_pending(x) (local_softirq_pending() = (x))
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))
#endif/* include/linux/irq_cpustat.h */
#ifndef __ARCH_IRQ_STAT
extern irq_cpustat_t irq_stat[];  /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#endif#define local_softirq_pending() \__IRQ_STAT(smp_processor_id(), __softirq_pending)

2.1.2 softirq 处理

/* arch/arm/kernel/entry-armv.S *//** 中断向量表。* 这里是第1级,每项是各模式下第2级向量表的指针,* 即中断向量表是按 vector[8][16] 的形式组织。* 第1级是各中断类型的入口: reset, undef, swi, ...* 第2级是各中断类型下,各CPU模式的入口: usr, svc, irq, fiq, ...*/.section .vectors, "ax", %progbits
.L__vectors_start:W(b)	vector_rst /* 复位 */W(b)	vector_und /* 未定义指令异常向量表指针: vector_stub	und, UND_MODE */.../* IRQ 中断 各 CPU 模式处理接口 组成 */W(b)	vector_irq /* IRQ: vector_stub	irq, IRQ_MODE, 4 */.../** Interrupt dispatcher*//* IRQ 中断 各 CPU 模式处理接口 组成 */vector_stub irq, IRQ_MODE, 4 /* vector_irq */// CPU User 模式 IRQ 中断处理入口.long __irq_usr   @  0  (USR_26 / USR_32).long __irq_invalid   @  1  (FIQ_26 / FIQ_32).long __irq_invalid   @  2  (IRQ_26 / IRQ_32)// CPU SVC 模式 IRQ 中断处理入口.long __irq_svc   @  3  (SVC_26 / SVC_32).......align 5
__irq_svc: // CPU SVC 模式 IRQ 中断处理入口 (中断发生在 内核态)...irq_handler....align 5
__irq_usr: // CPU User 模式 IRQ 中断处理入口 (中断发生在 用户态)...irq_handler...

我们看到,不管是内核态,还是用户态,中断处理都调用 irq_handler,看它的定义:

/** Interrupt handling.*/.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLERldr r1, =handle_arch_irq /* r1 = gic_handle_irq() */mov r0, spbadr lr, 9997fldr pc, [r1]
#else...
#endif
9997:.endm

irq_handler 是个汇编宏,它调用了 ARM GIC 芯片的中断处理接口 gic_handle_irq(),这个接口是在初始化 GIC 中断芯片时注册的。gic_handle_irq() 在其处理中断即将退出前,处理 softirq

gic_handle_irq(regs) /* drivers/irqchip/irq-gic.c *//* 处理 SPI, PPI */if (likely(irqnr > 15 && irqnr < 1020)) { /* 处理 PPI, SPI */handle_domain_irq(gic->domain, irqnr, regs); /* include/linux/irqdesc.h */__handle_domain_irq(domain, hwirq, true, regs);__handle_domain_irq(domain, hwirq, true, regs); /* kernel/irq/irqdesc.c */irq_enter();// 处理中断:这里不关心中断处理的细节...irq_exit(); /* 软中断, RCU 等等处理 */set_irq_regs(old_regs);return ret;...}if (irqnr < 16) { /* 处理 SGI */...#ifdef CONFIG_SMP...handle_IPI(irqnr, regs); /* arch/arm/kernel/smp.c */// 除了用来唤醒 CPU 的 IPI_WAKEUP 中断外,都会有 irq_enter() + irq_exit()。// 至于要被唤醒的 CPU ,都还在睡大觉,就别指望它来处理 softirq 了。irq_enter();// 处理 IPI 中断...irq_exit();...#endif...}

先看下 irq_enter(),因为它会更新一个和 sotfirq 处理相关的计数:

irq_enter() /* kernel/softirq.c */...__irq_enter(); /* include/linux/hardirq.h */.../* HARDIRQ_OFFSET 计数加 1 */preempt_count_add(HARDIRQ_OFFSET); /* include/linux/preempt.h */__preempt_count_add(HARDIRQ_OFFSET) /* include/asm-generic/preempt.h *///*preempt_count_ptr() += HARDIRQ_OFFSET;&current_thread_info()->preempt_count += HARDIRQ_OFFSET;...

这里的 current_thread_info()->preempt_count 有必要再展开下:

/* arch/arm/include/asm/thread_info.h */struct thread_info {.../** 以下类型的计数, 分别占用 @preempt_count 不同 bits:* PREEMPT_OFFSET, SOFTIRQ_OFFSET, SOFTIRQ_OFFSET, NMI_OFFSET*/int   preempt_count; /* 0 => preemptable, <0 => bug */.../* thread_info 所属的 进程(对象) */struct task_struct *task;  /* main task structure */...
};.../** how to get the current stack pointer in C*/
register unsigned long current_stack_pointer asm ("sp");/** how to get the thread information struct from C*/
static inline struct thread_info *current_thread_info(void) __attribute_const__;static inline struct thread_info *current_thread_info(void)
{/* current_stack_pointer: SP 寄存器的值 */return (struct thread_info *)(current_stack_pointer & ~(THREAD_SIZE - 1));
}

看到了吧,preempt_count_add(HARDIRQ_OFFSET) 修改的计数值,是当前 CPU 上被 IRQ 中断进程的 struct thread_infopreempt_count 成员变量。后面的讨论和这个计数变量密切相关,我们需要提前了解它的来源。
前面讲到,softirq 会在 中断上下文ksoftirqd 内核线程上下文 被处理,先来看在 中断上下文 处理 softirq 的细节。

2.1.2.1 在 中断上下文 处理 softirq
irq_exit().../* 这里减去 irq_enter() 增加的 HARDIRQ_OFFSET 计数,将 HARDIRQ_OFFSET 计数 归 0 */preempt_count_sub(HARDIRQ_OFFSET);...if (!in_interrupt() && local_softirq_pending())invoke_softirq(); /* 处理当前 CPU 挂起待处理 softirq 事件 *//** 如果 ksoftirqd 当前正在运行状态, 并且没有要求同步处理的* tasklet, tasklet hi softirq 事件, 则将挂起的 softirq 交给* ksoftirqd 处理, 而不是在这里的 IRQ 中断上下文处理.*/if (ksoftirqd_running(local_softirq_pending()))return;if (!force_irqthreads) { /* 如果 不是强制要求使用 ksoftirqd 处理 softirq, */#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK/** We can safely execute softirq on the current stack if* it is the irq stack, because it should be near empty* at this stage.*/__do_softirq();#else/** Otherwise, irq_exit() is called on the task stack that can* be potentially deep already. So call softirq in its own stack* to prevent from any overrun.*//** 在 IRQ 中断处理即将结束时, 如果 在 IRQ 中断上下文处理 softirq.* 当前 CPU 的本地中断处于禁用状态.*/ do_softirq_own_stack();__do_softirq();#endif} else { /* 强制通过 ksoftirqd 处理 softirq, 则唤醒 ksoftirqd 处理 softirq */wakeup_softirqd();}...
/* 中断上下文 和 ksoftirqd 内核线程上下文 处理 softirq 的公共逻辑 */
__do_softirq() /* kernel/softirq.c */unsigned long end = jiffies + MAX_SOFTIRQ_TIME; /* softirq 处理超时时间: 2ms */unsigned long old_flags = current->flags;int max_restart = MAX_SOFTIRQ_RESTART; /* softirq 处理最大轮次 */struct softirq_action *h;...__u32 pending;int softirq_bit;...pending = local_softirq_pending(); /* 读取当前 CPU 挂起的 softirq 事件 */.../** 禁用 softirq,防止 __do_softirq() 当前 CPU 上的重入。* 譬如中断抢占、嵌套的情形,可以避免 ksoftirqd 上下文 和 中断上下处理上下文* 并发的问题,这可以让我们编写 softirq action 接口时,不必考虑所有的竞争场景,* 这将在后面的章节 4. softirq 同步里面细述。*/__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);...restart:/* Reset the pending bitmask before enabling irqs */set_softirq_pending(0); /* 清除当前 CPU 挂起的 softirq */local_irq_enable(); /* 启用 CPU 本地中断,避免 softirq 耗时太长,使得中断得不到响应 */h = softirq_vec;/* * 返回当前 CPU 挂起未处理的、最高优先级 softirq 类型, * 按 softirq 优先级 从高到低 进行处理.*/while ((softirq_bit = ffs(pending))) {unsigned int vec_nr;...h += softirq_bit - 1; /* 软中断向量: softirq_vec[vec_nr] */vec_nr = h - softirq_vec; /* softirq 类型: HI_SOFTIRQ, ..., RCU_SOFTIRQ */.../** 统计当前 CPU @vec_nr 类型中断的发生次数。* 用户空间可通过文件 /proc/softirqs* 查看, 实现于代码文件 fs/proc/softirqs.c*/kstat_incr_softirqs_this_cpu(vec_nr);trace_softirq_entry(vec_nr);/** 各类型 softirq 处理接口, 优先级 从高到低:* HI_SOFTIRQ: tasklet_hi_action()* TIMER_SOFTIRQ: run_timer_softirq()* NET_TX_SOFTIRQ: net_tx_action()* NET_RX_SOFTIRQ: net_rx_action()* BLOCK_SOFTIRQ: blk_done_softirq()* IRQ_POLL_SOFTIRQ: irq_poll_softirq()* TASKLET_SOFTIRQ: tasklet_action()* SCHED_SOFTIRQ: run_rebalance_domains()* HRTIMER_SOFTIRQ: 没用到, 占位符, 工具依赖的编号顺序* RCU_SOFTIRQ: rcu_process_callbacks()*/h->action(h);trace_softirq_exit(vec_nr);...h++;pending >>= softirq_bit;}.../** 重新禁用 CPU 本地中断.* 在 接下来的一轮 (跳到 restart 处) softirq 处理* 或* 退出中断处理时* 会重新启用.*/local_irq_disable();/* * 软中断处理接口有可能又抛出了 softirq 事件.* 譬如有未启用的 tasklet, 后续需要在启用调度后得到机会* 执行, 需要重新抛出 TASKLET_SOFTIRQ, 详见 tasklet_action().* tasklet hi 也是类似的.*/pending = local_softirq_pending();if (pending) {/** 如果处理 softirq 期间, 又有新的 softirq 挂起, * 且 同时满足下列条件:* . 软中断处理没有超时 (MAX_SOFTIRQ_TIME == 2ms)* . 没有挂起调度请求* . 没有超过 softirq 处理轮数 (MAX_SOFTIRQ_RESTART == 10)* 则接着发起新的一轮 softirq 处理.*/if (time_before(jiffies, end) && !need_resched() && --max_restart)goto restart;/** 不满足在此立刻发起新的 softirq 处理的条件, 则唤醒 * ksoftirqd, 将挂起 softirq 交给该内核线程处理.*/wakeup_softirqd();}...__local_bh_enable(SOFTIRQ_OFFSET); /* 使能 softirq */...

可能在中断上下文处理 softirq ,昭示着一个很重要的事实,那就是所有的 softirq 的处理代码,都不能有导致睡眠、调度的代码

2.1.2.2 在 ksoftirqd 内核线程上下文 处理 softirq
/* kernel/softirq.c */static void run_ksoftirqd(unsigned int cpu)
{local_irq_disable();if (local_softirq_pending()) {/** We can safely run softirq on inline stack, as we are not deep* in the task stack here.*/__do_softirq(); /* 在 线程上下文 处理本地 CPU 上的 softirq 事件,细节同中断上下文的分析 */local_irq_enable();...return;}local_irq_enable();
}

3. softirq 之 tasklet

3.1 定义初始化 tasklet

/* include/linux/interrupt.h */struct tasklet_struct
{struct tasklet_struct *next;/** bit-0: 1 表示 tasklet 为调度状态.*        被 tasklet_trylock() 设置, tasklet_unlock() 清除.* bit-1: 1 表示 tasklet 为运行态(仅用于 SMP), *        被 tasklet_schedule() 设置, 被 __do_softirq()*        执行过后清除.*/unsigned long state;atomic_t count; /* 0 表示 tasklet 为启用状态,非 0 表示 tasklet 为禁用状态 */void (*func)(unsigned long);unsigned long data;
};
/* include/linux/interrupt.h *//* 方法一: 静态定义 tasklet 对象 */
#define DECLARE_TASKLET(name, func, data) \struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }DECLARE_TASKLET(my_tasklet, my_tasklet_function, (unsigned long)my_tasklet_data);/* 方法二:动态定义 tasklet 对象 */
/* include/linux/interrupt.h */struct tasklet_struct my_tasklet;
tasklet_init(&my_tasklet, my_tasklet_function, (unsigned long)my_tasklet_data); /* kernel/softirq.c */t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->func = func;t->data = data;

3.2 使能调度 tasklet

/* include/linux/interrupt.h */static inline void tasklet_schedule(struct tasklet_struct *t)
{/** 标记 tasklet 为 TASKLET_STATE_SCHED 状态: * TASKLET_STATE_SCHED 态的 tasklet 将在 softirq 里面被调度执行.*/if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) /* 不能对 tasklet 重复调度 */__tasklet_schedule(t);
}/* kernel/softirq.c *//** Tasklets*/
struct tasklet_head {struct tasklet_struct *head;struct tasklet_struct **tail;
};static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); /* 每 CPU 的 tasklet 队列 (TASKLET_SOFTIRQ) */
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec); /* 每 CPU 的 tasklet hi 队列 (HI_SOFTIRQ) */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); /* 抛出 tasklet 软中断 */local_irq_restore(flags);
}

用一张图来看下 tasklet 组织数据结构,有助于我们理解后面对 tasklet 执行过程 的分析:
在这里插入图片描述

3.3 执行 tasklet

/* kernel/softirq.c */
__do_softirq()...h->action(h);tasklet_action()...

这里只分析 tasklet_action()tasklet_hi_action() 的逻辑几乎完全一样,这里就不再赘述,感兴趣的读者可自行阅读相关源码。

static __latent_entropy void tasklet_action(struct softirq_action *a)
{struct tasklet_struct *list;/* 一次性处理当前 CPU 上所有挂起的 tasklet */local_irq_disable();list = __this_cpu_read(tasklet_vec.head); /* @list -> 当前 tasklet 列表的第 1 个 tasklet *//* * 清空当前 CPU 的 tasklet 列表: * .head -> NULL* .tail -> &.head*/__this_cpu_write(tasklet_vec.head, NULL);__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));local_irq_enable();/* 处理列表 @list 中所有启用的、被调度的 tasklet */while (list) {struct tasklet_struct *t = list;list = list->next;/* * 标记 tasklet 为 TASKLET_STATE_RUN 态锁定 tasklet:* . 如果返回 false 表示 tasklet 已经处于 TASKLET_STATE_RUN, *   锁定 tasklet 失败;* . 否则返回 true 表示锁定 tasklet 成功.*/if (tasklet_trylock(t)) {if (!atomic_read(&t->count)) { /* tasklet 为启用状态 */if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) /* 已执行的 tasklet 清除调度标记 *//** 如果* !test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)* 成立, 表示 tasklet 没有被设置 TASKLET_STATE_SCHED 位:* 非 TASKLET_STATE_SCHED 态的 tasklet 出现在 tasklet_vec * 中,被认为是一个 BUG.* 程序代码通过 tasklet_schedule() 设置 TASKLET_STATE_SCHED. */BUG();t->func(t->data);tasklet_unlock(t);/** 继续执行下一个 tasklet.** 可以看到, 启用并被调度的 tasklet 的执行是一次性的,* 要想反复执行 tasklet, 需要重新通过 tasklet_schedule()* 调度 tasklet 执行.*/continue;}/** tasklet 没有启用, 清除 tasklet 的 TASKLET_STATE_RUN 态释放* tasklet, 接着将该 tasklet 归还到当前 CPU 的 队列, 以备后续* 启用了再执行.*/tasklet_unlock(t);}/** tasklet 当前从当前 CPU 的 tasklet 队列中移除了, * 而且 tasklet 没有被启用, 仍然归还到当前 CPU 的* tasklet 队列中, 以备后续启用了再执行.*/local_irq_disable();/* 将没有执行的 tasklet 归还到当前 CPU 的 tasklet 队列 */t->next = NULL;*__this_cpu_read(tasklet_vec.tail) = t;__this_cpu_write(tasklet_vec.tail, &(t->next));/* * 当前 CPU 有未启用的、未被执行的 tasklet, * 重新抛出 TASKLET_SOFTIRQ, 让这些未启用的* tasklet 后续在启用并调度后有机会被执行.*/__raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_enable();}
}

4. softirq 同步

对于 tasklet hi (HI_SOFTIRQ)tasklet (TASKLET_SOFTIRQ) ,因为它们有每 CPU 独立的队列,所以它们总是在(通过 tasklet_schedule() )提交的 CPU 上执行同一 CPU 队列上的 tasklet按提交的顺序串行的执行;另外,同一个 tasklet,无法同时提交到多个 CPU 上去执行,看 tasklet_schedule() 的实现:

static inline void tasklet_schedule(struct tasklet_struct *t)
{/** 标记 tasklet 为 TASKLET_STATE_SCHED 状态: * TASKLET_STATE_SCHED 态的 tasklet 将在 softirq 里面被调度执行.*/if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) /* 不能对 tasklet 重复调度 */__tasklet_schedule(t);
}

而对于剩余其它类型的 softirq ,虽然它们也总是在提交的 CPU 上执行,但不同于 tasklet 的是,它们可能在多个 CPU 上并行,如 支持硬件多队列的网卡驱动,可能导致 net_rx_action() 在多个 CPU 上同时运行。
了解 softirq 的同步,有助于我们写出正确的代码,这是很重要的。

5. softirq 观测

# cat /proc/softirqsCPU0        CPU1        CPU2        CPU3HI:          0           0           0           0TIMER:       6817        6083        8633        5130NET_TX:          0           0           0           0NET_RX:          2          16           6          10BLOCK:      11161       11269        5199        4379IRQ_POLL:          0           0           0           0TASKLET:          1           1           8           1SCHED:        4522        3375        3217        2745HRTIMER:           0           0           0           0RCU:        5661        5083        6497        4399

第 1 行示了系统中 CPU,接下来的每一行显示了每种类型 softirq 在每个 CPU 上发生的次数。另外,从下面的代码:

static void __local_bh_enable(unsigned int cnt)
{...if (softirq_count() == (cnt & SOFTIRQ_MASK))trace_softirqs_on(_RET_IP_);...
}asmlinkage __visible void __softirq_entry __do_softirq(void)
{...while ((softirq_bit = ffs(pending))) {...trace_softirq_entry(vec_nr);h->action(h);trace_softirq_exit(vec_nr);...}...
}

看到,Linux 内核也提供 tracepoint / traceevent 来跟踪 softirq 的执行情况。

6. softirq 的未来

softirq 虽然存在发展很多年,但一直存在一些让人诟病的东西,社区有要移除 softirq (一部分) 的声音,感兴趣的读者,可以阅读这边文章 The end of tasklets。该篇文章的一些参考链接,也值得阅读一下。

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

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

相关文章

电子烟特效音语音方案选型-WTN6020-8S-E

随着科技的迅猛进步&#xff0c;电子烟行业亦在持续创新与突破&#xff0c;引领着全新的潮流。其中&#xff0c;电子烟产品所特有的吸烟声音特效播报功能&#xff0c;无疑成为了技术革新的璀璨亮点。这一设计巧妙地将吸烟的声效融入使用体验中&#xff0c;使得用户在吸电子烟时…

CSS3 立体 3D 变换

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍CSS3 立体 3D 变换&#x1f48e;1 坐标轴&#x1f48e;2 perspective 透视视…

linux管理进程

一、程序 程序&#xff1a;执行特定任务的一串代码 1.是一组计算机能识别和执行的指令&#xff0c;运行于电子计算机上&#xff0c;满足人们某种需求的信息化工具 2.用于描述进程要完成的功能&#xff0c;是控制进程执行的指令集 二、进程和线程 1.进程 进程是程序的执行…

vue2 二次封装element 组件,继承组件原属性,事件,插槽 示例

测试页面代码 这里主要记录如何封装element的el-input 并且封装后具有el-input原本的属性 事件 插槽 下面为测试页面即组件调用 <script> import CustomInput from /components/CustomInput.vue;export default {name: TestPage,components: { CustomInput },data() …

jenkins构建微信小程序并展示二维码

测试小程序的过程中&#xff0c;很多都是在回头和前端开发说一句&#xff0c;兄弟帮我打一个测试版本的测试码&#xff0c;开发有时间的情况下还好&#xff0c;就直接协助了&#xff0c;但是很多时候他们只修复了其中几个bug&#xff0c;其他需要修复的bug代码正在编写&#xf…

【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都已更新完毕&#xff0c;欢迎大家前往订阅本专题&#x1f38f; &#x1f38f;【蓝桥杯嵌入式】蓝桥杯第十届省赛真题 &#x1f38f;【蓝桥杯嵌入式】蓝桥…

BTI功能验证与异常解析

BTI分支目标识别精讲与实践系列 思考 1、什么是代码重用攻击&#xff1f;什么是ROP攻击&#xff1f;区别与联系&#xff1f; 2、什么是JOP攻击&#xff1f;间接分支跳转指令&#xff1f; 3、JOP攻击的缓解技术&#xff1f;控制流完整性保护&#xff1f; 4、BTI下的JOP如何…

论文略读:Window Attention is Bugged: How not to Interpolate Position Embeddings

iclr 2024 reviewer 打分 6666 窗口注意力、位置嵌入以及高分辨率微调是现代Transformer X CV 时代的核心概念。论文发现&#xff0c;将这些几乎无处不在的组件简单地结合在一起&#xff0c;可能会对性能产生不利影响问题很简单&#xff1a;在使用窗口注意力时对位置嵌入进行插…

密码学 | 椭圆曲线数字签名方法 ECDSA(下)

目录 10 ECDSA 算法 11 创建签名 12 验证签名 13 ECDSA 的安全性 14 随机 k 值的重要性 15 结语 ⚠️ 原文&#xff1a;Understanding How ECDSA Protects Your Data. ⚠️ 写在前面&#xff1a;本文属于搬运博客&#xff0c;自己留着学习。同时&#xff0c;经过几…

C++设计模式|创建型 2.工厂模式

1.简单工厂思想 简单工厂模式不属于23种设计模式之⼀&#xff0c;更多的是⼀种编程习惯。它的核心思想是将产品的创建过程封装在⼀个⼯⼚类中&#xff0c;把创建对象的流程集中在这个⼯⼚类⾥⾯。卡码网将其结构描述为下图所示的情况&#xff1a; 简单⼯⼚模式包括三个主要⻆⾊…

鸿蒙端云一体化开发--调用云函数--适合小白体制

如何实现在端侧调用云函数&#xff1f; 观看前&#xff0c;友情提示&#xff1a; 不知道《如何一键创建端云一体化模板》的小白同学&#xff0c;请看&#xff1a; 鸿蒙端云一体化开发--开发云函数--适合小白体制-CSDN博客 实现方法&#xff1a; 第一步&#xff1a;添加依赖 …

3D视觉引导麻袋拆垛破包 | 某大型化工厂

客户需求 此项目为大型化工厂&#xff0c;客户现场每日有大量麻袋拆垛破包需求&#xff0c;麻袋软包由于自身易变形、码放垛型不规则、运输后松散等情况&#xff0c;无法依靠机器人示教位置完成拆垛。客户遂引入3D视觉进行自动化改造。 工作流程&#xff1a; 3D视觉对紧密贴合…

libcurl 简单使用

LibCurl是一个开源的免费的多协议数据传输开源库&#xff0c;该框架具备跨平台性&#xff0c;开源免费&#xff0c;并提供了包括HTTP、FTP、SMTP、POP3等协议的功能&#xff0c;使用libcurl可以方便地进行网络数据传输操作&#xff0c;如发送HTTP请求、下载文件、发送电子邮件等…

服务器数据恢复—V7000存储raid5数据恢复案例

服务器数据恢复环境&#xff1a; P740AIXSybaseV7000存储阵列柜&#xff0c;阵列柜上有12块SAS机械硬盘&#xff08;包括1块热备盘&#xff09;。 服务器故障&#xff1a; 管理员在日常巡检过程中发现阵列柜中有一块磁盘发生故障&#xff0c;于是更换磁盘并同步数据&#xff0…

HTTP协议安全传输教程

HTTP协议有多个版本&#xff0c;包括但不限于HTTP/0.9、HTTP/1.0、HTTP/1.1、HTTP/2和HTTP/3。这些版本各自具有不同的特点和改进&#xff0c;以适应网络技术的发展和满足不同的需求。例如&#xff0c;HTTP/1.0使用文本格式传输数据&#xff0c;简单易用且兼容性好&#xff0c;…

电路笔记 : esp32pico-d4最小系统原理图

ESP32-PICO-D4 ESP32-PICO-D4是一款基于ESP32的系统级封装(SiP)模组&#xff0c;可提供完整的Wi-Fi和蓝牙功能。该模组的外观尺寸仅(7.0000.100)mm(7.0000.100)mm(0.9400.100)mm&#xff0c;整体占用的PCB面积最小&#xff0c;已集成1个4MB串行外围设备接口(SPI) flash。 ESP3…

网络层协议——IP协议

目录 IP协议 IP协议格式 分片与组装 网段划分 特殊IP地址 IP地址的数量限制 私有IP地址和公网IP地址 路由 路由表生成算法 IP协议 IP协议全称为“网际互连协议&#xff08;Internet Protocol&#xff09;”&#xff0c;IP协议是TCP/IP体系中的网络层协议。 在应用层我…

外卖小程序实战-接单后小票机自动打印订单

1、导入小票机的sdk https://www.feieyun.com/api/API-JAVA.zip public static String addprinter(String snlist){//通过POST请求&#xff0c;发送打印信息到服务器RequestConfig requestConfig RequestConfig.custom() .setSocketTimeout(30000)//读取超时 .setConnectTi…

DePIN打猎之旅:AI算力作饵,道阻且长

出品&#xff5c;OKG Research 作者&#xff5c;Hedy Bi 香港Web3嘉年华已告一段落&#xff0c;然而Web3自由的脉搏还在跳动&#xff0c;并不断向其他行业渗透。和上一轮周期相比&#xff0c;本轮牛市开启的逻辑是由“原生创新叙事”转变成“主流认可&#xff0c;资金驱动”的…

C语言详解指针

目录 一、指针的概念 1.1内存与地址 例子&#xff1a; 二、变量的指针与指针变量 2.1、指针变量的定义及使用 1、指针变量的定义 2、指针变量的使用 2.2 指针变量的大小 2.3、指针-整数 2.4、void*指针 三、指针的运算 1、指针- 整数 2、指针-指针 3、指针的关系运…