内存寻址一(分段)
内存地址
逻辑地址(logical address)
包括在机器语言指令中用来指定一个操作数或一条指令的地址。这样的寻址方式在80x86著名的分段结构表现的尤为详细。每一个逻辑地址都有一个段(segment)和偏移量(offset)组成,偏移量指明了从段開始的地方到实际地址之间的距离。
线性地址(linear address)(也称虚拟地址virtual address)
是一个32位无符号整数,能够用来表示高达4G的地址。线性地址通经常使用十六进制数字表示,值的范围从0x00000000到0xffffffff。
物理地址(physical address)
用于内存芯片级内存单元寻址。它们与微处理器的地址引脚发送到内存总线上的电信号相对应。
物理地址由32位或36位无符号整数表示。
内存管理单元(MMU)通过一种称为分段单元(segmentation unit)的硬件电路把一个逻辑地址转换成线性地址。接着,第二个称为分页单元(paging unit)的硬件电路把线性地址转换为一个物理地址。
当Intel处理器工作在保护模式下。分段机制是必须使用的。而分页是可选的。当分页没有使用时,线性地址空间就直接映射到物理地址空间。物理地址空间是指处理器在地址总线产生的地址空间范围,如处理器为32位但地址总线是36位。这物理地址空间就是0~2^36
硬件中的分段
一个逻辑地址由两部分组成:一个段标识符和一个指定段内相对地址的偏移量。
段标识符是一个16位长的字段,称为段选择符
段选择符(segment selector)
Index 指定了放在GDT或者LDT中的对应段描写叙述符的入口。
从存放在GDT或者LDT中的8192个描写叙述符中选择段描写叙述符,由于一个段描写叙述符是8个字节长(以下 会讲道),因此它在GDT或者LDT内的相对地址是由段选择符的最高13位的值乘以8得到的。比如:假设GDT在0x00020000(这个值保存在gdtr寄存器中),且由段选择符所指定的索引號为2,那么对应的段描写叙述符地址是0x00020000 + 2*8
TI TI(Table Indicator)标志。指明段描写叙述符是在GDT中(TI=0)或在LDT中(TI=1)
RPL 请求特权等级(Requested Privilege Level):当对应的段选择符装入到CS寄存器中时指示出CPU当前的特权等级。优先级范围从0到3,0代表最高优先级,而3代表最低优先级。Linux仅仅用到0级和3级。分别称之为内核态和用户态
为了高速方面地找到段选择符,处理器提供段寄存器。段寄存器的唯一目的是存放段选择符。这些寄存器称为cs,ss,ds,es,fs和gs。
6个寄存器中有3个有专门用途:
cs 代码段寄存器。指向包括程序指令的段。
ss 栈段寄存器。指向包括当前程序的段。
ds 数据段寄存器,指向包括静态数据或者全局数据段
对intel处理器,有两种类型的指令用于载入段寄存器
一类是显式引用。如MOV, POP, LDS, LES, LSS, LGS, and LFS
还有一类是隐式载入。如CALL,JMP,RET,SYSENTER,SYSEXIT,IRET,INTn等这些指令都会附带改动CS或者其它段寄存器内容
段描写叙述符表(Segment Descriptor Tables)
段描写叙述符表是一个段描写叙述符的数组,一个描写叙述符表的大小最多为8192(2^13)8byte的描写叙述符
有两种类型的描写叙述符表
全局描写叙述符表(Global Descriptor Table,GDT)
局部描写叙述符表(Local Descriptor Table,LDT)
通常仅仅定义一个GDT,而每一个进程除了存放在GDT中的段之外还须要创建附加的段,就能够有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正被使用的LDT地址和大小存放在ldtr控制寄存器中。
段描写叙述符(Segment Descriptor)
Base 包括段的首字节的线性地址,处理器把3个base address域形成一个32bit值。段基地址应该是要16字节边界对齐的但不是必须的。由于16字节对齐能够使程序性能最大化通过代码与数据16字节边界对齐
G 粒度(granularity)标志:假设该位清0,则段大小以字节为单位,否则以4096字节的倍数计
D/B 称为D或B的标志,取决于是代码段还是数据段。D或者B的含义情况在两种情况下稍有差别,可是假设段偏移量的地址是32位长,就是基本上把它置为 1,假设这个偏移量是16位长。它被清0
Limit 存放段中最后一个内存单元的偏移量,从而决定段的长度。假设G被设置为0,则一个段的大小在1个字节到1MB之间变化;否则,将在4KB到4GB之间 变化
P Segment-Present标志:等于0表示段当前不在主存中。Linux总是把这个标志设置为1。由于它从来不把整个段交换到磁盘上去。
DPL 描写叙述符特权级(Descriptor Privilege Level)字段:用于限制对这个段的存取。它表示为訪问这个段而要求的CPU最小的优先级。因此。DPL设 为0的段仅仅能当CPL为0时(即在内核态)才干够訪问的,而DPL设为3的段对不论什么CPL值都是能够訪问的
S 系统标志:假设它被清零,则这是一个系统段,存储诸如LDT这样的重要的作用结构,否则它是一个普通的代码段或数据段
Type 描写叙述了段的类型特征和它的存取权限
有几种不同类型的段以及和它们对应的段描写叙述符。以下列出了Linux中被广泛採用的类型:
代码段描写叙述符
表示这个段描写叙述符代表一个代码段,它能够放在GDT或者LDT中。该描写叙述符置S标志位1
数据段描写叙述符
表示这个段描写叙述符代表一个数据段,它能够放在GDT或者LDT中。该描写叙述符置S标志位1。栈段是通过一般数据段实现的
任务状态段描写叙述符(TSSD)
表示这个段描写叙述符代表一任务状态段(Task State Segment,TSS),也就是说这个段用于保存处理器寄存器的内容。
它仅仅能出如今GDT中。
依据对应的进程是否正在CPU上执行。其Type字段的值分别为11或9.这个描写叙述符的S标志置为0
局部描写叙述符表描写叙述符(LDTD)
表示这个段描写叙述代表一个包括LDT的段。它仅仅能出如今GDT中。对应的Type字段的值为12,S标志位0。
总结一下分段单元是怎样把逻辑地址转换成线性地址的
a、先检查段选择符的TI字段,以决定段描写叙述符保存在哪个描写叙述符表。
b、从段选择符的Index字段计算段描写叙述符的地址。Index字段的值乘以8,这个结果与gdtr或者ldtr寄存器的内容相加
c、把逻辑地址的偏移量与描写叙述符Base字段的值相加就得到了线性地址
Linux中的分段
Linux以很有限的方式使用分段。
与分段相比。Linux更喜欢使用分页方式,由于:
a、当全部进程使用同样的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址
b、Linux设计目标之中的一个是能够把它移植到绝大多数流行的处理器平台上。
然而,RISC体系结构对分段的支持有限
2.6版本号的Linux仅仅有在80x86结构下才须要使用分段。
执行在用户态的全部Linux进程都使用一对同样的段来对指令和数据寻址。这两个段就是所谓的用户代码段和用户数据段。相似地。执行在内核态的全部Linux进程都使用一对同样的段对指令和数据寻址:它们分别叫做内核态代码段和内核数据段。
四个基本的Linux段的段描写叙述符字段的值
段 Base G Limit S Type DPL D/B P
用户代码段 0x00000000 1 0xfffff 1 10 3 1 1
用户数据段 0x00000000 1 0xfffff 1 2 3 1 1
内核代码段 0x00000000 1 0xfffff 1 10 0 1 1
内核数据段 0x00000000 1 0xfffff 1 2 0 1 1
对应的段选择符由宏__USER_CS、__USER_DS、__KERNEL_DS、__KERNEL_CS。比如,为了对内核代码段寻址,内核仅仅须要把__KERNEL_CS宏产生的值装进cs段寄存器就可以
注意。与段相关的线性地址从0開始。达到2^32-1的寻址限长。这意味着在用户态或者内核态下的全部进程能够使用同样的逻辑地址。
全部段都从0x00000000開始。这能够得出还有一个重要结论,那就是在Linux下逻辑地址与线性地址是一致的。即逻辑地址的偏移量字段的值与对应的线性地址的值总是一致的
摘自linux-2.6.32/arch/x86/include/asm/segment.h
#define GDT_ENTRY_DEFAULT_USER_CS 14
#define GDT_ENTRY_DEFAULT_USER_DS 15
#define GDT_ENTRY_KERNEL_BASE 12
#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE + 0)
#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE + 1)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS* 8 + 3)
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS* 8 + 3)
Linux GDT
在单处理器系统中仅仅有一个GDT。而在多处理器系统中每一个CPU对应一个GDT。
全部的GDT都存放在cpu_gdt_table数组中。
每一个GDT中包括的18个段描写叙述符指向下列的段:
a、用户态和内核态下的代码段和数据段共4个
b、任务状态段(TSS)。每一个处理器有1个。
每一个TSS对应的线性地址空间都是内核数据段对应线性地址空间的一个子集。
c、一个包括缺省局部描写叙述符表的段。这个段一般是被全部进程共享的段
d、3个局部线程存储段(Thread-Local Storage,TLS):这样的机制同意多线程应用程序使用最多3个局部于线程的数据段。
系统调用set_thread_area()和get_thread_area()分别为正在执行的进程创建和撤销一个TLS段
Linux LDT
大多数用户态下的Linux程序不使用局部描写叙述符表,这样内核就定义了一个缺省的LDT共大多数进程共享。