以下主要看了 linux 3.2 中,從 start_kernel() 開始的一些跟中斷有關的初始化代碼,並做了一點點簡單的分析。start_kernel() 在 init/main.c 中,其中和中斷有關的大概就有這樣一些函數:
/* filename: init/main.c */
467 asmlinkage void __init start_kernel(void)
468 {
... ...
488 local_irq_disable();
... ...
499 setup_arch(&command_line);
... ...
526 trap_init();
... ...
550 early_irq_init();
551 init_IRQ();
... ...
564 local_irq_enable();
在進行各種初始化之前,要先關閉中斷,因爲這個時候調度程序等還沒有初始化,如果發生中斷,進行進程切換時會出現問題(?)。所以會調用 local_irq_disable() 來關閉中斷。local_irq_disable() 定義在:
/* filename: include/linux/irqflags.h */
59 #define raw_local_irq_disable() arch_local_irq_disable()
137 #define local_irq_disable() do { raw_local_irq_disable(); } while (0)
可以看出,不同的架構,其具體的 local_irq_disable() 實現各不相同。但都由 arch_local_irq_disable() 來統一調用。對於 x86_32 來說,arch_local_irq_disable() 的實現如下:
/* filename: arch/x86/include/asm/irqflags.h */
37 static inline void native_irq_disable(void)
38 {
39 asm volatile("cli": : :"memory");
40 }
75 static inline notrace void arch_local_irq_disable(void)
76 {
77 native_irq_disable();
78 }
這裏用內聯彙編的形式,直接調用 cli 指令來清除 EFLAGS 寄存器的中斷允許位,以關閉中斷。
Linux 內核在初始化階段完成了對頁式虛擬內存管理的初始化後,便調用 trap_init() 和 init_IRQ() 兩個函數進行中斷機制的初始化。trap_init() 主要是對一些系統保留的中斷向量進行初始化。而 init_IRQ() 則主要用於外設的中斷。函數 trap_init() 是在 arch/x86/kernel/traps.c 中定義的。
/* filename: arch/x86/kernel/traps.c */
669 void __init trap_init(void)
670 {
671 int i;
672
673 #ifdef CONFIG_EISA
674 void __iomem *p = early_ioremap(0x0FFFD9, 4);
675
676 if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
677 EISA_bus = 1;
678 early_iounmap(p, 4);
679 #endif
680
681 set_intr_gate(0, ÷_error);
682 set_intr_gate_ist(2, &nmi, NMI_STACK);
683 /* int4 can be called from all */
684 set_system_intr_gate(4, &overflow);
685 set_intr_gate(5, &bounds);
686 set_intr_gate(6, &invalid_op);
687 set_intr_gate(7, &device_not_available);
688 #ifdef CONFIG_X86_32
689 set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
690 #else
691 set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);
692 #endif
693 set_intr_gate(9, &coprocessor_segment_overrun);
694 set_intr_gate(10, &invalid_TSS);
695 set_intr_gate(11, &segment_not_present);
696 set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);
697 set_intr_gate(13, &general_protection);
698 set_intr_gate(15, &spurious_interrupt_bug);
699 set_intr_gate(16, &coprocessor_error);
700 set_intr_gate(17, &alignment_check);
701 #ifdef CONFIG_X86_MCE
702 set_intr_gate_ist(18, &machine_check, MCE_STACK);
703 #endif
704 set_intr_gate(19, &simd_coprocessor_error);
705
706 /* Reserve all the builtin and the syscall vector: */
707 for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
708 set_bit(i, used_vectors);
709
710 #ifdef CONFIG_IA32_EMULATION
711 set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
712 set_bit(IA32_SYSCALL_VECTOR, used_vectors);
713 #endif
714
715 #ifdef CONFIG_X86_32
716 set_system_trap_gate(SYSCALL_VECTOR, &system_call);
717 set_bit(SYSCALL_VECTOR, used_vectors);
718 #endif
719
720 /*
721 * Should be a barrier for any external CPU state:
722 */
723 cpu_init();
724
725 x86_init.irqs.trap_init();
726 }
函數中先設置中斷向量表開頭的 17 個門,這些中斷向量都是 CPU 保留用於異常處理的。用於 debug 的 1 號門,int3 的 3 號門,和用於處理頁面異常的 14 號門不在這個函數裏初始化,而是在 early_trap_init() 中初始化的,這是爲了能夠方便於內核啓動早期的 debug 而設置的。early_trap_init() 是在 setup_arch() 中調用的。因此在 trap_init() 以後,系統的 20 個保留的中斷向量都已經設置好了。系統中有一個記錄哪些中斷向量被設置過的位圖 used_vectors,707-708 行就是用於設置這個位圖的相應位。其中,FIRST_EXTERNAL_VECTOR 定義在 arch/x86/include/asm/irq_vectors.h,值爲 0x20,也就是說,在 IDT 中,所有的外部中斷都是從 0x20 開始的,除了 0x80。716 行有個 SYSCALL_VECTOR,定義在 arch/x86/include/asm/irq_vectors.h,值爲 0x80,也就是系統調用 int 0x80 的入口。
x86_init 是一個 x86_init_ops 的結構體。x86_init_ops 定義了一些用於 x86 平臺的專用初始化函數。
/* filename: arch/x86/include/asm/x86_init.h */
132 struct x86_init_ops {
133 struct x86_init_resources resources;
134 struct x86_init_mpparse mpparse;
135 struct x86_init_irqs irqs;
136 struct x86_init_oem oem;
137 struct x86_init_mapping mapping;
138 struct x86_init_paging paging;
139 struct x86_init_timers timers;
140 struct x86_init_iommu iommu;
141 struct x86_init_pci pci;
142 };
這個結構中,我們暫時只關心 irqs 成員,它是一個 x86_init_irqs 結構體,該結構體是用於定義 x86 平臺的一些和中斷相關的函數。
/* filename: arch/x86/include/asm/x86_init.h */
47 /**
48 * struct x86_init_irqs - platform specific interrupt setup
49 * @pre_vector_init: init code to run before interrupt vectors
50 * are set up.
51 * @intr_init: interrupt init code
52 * @trap_init: platform specific trap setup
53 */
54 struct x86_init_irqs {
55 void (*pre_vector_init)(void);
56 void (*intr_init)(void);
57 void (*trap_init)(void);
58 };
在 x86_init 這個變量中,irqs.trap_init 被初始化爲 x86_init_noop。
/* filename: arch/x86/kernel/x86_init.c */
37 struct x86_init_ops x86_init __initdata = {
... ...
55 .irqs = {
56 .pre_vector_init = init_ISA_irqs,
57 .intr_init = native_init_IRQ,
58 .trap_init = x86_init_noop,
59 },
... ...
91 };
而事實上,x86_init_noop() 是一個空函數,和 x86_init 定義在同一個 C 文件中。這樣的空函數應該是爲某些特殊的基於 x86 架構的平臺預留的,如果某些平臺需要保留自己的中斷向量,就需要去填充這些空函數。
現在回到 trap_init() 函數中,其中最重要的幾個函數是:set_intr_gate,set_system_intr_gate,set_intr_gate_ist 和 set_system_trap_gate。這幾個 set 系列的函數有相同之處,最終都是通過調用 _set_gate() 來完成設置 IDT 的。
/* filename: arch/x86/include/asm/desc.h */
310 static inline void _set_gate(int gate, unsigned type, void *addr,
311 unsigned dpl, unsigned ist, unsigned seg)
312 {
313 gate_desc s;
314
315 pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
316 /*
317 * does not need to be atomic because it is only done once at
318 * setup time
319 */
320 write_idt_entry(idt_table, gate, &s);
321 }
329 static inline void set_intr_gate(unsigned int n, void *addr)
330 {
331 BUG_ON((unsigned)n > 0xFF);
332 _set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS);
333 }
359 static inline void set_system_intr_gate(unsigned int n, void *addr)
360 {
361 BUG_ON((unsigned)n > 0xFF);
362 _set_gate(n, GATE_INTERRUPT, addr, 0x3, 0, __KERNEL_CS);
363 }
364
365 static inline void set_system_trap_gate(unsigned int n, void *addr)
366 {
367 BUG_ON((unsigned)n > 0xFF);
368 _set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS);
369 }
383 static inline void set_intr_gate_ist(int n, void *addr, unsigned ist)
384 {
385 BUG_ON((unsigned)n > 0xFF);
386 _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS);
387 }
_set_gate() 有幾個參數,第一個 gate 描述的是 idt_table 中的第幾個入口項;第二個 type 是這個門的類型,中斷門 (110)、陷井門 (111)、調用門 (100)或者是任務門 (101);第三個 addr 是對應的中斷處理程序的入口地址;第四個 dpl 是 Descriptor Privilege Level,0 爲內核空間,3 爲用戶空間;第五個 ist 是 Interrupt Stack Table,共 3 位,表示 ist0 ~ ist7;第六個 seg,是內核段選擇子的值。在幾個 set 函數中,type 的值都被定義爲宏,這些宏定義在 arch/x86/include/asm/desc_defs.h 中,分別爲:GATE_INTERRUPT = 0xE,0xE = b110,表示中斷門;GATE_TRAP = 0xF,0xF = b111,表示陷井門。_set_gate() 中,先定義了一個 gate_desc 類型的變量 s,這個 s 是要寫入 idt_table 的門描述符的地址。gate_desc 是一個 desc_struct 的結構體:
/* filename: arch/x86/include/asm/desc_defs.h */
22 struct desc_struct {
23 union {
24 struct {
25 unsigned int a;
26 unsigned int b;
27 };
28 struct {
29 u16 limit0;
30 u16 base0;
31 unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
32 unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
33 };
34 };
35 } __attribute__((packed));
在 315 行的 pack_gate() 函數中,只用到了聯合體的第一個元素,所以這裏只按照使用到的情況來介紹。聯合體的第一個元素用來表示一個 64 位的數據,其中 a 表示該地址的低 32 位;b 表示高 32 位。
/* filename: arch/x86/include/asm/desc.h */
68 static inline void pack_gate(gate_desc *gate, unsigned char type,
69 unsigned long base, unsigned dpl, unsigned flags,
70 unsigned short seg)
71 {
72 gate->a = (seg << 16) | (base & 0xffff);
73 gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8);
74 }
以 set_system_intr_gate() 爲例,這是對系統調用的門的設置,因爲系統調用是用戶程序調用的,因此係統調用發生在用戶空間,進而需要切換進入內核空間,所以系統調用的 dpl 值爲 0x3,這樣可以使得系統調用發生時,檢查權限等級的時候,用戶程序的權限等級不會比系統調用門的 dpl 等級低。另外,從之前的代碼中可以看到,seg 的值是 __KERNEL_CS,這是內核代碼段的地址,事實上,在保護模式下,__KERNEL_CS 是內核代碼段在 GDT 中的選擇子,其值爲 0x10。(base & 0xffff) 可以得到函數地址的低 16 位,假設爲 0xllll。那麼 72 行計算後得到的 gate->a 的值轉換爲二進制應該是 1 0000 llll llll llll llll。(base & 0xffff0000) 自然就得到函數地址的高 16 位,假設爲 0xhhhh,同時,(((0x80|type|(dpl<<5))&0xff)<<8) 的二進制結果爲 1110 0110 0000 0000。則 73 行計算後得到的 gate->b 的值轉換爲二進制應該是 hhhh hhhh hhhh hhhh 1110 0110 0000 0000。gate->b 和 gate->a 則是當前這個門描述符的高 32 位和低 32 位,組成了根據中斷門 (64 位) 和陷井門 (64 位) 門描述符的格式定義如下:
因此 pack_gate() 函數就是通過所提供的這些參數,構造出一箇中斷門描述符,並把這個中斷門描述符保存到臨時變量 s 中。接着 320 行調用 write_idt_entry() 把這個新產生的中斷門描述符寫入 idt_table 中的相應位置。
/* filename: arch/x86/include/asm/desc.h */
103 #define write_idt_entry(dt, entry, g) native_write_idt_entry(dt, entry, g)
116 static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
117 {
118 memcpy(&idt[entry], gate, sizeof(*gate));
119 }
write_idt_entry() 是一個宏定義,其原型爲 native_write_idt_entry()。320 行 write_idt_entry() 的第一個參數 idt_table 是一個全局的 gate_desc 類型的數組,定義在 arch/x86/kernel/traps.c 中,這個數組的大小爲 NR_VECTORS,即 256。native_write_idt_entry() 調用 memcpy() 將剛創建的 s 保存到 idt_table 的指定位置,這個指定位置是距 idt_table 基地址的一個偏移量,由 set_system_intr_gate() 中的第一個參數 n 給出。
當配置或沒配置 CONFIG_SPARSE_IRQ 時,early_irq_init() 函數實現形式不同。CONFIG_SPARSE_IRQ配置項,用於支持稀疏irq號,對於發行版的內核很有用,它允許定義一個高CONFIG_NR_CPUS值,但仍然不希望消耗太多內存的情況。如果配置了這個選項,那麼 irq_desc 數組就有一個稀疏的佈局,可以更普遍地來設置 irq_desc 數組的大小,一般是根據 CPU 的個數、IO-APIC 的個數來線性的擴展。而不配置該選項時,就必須小心的對待數組的大小,避免創建了過於大的靜態數組(?)。我們就按照不配置
CONFIG_SPARSE_IRQ 的情況接着往下看了:
/* filename: kernel/irq/irqdesc.c */
240 #else /* !CONFIG_SPARSE_IRQ */
241
242 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
243 [0 ... NR_IRQS-1] = {
244 .handle_irq = handle_bad_irq,
245 .depth = 1,
246 .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
247 }
248 };
249
250 int __init early_irq_init(void)
251 {
252 int count, i, node = first_online_node;
253 struct irq_desc *desc;
254
255 init_irq_default_affinity();
256
257 printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
258
259 desc = irq_desc;
260 count = ARRAY_SIZE(irq_desc);
261
262 for (i = 0; i < count; i++) {
263 desc[i].kstat_irqs = alloc_percpu(unsigned int);
264 alloc_masks(&desc[i], GFP_KERNEL, node);
265 raw_spin_lock_init(&desc[i].lock);
266 lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
267 desc_set_defaults(i, &desc[i], node, NULL);
268 }
269 return arch_early_irq_init();
270 }
first_online_node???對於單處理器來說,init_irq_default_affinity() 爲空函數。接着打印出中斷個數的信息,中斷個數爲 NR_IRQS,它定義在 arch/x86/include/asm/irq_vectors.h 中,在沒有使用 IO_APIC 的情況下,NR_IRQS 的值等於 NR_IRQ_LEGACY,爲 16,這裏的 16 也就是兩片 8259A 級聯可以允許的最大中斷請求數。當然,如果使用了 APIC,中斷請求的數量肯定會遠遠大於 16,不過這裏還是按簡單的過程來討論。early_irq_init() 中定義了一個 irq_desc 結構的指針 desc,指向 242 行的 irq_desc 數組的開頭。irq_desc 是描述中斷的結構體,定義如下:
/* filename: include/linux/irqdesc.h */
15 /**
16 * struct irq_desc - interrupt descriptor
17 * @irq_data: per irq and chip data passed down to chip functions
18 * @timer_rand_state: pointer to timer rand state struct
19 * @kstat_irqs: irq stats per cpu
20 * @handle_irq: highlevel irq-events handler
21 * @preflow_handler: handler called before the flow handler (currently used by sparc)
22 * @action: the irq action chain
23 * @status: status information
24 * @core_internal_state__do_not_mess_with_it: core internal status information
25 * @depth: disable-depth, for nested irq_disable() calls
26 * @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
27 * @irq_count: stats field to detect stalled irqs
28 * @last_unhandled: aging timer for unhandled count
29 * @irqs_unhandled: stats field for spurious unhandled interrupts
30 * @lock: locking for SMP
31 * @affinity_hint: hint to user space for preferred irq affinity
32 * @affinity_notify: context for notification of affinity changes
33 * @pending_mask: pending rebalanced interrupts
34 * @threads_oneshot: bitfield to handle shared oneshot threads
35 * @threads_active: number of irqaction threads currently running
36 * @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
37 * @dir: /proc/irq/ procfs entry
38 * @name: flow handler name for /proc/interrupts output
39 */
40 struct irq_desc {
41 struct irq_data irq_data;
42 struct timer_rand_state *timer_rand_state;
43 unsigned int __percpu *kstat_irqs;
44 irq_flow_handler_t handle_irq;
45 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
46 irq_preflow_handler_t preflow_handler;
47 #endif
48 struct irqaction *action; /* IRQ action list */
49 unsigned int status_use_accessors;
50 unsigned int core_internal_state__do_not_mess_with_it;
51 unsigned int depth; /* nested irq disables */
52 unsigned int wake_depth; /* nested wake enables */
53 unsigned int irq_count; /* For detecting broken IRQs */
54 unsigned long last_unhandled; /* Aging timer for unhandled count */
55 unsigned int irqs_unhandled;
56 raw_spinlock_t lock;
57 struct cpumask *percpu_enabled;
58 #ifdef CONFIG_SMP
59 const struct cpumask *affinity_hint;
60 struct irq_affinity_notify *affinity_notify;
61 #ifdef CONFIG_GENERIC_PENDING_IRQ
62 cpumask_var_t pending_mask;
63 #endif
64 #endif
65 unsigned long threads_oneshot;
66 atomic_t threads_active;
67 wait_queue_head_t wait_for_threads;
68 #ifdef CONFIG_PROC_FS
69 struct proc_dir_entry *dir;
70 #endif
71 struct module *owner;
72 const char *name;
73 } ____cacheline_internodealigned_in_smp;
start_kernel() 中 551 行是 init_IRQ():
/* filename: arch/x86/kernel/irqinit.c */
119 void __init init_IRQ(void)
120 {
121 int i;
122
123 /*
124 * We probably need a better place for this, but it works for
125 * now ...
126 */
127 x86_add_irq_domains();
128
129 /*
130 * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
131 * If these IRQ's are handled by legacy interrupt-controllers like PIC,
132 * then this configuration will likely be static after the boot. If
133 * these IRQ's are handled by more mordern controllers like IO-APIC,
134 * then this vector space can be freed and re-used dynamically as the
135 * irq's migrate etc.
136 */
137 for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
138 per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;
139
140 x86_init.irqs.intr_init();
141 }
對於不支持 IO_APIC 的內核來說,x86_add_irq_domains() 是一個空函數。137 行中,legacy_pic 是一個描述 8259A 芯片的結構體,主要定義了一些操作:
/* filename: arch/x86/include/asm/i8259.h */
55 struct legacy_pic {
56 int nr_legacy_irqs;
57 struct irq_chip *chip;
58 void (*mask)(unsigned int irq);
59 void (*unmask)(unsigned int irq);
60 void (*mask_all)(void);
61 void (*restore_mask)(void);
62 void (*init)(int auto_eoi);
63 int (*irq_pending)(unsigned int irq);
64 void (*make_irq)(unsigned int irq);
65 };
legacy_pic 在 arch/x86/kernel/i8259.c 中被初始化爲 default_legacy_pic。其中,nr_cpu_ids 被初始化爲 NR_IRQS_LEGACY,即 16。per_cpu() 是一個宏定義,在非 SMP 的系統中:
/* filename: include/asm-generic/percpu.h */
77 #define VERIFY_PERCPU_PTR(__p) ({ \
78 __verify_pcpu_ptr((__p)); \
79 (typeof(*(__p)) __kernel __force *)(__p); \
80 })
81
82 #define per_cpu(var, cpu) (*((void)(cpu), VERIFY_PERCPU_PTR(&(var))))
per_cpu(var, cpu) 獲取編號 cpu 的處理器上面的變量 var 的副本(參考:http://blog.csdn.net/fengtaocat/article/details/7078472)。138 行可以簡單的看作 vector_irq[IRQ0_VECTOR + i] = i;。vector_irq 是一個長度爲 NR_VECTORS (256) 的整形數組,每個數組元素的值都初始化爲 -1。IRQ0_VECTOR 定義在 arch/x86/include/asm/irq_vectors.h 中,其值爲 0x30:
/* filename: arch/x86/include/asm/irq_vectors.h */
54 /*
55 * Vectors 0x30-0x3f are used for ISA interrupts.
56 * round up to the next 16-vector boundary
57 */
58 #define IRQ0_VECTOR ((FIRST_EXTERNAL_VECTOR + 16) & ~15)
59
60 #define IRQ1_VECTOR (IRQ0_VECTOR + 1)
61 #define IRQ2_VECTOR (IRQ0_VECTOR + 2)
62 #define IRQ3_VECTOR (IRQ0_VECTOR + 3)
63 #define IRQ4_VECTOR (IRQ0_VECTOR + 4)
64 #define IRQ5_VECTOR (IRQ0_VECTOR + 5)
65 #define IRQ6_VECTOR (IRQ0_VECTOR + 6)
66 #define IRQ7_VECTOR (IRQ0_VECTOR + 7)
67 #define IRQ8_VECTOR (IRQ0_VECTOR + 8)
68 #define IRQ9_VECTOR (IRQ0_VECTOR + 9)
69 #define IRQ10_VECTOR (IRQ0_VECTOR + 10)
70 #define IRQ11_VECTOR (IRQ0_VECTOR + 11)
71 #define IRQ12_VECTOR (IRQ0_VECTOR + 12)
72 #define IRQ13_VECTOR (IRQ0_VECTOR + 13)
73 #define IRQ14_VECTOR (IRQ0_VECTOR + 14)
74 #define IRQ15_VECTOR (IRQ0_VECTOR + 15)
因此,137 ~ 138 行的 for 循環就是把 vector_irq 數組中,第 0x30 ~ 0x3f 項的值設置爲 0 ~ 15。這是爲了???接着 140 行 intr_init() 是對中斷的初始化。intr_init() 函數的原型是 native_init_IRQ。
/* filename: arch/x86/kernel/irqinit.c */
295 void __init native_init_IRQ(void)
296 {
297 int i;
298
299 /* Execute any quirks before the call gates are initialised: */
300 x86_init.irqs.pre_vector_init();
301
302 apic_intr_init();
303
304 /*
305 * Cover the whole vector space, no vector can escape
306 * us. (some of these will be overridden and become
307 * 'special' SMP interrupts)
308 */
309 for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
310 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
311 if (!test_bit(i, used_vectors))
312 set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
313 }
314
315 if (!acpi_ioapic && !of_ioapic)
316 setup_irq(2, &irq2);
317
318 #ifdef CONFIG_X86_32
319 /*
320 * External FPU? Set up irq13 if so, for
321 * original braindamaged IBM FERR coupling.
322 */
323 if (boot_cpu_data.hard_math && !cpu_has_fpu)
324 setup_irq(FPU_IRQ, &fpu_irq);
325
326 irq_ctx_init(smp_processor_id());
327 #endif
328 }
在這些調用門初始化之前,需要先做一些預先的準備,即 pre_vector_init()。pre_vector_init() 的原型是 init_ISA_irqs():
/* filename: arch/x86/kernel/irqinit.c */
104 void __init init_ISA_irqs(void)
105 {
106 struct irq_chip *chip = legacy_pic->chip;
107 const char *name = chip->name;
108 int i;
109
110 #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
111 init_bsp_APIC();
112 #endif
113 legacy_pic->init(0);
114
115 for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
116 irq_set_chip_and_handler_name(i, chip, handle_level_irq, name);
117 }
106 和 107 行首先取得系統中 8259A 芯片的描述結構體和芯片的名字。legacy_pic 是一個描述傳統 8259A 芯片的結構體,被初始化爲 default_legacy_pic。
/* filename: arch/x86/kernel/i8259.c */
223 struct irq_chip i8259A_chip = {
224 .name = "XT-PIC",
225 .irq_mask = disable_8259A_irq,
226 .irq_disable = disable_8259A_irq,
227 .irq_unmask = enable_8259A_irq,
228 .irq_mask_ack = mask_and_ack_8259A,
229 };
380 struct legacy_pic default_legacy_pic = {
381 .nr_legacy_irqs = NR_IRQS_LEGACY,
382 .chip = &i8259A_chip,
383 .mask = mask_8259A_irq,
384 .unmask = unmask_8259A_irq,
385 .mask_all = mask_8259A,
386 .restore_mask = unmask_8259A,
387 .init = init_8259A,
388 .irq_pending = i8259A_irq_pending,
389 .make_irq = make_8259A_irq,
390 };
391
392 struct legacy_pic *legacy_pic = &default_legacy_pic;
那麼這裏的 chip 和 name 則分別被初始化爲 i8259A_chip 和 "XT-PIC"。113 行 legacy_pic->init() 將初始化 8259A 芯片。
/* filename: arch/x86/kernel/i8259.c */
300 static void init_8259A(int auto_eoi)
301 {
302 unsigned long flags;
303
304 i8259A_auto_eoi = auto_eoi;
305
306 raw_spin_lock_irqsave(&i8259A_lock, flags);
307
308 outb(0xff, PIC_MASTER_IMR); /* mask all of 8259A-1 */
309 outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */
310
311 /*
312 * outb_pic - this has to work on a wide range of PC hardware.
313 */
314 outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */
315
316 /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64,
317 to 0x20-0x27 on i386 */
318 outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR);
319
320 /* 8259A-1 (the master) has a slave on IR2 */
321 outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR);
322
323 if (auto_eoi) /* master does Auto EOI */
324 outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
325 else /* master expects normal EOI */
326 outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
327
328 outb_pic(0x11, PIC_SLAVE_CMD); /* ICW1: select 8259A-2 init */
329
330 /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */
331 outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR);
332 /* 8259A-2 is a slave on master's IR2 */
333 outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);
334 /* (slave's support for AEOI in flat mode is to be investigated) */
335 outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);
336
337 if (auto_eoi)
338 /*
339 * In AEOI mode we just have to mask the interrupt
340 * when acking.
341 */
342 i8259A_chip.irq_mask_ack = disable_8259A_irq;
343 else
344 i8259A_chip.irq_mask_ack = mask_and_ack_8259A;
345
346 udelay(100); /* wait for 8259A to initialize */
347
348 outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */
349 outb(cached_slave_mask, PIC_SLAVE_IMR); /* restore slave IRQ mask */
350
351 raw_spin_unlock_irqrestore(&i8259A_lock, flags);
352 }
/* filename: arch/x86/include/asm/i8259.h */
12 /* i8259A PIC registers */
13 #define PIC_MASTER_CMD 0x20
14 #define PIC_MASTER_IMR 0x21
15 #define PIC_MASTER_ISR PIC_MASTER_CMD
16 #define PIC_MASTER_POLL PIC_MASTER_ISR
17 #define PIC_MASTER_OCW3 PIC_MASTER_ISR
18 #define PIC_SLAVE_CMD 0xa0
19 #define PIC_SLAVE_IMR 0xa1
20
21 /* i8259A PIC related value */
22 #define PIC_CASCADE_IR 2
23 #define MASTER_ICW4_DEFAULT 0x01
24 #define SLAVE_ICW4_DEFAULT 0x01
25 #define PIC_ICW4_AEOI 2
8259A 初始化期間是不允許使用的,所以要先加鎖。主片 8259A 的端口是 20H 和 21H,從片 8259A 的端口是 a0H 和 a1H。按照 8259A 的編程方式,先使用 OCW1 向兩個級聯的芯片寫屏蔽碼。OCW1 表示對該片 8259A 上相應中斷的屏蔽與否。308 和 309 兩行先屏蔽主從兩片 8259A 的所有 16 箇中斷請求端口。接着對芯片進行初始化操作,初始化中斷的過程是固定的。314 行使用 ICW1 向主片寫初始化命令 0x11,即 0001 0001,意爲 bit4 表示 ICW1 的特徵位;bit3 表示邊沿出發;bit1 表示多片級聯;bit0表示要寫 ICW4。318 行寫 ICW2,IRQ0_VECTOR (0x30,0011 0000),bit7 ~ bit3 表示中斷號的高 5 位;餘下的位爲 0。因爲有級聯,所以 321 行接着寫 ICW3,PIC_CASCADE_IR 值爲 2,則 321 行表示主片的 IR2 上有從片連接。最後 326 行寫 ICW4,MASTER_ICW4_DEFAULT 的值爲 0x1,bit4 表示爲一般嵌套;bit3 ~ bit2 表示爲非緩衝方式;bit1 表示爲自動結束中斷;bit0 表示爲 16 位系統。接着 328 ~ 335 初始化從片,和初始化主片的過程一樣。由於 ICW4 設置了自動結束中斷方式,所以中斷響應時,8259A 會自動將 ISR 對應位清除,所以需要將 8259A 描述結構體的 irq_mask_ack 設置爲 mask_and_ack_8259A 來屏蔽相應位的中斷嵌套。(參考《32 位微型計算機接口技術及應用》P137 ~ P144)。接下來的兩個變量 cached_master_mask 和 cached_slave_mask 的設置也很巧妙:
/* filename: arch/x86/include/asm/i8259.h */
8 #define __byte(x, y) (((unsigned char *)&(y))[x])
9 #define cached_master_mask (__byte(0, cached_irq_mask))
10 #define cached_slave_mask (__byte(1, cached_irq_mask))
cached_irq_mask 是一個定義在 arch/x86/kernel/i8259.c 中的無符號整形數,初值爲 0xffff,其每一位代表了兩片 8259A 芯片的 16 箇中斷的使能狀態。在 __byte() 這個宏中,&(cached_irq_mask) 是一個無符號整形值的地址,現在把它強制類型轉換爲一個字符類型的地址,則 cached_irq_mask 就表示一個字符數組。因此,字符數組的第 0 個元素就是 cached_irq_mask 的低 8 位;而第 1 個元素就是高 8 位。這樣來表示兩片 8259A 中斷。最後把 cached_master_mask 和 cached_slave_mask 寫回,以使能相應的中斷。
init_ISA_irqs() 中 115 ~ 116 行爲每一個 IRQ 設置一個 IRQ 芯片和相應的處理函數,這裏的 handle_level_irq 被設置在對應的中斷描述符結構的 handle_irq 變量中。
當所有的設置都完成後,最後 local_irq_enable() 根據不同的架構,打開中斷控制器的中斷,在 x86 中使用 sti 指令來開啓中斷。