Linux中斷機制之二:初始化

相關概念和關鍵數據結構

1、 irq號:在當前系統中全局唯一,對應內核數據結構struct irq_desc,每個外設的中斷有一個irq號(體系結構預留的中斷,是沒有對應的irq_desc結構和irq號的),該irq在該中斷的生命週期內都不會改變,且和該中斷的中斷處理函數關聯;內核使用一個bitmap allocated_irqs來標識當前系統已經分配的irq;irq號的管理與底層中斷設備和配置無關,屬於Generic Interrupt Layer;對於irq號分佈集中的情況,不配置CONFIG_SPARSE_IRQ,內核採用數組直接管理,數組下標就是irq號;而對於irq號比較分散的,設置CONFIG_SPARSE_IRQ,內核採用radix tree來管理所有的irq號。
2、 vector號:內核使用全局bitmap used_vectors來標識那些vector被系統預留,不能被外設分配使用。
3、 irq號和vector號的關聯:內核中使用per-cpu變量vector_irq來描述irq號和vector號的關聯,對每個CPU,vector_irq是一個數組,在X86架構下成員數量爲256,其數組的index爲vector,值爲irq,如果爲-1則表示該CPU上的這個vector尚未分配。
4、 struct irq_desc結構,用來描述一箇中斷,是內核generic interrupt layer的關鍵數據結構,其包含了中斷的大部分信息,並連接了driver層和物理中斷設備層,每個irq號對應一個該結構,共享相同irq號的中斷共享該結構。它的關鍵成員包括:

a)  irq_data  :爲該中斷對應的物理中斷設備層相關的數據。     
b)  handle_irq:爲該該中斷使用的通用邏輯接口。
c)  action:爲driver層提供的ISR信息,其爲一個單向鏈表結構,所有共享該中斷的設備的ISR都鏈接在這裏。

內核關鍵數據結構和相關初始化

對X86 CPU,Linux內核使用全局idt_table來表達當前的IDT,該變量定義在traps.c

gate_desc  idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };//初始化爲全0。

對中斷相關的初始化,內核主要有以下工作:
1、 設置used_vectors,確保外設不能分配到X86保留使用的vector(預留的vector範圍爲[0,31],另外還有其他通過apic_intr_init等接口預留的系統使用的vector);
2、 設置X86CPU保留使用的vector對應的IDT entry;這些entry使用特定的中斷處理接口;
3、 設置外設 (包括ISA中斷)使用的中斷處理接口,這些中斷處理接口都一樣。
4、 設置ISA IRQ使用的irq_desc;
5、 把IDT的首地址加載到CPU的IDTR(Interrupt Descriptor Table Register);
6、 初始化中斷控制器(下一章描述)
以上工作主要在以下函數中完成:

start_kernel
    trap_init 
        使用set_intr_gate等接口初始化保留vector
        used_vectors中[0,0x1f]對應的vector被置1,表示已經預留不能再使用
        cpu_init
            load_idt((const struct desc_ptr *)&idt_descr) 把&idt_descr的地址加載到idtr
                native_load_idt()
    init_IRQ
        初始化0號CPU的vector_irq:其vector[0x30-0x3f]和irq號[0-15](ISA中斷)對應
        x86_init.irqs.intr_init(native_init_IRQ)
            x86_init.irqs.pre_vector_init(init_ISA_irqs)
                init_ISA_irqs:初始化ISA,設置irq號[0,15] (ISA中斷)的irq_desc
            apic_intr_init 設置APIC相關中斷(使用的alloc_intr_gate會設置used_vectors)
            使用interrupt數組初始化設置外設中斷idt entry

可以看到,這個過程會完成每個中斷vector對應的idt entry的初始化,系統把這些中斷vector分成以下幾種:
1、X86保留vector,這些vector包括[0,0x1f]和APIC等系統部件佔用的vector,對這些vector,會記錄在bitmap used_vectors中,確保不會被外設分配使用;同時這些vector都使用各自的中斷處理接口,其中斷處理過程相對簡單(沒有generic interrupt layer的參與,CPU直接調用到各自的ISR)。
2、ISA irqs,對這些中斷,在初始化過程中已經完成了irq_desc、vector_irq、以及IDT中對應entry的分配和設置,同時可以發現ISA中斷,在初始化的時候都被設置爲運行在0號CPU。
3、其它外設的中斷,對這些中斷,在初始化過程中僅設置了對應的IDT,和ISA中斷一樣,其中斷處理接口都來自interrupt數組。

