在Lab1的实验中需要了解GDT表的建立,于是我在mm/pmm.c文件中找到了如下的定义:
/* * * Global Descriptor Table: * * The kernel and user segments are identical (except for the DPL). To load * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the * segments for the user and the kernel. Defined as follows: * - 0x0 : unused (always faults -- for trapping NULL far pointers) * - 0x8 : kernel code segment * - 0x10: kernel data segment * - 0x18: user code segment * - 0x20: user data segment * - 0x28: defined for tss, initialized in gdt_init * */ static struct segdesc gdt[] = { SEG_NULL, [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), [SEG_TSS] = SEG_NULL, };
老师在课堂和MOOC中均提到,uCore将x86提供的段机制看作是直接映射,在页机制未启用的前提下,达到了逻辑地址=线性地址=物理地址的效果。观察上面的代码,除了第一个SEG_NULL是硬件需要之外,其他的五个SEG有四个都将地址映射到了0x00000000-0xFFFFFFFF,也就是说:当访存的逻辑地址唯一确定的时候,对应的线性地址就确定了,而对该逻辑地址的权限则由CS或是DS等寄存器决定,因此也可以通过观察CS的值确定当前到底是用户态还是内核态。
那么为什么还需要这6个段呢?在课下与陈老师的讨论中,得知这样做的目的是权限控制。
举个例子,当CS为用户态时,如果试图执行某些特权指令或是内核态中断,就会触发x86的13号一般保护异常( http://ilinuxkernel.com/?p=1388 )。 在lab1的challenge实验中,建立IDT表的时候,需要把T_SWITCH_TOK特权级别设置为DPL_USER,这样一来,在用户态执行lab1_switch_to_kernel的int指令时,就不会触发一般保护异常了。
至于SEG_NULL和SEG_TSS,前者应该是x86硬件需求,后者是之后的lab中会用到的用户用户态和内核态栈切换存储寄存器的临时区域,是动态建立的。
但其实写到这里,我还是有一个不太理解的地方,那就是既然之后会有页机制来进行权限控制,为什么还要在这里重复进行检查呢,这一步是多余的吗(即段表只设置NULL和KERNEL可以吗)?
希望同学一起讨论一下,如果我有新的理解再更新。