Linux 下半部机制介绍(二)——softirq(软中断)框架介绍
Linux 下半部机制介绍(二)——softirq(软中断)框架介绍
在分析softirq框架时,我们按照数据结构+算法拆开来分析(转载请标明出处)
一、抛开代码细节,聊聊软中断关键的数据结构
在上一章介绍中,我们看到了程序员是如何通过上下部机制来压榨CPU的剩余价值。
里面提到了上下部分机制的理解——上半部记录,下半部处理,软中断设计思路就按照这个中心思想来
我们抛开代码细节逻辑,先看看软中断需要哪些信息才能正常工作
1.之前提到了上下半部工作机制,上半部主要工作就是记录
那么我们首先需要一个数据结构来保存这个信息,上半部记录到我们需要处理什么,下半部拿到记录信息,逐个处理待处理的任务,这个记录的数据结构就是嵌入到irq_stat中的__softirq_pending变量,__softirq_pending每一位表示一类软中断,irq_stat是一个每CPU变量。相关代码如下
DEFINE_PER_CPU_ALIGNED(irq_cpustat_t, irq_stat)
typedef struct {
unsigned int __softirq_pending;
unsigned int ipi_irqsp[NR_IPI];
} ____cacheline_aligned irq_cpustat_t;
2.在__softirq_pending中纪录下来需要处理那些软中断后,具体要做哪些工作,也需要一个数据结构来保存,softirq使用了一个函数指针数组softirq_vec来保存具体要做哪些事情
struct softirq_action
{
void (*action)(struct softirq_action *);
};
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
3.在处理软中断与硬件中断类似,在软中断处理过程中,虽然可以响应硬件中断,但是不应该被抢占的,而且有时候我们需要禁用软中断,所以也需要一个值表示是否在软中断处理过程中,以及软中断禁用深度。
我们使用preempt_count[15:8](8-16位)标志来表示这些信息。
第8位标志是否在软中断处理过程中
9-15位表示软中断禁用深度, local_bh_disable() [15:9]位对应的值+1,local_bh_enable() [15:9]位对应的值-1
* PREEMPT_MASK: 0x000000ff
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_MASK: 0x000f0000
* NMI_MASK: 0x00f00000
* PREEMPT_NEED_RESCHED: 0x80000000
二、softirq主要逻辑(算法)
第一节分析了softirq中关键的一些数据数据结构,这一节分析一下softirq主要的一些算法逻辑
1.软中断注册
软中断注册其实就是填充softirq_vec这个函数指针数组的过程,之前介绍数据结构的时候提到过,__softirq_pending中的每一位,都表示一类软中断,软中断注册其实就是针对每一类软中断,都将它的处理函数挂到softirq_vec这个函数指针数组中
具体代码如下
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
例如注册HI_SOFTIRQ,注册完后效果如下
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
全部注册完以后,效果如下
2.软中断触发
软中断触发过程,其实就是标记__softirq_pending记录过程,触发软中断,就是讲__softirq_pending中对应位置1,这样在软中断处理的时候,发现某一位被置1的话,就回调用注册的对应处理函数进行处理,关键代码如下
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
void __raise_softirq_irqoff(unsigned int nr)
{
lockdep_assert_irqs_disabled();
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr); //设置对应软中断位
}
3.软中断处理
上面两节我们已经介绍了软中断注册和触发,那软中断什么时候处理呢?
主要有如下几个地方
- 硬件代码返回(irq_exit)
- ksoftirqd内核线程中(每个CPU都对应一个ksoftirqd 内核线程来处理当前CPU的softirq)
- 网络子系统显示调用的地方
- 使能下半部机制中local_bh_enable()
软中断处理函数的核心逻辑在__do_softirq中,下面就详细介绍一下__do_softirq
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(); //获取当前CPU的__softirq_pending值
softirq_handle_begin(); //标记preempt_count第8位,表示在软中断上下文中
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current); //记录软中断处理开始的时间
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0); //清空__softirq_pending值,接下来正式开始处理软中断
local_irq_enable(); //使能local_irq,这样就可以处理硬件中断,也就是软中断设置的初衷
h = softirq_vec; //指向软中断处理函数指针数组softirq_vec
while ((softirq_bit = ffs(pending))) { //遍历pending值,逐个处理软中断
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1; //h指向对应软中断处理函
vec_nr = h - softirq_vec;
prev_count = preempt_count(); //获取当前preempt_count值,之前提到过过,这值可以显示各类中断使能状态等,详情见第一节
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr); //可以通过ftrace追踪软中断处理
h->action(h); //指向对应的软中断处理函数
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) { //判断一下preempt_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 (!IS_ENABLED(CONFIG_PREEMPT_RT) &&
__this_cpu_read(ksoftirqd) == current)
rcu_softirq_qs();
local_irq_disable();
pending = local_softirq_pending(); //重新获取__softirq_pending值,之前提到过,软中断处理过程中,硬件中断时打开的,所有有可能在处理过程中,会有新的softirq被标记
if (pending) {//如果真的有新的软中断到来
if (time_before(jiffies, end) && !need_resched() &&
--max_restart) //这里会判断本轮软中断处理时间是否超过2s,是否超过10次,避免软中断频繁触发,导致长时间占用CPU
goto restart;
wakeup_softirqd(); //如果本轮软中断处理超过2s或者超过10次,就先退出软中断处理,让出CPU来,唤醒ksoftirqd task,稍后处理这些新的软中断
}
account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
softirq_handle_end(); //清空preempt_count第8位,表示目前不在处理软中断上下文中
current_restore_flags(old_flags, PF_MEMALLOC);
}
三、softirq关键变量或函数说明
关键信息 | 说明 |
__softirq_pending值 | pre_cpu变量,表示目前有哪些软中断待处理,这个值在上半部分标记 |
softirq_vec[]函数指针 | 函数指针数组,指向各个软中断处理函数 |
preempt_count值 | 记录当期hardirq/softirq/NMI irq/抢占新等 使用[15:8]记录softirq信息,第8位表示是否在软中断处理过程中,9-15位表示软中断禁用深度 |
open_softirq 函数 | 将软中断处理函数填充到softirq_vec函数指针数组中 |
raise_softirq 函数 | 标记对应的__softirq_pending值,供软中断处理函数处理 |
__do_softirq函数 | 软中断处理的核心函数 |
__local_bh_enable(SOFTIRQ_OFFSET) __local_bh_disable(SOFTIRQ_OFFSET) | 禁用/使能软中断函数,实际就是设置preempt_count的值 |
local_softirq_pending 函数 | 返回当前cpu的__softirq_pending值,表示有那些软中断待处理 |
ksoftirqd进程 | 每个cpu都有一个对应的ksoftirqd进程,用于处理软中断 一般软中断在硬件中断返回时就会处理掉,但有时候软中断处理时间过长,避免软中断长时间占用CPU,也先让出CPU ,并唤醒ksoftirqd来处理软中断 |
in_softirq()函数 | 返回是否在软中断上下文中,就是检查preempt_count对应8-15位是否为0 |
更多推荐
所有评论(0)