中斷處理接口interrupt數組
interrupt數組是內核中外設中斷對應的IDT entry,其在entry_64.S中定義,定義如下:

    .section .init.rodata,"a"/*符號interrupt位於數據段*/
ENTRY(interrupt)
    .section .entry.text/*以下開始內容輸出到text段*/
    .p2align 5
    .p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)/*符號irq_entries_start位於代碼段*/
    INTR_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7/*一共224/7次外層循環*/
    .balign 32
  .rept 7/*內層循環每組爲7個vector對應的處理接口*/
    .if vector < NR_VECTORS
      .if vector <> FIRST_EXTERNAL_VECTOR
    CFI_ADJUST_CFA_OFFSET -8
      .endif
1:  pushq_cfi $(~vector+0x80) /* 代碼段內容*/
      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6/*每組最後一個接口不需要jmp*/
    jmp 2f
      .endif
      .previous/*返回之前的段,也就是以下內容輸出到數據段*/
    .quad 1b /*數據段的數據爲標號1的地址,b表示這個標號在前面*/
      .section .entry.text/*切換回代碼段*/
vector=vector+1
    .endif
  .endr/*循環7次,跳轉到.rept7 */
2:  jmp common_interrupt
.endr/*循環,對應前面的.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7對應*/
    CFI_ENDPROC
END(irq_entries_start)
.previous
END(interrupt)
.previous

