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_SMP
			open_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 the
			numbering. 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 ksoftirqd
   10 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);
#endif

void 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_HANDLER
	ldr r1, =handle_arch_irq /* r1 = gic_handle_irq() */
	mov r0, sp
	badr lr, 9997f
	ldr 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 */
	...
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/softirqs
                    CPU0        CPU1        CPU2        CPU3
          HI:          0           0           0           0
       TIMER:       6817        6083        8633        5130
      NET_TX:          0           0           0           0
      NET_RX:          2          16           6          10
       BLOCK:      11161       11269        5199        4379
    IRQ_POLL:          0           0           0           0
     TASKLET:          1           1           8           1
      SCHED:        4522        3375        3217        2745
    HRTIMER:           0           0           0           0
        RCU:        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。该篇文章的一些参考链接,也值得阅读一下。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Facial Micro-Expression Recognition Based on DeepLocal-Holistic Network 阅读笔记

中科院王老师团队的工作&#xff0c;用于做微表情识别。 摘要&#xff1a; Toimprove the efficiency of micro-expression feature extraction,inspired by the psychological studyof attentional resource allocation for micro-expression cognition,we propose a deep loc…

HTTP与HTTPS:深度解析两种网络协议的工作原理、安全机制、性能影响与现代Web应用中的重要角色

HTTP (HyperText Transfer Protocol) 和 HTTPS (Hypertext Transfer Protocol Secure) 是互联网通信中不可或缺的两种协议&#xff0c;它们共同支撑了全球范围内的Web内容传输与交互。本文将深度解析HTTP与HTTPS的工作原理、安全机制、性能影响&#xff0c;并探讨它们在现代Web…

[leetcode]remove-duplicates-from-sorted-list-ii

. - 力扣&#xff08;LeetCode&#xff09; 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&…

百度OCR身份证识别C++离线SDKV3.0 C#对接

百度OCR身份证识别C离线SDKV3.0 C#对接 目录 说明 效果 问题 项目 代码 下载 说明 自己根据SDK封装了动态库&#xff0c;然后C#调用。 SDK 简介 本 SDK 适应于于 Windows 平台下的⾝份证识别系统,⽀持 C接⼜开发的 SDK,开发者可在VS2015 下⾯进⾏开发&#xff08;推荐…

爬虫+RPC+js逆向---直接获取加密值

免责声明:本文仅做技术交流与学习,请勿用于其它违法行为;如果造成不便,请及时联系... 目录 爬虫RPCjs逆向---直接获取加密值 target网址: 抓包 下断点 找到加密函数 分析参数 RPC流程 一坨: 二坨: 运行py,拿到加密值 爬虫RPCjs逆向---直接获取加密值 target网址: 优志…

Django+Celery框架自动化定时任务开发

本章介绍使用DjCelery即DjangoCelery框架开发定时任务功能&#xff0c;在Autotestplat平台上实现单一接口自动化测试脚本、业务场景接口自动化测试脚本、App自动化测试脚本、Web自动化测试脚本等任务的定时执行、调度、管理等&#xff0c;从而取代Jenkins上的定时执行脚本和发送…

R语言复现:轨迹增长模型发表二区文章 | 潜变量模型系列(2)

培训通知 Nhanes数据库数据挖掘&#xff0c;快速发表发文的利器&#xff0c;你来试试吧&#xff01;欢迎报名郑老师团队统计课程&#xff0c;4.20直播。 案例分享 2022年9月&#xff0c;中国四川大学学者在《Journal of Psychosomatic Research》&#xff08;二区&#xff0c;I…

南京航空航天大学-考研科目-513测试技术综合 高分整理内容资料-01-单片机原理及应用分层教程-单片机有关常识部分

系列文章目录 高分整理内容资料-01-单片机原理及应用分层教程-单片机有关常识部分 文章目录 系列文章目录前言总结 前言 单片机的基础内容繁杂&#xff0c;有很多同学基础不是很好&#xff0c;对一些细节也没有很好的把握。非常推荐大家去学习一下b站上的哈工大 单片机原理及…

AI大模型引领未来智慧科研暨ChatGPT自然科学高级应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

大数据基础学习

