设置APIC中断服务
2019-04-15 17:15发布
生成海报
5.10 初始化中断处理系统
start_kernel接下来要做的事是初始化中断处理系统。整个内核的中断系统的核心就是我们在“初始化中断描述符表”里面设置的那个中断描述符表。而这个表的前19个表项我们已经在“初始化异常服务”中设置为了一些中断和异常的服务。内核接下来会如何设置其余的表项呢?
前面提到过高级可编程中断控制器APIC,这里简单地提一下它的体系结构,简单地说就是由两部分组成:本地高级中断控制器(Local APIC,LAPIC),位于每个CPU中,主要负责传递中断信号到指定的处理器中;I/O高级中断控制器(I/O APIC,)负责搜集来自I/O设备的中断信号并分发给LAPIC,形成一个多级APIC中断分发网络。
接下来的大部分初始化工作都将围绕着LAPIC和IOAPIC这两个东西进行。
5.10.1 设置APIC中断服务
回到start_kernel,由于我们没有配置CONFIG_SPARSE_IRQ,所以605行的early_irq_init()函数是个空函数。606行,调用init_IRQ函数,其本质上是调用x86_init.irqs.intr_init函数,也就是前面“拷贝可用内存区信息”的那个x86_init中看到的那个native_init_IRQ函数,用来初始化LAPIC:
235void __init native_init_IRQ(void)
236{
237 int i;
238
239 /* Execute any quirks before the call gates are initialised: */
240 x86_init.irqs.pre_vector_init();
241
242 apic_intr_init();
243
244 /*
245 * Cover the whole vector space, no vector can escape
246 * us. (some of these will be overridden and become
247 * 'special' SMP interrupts)
248 */
249 for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
250 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
251 if (!test_bit(i, used_vectors))
252 set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
253 }
254
255 if (!acpi_ioapic)
256 setup_irq(2, &irq2);
257
258#ifdef CONFIG_X86_32
259 /*
260 * External FPU? Set up irq13 if so, for
261 * original braindamaged IBM FERR coupling.
262 */
263 if (boot_cpu_data.hard_math && !cpu_has_fpu)
264 setup_irq(FPU_IRQ, &fpu_irq);
265
266 irq_ctx_init(smp_processor_id());
267#endif
268}
该函数初始化中断向量表中前面一些我们没有设置的表项,首先是240,调用x86_init.irqs.pre_vector_init函数,也就是init_ISA_irqs函数:
101void __init init_ISA_irqs(void)
102{
103 int i;
104
105#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
106 init_bsp_APIC();
107#endif
108 legacy_pic->init(0);
109
110 /*
111 * 16 old-style INTA-cycle interrupts:
112 */
113 for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) {
114 struct irq_desc *desc = irq_to_desc(i);
115
116 desc->status = IRQ_DISABLED;
117 desc->action = NULL;
118 desc->depth = 1;
119
120 set_irq_chip_and_handler_name(i, &i8259A_chip,
121 handle_level_irq, "XT");
122 }
123}
106行,由于我们配置了CONFIG_X86_LOCAL_APIC,所以首先调用init_bsp_APIC函数来配置本地LAPIC芯片。该函数调用apic_read或apic_write调用全局变量apic的read和write方法。先说说这个全局变量apic的数据结构,其代表一块LAPIC控制器芯片,于arch/x86/include/asm/apic.h文件中定义:
struct apic {
char *name;
int (*probe)(void);
int (*acpi_madt_oem_check)(char *oem_id, char *oem_table_id);
int (*apic_id_registered)(void);
……
/* apic ops */
u32 (*read)(u32 reg);
void (*write)(u32 reg, u32 v);
u64 (*icr_read)(void);
void (*icr_write)(u32 low, u32 high);
void (*wait_icr_idle)(void);
u32 (*safe_wait_icr_idle)(void);
};
而全局变量apic在文件arch/x86/kernel/apic/probe_32.c文件中被定义成:
struct apic *apic = &apic_default;
于是乎,编译vmlinux的时候,apic_default就成为了该变量实际的值:
struct apic apic_default = {
.name = "default",
.probe = probe_default,
.acpi_madt_oem_check = NULL,
.apic_id_registered = default_apic_id_registered,
……
.read = native_apic_mem_read,
.write = native_apic_mem_write,
.icr_read = native_apic_icr_read,
.icr_write = native_apic_icr_write,
.wait_icr_idle = native_apic_wait_icr_idle,
.safe_wait_icr_idle = native_safe_apic_wait_icr_idle,
};
我们看到,init_bsp_APIC函数实际上调用native_apic_mem_read和native_apic_mem_write等方法。回到init_ISA_irqs中,108行又是一个全局变量,legacy_pic:
struct legacy_pic *legacy_pic = &default_legacy_pic;
其值被初始化成了default_legacy_pic:
struct legacy_pic default_legacy_pic = {
.nr_legacy_irqs = NR_IRQS_LEGACY,
.chip = &i8259A_chip,
.mask_all = mask_8259A,
.restore_mask = unmask_8259A,
.init = init_8259A,
.irq_pending = i8259A_irq_pending,
.make_irq = make_8259A_irq,
};
struct legacy_pic *legacy_pic = &default_legacy_pic;
我们熟悉的8259A中断控制器芯片就是这个default_legacy_pic,所以init_ISA_irqs的108行是调用该芯片的初始化函数init_8259A, 初始化了8259A的一些硬件状态,保证8259A能够正常工作:
static void init_8259A(int auto_eoi)
{
unsigned long flags;
i8259A_auto_eoi = auto_eoi;
raw_spin_lock_irqsave(&i8259A_lock, flags);
outb(0xff, PIC_MASTER_IMR); /* mask all of 8259A-1 */
outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */
/*
* outb_pic - this has to work on a wide range of PC hardware.
*/
outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */
/* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64,
to 0x20-0x27 on i386 */
outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR);
/* 8259A-1 (the master) has a slave on IR2 */
outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR);
if (auto_eoi) /* master does Auto EOI */
outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
else /* master expects normal EOI */
outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
outb_pic(0x11, PIC_SLAVE_CMD); /* ICW1: select 8259A-2 init */
/* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */
outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR);
/* 8259A-2 is a slave on master's IR2 */
outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);
/* (slave's support for AEOI in flat mode is to be investigated) */
outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);
if (auto_eoi)
/*
* In AEOI mode we just have to mask the interrupt
* when acking.
*/
i8259A_chip.mask_ack = disable_8259A_irq;
else
i8259A_chip.mask_ack = mask_and_ack_8259A;
udelay(100); /* wait for 8259A to initialize */
outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */
outb(cached_slave_mask, PIC_SLAVE_IMR); /* restore slave IRQ mask */
raw_spin_unlock_irqrestore(&i8259A_lock, flags);
}
函数具体内容我就不详细分析了,无非是些outb或者outb_pic通过这个芯片所占用的总线端口对它进行初始化。回到init_ISA_irqs,113行,刚才看见nr_legacy_irqs为 NR_IRQS_LEGACY,也就是16次循环,将全局irq_desc[irq]数组的16个irq_desc元素进行初始化;然后16次通过set_irq_chip_and_handler_name函数将每个irq_desc的chip字段通过set_irq_chip设置成全局变量i8259A_chip,以及将每个irq_desc的handle_irq和name字段通过__set_irq_handler分别设置为handle_level_irq和” XT”。
回到native_init_IRQ中,随后242行调用apic_intr_init()函数:
202static void __init apic_intr_init(void)
203{
204 smp_intr_init();
205
206#ifdef CONFIG_X86_THERMAL_VECTOR
207 alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);
208#endif
209#ifdef CONFIG_X86_MCE_THRESHOLD
210 alloc_intr_gate(THRESHOLD_APIC_VECTOR, threshold_interrupt);
211#endif
212#if defined(CONFIG_X86_MCE) && defined(CONFIG_X86_LOCAL_APIC)
213 alloc_intr_gate(MCE_SELF_VECTOR, mce_self_interrupt);
214#endif
215
216#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
217 /* self generated IPI for local APIC timer */
218 alloc_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);
219
220 /* IPI for X86 platform specific use */
221 alloc_intr_gate(X86_PLATFORM_IPI_VECTOR, x86_platform_ipi);
222
223 /* IPI vectors for APIC spurious and error interrupts */
224 alloc_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);
225 alloc_intr_gate(ERROR_APIC_VECTOR, error_interrupt);
226
227 /* Performance monitoring interrupts: */
228# ifdef CONFIG_PERF_EVENTS
229 alloc_intr_gate(LOCAL_PENDING_VECTOR, perf_pending_interrupt);
230# endif
231
232#endif
233}
smp_intr_init()函数,32位x86体系中是一个空函数。后面206~229设置中断描述符表中APIC相关的中断服务层序,也就是把位于32~255之间,除去系统调用外其他中断向量的中断处理程序设置为interrupt[i]。interrupt共有NR_VECTORS-FIRST_EXTERNAL_VECTOR个表项:
#define NR_VECTORS 256
#define FIRST_EXTERNAL_VECTOR 0x20 //32
并且interrupt数组在arch/x86/kernel/entry_32.S中定义并初始化:
823.section .init.rodata,"a"
824ENTRY(interrupt)
825.text
826 .p2align 5
827 .p2align CONFIG_X86_L1_CACHE_SHIFT
828ENTRY(irq_entries_start)
829 RING0_INT_FRAME
830vector=FIRST_EXTERNAL_VECTOR
831.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
832 .balign 32
833 .rept 7
834 .if vector < NR_VECTORS
835 .if vector <> FIRST_EXTERNAL_VECTOR
836 CFI_ADJUST_CFA_OFFSET -4
837 .endif
8381: pushl $(~vector+0x80) /* Note: always in signed byte range */
839 CFI_ADJUST_CFA_OFFSET 4
840 .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
841 jmp 2f
842 .endif
843 .previous
844 .long 1b
845 .text
846vector=vector+1
847 .endif
848 .endr
8492: jmp common_interrupt
850.endr
851END(irq_entries_start)
852
853.previous
854END(interrupt)
看到上面的代码,从824到854行都属于数据段中,看到828~851之间的代码,虽然表面上看上去它们是分隔开的,但是实际上编译之后会在数据段中一块连续的区域中。我们看到从831行~850行,执行循环NR_VECTORS-FIRST_EXTERNAL_VECTOR+6次,也就是说是256-32+6=230次,那么代码展开后,这段数据的内容就类似于:
ENTRY(interrupt)
.long 1b
.long 1b
.long 1b
……
即构成225个项的interrupt[]数组,每个
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