linux内核学习笔记——x86分段
以前从来不做笔记,好多东西学了忘,忘了学,今天开始记录下来。
X86的分段
x86微处理器有两种工作模式,实模式和保护模式。实模式仅仅是为了与之前产品的兼容。因为linux是运行在保
护模式上,所以这里只讨论保护模式.
X86分段是将内存地址按段区分开来,cs,ss,ds,es,fs和gs都是段寄存器。
16位的高13位是段描述的索引号(段描述符马上就讨论),TI位指示是全局描述附表(TI=0)还是局部描述附表(TI=1),RPL是特权 0为最高3最低。
段描述符:
我们就是通过它真正去找到一段内存地址的。一个描述符占8个字节。它的结构一会儿说。
段描述符表:
你可以看成是排列在连续内存上的一组段描述符。分全局描述符表、局部描述符表等。
情景分析 :
假设我们现在有一段指令mov cs 8 即cs段寄存器中为
0000000000001000
索引号为1,到全局描述符表去找,特权级为0.那么全局描述符表在哪呢。X86为保护模式增加了两个寄存器gdtr和ldtr。这两个寄存器分别用来存放全局描述符表和局部描述符表的基地址。如gdtr中的值为0x00020000.那么现在根据索引为1,可以找到段描述符地址。0x00020000+8*1=0x00020008。为什么是8,因为一个段描述就占8个字节。
那么gdtr和ldtr中的值是怎么来的呢?保护模式的运行必须要用这两个值,所以这两个寄存器当然不可能在保护模式下再赋值,而是在实模式下就将描述符符表制作好,然后将表基地址赋值给两个寄存器,等到模式切换成保护模式后就可以直接用这两个值去找到这两个表了。
现在我们来到了段描述符了,来看看描述符的结构,段描述符的结构比较复杂,最恶心的是有好多种段描述符还多少有些区别。
Base:段首地址(之所以分在不同的区域是为了兼容以前的芯片)
G: 段大小计算单位是以字节计算还是以4096字节倍数计算
Limit: 存放段最后一个内存单元的偏移量 如果G=0那么段大小为0~1MB, G=1段大小为4KB~4GB
S: 如果s=0 这是一个系统段 否则 是普通代码段或数据段
TYPE 段的类型特征和存取权限
DPL 描述符特权级 0~3
P: 等于0表示段当前不再主存中。Linux总是把这个值设为1,因为它从来不把整个段交换到磁盘上
D或B 这个我还没搞懂 以后再说只要知道32位访问为1
AVL: linux忽略这个
现在假设我们通过cs寄存器找到了在地址0x00020008处的段描述符为
0011 0000 010011111001 1010 0000 0010
0000 0000 0000 0000 1111 1111 1111 1111
这个值是我们在实模式时就设置好的,与下面的格式图对比。
可知段基址为110000 00000010 00000000 00000000即0x30020000 ,G=0所以段长为2^20即1MB ,D=1为32地址访问 DPL=0特权级为最高。S=1表示代码段或数据段。TYPE=1010表示代码段可读、可执行、尚未受到访问。通过特权级比较现在找到了基地址为0x30020000的段
一个逻辑地址分为两部分,段选择符:偏移量。段选择符即例子中cs中存的值。通过它我们找到了一个基地址,再加上偏移量就是逻辑地址对应的线性地址了。(此时我们还没有做分页,所以线性地址即物理地址)。至此我们通过分段实现了一个逻辑地址到线性地址的映射。
我们知道运行在操作系统上的程序翻译成机器指令后其实就是对逻辑地址的操作。但是如果我们运行程序时,每执行一条指令都通过这样的方式映射那效率岂不是太低了。所以x86还提供了6个附加的非编程寄存器,这些寄存器正好也是8个字节的,当段寄存器改变时,就将通过段寄存器索引到段描述符装入其中的一个寄存器。之后只要这个段寄存器不变,就不需要再通过选择符去索引,直接通过这个8字节的附加寄存器就可以找到对应的线性地址。