這段彙編的效果是:在代碼段,生成了一個符號irq_entries_start,該符號對應的內容是一組可執行代碼,一共(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7組,每組爲7箇中斷入口,爲:

pushq_cfi $(~0x20+0x80)
jmp 2f /*2f是一個地址,表示爲後面標號2的地址,也就是後面jmp common_interrupt 這段代碼的地址*/
以上內容重複5次,一共6次,下面是第7箇中斷入口。
pushq_cfi $(~0x26+0x80) 
jmp common_interrupt

每組的最後一箇中斷入口不需要jmp 2f是因爲其pushq_cfi(就是pushq咯)下面就
是2f這個標號的地址了。(不明白的是:爲什麼不在jmp 2f的地方直接寫上jmp common_interrupt?非要jmp 2f,2f的地方再次jmp common_interrupt?

而interrupt是一個數組,該數組在初始化完成後釋放,其每個數組項都是一個地址,是對應的“pushq_cfi”代碼的地址(每個代表中斷入口的標號)。系統在初始化的時候,對外設使用interrupt數組作中斷處理接口,就是在中斷髮生時,執行代碼段:

pushq_cfi $(~vector+0x80)
jmp 2f (執行jmp common_interrupt)

初始化中斷控制器

對中斷控制器的使用基本上有三種機制:
1、 中斷路由表 $PIR
struct irq_routing_table,該結構用於使用PIR和8259A的系統,在微軟的文獻《PCI IRQ Routing Table Specification》中描述了該結構詳細信息。其描述了一個PCI設備的INT#是如何連接到PIR設備的輸入端口的。其關鍵數據是一個可變長的struct irq_info數組,每個struct irq_info描述了一個PCI物理設備的4個INT#相關的中斷路由信息和對應可用的ISA IRQs的bitmap。BIOS根據相關設備的物理連接填寫該數據結構,OS在設備初始化過程中使用這些信息爲使用INT#的設備分配對應的vector和irq。
2、 MP table
struct mpc_intsrc,該數據結構用於使用I/O APIC的系統中,描述系統中所有PCI設備4個INT#信號和I/O APIC輸入引腳的對應關係。該數據結構的srcbus成員爲對應PCI設備的bus id;srcbusirq描述了一個INT#信號,其bit0-bit1用於描述是INTA#–INTD#中的哪一個(對應值爲0-3),bit2-bit6描述該PCI設備的slot id。dstapic爲該描述對應的I/O APIC的ID。dstirq描述srcbus和srcbusirq確定的INT#對應的irq號信息(具體的解析有多種情況)。在系統中有一個以該數據結構爲成員的全局數組mp_irqs,用於管理系統中所有的硬件中斷信號和irq之間的關聯。對MP table及其使用的更加詳細的描述,見《Multiprocessor Specification v1.4》
3、 ACPI(Advanced Configuration and PowerInterface)機制
這種機制爲I/O APIC機制和中PIR機制提供統一的管理界面,該機制使用struct acpi_prt_entry描述INT#和GSI(能和vector、irq對應)的關係,系統中所有的struct acpi_prt_entry由OS從BIOS提供的信息中獲取,並保存在鏈表acpi_prt_list中。
注:對GSI的說明,GSI(global system interrupt)表示的是系統中中斷控制器的每個輸入管腳的唯一編號,在使用ACPI模式管理中斷控制器的時候使用。對使用8259A的系統,GSI和ISA IRQ是一一對應的。對於使用APIC的,每個I/O APIC會由BISO分配一個基址,這個base+對應管腳的編號(從0開始)就是對應的GSI。通常是基址爲0的I/O APIC的前16個管腳用於ISA IRQS,對GSI更加詳細的描述,見《Advanced Configuration and Power Interface Revision 2.0》

除了中斷路由表,其它兩種機制的初始化(包括相關中斷路由信息的初始化)的在《interrupt in linux》中有很詳細的描述。這些初始化操作都在內核初始化的時候完成。

爲PCI設備配置中斷

爲PCI設備配置中斷,分爲兩個步驟,
步驟一:爲設備分配irq號(對MSIX,會有多個),爲該中斷分配執行CPU和它使用的vector,並通過對中斷控制器的設置,確保對應的中斷信號和vector匹配。對於使用INT#類型的中斷,通常通過pci_enable_device/pci_enable_device_mem/pci_enable_device_io中對函數pcibios_enable_device的調用來完成(只有在沒有開啓MSI/MSIX的時候纔會爲INT#做配置),而要配置MSI/MSIX中斷要使用的是pci_enable_msix。
步驟二:request_irq爲該設備的irq指定對應的中斷處理例程,把irq號和驅動定義ISR關聯。

pcibios_enable_device

該接口用於使能PCI設備INT#模式的中斷。其主要功能由pcibios_enable_irq(dev)完成,pcibios_enable_irq是一個函數指針,對於ACPI模式,其在上電過程中被設置爲acpi_pci_irq_enable,其它情況被設置爲pirq_enable_irq。

對ACPI模式,其執行過程爲:
1、 acpi_pci_irq_enable:其先根據設備的管腳信息獲取一個GSI(可以認爲有了GSI,就有了irq號,gsi_to_irq可以完成其轉換),有了gsi/irq,要完成設置還必須有vector並且把它們關聯起來,因此如果GSI獲取成功,會使用acpi_register_gsi來完成後續操作。
2、 acpi_register_gsi:其主要功能由__acpi_register_gsi來完成,該函數指針在ACPI模式下被設置爲acpi_register_gsi_ioapic,acpi_register_gsi_ioapic的執行過程如下:
mp_register_gsi===>io_apic_set_pci_routing===>io_apic_set_pci_routing===>io_apic_setup_irq_pin_once===>io_apic_setup_irq_pin===>setup_ioapic_irq,在setup_ioapic_irq中,就會利用assign_irq_vector爲該irq選擇對應的執行CPU,並分配該CPU上的vector,同時還把該vector等配置寫入到I/O APIC對應管腳的RTE,從而完成整個中斷的配置。這樣在該INT#信號到來的時候,I/O APIC就能根據對應管腳的RTE,把該信號翻譯爲一個vector,並通過中斷消息發送到local APIC。同時在setup_ioapic_irq中,還通過ioapic_register_intr===>irq_set_chip_and_handler_name爲得到的irq號對應的irq_desc設置了->irq_data.chip和handle_irq函數指針(對level觸發的,爲handle_fasteoi_irq,否則爲handle_edge_irq)

對其它模式,其通過pcibios_lookup_irq完成執行:
在配置了I/O APIC的場景,pirq_enable_irq通過IO_APIC_get_PCI_irq_vector獲取到irq號,然後和ACPI模式一樣,通過io_apic_set_pci_routing完成對I/O APIC的配置。而對沒有配置I/O APIC的場景,主要通過pcibios_lookup_irq來完成相關操作:
1、 pcibios_lookup_irq通過讀取BIOS提供的中斷路由表 ($PIR表,irq_routing_table)信息和當前irq分配情況(pirq_penalty數組),在考慮均衡的前提下爲當前設備分配一個可用的irq。
2、 根據當前PIR的相關信息,決定最終的irq號選擇,相關代碼行如下

    if ((pirq & 0xf0) == 0xf0) {
        irq = pirq & 0xf;
        msg = "hardcoded";
    } else if (r->get && (irq = r->get(pirq_router_dev, dev, pirq)) && \ /*pirq_piix_get*/
    ((!(pci_probe & PCI_USE_PIRQ_MASK)) || ((1 << irq) & mask))) {
        msg = "found";
        eisa_set_level_irq(irq);
    } else if (newirq && r->set &&
        (dev->class >> 8) != PCI_CLASS_DISPLAY_VGA) {
        if (r->set(pirq_router_dev, dev, pirq, newirq)) {
            eisa_set_level_irq(newirq);
            msg = "assigned";
            irq = newirq;
        }
    }

也就是:如果是硬鏈接(INT#直接連接到了8259A,沒有經過PIR),直接獲取irq號,如果PIR中已經有該輸入線的配置,使用已有的值,否則利用剛剛分配的可用irq,並寫入到PIR,以便能夠完成中斷信號到irq號的轉換。
注意:
1、這裏的r,也就是pirq_router,代表一種PIR硬件,全局配置pirq_routers中描述了當前支持的PIR,並在初始化的時候通過pirq_find_router獲取了對應當前配置的PIR對應的描述。
2、這裏沒有分配vector,是因爲這裏使用的irq號範圍爲0-16,是ISA IRQs,其與vector的對應關係簡單:vector = IRQ0_VECTOR + irq,並在系統初始化過程中,已經通過early_irq_init中分配了irq_desc結構,通過init_IRQ設置了vector_irq(只運行於CPU0上),然後通過x86_init.irqs.intr_init(native_init_IRQ)===> x86_init.irqs.pre_vector_init(init_ISA_irqs)設置了->irq_data.chip(i8259A_chip)和handle_irq函數指針(handle_level_irq)。

Pci_enable_msix

該函數完成MSIX中斷相關的設置。

int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec)
{
    int status, nr_entries;
    int i, j;

    if (!entries)
        return -EINVAL;
    /*檢查MSIX中斷功能在當前設備上是否可用*/
    status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSIX);
    if (status)
        return status;
    /*該設備支持的MSI-X中斷數量*/
    nr_entries = pci_msix_table_size(dev);
    if (nvec > nr_entries)
        return nr_entries;

    /* Check for any invalid entries */
    for (i = 0; i < nvec; i++) {
        if (entries[i].entry >= nr_entries)
            return -EINVAL;     /* invalid entry */
        for (j = i + 1; j < nvec; j++) {
            if (entries[i].entry == entries[j].entry)
                return -EINVAL; /* duplicate entry */
        }
    }
    WARN_ON(!!dev->msix_enabled);

    /* Check whether driver already requested for MSI irq */
    if (dev->msi_enabled) {
        dev_info(&dev->dev, "can't enable MSI-X "
               "(MSI IRQ already assigned)\n");
        return -EINVAL;
    }
    /*關鍵函數:完成MSI-X中斷相關設置*/
    status = msix_capability_init(dev, entries, nvec);
    return status;
}       

msix_capability_init中實現中斷初始化的是arch_setup_msi_irqs,對於X86系統,其爲x86_setup_msi_irqs,x86_setup_msi_irqs中直接調用了native_setup_msi_irqs,該函數是X86系統中實現MSIX中斷初始化的關鍵函數,對於沒有啓用interrupt remap的系統,其實現如下:

int native_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
{
    node = dev_to_node(&dev->dev);
    /*nr_irqs_gsi是MSI-X的irq號起始地址,是按照IOAPIC引腳分配
    的最大irq號+1*/
    irq_want = nr_irqs_gsi;
    sub_handle = 0;
    list_for_each_entry(msidesc, &dev->msi_list, list) {
        /*按照全局配置分配irq號和對應的irq_desc,並
        在目標*/
        irq = create_irq_nr(irq_want, node);
        if (irq == 0)
            return -1;
        irq_want = irq + 1;
        if (!intr_remapping_enabled)/*對於沒有啓用interrupt remap的系統,intr_remapping_enabled=0*/
            goto no_ir;
no_ir:
        /*使能MSIX中斷,把中斷信息(關鍵的vector)
        寫入PCIE配置區,設置irq_desc數據*/
        ret = setup_msi_irq(dev, msidesc, irq);
        if (ret < 0)
            goto error;
        sub_handle++;
    }
    return 0;
error:
    destroy_irq(irq);
    return ret;
}

該函數中有兩個關鍵函數,分別是create_irq_nr和setup_msi_irq,其中create_irq_nr是分配一個vector給當前的中斷,分配vector的同時,也爲該中斷指定了執行CPU。setup_msi_irq則負責把相關配置信息寫入到PCIE配置區,並設置irq_desc的數據,其中關鍵的是irq_desc的handle_irq被設置爲handle_edge_irq。
create_irq_nr的實現如下:

unsigned int create_irq_nr(unsigned int from, int node)
{
    if (from < nr_irqs_gsi)
        from = nr_irqs_gsi;
    /*從全局bitmap中查找一個沒有用的irq號,並分配和
    初始化一個對應的irq_desc*/
    irq = alloc_irq_from(from, node);
    if (irq < 0)
        return 0;
    cfg = alloc_irq_cfg(irq, node);
    if (!cfg) {
        free_irq_at(irq, NULL);
        return 0;
    }
    raw_spin_lock_irqsave(&vector_lock, flags);
    /*關鍵函數:負責分配一個vector,並和irq號關聯
    其中的"apic"是根據系統配置在系統加載過程中
    初始化,在當前系統爲apic_physflat*/
    if (!__assign_irq_vector(irq, cfg, apic->target_cpus()))
        ret = irq;
    raw_spin_unlock_irqrestore(&vector_lock, flags);

    if (ret) {
        irq_set_chip_data(irq, cfg);
        irq_clear_status_flags(irq, IRQ_NOREQUEST);
    } else {
        free_irq_at(irq, cfg);
    }
    return ret;
}

其中__assign_irq_vector負責分配vector,並和中斷在CPU上的調度相關,其實現如下

/*@mask:該vector可以分配在哪些CPU上*/
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 % 8;
    unsigned int old_vector;
    int cpu, err;
    cpumask_var_t tmp_mask;

    if (cfg->move_in_progress)
        return -EBUSY;

    if (!alloc_cpumask_var(&tmp_mask, GFP_ATOMIC))
        return -ENOMEM;

    old_vector = cfg->vector;
    if (old_vector) {/*如果已經存在滿足CPU綁定要求的vector,不
        多次分配*/
        cpumask_and(tmp_mask, mask, cpu_online_mask);
        cpumask_and(tmp_mask, cfg->domain, tmp_mask);
        if (!cpumask_empty(tmp_mask)) {
            free_cpumask_var(tmp_mask);
            return 0;
        }
    }

    /* Only try and allocate irqs on cpus that are present */
    err = -ENOSPC;
    for_each_cpu_and(cpu, mask, cpu_online_mask) {
        int new_cpu;
        int vector, offset;
        /*獲取當前APIC配置要求的中斷運行cpu,大部分
        (包括當前系統apic_physflat)爲tmp_mask就對應變量'cpu'*/
        apic->vector_allocation_domain(cpu, tmp_mask);

        vector = current_vector;
        offset = current_offset;
next:
        vector += 8;
        if (vector >= first_system_vector) {
            /* If out of vectors on large boxen, must share them. */
            offset = (offset + 1) % 8;
            vector = FIRST_EXTERNAL_VECTOR + offset;
        }
        if (unlikely(current_vector == vector))
            continue;
        /*如果是系統預留的vector,不可使用*/
        if (test_bit(vector, used_vectors))
            goto next;
        /*如果apic要求的任何cpu核心上該vector已經被佔用,則
        該vector不可用*/
        for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask)
            if (per_cpu(vector_irq, new_cpu)[vector] != -1)
                goto next;
        /* Found one! */
        current_vector = vector;
        current_offset = offset;
        if (old_vector) {
            cfg->move_in_progress = 1;
            cpumask_copy(cfg->old_domain, cfg->domain);
        }
        /*關鍵步驟:對當前apic配置的可能運行該中斷的所有CPU
        配置其vector_irq,以便中斷髮生時可以通過vector查找到
        irq號*/
        for_each_cpu_and(new_cpu, tmp_mask, cpu_online_mask)
            per_cpu(vector_irq, new_cpu)[vector] = irq;
        cfg->vector = vector;
        cpumask_copy(cfg->domain, tmp_mask);
        err = 0;
        break;
    }
    free_cpumask_var(tmp_mask);
    return err;
}

