深入理解Linux内核 第二章 内存寻址

时间:2022-03-29 05:44:23

内存地址

  当使用80x86微处理器时,必须区分以下三种不同的地址:

    1)逻辑地址(logical address),每一个逻辑地址都由一个段(segment)和偏移量(offset或者displacement)组成,偏移量指明了从段开始的地方到实际地址之间的距离。

    2)线性地址(linear address),也称虚拟地址(virtual address),是一个32bit无符整数,可以用来表示4G的地址,通常由16制数字表示。

    3)物理地址(physical address),用于内存芯片级内存单元寻址,他们与从微处理器的地址引脚范松到内存总线上的电信号相对应,物理地址由32bit或36bit无符号整数表示。

        逻辑地址——>【分段单元】——>线性地址——>【分页单元】——>物理地址

 

硬件中的分段

  段选择符(Segment Selector)和段寄存器

    逻辑地址:段选择符(Segment Selector)和段内偏移量,前者16bit,后者32bit。

    段选择符:16bit中,高13bit为索引号(可以标识8K,所以一个GDT或者LDT中可以放8K个段描述符),然后接着的是1bit是TI(表指示器,指示是在GDT中还是LDT中),然后是2bit的RPL(请求者特权级)

    段寄存器:有6个段寄存器,分别为:cs(代码段寄存器)、ss(栈段寄存器)、ds(数据段寄存器)、es、fs和gs,用于存放段选择符。

  段描述符符(Segment Descriptor)

    每个段描述符(segment descriptor)为8Byte(是字节不是位),描述段的特征。放在全局描述符表(Global Descriptor Table,GDT)或者局部描述符表(Local Descriptor Table,LDT)中。

    通常GDT在系统中只定义一个,LDT不止一个,每个进程都可以有自己的LDT(可以理解为GDT为一级段描述符表,LDT为二级描述符表,LDT嵌套在GDT中)。

    GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正在被使用的LDT的地址和大小放在ldtr控制寄存器中。

    段描述符字段:Base(段首字节线性地址,32-bit)、G(0 表示以Byte为单位,否则以4096Byte为单位,1-bit)、Limit(段最后一个内存单元的偏移量,配合G描述段的大小,最多4GB,即4096*10^20Byte,20-bit)、S(系统标志,如果为0,则这是一个系统段,存储一些关键数据结构,1-bit)、Type(描述段的类型特征和它的存取权限,4-bit)、DPL(Descriptor Privilege Level,表示访问该段而要求的CPU最小优先级,Linux只有0和3,0为内核态才可以访问,3是任何都可以访问,2-bit)、P(Segment-Present标志:0表示段前段不再主存中,1-bit)、D或者B、AVL标志。

    常见的几种段描述符Type(即上面Type字段):

      代码段描述符(Code Segment Descriptor),代表一个代码段,可以放在GDT或者LDT中,该描述符S标志为1(非系统段)。

      数据段描述符(Data Segment Descriptor),代表一个数据段,可以放在GDT或者LDT中,S标志为1。

      任务状态段描述符(Task State Segment Descriptor,TSSD),代表一个任务状态段(Task State Segment,TSS),也就是说这个段用于保存处理器寄存器的内容,只能出现在GDT中。

      局部描述符表描述符(Local Descriptor Table Descriptor,LDTD),代表一个包含LDT的段,只出现在GDT中,S标志为0。

      深入理解Linux内核 第二章 内存寻址

 

  分段单元,执行以下操作:

    1)先检查Segment Selector中的TI字段,即判断是GDT还是LDT。

    2)根据Segment Selector中的Index计算出Segment Descriptor的地址(即乘以8),这个结果与gdtr或者ldtr寄存器中的内容相加。

    3)把逻辑地址的偏移量与段描述符(Segment Descriptor)Base字段的值相加就得到了线性地址。

    深入理解Linux内核 第二章 内存寻址

 

Linux中的分段

  Linux以非常有限的方式使用分段。分段和分页在某种程度上有些多余,他们都可以划分进程的物理地址空间:分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。而Linux更喜欢分页的方式。(原因,只分页结构更简单,RISC体系结构对分段支持有限)

  2.2/2.6版的Linux只有在80x86结构下才使用分段。

  因为基本不使用分段机制,所以,GDT容量(8K条)足够用。

  内核是不使用LDT的,即使系统仍然是提供这样的接口,对于Wine这种运行MS程序提供了方便。

  以下是Linux常用的段:

    A kernel code segment  Base=0x00000000  Limit=0xFFFFF  G=1  S=1  Type=0xA  DPL=0  D/B=1

    A kernel data segment   Base=0x00000000  Limit=0xFFFFF  G=1  S=1  Type=2   DPL=0  D/B=1

    A user code segment     Base=0x00000000  Limit=0xFFFFF  G=1  S=1  Type=0xA  DPL=3  D/B=1

    A user data segment      Base=0x00000000  Limit=0xFFFFF  G=1  S=1  Type=2   DPL=3  D/B=1

  上面列举的四项可以看出,段首地址基本相同,都是0x00000000,呵呵呵,Linux下面逻辑地址和线性地址是一样的。

  Linux GDT

    单处理器系统只有一个GDT,多处理器系统中每个CPU对应一个GDT,GDT包含18个Segment Descriptor和14个空的,未使用或者保留的项。这18个Segment分别是:

      1)用户态和内核态的代码段和数据段,共4个

      2)任务状态段(TSS),每个处理器有1个,每个TSS相应的线性地址空间都是内核数据段相应线性地址空间的一个小子集。