内核IRQ中断向量

首先看一下vector_irq的定义,此每处理器数组变量,保存每个处理器上中断向量所对应的中断号,其以中断向量值为索引。系统中定义了256个中断向量。相关代码如下:

typedef int vector_irq_t[NR_VECTORS];

DEFINE_PER_CPU(vector_irq_t, vector_irq) = {
    [0 ... NR_VECTORS - 1] = VECTOR_UNDEFINED,
};


#define NR_VECTORS  256

另一个是used_vector的定义,其为一个位图变量,用来标记系统中已经使用的中断向量。

DECLARE_BITMAP(used_vectors, NR_VECTORS);

中断向量初始化

中断初始化入口函数init_IRQ。为支持传统的PIC类型的终端控制器,如8259,内核预留了IRQ0_VECTOR 到 IRQ15_VECTOR 共16个中断向量,分别对应IRQ号0到15。在初始化时,也不是全部预留这16个中断向量,而是根据检测到的传统PIC中断控制器所支持中断数量在预留,有可能小于16个。x86_init.irqs.intr_init()指针对应的为native_init_IRQ函数,接下来介绍。

void __init init_IRQ(void)
{

    for (i = 0; i < nr_legacy_irqs(); i++)
        per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;

    x86_init.irqs.intr_init();
}

函数apic_intr_init负责分配系统自身所需的中断向量。x86架构的IDT中断描述表可用的外部中断向量由0x20(FIRST_EXTERNAL_VECTOR)开始,其中0x80(IA32_SYSCALL_VECTOR)为系统调用所用的中断向量;0x30-0x3f共16个中断向量保留给ISA使用。interrupt函数参见文件arch/x86/kernel/entry_64.S中的定义,表示中断处理程序的入口地址,for_each_clear_bit_from循环将所有未使用的中断向量的处理程序入口设定为interrupt的相应偏移。最后对于传统的中断控制器,irq2用作两片控制器的级联。

void __init native_init_IRQ(void)
{
    apic_intr_init();

    i = FIRST_EXTERNAL_VECTOR;
    for_each_clear_bit_from(i, used_vectors, NR_VECTORS) {
        set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]);
    }

    if (!acpi_ioapic && !of_ioapic)
        setup_irq(2, &irq2);
}

系统中断向量分配

函数apic_intr_init。首先分配SMP相关的中断向量,参见函数smp_intr_init,随后是分配其他的向量。这些中断向量值由大到小分配,首先是:SPURIOUS_APIC_VECTOR(0xff),目前最小的是:LOCAL_TIMER_VECTOR(0xef)。内核中定义了变量first_system_vector表示当前系统中断向量的最小值。

static void __init apic_intr_init(void)
{
    smp_intr_init();

#ifdef CONFIG_X86_THERMAL_VECTOR
    alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);
}

static void __init smp_intr_init(void)
{
#ifdef CONFIG_SMP
#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
    alloc_intr_gate(RESCHEDULE_VECTOR, reschedule_interrupt);
}

alloc_intr_gate宏由alloc_system_vector和set_intr_gate两个函数组成。前者为注册的系统中断向量设置used_vectors中的使用标志,并且更新系统最小向量值:first_system_vector。后者set_intr_gate函数设置处理器的IDT表。

static inline void alloc_system_vector(int vector)
{
    if (!test_bit(vector, used_vectors)) {
        set_bit(vector, used_vectors);
        if (first_system_vector > vector)
            first_system_vector = vector;
    } else {
        BUG();
    }
}

#define alloc_intr_gate(n, addr)                \
    do {                            \
        alloc_system_vector(n);             \
        set_intr_gate(n, addr);             \
    } while (0)

外部中断向量分配

由如下函数irq_alloc_hwirqs可见,首先调用函数__irq_alloc_descs分配中断号,内核中已经使用的中断号标记在全局位图变量allocated_irqs中,函数的第一个参数表示期望的中断号,-1表示由系统来选择中断号。系统将在allocated_irqs位图中选择一个不为零的位返回,作为新的中断号。

函数arch_setup_hwirq负责为中断号查找合适的中断向量。注意在执行过程中出现错误的话,函数将执行回退处理,包括由函数irq_free_descs复位之前分配中断号时设置的allocated_irqs中的位。arch_teardown_hwirq函数是对应arch_setup_hwirq函数的清理函数。

unsigned int irq_alloc_hwirqs(int cnt, int node)
{
    int i, irq = __irq_alloc_descs(-1, 0, cnt, node, NULL);

    if (irq < 0)
        return 0;

    for (i = irq; cnt > 0; i++, cnt--) {
        if (arch_setup_hwirq(i, node))
            goto err;

        irq_clear_status_flags(i, _IRQ_NOREQUEST);
    }
    return irq;

err:
    for (i--; i >= irq; i--) {
        irq_set_status_flags(i, _IRQ_NOREQUEST | _IRQ_NOPROBE);
        arch_teardown_hwirq(i);
    }
    irq_free_descs(irq, cnt);
    return 0;
}

arch_setup_hwirq函数的主体是由__assign_irq_vector函数完成,其定义如下。由于终端级别是由中断向量后4位决定的,而本地的APIC不能够很好的处理多个同级别的中断,所以内核以2的4次方,即16为区间,进行中断向量的分配。

使用静态变量current_vector和current_offset分别记录当前的区间起始值和区间内偏移。第一次分配中断向量时,current_vector为33,偏移值为1,所以第一个可供分配的中断向量为34。

static int __assign_irq_vector(int irq, struct irq_cfg *cfg, const struct cpumask *mask)
{
    static int current_vector = FIRST_EXTERNAL_VECTOR + VECTOR_OFFSET_START;
    static int current_offset = VECTOR_OFFSET_START % 16;

以下的中断向量查找由两个循环组成。第一个循环遍历中断可用的处理器列表,由APIC控制器的可用处理器列表和在线处理器列表中第一个同时置位的处理器开始,第二个循环(内部循环)遍历所有可用的中断向量vector,如果没有合适的选择,即vector的值又回到了最初的current_vector,进行第二次循环,遍历第二个可用的处理器。

获取第二个可用处理器的算法为:1)当前分配的处理器域domain(处理器列表),与老的域值old_domain进行或操作;2)将上一步的结果和APIC处理器列表进行与操作;3)在第二步所得结果和在线处理器列表中获取第一个同时置位的处理器。此即下一个循环要遍历的处理器。

1287     cpumask_clear(cfg->old_domain);
1288     cpu = cpumask_first_and(mask, cpu_online_mask);
1289     while (cpu < nr_cpu_ids) {
1290         int new_cpu, vector, offset;
1291
1294         apic->vector_allocation_domain(cpu, tmp_mask, mask);

1335         if (unlikely(current_vector == vector)) {
1340             cpumask_or(cfg->old_domain, cfg->old_domain, tmp_mask);
1345             cpumask_andnot(tmp_mask, mask, cfg->old_domain);
1348             cpu = cpumask_first_and(tmp_mask, cpu_online_mask);
1350             continue;
1351         }

内部的循环由标签和goto语句组成。每次遍历16长度的区间中的第一个偏移向量值,一直遍历完所有的区间。之后,当vector向量值大于最大可用向量值first_system_vector时,重新由第一个区间的第二个偏移值开始遍历。

如果遍历的向量值没有被使用,即used_vectors位图没有置位。并且,tmp_mask中的所有有效处理器上vector_irq映射还没有被占用,表明此中断向量可用。

1324         vector = current_vector;
1325         offset = current_offset;
1326 next:
1329         vector += 16;
1330         if (vector >= first_system_vector) {
1331             offset = (offset + 1) % 16;
1332             vector = FIRST_EXTERNAL_VECTOR + offset;
1333         }
1334
1335         if (unlikely(current_vector == vector)) {
1340             cpumask_or(cfg->old_domain, cfg->old_domain, tmp_mask);
1345             cpumask_andnot(tmp_mask, mask, cfg->old_domain);
1348             cpu = cpumask_first_and(tmp_mask, cpu_online_mask);
1350             continue;
1351         }
1352
1353         if (test_bit(vector, used_vectors))
1355             goto next;
1357
1358         for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask) {
1359             if (per_cpu(vector_irq, new_cpu)[vector] > VECTOR_UNDEFINED)
1360                 goto next;
1361         }

成功找到可用的中断向量之后,更新静态的current_vector和current_offset的值,并更新tmp_mask中所有处理器的vector_irq中断向量和中断号映射值,写入分配的irq中断号,表明此中断向量已被占用。使用cfg中的domain成员记录有效的处理器列表。

除去第一次申请中断向量外,系统也可能动态的调整中断向量,及为其服务的处理器列表,old_domain用来保存调整前的处理器列表,move_in_progress表示是否正处于调整期间,调整结束之后old_domain为空,move_in_progress也值为0。

1362         /* Found one! */
1363         current_vector = vector;
1364         current_offset = offset;
1365         if (cfg->vector) {
1371             cpumask_copy(cfg->old_domain, cfg->domain);
1374             cfg->move_in_progress = cpumask_intersects(cfg->old_domain, cpu_online_mask);
1377         }

1380         for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask)
1381             per_cpu(vector_irq, new_cpu)[vector] = irq;
1382         cfg->vector = vector;
1383         cpumask_copy(cfg->domain, tmp_mask);
1387
1388         err = 0;
1389         break;

另外,在中断调整时,如果服务的处理器列表调整前后一致,直接跳出循环(cpumask_equal(tmp_mask, cfg->domain))。如果新的处理器列表是调整前处理器列表的子集,移除多余的处理器,并更新domain为调整之后的处理器列表。

1294         apic->vector_allocation_domain(cpu, tmp_mask, mask);
1295
1296         if (cpumask_subset(tmp_mask, cfg->domain)) {
1297             err = 0;

1303             if (cpumask_equal(tmp_mask, cfg->domain))
1304                 break;
1305             /*
1306              * New cpumask using the vector is a proper subset of the current in use mask. So cleanup the vector
1308              * allocation for the members that are not used anymore.
1309              */
1310             cpumask_andnot(cfg->old_domain, cfg->domain, tmp_mask);
1313
1314             cfg->move_in_progress = cpumask_intersects(cfg->old_domain, cpu_online_mask);
1317             cpumask_and(cfg->domain, cfg->domain, tmp_mask);
1320
1321             break;
1322         }

以上的介绍打乱了代码的顺序,以行号为准。

内核版本:3.10.0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值