從實現中可以看到,該函數從FIRST_EXTERNAL_VECTOR(外設中斷的起始vector號,通常是0x20) 到first_system_vector(外部中斷結束vector號,通常是254,255被系統作爲保留的SPURIOUS_APIC_VECTOR使用)的範圍中,爲當前中斷分配一個vector,要求該vector在對應的cpu上均可用,該vector按照系統配置的要求和對應的cpu核心綁定,並在要求的cpu中沒有被其它中斷使用。需要說明的是,在setup_msi_irq中會再次通過msi_compose_msg再次調用__assign_irq_vector,但是由於這時已經存在滿足CPU綁定要求的vector,不會多次分配。

從以上分析可以得到MSI-X中斷的一個綁定特徵:根據當前APIC配置,每個中斷都有對應的可以運行的cpu,pci_enable_msix在這些要求的cpu核心上建立了vector (APIC的配置由數據結構struct apic來抽象,其vector_allocation_domain用於決定需要在那些cpu核心上爲該中斷建立vector),當前我的系統使用的是apic_physflat,對每個MSI中斷,其只在一個cpu核心上建立vector,對應的MSI-X中斷事實上被綁定到該cpu核心上。在用戶通過echo xxx > /proc/irq/xxx/affinity來調整中斷的綁定屬性時,內核會重新爲該中斷分配一個新的在對應核心上可用的vector,但是irq號不會改變。綁定屬性調整的調用路徑大致爲irq_affinity_proc_fops===>irq_affinity_proc_write===> write_irq_affinity===>irq_set_affinity===>__irq_set_affinity_locked===>chip->irq_set_affinity(msi_set_affinity)。也就是最終通過msi_set_affinity來實現,在該函數中首先通過 __ioapic_set_affinity在綁定屬性要求的cpu中選擇空閒vector,然後通過__write_msi_msg把配置寫入PCIE配置區。需要說明的是:該irq最終可以運行的cpu數量並不完全由用戶指定,還與apic的模式相關,對於apic_physflat,實際上只爲該irq分配了一個cpu核心,該irq只能運行在用戶指定的cpu中的一個,而不是全部。

