內核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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章