主要了解一下xen项目中软中断是怎么使用,如何实现的。同时也对自己学习过程的一次记录。
文章 | 链接 |
---|---|
中断子系统之softirq | http://www.wowotech.net/irq_subsystem/soft-irq.html |
Linux的中断处理机制 [四] - softirq(1) | https://zhuanlan.zhihu.com/p/80371745 |
Linux的中断处理机制 [五] - softirq(2) | https://zhuanlan.zhihu.com/p/80680484 |
Linux中断子系统(三)-softirq和tasklet | https://www.cnblogs.com/LoyenWang/p/13124803.html |
include/xen/softirq.h // 数据结构
include/asm-arm/hardirq.h // 数据结构
include/xen/irq_cpustat.h // 数据结构
common/softirq.c // softirq具体实现
softir不支持动态分配,这和内核的原则是一致的,因为是静态分配,自然会优先是用数组管理所有的softirq实例,其关键的数据提描述如下,可以类比硬件中断去理解softirq。
/*支持的软中断类型,逻辑上的软中断号,其中从上到下优先级递减 */
enum {
TIMER_SOFTIRQ = 0, /* timer 定时器软中断 优先级最高 */
RCU_SOFTIRQ, /* rcu 定时器软中断 */
SCHED_SLAVE_SOFTIRQ, /* 调度软中断 */
SCHEDULE_SOFTIRQ, /* 调度软中断 */
NEW_TLBFLUSH_CLOCK_PERIOD_SOFTIRQ, /* 没用上 */
TASKLET_SOFTIRQ, /* tasklet 软中断 */
NR_COMMON_SOFTIRQS /* 数组size */
};
typedef void (*softirq_handler)(void); /* handler函数指针 */
/* 软中断描述表,实际就是一个handler函数指针的数组
* 维护的是软中断号与softirq handlers的关系表
*/
softirq_handler softirq_handlers[NR_SOFTIRQS];
根据实际的注册情况,软中断描述符的结果如下:
序号 | 软中断号 | 处理函数 |
---|---|---|
0 | TIMER_SOFTIRQ | timer_softirq_action |
1 | RCU_SOFTIRQ | rcu_process_callbacks |
2 | SCHED_SLAVE_SOFTIRQ | sched_slave |
3 | SCHEDULE_SOFTIRQ | schedule |
4 | NEW_TLBFLUSH_CLOCK_PERIOD_SOFTIRQ | NULL |
5 | TASKLET_SOFTIRQ | tasklet_softirq_action |
类似于硬件中断,需要一些机制表示中断触发标志,软中断需要软件的一些变量去维护每个类型软中断触发的标志。
irq_cpustat_t irq_stat[NR_CPUS]
typedef struct {
unsigned long __softirq_pending;
unsigned int __local_irq_count;
} __cacheline_aligned irq_cpustat_t;
/*
* 当某个软中断触发时, __softirq_pending会置位对应的bit.
* 每cpu都会保存一份软中断的状态描述。在运行的时候cpu只会handler自己
* __cacheline_aligned保证cache对齐,防止伪共享问题,提高性能
*/
通过以上对数据结构的描述,我们可以得出一些简单的结论。如下:
在看文章的时候,知道一个软中断,需要关心如下几点:
软中断通过open_softirq
接口来注册,代码很简单。就是把软中断服务函数写到softirq_handlers[]
软中断描述符表中,保存软中断号都服务函数的一一对应关系。
void open_softirq(int nr, softirq_handler handler)
{
ASSERT(nr < NR_SOFTIRQS); /* 数组越界检查 */
softirq_handlers[nr] = handler;
}
举个软中断注册的例子,来看看注册是有多简单。
void __init timer_init(void)
{
......
open_softirq(TIMER_SOFTIRQ, timer_softirq_action);
......
}
软中断触发通过如下接口实现,根据接口名称都可以知道接口的具体区别如何。
void raise_softirq(unsigned int nr)
{
set_bit(nr, &softirq_pending(smp_processor_id()));
}
#define softirq_pending(cpu) __IRQ_STAT((cpu), __softirq_pending)
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
/*
* softirq_pengding()拓展后的结果如下:
* irq_stat[local_cpu].__softirq_pending
* 符合之前的结论,触发的状态就是保存在irq_stat,每cpu都会保存
*/
void cpu_raise_softirq(unsigned int cpu, unsigned int nr)
{
// 1: 获取本地cpu,当前接口就有两个cpu id成员了
unsigned int this_cpu = smp_processor_id();
/* test_and_set_bit就是设置具体cpu上softirq的pending
*
* 如下条件之一成立就不会往下执行
* a. cpu上已经存在softirq pending,
* b. 设置的cpu == local cpu
* c. arch_skip_send_event_check(cpu)为0,绝对不会成立
*/
if ( test_and_set_bit(nr, &softirq_pending(cpu))
|| (cpu == this_cpu)
|| arch_skip_send_event_check(cpu) )
return;
/*
* batching不为空或者在硬中断时,触发核间中断:
* send_SGI_mask(mask, GIC_SGI_EVENT_CHECK)
* 核间中断的重要是要处理的核在WFI触发的低功耗阶段,需要使用中断来把它唤醒,再处理软中断
*
* 判断是false时,就是保存batch_mask
*/
if ( !per_cpu(batching, this_cpu) || in_irq() )
smp_send_event_check_cpu(cpu);
else
__cpumask_set_cpu(cpu, &per_cpu(batch_mask, this_cpu));
}
/*
* 该接口看起来就是raise_softirq,只是可以设置所有cpu
* 同时通过核间中断让cpu退出wfi触发的低功耗模式
*/
大致实现和cpu_raise_softirq类似,只是使用cpumask去配置对应cpu的softirq的pending。
软中断执行的总入口为__do_softirq
,由如下两个函数的调用
讨论的时候去掉了关于rcu的处理,后期在rcu的时候再统一讨论
static void __do_softirq(unsigned long ignore_mask)
{
unsigned int i, cpu;
unsigned long pending;
for ( ; ; )
{
// 1: 获取本地cpu id,pengding的讨论都与cpu相关
cpu = smp_processor_id();
// 2: 一直循环检查是否还有pending,没就退出循环
if ( ((pending = softirq_pending(cpu)) == 0)
|| cpu_is_offline(cpu) )
break;
// 3:查找pengding的中断号,从优先级高到低
i = find_first_set_bit(pending);
// 4: 处理handler前清掉pending,与硬件中断类似
clear_bit(i, &softirq_pending(cpu));
// 5: 执行软中断服务函数
(*softirq_handlers[i])();
}
}
/* 结论
* 比linxu的处理逻辑要简单,没有很多的权衡代码
* 处理时还需要照顾rcu,后期补上相关联的逻辑
*/
我们已经讨论了软中断怎么去执行到每个被触发的softirq处理函数。那我们需要处理另外一个问题,__do_softirq的执行点在哪里。
--> exit
--> leave_hypervisor_to_guest
--> check_for_pcpu_work
--> do_softirq()