【Linux中断】中断下半部-软中断softirq的原理与使用
软中断
软中断是中断下半部的典型处理机制,是随着SMP的出现应运而生的,也是tasklet实现的基础,软中断的出现是为了满足中断上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。
软中断有以下特性:
- 产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)
- 可以并发运行在多个cpu上。所以软中断必须要设计为可重入的函数(允许多个CPU同时操作)。
软中断数据结构
- 软中断描述符
sturct softirq_action
{
void *(action)(struct softirq_action *);
};
描述每一种类型的软中断,void (*action)是软中断触发时的执行函数。
- 软中断全局数据和支持的枚举类型
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
enum
{
HI_SOFTIRQ=0, // 高优先级的tasklet
TIMER_SOFTIRQ, // 用于定时器的下半部
NET_TX_SOFTIRQ, // 用于网络层发数据包
NET_RX_SOFTIRQ, // 用于网络层收数据包
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ, // 低优先集的tasKlet
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
};
软中断的API接口
- 注册软中断
void open_softirq(int nr, void (*action)(struct softirq_action *));
注册对应类型的处理函数到全局数组softirq_vec中。 网络法宝的对应类型是NET_TX_SOFTIRQ的处理函数net_tx_action。
- 触发软中断
void raise_softirq(unsigned int nr);
软中断类型nr作为偏移量置位每个CPU变量irq_stat[cpu_id]的成员变量_softirq_pending,这是同一类型的软中断可以在多个cpu核心上并行运行的根本原因。
- 软中断执行函数
do_softirq() -> __do_softirq()
在执行软中断处理函数__do_softirq()前需要满足两个条件:
(1)不在中断中(包括硬中断,软中断和NMI)
(2)有软中断处于pending状态
这样设计的原因是为了避免软件中断在中断嵌套中调用,达到在单个CPU上软件中断不能被重入的目的。
ARM架构的CPU不存在中断嵌套中调用软件中断的问题,因为ARM架构的CPU在处理硬件中断的过程中关闭掉中断,在进入软中断处理过程之后才会重新开启硬件中断,如果在软件中断处理过程中有硬件中断嵌套,也不会再次调用软中断,因为硬中断是软中断处理过程中再次进入的。
软中断实现原理
中断处理过程图
软中断的调度时机
1.do_irq完成I/O中断时调用irq_exit
2.系统使用I/O APIC,在处理玩本地时钟中断时
3.local_bh_enable,开启本地软中断时
4.SMP系统中,cpu处理完备CALL_FUNCTION_VECTOR处理器间中断所触发的函数时。
5.ksoftirqd/n线程被唤醒时
软中断处理流程
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;
/*
* Mask out PF_MEMALLOC as the current task context is borrowed for the
* softirq. A softirq handled, such as network RX, might set PF_MEMALLOC
* again if the socket is related to swapping.
*/
current->flags &= ~PF_MEMALLOC;
// 获取当前有哪些等待的软中断
pending = local_softirq_pending();
account_irq_enter_time(current);
// 关闭软中断,实质是设置正在处理软件的中断标记,在同一个CPU上使得不能重入_do_softirq函数
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
in_hardirq = lockdep_softirq_start();
restart:
/* Reset the pending bitmask before enabling irqs */
// 重新设置软中断标志为0,这样在重新烤漆中断后硬件中断中又可以设置软件中断位
set_softirq_pending(0);
// 开启硬件中断
local_irq_enable();
h = softirq_vec;
// 遍历pending标志的每一位,如果这一位设置就会调用软件中断的处理函数
while ((softirq_bit = ffs(pending))) {
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1;
vec_nr = h - softirq_vec;
prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h);
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
vec_nr, softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count_set(prev_count);
}
h++;
pending >>= softirq_bit;
}
if (__this_cpu_read(ksoftirqd) == current)
rcu_softirq_qs();
// 关闭硬件中断
local_irq_disable();
// 查看是否有软件中断处于pending状态
pending = local_softirq_pending();
if (pending) {
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;
// 如累积重复进入软件中断处理的次数超过max_restart=10次,就唤醒内核进程来处理软件中断
wakeup_softirqd();
}
lockdep_softirq_end(in_hardirq);
account_irq_exit_time(current);
// 开启软中断
__local_bh_enable(SOFTIRQ_OFFSET);
WARN_ON_ONCE(in_interrupt());
current_restore_flags(old_flags, PF_MEMALLOC);
}
软中断内核线程
触发软件中断的位置是在中断上下文,而软中断的内核线程就已经进入到进程的上下文。软中断的上下文是系统为每个CPU建立的ksoftirqd进程。软中断的内核进程中主要有两个大循环,外层的循环处理有软件中断就处理,没有软件中断就休眠。内层的循环处理软件中断,每循环一次都试探一次是否过长时间占据了CPU,需要调度就是放CPU给其他进程。
set_current_state(TASK_INTERRUPTIBLE);
//外层大循环。
while (!kthread_should_stop()) {
preempt_disable();//禁止内核抢占,自己掌握cpu
if (!local_softirq_pending()) {
preempt_enable_no_resched();
//如果没有软中断在pending中就让出cpu
schedule();
//调度之后重新掌握cpu
preempt_disable();
}
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
//有软中断则开始软中断调度
do_softirq();
//查看是否需要调度,避免一直占用cpu
preempt_enable_no_resched();
cond_resched();
preempt_disable();
rcu_sched_qs((long)__bind_cpu);
}
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
wait_to_die:
preempt_enable();
/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
参考文章: