xen-softirq

目的

主要了解一下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

xen代码

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对齐,防止伪共享问题,提高性能
 */

通过以上对数据结构的描述,我们可以得出一些简单的结论。如下:

  1. softirq_handlers[]数组类似linux中的中断描述符表irq_desc[],通过软中号可以找到对应的handler进行处理,例如,通过SCHEDULE_SOFTIRQ可以查到schedule,并且执行schedule函数。
  2. 软中断可以在不同的cpu上并行运行(需防止并发),在同一个cpu上只能是串行运行的。
  3. 每个CPU维护irq_cpustat_t状态结构,当某个软中断需要进行处理,会在结构体的__softirq_pending置对应位(raise_softirq实现该功能)。

流程分析

在看文章的时候,知道一个软中断,需要关心如下几点:

  • 软中断注册
  • 软中断触发
  • 软中断执行

软中断注册

软中断通过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);
    ......
}

软中断触发

软中断触发通过如下接口实现,根据接口名称都可以知道接口的具体区别如何。

raise_softirq

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都会保存
 */

cpu_raise_softirq

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触发的低功耗模式
 */

cpumask_raise_softirq

大致实现和cpu_raise_softirq类似,只是使用cpumask去配置对应cpu的softirq的pending。

软中断执行

执行入口

软中断执行的总入口为__do_softirq,由如下两个函数的调用

  • process_pending_softirqs
  • 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的执行点在哪里。

主动调度

  1. idle_loop

异常退出

--> exit 
    --> leave_hypervisor_to_guest
        --> check_for_pcpu_work
            --> do_softirq()

你可能感兴趣的:(虚拟化,linux,arm开发)