附:關於全局變量apic

該全局變量爲local apic的抽象,在不同的系統配置下,有不同的選擇,其最終的選擇結果,由內核的config(反應在/arch/x86/kernel/apic/Makefile)和硬件配置等來決定。
1、 定義各種apic driver
首先,每種apic配置都會使用apic_driver/ apic_drivers來定義,apic_driver的定義如下

#define apic_driver(sym)                    \
    static struct apic *__apicdrivers_##sym __used      \
    __aligned(sizeof(struct apic *))            \
    __section(.apicdrivers) = { &sym }

這個定義的目的是把sym的地址寫入到名爲” .apicdrivers”的段中。
2、 定義全局符號__apicdrivers和__apicdrivers_end
在linker script vmlinux.lds.S中,定義了__apicdrivers爲” .apicdrivers”段的開始地址,而__apicdrivers_end爲結束地址。” .apicdrivers”段中是各個不同的apic配置對應的struct apic。

    . = ALIGN(8);
    .apicdrivers : AT(ADDR(.apicdrivers) - LOAD_OFFSET) {
        __apicdrivers = .;
        *(.apicdrivers);
        __apicdrivers_end = .;
    }

3、 apic的probe
在初始化過程(start_kernel)中,會調用default_setup_apic_routing(probe_64.c中定義)來完成apic的probe,該函數會按照各個struct apic結構在.apicdrivers中的順序,依次調用其probe接口,第一個調用返回非0的struct apic結構就被初始化到全局變量apic。也就是:如果有多個apic結構可用,最終會選擇在.apicdrivers段中出現的第一個;所以makefile文件中各個.o出現的順序也會覺得最終的apic probe結果。

request_irq

該函數把irq和用戶指定的中斷處理函數關聯。用戶指定的每個處理函數對應於一個struct irqaction結構,這些處理函數構成一個鏈表,保存在struct irq_desc::action成員中。詳細見request_irq===>request_threaded_irq中的處理。

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