前言:
linux下的中断描述符IDT是一个系统表,它与每一个中断或异常向量相联系,所以重要性不言而喻。关于对IDT的描述、解析及初始化参考我的前边一篇文章(http://blog.chinaunix.net/uid-27717694-id-3942170.html)。这里主要解析一下最近我遇到的linux下IDT的一些特性。
1.IDT数量问题
做windows的程序员可能知道,在windows下IDT的数量是与cpu core的数量一致的,即在windows下有几个cpu,就有几个IDT表。那在linux下是否如此呢?
一开始我也以为linux应该跟windows是一致的,但是我做了很多测试之后发现并非如此。一般我们取IDT地址都是采用如下方法:
asm volatile("sidt %0":"=m" (*pIdt));
printk("idt at: 0x%x, limit=0x%x\n", pIdt->dwBase, pIdt->wLimit);
因为我的机器是双核的,我让两个cpu都执行这段代码,可以通过以下代码在不同的cpu上执行相同的函数。(这两个函数的解析参考我的另一篇文章:http://blog.chinaunix.net/uid-27717694-id-4033413.html)
for_each_online_cpu(cpu){
smp_call_function_single(cpu,cpu_smp_fedcore_init,&error,1);
}
通过以上的多次打印发现在不同的cpu上IDT表的地址都是一致的,即不管多少cpu,linux内核都维护一个IDT表。
原因也是显而易见的,在linux内核代码中IDT表的地址就是内核变量idt_table的地址,该变量声明在arch/x86/kernel/traps.c中:
gate_desc idt_table[NR_VECTORS] __page_aligned_bss;
//NR_VECTORS = 256,即表示IDT表项为256项,每一项的结构都是gate_desc结构。
所以汇编指令就是把idt_table的地址作为IDT表的地址存入IDTR寄存器中,所以即使有多个cpu,每个cpu都有一个IDTR寄存器,但是这些IDTR寄存器中存储的IDT表的地址都是idt_table在内核中的线性地址。所以在linux内核中IDT表的地址只有一个。
2.IDT表地址异常
原来我的机器是3.8 版本的内核,通过sidt取出的地址,与内核符号表中idt_table的地址是一致的.最近呢我的机器升级为了3.12 的版本,然后发现通过sidt取出的IDT表地址为:0xfffba000,而通过查内核符号表idt_table的地址是:0xc19ca000。总是感觉0xfffba000这个地址怪怪的,好像不是IDT表的地址,而且该地址还是只读的,不能在此地址上对IDT表进行修改。为什么会不一致呢?
首先我将这两个IDT地址下的内容打印出来比较了一下,发现”两个“IDT表下的内容是一模一样的,所以就想到0xfffba000这个地址应该是对idt_table物理地址的重新映射,即内核线性地址0xfffba000和0xc19ca000都映射了idt_table的物理地址,只不过0xfffba000地址比较特殊,它是linux内核的固定映射地址,而且是只读的。既然两个线性地址都映射了IDT表的物理地址,那么IDTR寄存器中保存这两个地址中的任意一个都是正确的,但是linux内核让它存储了一个只读的固定映射的地址,这样做的好处是,你通过sidt读出IDT表的地址,是不能对IDT进行修改的。如果你想修改IDT表,就从内核符号表中读出idt_table的线性地址0xc19ca000,对其进行修改是可以的。同时也对只读的固定映射地址下的IDT表内容做了修改,因为都是对应一块物理地址。
这一机制是从3.10 版本开始才加入到linux内核中,所以在3.8 内核中不会遇到,而在3.12 内核中会遇到此问题。下面来看一下内核的实现:
-
//内核启动过程中start_kernel()会调用trap_init()函数只是对IDT的前20项(0-19)和128号(SYSCALL_VECTOR))进行了最终初始化。
-
void __init trap_init(void)
-
{
-
int i;
-
-
//对IDT表项进行初始化
-
set_intr_gate(X86_TRAP_DE, ÷_error);
-
set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
-
/* int4 can be called from all */
-
set_system_intr_gate(X86_TRAP_OF, &overflow);
-
set_intr_gate(X86_TRAP_BR, &bounds);
-
set_intr_gate(X86_TRAP_UD, &invalid_op);
-
set_intr_gate(X86_TRAP_NM, &device_not_available);
-
#ifdef CONFIG_X86_32
-
set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
-
#else
-
set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
-
#endif
-
set_intr_gate(X86_TRAP_OLD_MF, &coprocessor_segment_overrun);
-
set_intr_gate(X86_TRAP_TS, &invalid_TSS);
-
set_intr_gate(X86_TRAP_NP, &segment_not_present);
-
set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
-
set_intr_gate(X86_TRAP_GP, &general_protection);
-
set_intr_gate(X86_TRAP_SPURIOUS, &spurious_interrupt_bug);
-
set_intr_gate(X86_TRAP_MF, &coprocessor_error);
-
set_intr_gate(X86_TRAP_AC, &alignment_check);
-
set_intr_gate(X86_TRAP_XF, &simd_coprocessor_error);
-
-
/* Reserve all the builtin and the syscall vector: */
-
for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
-
set_bit(i, used_vectors);
-
-
#ifdef CONFIG_X86_32
-
set_system_trap_gate(SYSCALL_VECTOR, &system_call);
-
set_bit(SYSCALL_VECTOR, used_vectors);
-
#endif
-
-
//注意:与3.10之前内核不同之处就在于下边这两行代码。
-
/*
-
* Set the IDT descriptor to a fixed read-only location, so that the
-
* "sidt" instruction will not leak the location of the kernel, and
-
* to defend the IDT against arbitrary memory write vulnerabilities.
-
* It will be reloaded in cpu_init()
-
*/
-
-
//将idt_table的物理地址映射到内核固定映射线性地址FIX_RO_IDT处,即在固定映射线性地址FIX_RO_IDT处也存在了一个IDT表项的拷贝
-
__set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
-
//将固定映射线性地址FIX_RO_IDT赋值给IDT描述符,在cpu_init()用该地址初始化IDTR寄存器
-
idt_descr.address = fix_to_virt(FIX_RO_IDT);
-
-
cpu_init();
-
x86_init.irqs.trap_init();
-
}
-
-
__pa_symbol是获得物理地址,至于其中的转换不用去深究,最后就是((x) - PAGE_OFFSET),即0xc0000000的偏差。
-
-
//将idt_table的物理地址映射到固定映射的线性地址上,并且该线性地址设置为只读属性
-
static inline void __set_fixmap(enum fixed_addresses idx,phys_addr_t phys, pgprot_t flags)
-
{
-
native_set_fixmap(idx, phys, flags);
-
}
-
-
void native_set_fixmap(enum fixed_addresses idx, phys_addr_t phys,pgprot_t flags)
-
{
-
//phys >> PAGE_SHIFT获得该物理地址所在物理内存中的页框号
-
//pfn_pte获得第(phys >> PAGE_SHIFT)个物理页框页表项内容
-
__native_set_fixmap(idx, pfn_pte(phys >> PAGE_SHIFT, flags));
-
}
-
-
void __native_set_fixmap(enum fixed_addresses idx, pte_t pte)
-
{
-
//根据索引号获得该固定映射的线性地址
-
unsigned long address = __fix_to_virt(idx);
-
-
//是否超出固定映射线性地址的尾部
-
if (idx >= __end_of_fixed_addresses) {
-
BUG();
-
return;
-
}
-
-
//用idt_table所在物理页框对应的页表项内容填充固定映射的线性地址,即完成了映射
-
set_pte_vaddr(address, pte);
-
fixmaps_set++;
-
}
-
-
//在该函数里将映射完成的IDT只读固定线性地址,保存到IDTR寄存器
-
void __cpuinit cpu_init(void)
-
{
-
.......
-
-
load_idt(&idt_descr);
-
-
......
-
}
-
-
#define load_idt(dtr) native_load_idt(dtr)
-
-
//通过lidt汇编指令保存IDT只读固定线性地址到IDTR寄存器
-
static inline void native_load_idt(const struct desc_ptr *dtr)
-
{
-
asm volatile("lidt %0"::"m" (*dtr));
-
}
阅读(1019) | 评论(0) | 转发(0) |