目录 一.什么是大数据二.数据处理技术分类&#xff08;OLAP vs OLTP&#xff09;OLAP&#xff08;Online Analytical Processing&#xff09;OLTP&#xff08;Online Transaction Processing&#xff09;区别联系 三.储存的方式&#xff08;列式 vs 行式&#xff09;行式存储列…

【Vue】webpack polyfilling 报错

1. 出现问题描述 npm run serve 项目时报错 ERROR Failed to compile with 1 error 10:33:22 ├F10: AM┤ error in ./src/router/routes.js Module not found: Error: Cant resolve path in /U…

Harmony鸿蒙南向驱动开发-SDIO

SDIO&#xff08;Secure Digital Input and Output&#xff09;由SD卡发展而来&#xff0c;与SD卡统称为MMC&#xff08;MultiMediaCard&#xff09;&#xff0c;二者使用相同的通信协议。SDIO接口兼容以前的SD卡&#xff0c;并且可以连接支持SDIO接口的其他设备。 运作机制 …

Vue的学习之旅-part6-循环的集中写法与ES6增强语法

Vue的学习之旅-循环的集中写法与ES6增强语法 vue中的几种循环写法for循环for in 循环 for(let i in data){}for of 循环 for(let item of data){}reduce() 遍历 reduce( function( preValue, item){} , 0 ) ES6增强写法 类似语法糖简写对象简写函数简写 动态组件中使用 <kee…

MySQL 主从复制部署(8.0)

什么是主从数据库 主从数据库是一种数据库架构模式&#xff0c;通常用于提高数据库的性能、可用性和可伸缩性。 它包括两种类型的数据库服务器&#xff1a; 1&#xff09;主数据库&#xff08;Master&#xff09;&#xff1a;主数据库是读写数据的主要数据库服务器。所有写操…

【数据结构】单链表(一)

上一篇【数据结构】顺序表-CSDN博客 我们了解了顺序表&#xff0c;但是呢顺序表涉及到了一些问题&#xff0c;比如&#xff0c;中间/头部的插入/删除&#xff0c;时间复杂度为O(N);增容申请空间、拷贝、释放旧空间会有不小的消耗&#xff1b;增容所浪费的空间... 我们如何去解…

IOS虚拟键盘弹出后,弹窗的按钮点击不起作用,不会触发click事件

背景 讨论区项目的回复框&#xff0c;使用的是Popup和TextArea做&#xff0c;布局如下图&#xff0c;希望键盘弹出时候&#xff0c;回复框可以紧贴键盘&#xff0c;这点实现起来比较简单&#xff0c;监听resize事件&#xff0c;动态修改popup的这题内容的top值即可&#xff0c…

ONERugged车载平板电脑厂家丨工业车载电脑优势体现丨3年质保

作为现代社会中必不可少的出行工具&#xff0c;汽车不仅仅是代步工具&#xff0c;更是我们生活中的重要一部分。而在如此多功能的汽车内&#xff0c;一款高可靠性、适应不同行业应用的车载平板电脑成为了当下的热门选择。ONERugged车载平板电脑以其卓越的品质和强大的功能而备受…

自动化 单元测试Test

XCTest测试框架(单元测试XCTests、性能测试XCPPerformanceTests、用户界面测试XCUItests) 单元测试XCTests&#xff1a;测试应用中事件或逻辑是否预期工作。 用户界面测试XCUItests&#xff1a;测试用户与应用的UI交互(如点击按钮、滑动屏幕)。 性能测试XCPPerformanceTests&am…

电池电量监测系统设计 单片机+LabVIEW+Matlab+Protues+Keil程序

目录 前言 提供 软件 系统展示 1.放电试验及其处理 2.硬件系统原理图 3.下位机程序 4.显示 5.上位机界面 6.上位机程序 7.文档 资料下载地址&#xff1a;电池电量监测系统设计 单片机LabVIEWMatlabProtuesKeil程序 前言 这套系统首先使用Matlab分析获得了电压…

【opencv】示例-essential_mat_reconstr.cpp 从两幅图像中恢复3D场景的几何信息

导入OpenCV的calib3d, highgui, imgproc模块以及C的vector, iostream, fstream库。定义了getError2EpipLines函数&#xff0c;这个函数用来计算两组点相对于F矩阵&#xff08;基础矩阵&#xff09;的投影误差。定义了sgn函数&#xff0c;用于返回一个双精度浮点数的符号。定义了…