【Linux中断】中断下半部-软中断softirq的原理与使用

【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;

参考文章:

https://zhuanlan.zhihu.com/p/265705850

相关推荐

合作伙伴