第二章 内存寻址
内存地址
内存地址分为三种:逻辑地址(logical address)(段+偏移量)
线性地址(linear address)(共32位 可以表示高达4G 地址)
物理地址(physical address)(用于内存芯片级内存单元寻址)
地址转换关系:逻辑地址------>线性地址------>物理地址
段选择符和段寄存器
逻辑地址由段标识符和段偏移量组成
段标识符总共16位 称为段选择符
段选择符见下图:
80x86中的段寄存器有:cs(code segment)代码段寄存器
ss(stack segment)栈段寄存器
ds(data segment) 数据段寄存器
es fs gs 等等
段描述符
段描述符包含8个字节的段信息(其中包含段的首字节的线性地址)
段描述符放在全局描述表(Global Descriptor Table,GDT)和局部描述表(Local Descriptor Table,LDT)中
GDT:整个系统只有一个
LDT:如果进程需要创建自己的段,就可以创建自己的LDT
GDT所在主存的地址和大小放在gdtr控制寄存器中,当前正在被使用的LDT地址和大小放在ldtr控制寄存器中
2015.10.15
快速段描述
不可编程寄存器:不能由程序员设置的寄存器,可以存放8字节的段描述符,每当一个段选择符被选定时, 相应的段描述符就被装入不可编程寄存器中。
逻辑地址和线性地址的转换
linux中的分段
linux中更喜欢分页,因为RISC对分段的支持有限,采用分页使可移植性得到加强
用户态和内核态程序都是用同一段对指令和数据进行寻址
四个主要的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 __KENERL_CS __KENERL_DS定义
linux GDT和LDT
在单处理器的系统中,GDT只有一个
在多处理器的系统中,GDT有多个,每个处理器有自己的一个GDT
所有GDT都存放在cpu_gdt_table数组中,所有GDT的地址和大小则存储在cpu_gdt_descr数组中
对于LDT,大多数用户态的linux程序不使用局部描述表,这样内核就定义了一个缺省的LDT供大多
数进程共享
硬件中的分页
分页单元把线性地址转换为物理地址
线性地址被分成以固定长度为单位的组,称为页(page),一般4KB
分页单元把所有RAM分为固定长度的页框(page frame),一般4KB
页:一个数据块,存放在一个页框或者磁盘中
页框:物理页
把线性地址映射到物理地址的数据结构成称为页表(page table)
常规分页
二级分页的32位线性地址划分:
Directory(目录):最高10位
Table(页表):最高10位
Offset(偏移量):最低12位
线性地址的转换分两步完成,每一布都基于一种转换表,第一种转换表称为页目录表(page directory ),第二种转换表称为页表(page table)
采用二级结构主要为了减少每个页表所需的RAM的数量
正在使用的页目录项的物理地址存放于cr3中
常规分页下线性地址向物理地址的转换:
页表项的字段:
Present (1则指的页在主存中,0则不再主存中)
包含页框物理地址的最高20位(如果字段指向页目录,则相应页框含有一个页表,如果字段指向页表,则相应的页框就是一页数据)
Accessed(每当分页单元对相应的页框进行寻址时就设置这个标志)
Dirty(每当对一个页框进行写操作时设置此标志)
Read/Write(含有页或者页表的存取权限)
User/Supervisor(含有访问页或者页表所需的特权级)
Pcd和PWT(控制硬件高速缓存处理页或者页表的方式)
Page Size(只能应用与页目录项,为1则页目录项指的时2MB或者4MB的页框)
Global (只应用于页表项,用来防止常用页从高速缓存中刷新出去)
2015.10.16
扩展分页
从Pentium模型开始,80x86微处理器引入扩展分页(extended page),它允许页框的大小位4MB而不是4KB
此时的32位线性地址结构:
Directory(目录):最高10位
Offset(偏移量):其余22位
扩展分页下线性地址向物理地址转换:
处理器所支持的RAM容量受连接到地址总线的地址管脚数限制。早期Intel处理器从80386到Pentium使用32位物理地址。从理论上讲,这样的系统上可以安装高达4GB的RAM;而实际上,由于用户进程线性地址空间的需要,内核不能对1GB以上的RAM进行寻址。
为了扩展32位80x86结构所支持的RAM容量,Intel通过在它的处理器上把管脚数从32增加到36来满足这些需求。此时,Intel所有的处理器现在的寻址能力为2^36=64GB
Intel通过引入一种叫作物理地址扩展的机制来使用增加的物理地址
通过设置cr4控制寄存器来激活PAE机制
激活PAE机制后分页的特点:
1.一个页表的页表项从1024个变为原来的一半512个
2.引入一个叫作页目录指针表(Page Directory Pointer Table,PDPT)的页表新级别,它由4个64位表项组成
3.cr3控制寄存器包含一个27位的页目录指针表基地址字段,因为PDPT按32字节对齐
4.当把线性地址映射到4KB的页时(页目录项中的PS标志清零),32位线性地址按下列方式解释
cr3:指向一个PDPT
位31-30:指向PDPT四个中的一个
位29-21:指向页目录中512个的一个
位20-12:指向页表中的512个的一个
位11-0:4KB页中的偏移量
5.当把线性地址映射到2MB的页时(页目录项中的PS标志为1),32位线性地址按下列方式解释
cr3:指向一个PDPT
位31-30:指向PDPT四个中的一个
位29-21:指向页目录中512个的一个
位20-0:4MB页中的偏移量
linux中的分页
Linux 采用了一种同时适用于32位和64位系统的普通分因为模型
直到内核2.6.10,Linux 采用三级分页模型
内核2.6.11开始,Linux 采用四级分页模型:
页全局目录(Page Global Directory)
页上级目录(Page Upper Directory)
页中级目录(Page Middle Directory)
页表(Page Table)
对于没有激活PAE的32位系统,采用两级页表(页全局目录+页表)
对于启用了PAE的32位系统,采用三级页表(页全局目录+页中间目录+页表)
对于64位系统,采用三级页表还是四级页表由具体内核决定
2015.10.17
线性地址字段
PAGE_SHIFT:指定Offset字段的位数,在80x86处理器上为12
PAGE_SIZE为2^12=4KB
PAGE_MASK为0xfffff000,用于屏蔽Offset的所有位
PMD_SHIFT:指定Offste字段和Table字段的总位数,既页中间目录项可以映射的区域大小的对数
PAGE_MASK用于屏蔽Offset和Table的所有位
当未激活PAE时,PMD_SHIFT为22,PMD_SIZE为2^22=4MB
当激活PAE时,PMD_SHIFT为21,PMD_SIZE为2^21=2MB
PUD_SHIFT:确定页上级目录项能映射的区域的大小的对数
在80x86处理器中,PUD_SHIFT和PMD_SHIFT相同,PUD_SIZE为4MB或者2MB
PGDIR_SHIFT:确定页全局目录项能映射的区域的大小的对数
PTRS_PER_PTE,PTRS_PER_PMD,PTRS_PER_PUD,PTRS_PER_PGD:用于计算页表 页中间目录 页上级目录和页全局目录表中表项的个数。当PAE禁用时,它们产生的值分别为1024,1,1,1024。当PAE激活时,产生的值分别时512,512,1,4。
页表处理
pte_t,pmd_t,pud_t和pgd_t分别描述页表项 页中间目录项 页上级目录项和页全局目录项的格式
当PAE激活时,它们都是64位的数据类型,否则都是32位的数据类型。pgprot_t是另一个64位
(PAE激活时)或者32位(PAE禁止时)的数据类型,它表示与一个单独表项相关的保护标志。
_ _pte _ _pmd _ _pud _ _pgd 和 _ _ pgprot可以把无符号整数转换为相应的数据类型
pte_val pmd_val pud_val pgd_val 和pgprot_val则执行相反的转换
内核提供了许多宏和函数用于读或者修改页表表项:
1.如果相应的表项值为0,则宏pte_none pmd_none pud_none pgd_none返回值为1,否则返回0
2.宏pte_clear pmd_clear pud_clear 和 pgd_clear清除相应页表的一个表项,由此禁止进程使用由该表项映射的线性地址。ptep_get_and_clear()函数用于清除一个页表项并返回前一个值
3.set_pte set_pmd set_pud 和 set_pgd 向一个页表项写入指定的值
4.如果a 和 b 两个页表项指向同一页并且指定相同的访问优先级,那么pte_same(a,b)返回1,否则返回 0
5.如果页中间项e指向一个大型页(4MB或者2MB),那么pmd_larger(e)返回 1,否则返回 0
物理内存布局
一般来说Llinux 内核安装在RAM中的物理地址0x00100000开始的地方,即从第2MB开始
从第2MB开始的原因:
1.页框0由Bios使用
2.物理地址从0x000a0000到0x000fffff的范围通常留给Bios例程,即680KB到1MB
3.第1MB的其它页框为其他特定计算机保留
进程页表
进程的线性空间分成两部分:
1.从0x00000000到0xbfffffff的线性地址,无论进程运行在用户态还是内核态都可以寻址(前3GB)
2.从0xc0000000到0xffffffff的线性地址,只有内核态的进程可以寻址(第4GB)
宏PAGE_OFFSET 产生的值为0xc0000000,这就是进程在线性地址空间的偏移量,也是内核生存空间的开始之处
内核页表
内核维持着一组自己使用的页表,驻留在所谓的主内核页全局目录(master kenerl Page Global Directory)中
临时内核列表
在此阶段PAE未被激活,所以页上级目录和页中级目录相当于页全局目录,在此不再提及
临时页全局目录放在swapper_pg_dir变量中,临时页表在pg0变量处开始存放
为简单起见,我们假设内核使用的段 临时页表和128KB的内存范围能容纳于RAM前8MB空间里。
分页第一个阶段的目标时允许在实模式和保护模式下都能很容易地对8MB寻址。
因此,内核必须建立一个映射,把从0x00000000到0x007fffff的线性空间和0xc0000000到0xc07fffff的线性地址映射到从0x00000000到0x007fffff的物理地址。还句话说,内核在初始化的第一阶段,可以通过与物理地址相同的线性地址或者通过0xc0000000开始的8mb线性地址对RAM的前8MB仅进行寻址
固定映射的线性地址
固定映射的线性地址(fix-mapped linear address)基本上时一种类似于0xffffc000这样的常量线性地址,其对应的物理地址可以以任意方式建立。因此,每个固定映射的线性地址都映射一个物理内存的页框
2015.10.18.22:55