相關概念和關鍵數據結構
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中的處理。