Chinaunix首页 | 论坛 | 博客
  • 博客访问: 546361
  • 博文数量: 469
  • 博客积分: 50
  • 博客等级: 民兵
  • 技术积分: 1495
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-15 21:04
文章分类

全部博文(469)

文章存档

2015年(81)

2014年(125)

2013年(261)

2012年(2)

分类: LINUX

2013-12-30 13:55:37

原文地址:Linux下IDT进一步研究 作者:wangbaolin719

前言:
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 内核中会遇到此问题。下面来看一下内核的实现:

  1. //内核启动过程中start_kernel()会调用trap_init()函数只是对IDT的前20项(0-19)和128号(SYSCALL_VECTOR))进行了最终初始化。
  2. void __init trap_init(void)
  3. {
  4.     int i;
  5.     
  6.     //对IDT表项进行初始化
  7.     set_intr_gate(X86_TRAP_DE, &divide_error);
  8.     set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
  9.     /* int4 can be called from all */
  10.     set_system_intr_gate(X86_TRAP_OF, &overflow);
  11.     set_intr_gate(X86_TRAP_BR, &bounds);
  12.     set_intr_gate(X86_TRAP_UD, &invalid_op);
  13.     set_intr_gate(X86_TRAP_NM, &device_not_available);
  14. #ifdef CONFIG_X86_32
  15.     set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
  16. #else
  17.     set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
  18. #endif
  19.     set_intr_gate(X86_TRAP_OLD_MF, &coprocessor_segment_overrun);
  20.     set_intr_gate(X86_TRAP_TS, &invalid_TSS);
  21.     set_intr_gate(X86_TRAP_NP, &segment_not_present);
  22.     set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
  23.     set_intr_gate(X86_TRAP_GP, &general_protection);
  24.     set_intr_gate(X86_TRAP_SPURIOUS, &spurious_interrupt_bug);
  25.     set_intr_gate(X86_TRAP_MF, &coprocessor_error);
  26.     set_intr_gate(X86_TRAP_AC, &alignment_check);
  27.     set_intr_gate(X86_TRAP_XF, &simd_coprocessor_error);
  28.     
  29.     /* Reserve all the builtin and the syscall vector: */
  30.     for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
  31.         set_bit(i, used_vectors);
  32.     
  33. #ifdef CONFIG_X86_32
  34.     set_system_trap_gate(SYSCALL_VECTOR, &system_call);
  35.     set_bit(SYSCALL_VECTOR, used_vectors);
  36. #endif

  37.     //注意:与3.10之前内核不同之处就在于下边这两行代码。
  38.     /*
  39.     * Set the IDT descriptor to a fixed read-only location, so that the
  40.     * "sidt" instruction will not leak the location of the kernel, and
  41.     * to defend the IDT against arbitrary memory write vulnerabilities.
  42.     * It will be reloaded in cpu_init()
  43.     */
  44.     
  45.     //将idt_table的物理地址映射到内核固定映射线性地址FIX_RO_IDT处,即在固定映射线性地址FIX_RO_IDT处也存在了一个IDT表项的拷贝
  46.     __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
  47.     //将固定映射线性地址FIX_RO_IDT赋值给IDT描述符,在cpu_init()用该地址初始化IDTR寄存器
  48.     idt_descr.address = fix_to_virt(FIX_RO_IDT);
  49.     
  50.     cpu_init();
  51.     x86_init.irqs.trap_init();
  52. }

  53. __pa_symbol是获得物理地址,至于其中的转换不用去深究,最后就是((x) - PAGE_OFFSET),即0xc0000000的偏差。

  54. //将idt_table的物理地址映射到固定映射的线性地址上,并且该线性地址设置为只读属性
  55. static inline void __set_fixmap(enum fixed_addresses idx,phys_addr_t phys, pgprot_t flags)
  56. {
  57.     native_set_fixmap(idx, phys, flags);
  58. }

  59. void native_set_fixmap(enum fixed_addresses idx, phys_addr_t phys,pgprot_t flags)
  60. {
  61.     //phys >> PAGE_SHIFT获得该物理地址所在物理内存中的页框号
  62.     //pfn_pte获得第(phys >> PAGE_SHIFT)个物理页框页表项内容
  63.     __native_set_fixmap(idx, pfn_pte(phys >> PAGE_SHIFT, flags));
  64. }

  65. void __native_set_fixmap(enum fixed_addresses idx, pte_t pte)
  66. {
  67.     //根据索引号获得该固定映射的线性地址
  68.     unsigned long address = __fix_to_virt(idx);
  69.     
  70.     //是否超出固定映射线性地址的尾部
  71.     if (idx >= __end_of_fixed_addresses) {
  72.         BUG();
  73.         return;
  74.     }
  75.     
  76.     //用idt_table所在物理页框对应的页表项内容填充固定映射的线性地址,即完成了映射
  77.     set_pte_vaddr(address, pte);
  78.     fixmaps_set++;
  79. }

  80. //在该函数里将映射完成的IDT只读固定线性地址,保存到IDTR寄存器
  81. void __cpuinit cpu_init(void)
  82. {
  83.     .......
  84.     
  85.     load_idt(&idt_descr);
  86.     
  87.     ......
  88. }

  89. #define load_idt(dtr)    native_load_idt(dtr)

  90. //通过lidt汇编指令保存IDT只读固定线性地址到IDTR寄存器
  91. static inline void native_load_idt(const struct desc_ptr *dtr)
  92. {
  93.     asm volatile("lidt %0"::"m" (*dtr));
  94. }


阅读(1019) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~