上节主要讲了Linux内核do_IRQ函数中中irq_enter以及generic_handle_irq(irq)函数,这是中断上半部。
这节要开始剖析中断下半部的实现原理
内核用一个无符号整型**__sofitirq_pending**来表示当前正在等待被处理的softirq,每一种softirq在__softirq_pending中占据一位
每个CPU有自己的__softirq_pending变量
typedef struct {
unsigned int __softirq_pending; //32bit无符号整型,足够用了,默认软中断只用了10bit
}__cacheline_aligned irq_cpustat_t;
//per-CPU变量 如上述所言,每个CPU都有自己的__softirq_pending变量
irq_cpustat_t irq_stat[NR_CPUS] __cacheline_aligned;
softirq调用在irq_exit中,irq_exit结束一个上下文,如果有需要的话执行softriqs
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
//首先把当前栈中的preempt_count变量减去IRQ_EXIT_OFFSET来标识一个HARDIRQ中断上下文的结束
sub_preempt_count(IRQ_EXIT_OFFSET);
//in_interrupt函数用于判断当前是否在一个中断上下文中执行
//local_softirq_pending用于判断__softirq_pending是否有等待的softirq
//保证如果中断在softirq部分发生,那么只会处理hardirq部分,然后直接返回到softirq被中断的部分继续执行。
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
rcu_irq_exit();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
}
//下面这个宏用来决定在hardirq部分结束时,有没有关闭处理器响应外部中断的能力。
//体系结构相关,有的结构没有这个能力,就需要调用do_softirq,它会先做一些中断屏蔽的事情然后再调用__do_softirq
//否则直接调用__do_softirq
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
static inline void invoke_softirq(void)
{
if (!force_irqthreads)
__do_softirq();
else
wakeup_softirqd();
}
#else
static inline void invoke_softirq(void)
{
if (!force_irqthreads)
do_softirq();
else
wakeup_softirqd();
}
#endif
下面要看do_softirq函数,先要了解内核中已经定义好的软中断类型有哪些:
每一个softirq对应__softirq_pending中的一个位
通过枚举类型来静态声明软中断,并且每一种软中断都使用索引来表示一种相对的优先级,索引号越小,软中断优先级高,并在一轮软中断处理中得到优先执行。其中:
HI_SOFTIRQ,优先级为0,是最高优先级的软中断类型。
TIMER_SOFTIRQ,优先级为1,用于定时器的软中断。
NET_TX_SOFTIRQ,优先级为2,用于发送网络数据包的软中断。
NET_RX_SOFTIRQ,优先级为3,用于接收网络数据包的软中断。
BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ,优先级分别是4和5,用于块设备的软中断。
TASKLET_SOFTIRQ,优先级为6,专门为tasklet机制准备的软中断。
SCHED_SOFTIRQ,优先级为7,进程调度以及负载均衡。
HRTIMER_SOFTIRQ,优先级为8,高精度定时器。
RCU_SOFTIRQ,优先级为9,专门为RCU服务的软中断。
SoftIRQ软中断的接口函数如下。
void open_softirq(int nr, void (*action)(struct softirq_action *))
void raise_softirq(unsigned int nr)
open_softirq()函数接口可以注册一个软中断,其中参数nr是软中断的序号。
raise_softirq()函数是主动触发一个软中断的API接口函数。
可以说每个softirq类型都是一个坑位,势必要将对应类型的softirq和实际的处理函数对应起来:
内核定义了一个struct softirq_action类型的数组softirq_vec来保存实际的处理函数:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
__do_softirq的核心思想就是从当前CPU的__softirq_pending的最低位开始依次往高位遍历,如果bitn是1,这执行softirq_vec数组对应索引上的action函数:
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
account_system_vtime(current);
//表示softirq上下文
__local_bh_disable((unsigned long)__builtin_return_address(0),
SOFTIRQ_OFFSET);
lockdep_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
//开中断执行
local_irq_enable();
h = softirq_vec; //要对h操作,softirq_vec数组保存到h中,防止被改更
//循环,__softirq_pending的低位先执行
do {
if (pending & 1) {
unsigned int vec_nr = h - softirq_vec;
int 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())) {
printk(KERN_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() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
//唤醒ksoftirqd处理 防止softirq部分耗费太多时间,所以将这些处理放到进程上下文执行
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
__local_bh_enable(SOFTIRQ_OFFSET);
}
Linux系统初始化期间通过调用softirq_init为TASKLET_SOFTIRQ和HI_SOFTIRQ安装执行函数
void softirq_init(void)
{
for_each_possible_cpu(cpu){
...
for(int i=0; i<NR_SOFTIRQS; i++)
}
//给softirq_vec上的TASKLET_SOFTIRQ和HI_SOFITIRQ位置上安装对应的执行函数
//相当于:
//softirq_vec[TASKLET_SOFTIRQ].action = tasklet_action;
//softirq_vec[HI_SOFTIRQ].action = tasklet_hi_action;
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
一个tasklet对象被设计为struct tasklet_struct类
struct tasklet_struct
{
struct tasklet_struct *next; //用于让tasklet成员构成链表
unsigned long state; //记录每个tasklet在系统中的状态:SCHED or RUN
atomic count; //count=0则tasklet是enabled
void (*func)(unsigned long); //tasklet上的执行函数或者延迟函数
unsigned long data; //传入func函数的参数
}
声明并静态初始化一个tasklet对象,内核已经为驱动程序准备了DECLARE_TASKLET宏:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(0), func, data}
如果驱动在运行过程中构建了一个tasklet对象,对齐初始化可以调用tasklet_init函数完成:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
...
}
向系统提交这个tasklet使用tasklet_shcedule也就是将tasklet对象加入到tasklet_vec管理的链表中。
HI_SOFTIRQ提交的tasklet对象的函数为tasklet_hi_schedule
流水线 | 客户 | 加工件打包 | 送到流水线 | 工人 |
---|---|---|---|---|
worklist | 需要使用工作队列的驱动 | structure work_struct对象 | queue_work函数调用 | worker_thread |