最近拿起深入理解linux内核这本书来看,开篇绪论一过,直接就是linux内存寻址。回首大学四年,我已经是第三次学习内存寻址了,但现在又忘记的差不多了。
第一次是看操作系统概论时学习过一遍分段和分页,当时学的比较模糊,只是读懂了流程,却不知道具体有什么用。
第二次是看深入理解计算机系统,里面有一部分讲述虚拟内存,当时读完后感觉以前我理解内存的迷惑已经全部解开了。
而这一次翻看,我又忘记了分段,只记得分页。于是打算边读边记重点,下面是我记录的重点内容:
80x86微处理器进行芯片级的内存寻址:
1、内存地址:
逻辑地址 -》【分段单元】-》线性地址-》【分页单元】-》物理地址
逻辑地址=段选择符(16位)+段内偏移(32位) 线性地址=[0, 0xffffffff] 物理地址=内存芯片级内存单元寻址
多CPU共享同一内存,RAM芯片上的读写必须是串行执行的,由内存仲裁器保证。编程观点看,内存仲裁器是由硬件电路管理,因此是隐藏的。
2、硬件中的分段:
Intel微处理器以两种不同的方式执行地址转换:实模式(real mode,的、为了维持处理器与早期模型兼容,并让操作系统自举)、保护模式(protected mode,大多数时候在此模式下执行地址转换)
段选择符(16位,也称段标识符):通过段选择符能够找到一个对应的段。=index[13](存段描述符的入口) + TI[1](GDT or LDT) + RPT[2](cs寄存器时指定CPU特权)
段寄存器(用于存放段选择符,加快访问段选择符的速度):cs(代码段寄存器)、ss(栈段寄存器)、ds(数据段寄存器)、es(可以存任意段)、fs(任意段)、gs(任意段)。
段描述符(64位,描述段的特征及段的起始线性地址):段描述符放在全局描述符表(GDT)或者局部描述符表(LDT)。通常只定义一个GDT,每个进程除了GDT中的段还要其他段则可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正在被使用的LDT的地址和大小放在ldtr控制寄存器中。广泛使用的:代码段描述符、数据段(栈段也是通过数据段实现的)描述符、任务状态段(保存处理器寄存器内容的段)描述符(只出现在GDT中)、局部描述符表描述符(只出现在GDT中,增加LDT时需要使用)。
非编程寄存器(不能被程序员设置的寄存器,只用来缓存段描述符,加快访问段描述符的速度,个数和段寄存器个数对应):每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装到对应的非编程CPU寄存器,从那时起,针对那个段的逻辑地址转换就可以不访问主存中的GDT或LDT,处理器只需要直接引用这个寄存器即可。仅当段寄存器的内容改变时,才有必要访问GDT或LDT。
GDT的第一项总是设为0,确保了空的段选择符的逻辑地址会被认为是无效的,引起一个处理器异常。
找到段描述符的线性地址:段选择符中的index有13位,所指定的段描述符的地址 = GDT或LDT首地址(gdtr或ldtr中) + index * 8 Byte(一个段描述符的大小8Byte)。GDT中段描述符的最大数目是2^13-1(第一个总是0)。
逻辑地址转换线性地址:通过段寄存器中的段选择符和gdtr(或ldtr)的内容,能够找到段描述符,段描述符中的Base字段则是段的起始线性地址,这个地址再加上32位的段内偏移就是该逻辑地址对应的线性地址。
个人理解说明:80x86等微处理器本身支持分段,为操作系统提供了分段的基础,而各个操作系统的利用硬件支持的方式不同。第3节说明Linux是如何利用硬件,并实现自己的分段的。
3、Linux中的分段:
四个主要的linux段:用户代码段、用户数据段、内核代码段、内核数据段。
相应的段选择符和段描述符的内容是固定的。段选择符由宏__USER_CS、__USER_DS、__KERNEL_CS、__KERNEL_DS分别定义。
与段相关的线性地址从0开始,达到2^32-1的寻址限长,这意味着用户态或内核态下的所有进程可以使用相同的逻辑地址(相当于没有把内存分段)。所有段的Base段都是从0开始,这意味着Linux下逻辑地址和线性地址是相等的,及段内偏移等于线性地址(基本上废弃了分段机制)。
只要当前特权级被改变,一些段寄存器必须相应的更新。
单处理器系统中只有一个GDT,多处理器系统中每个CPU对应一个GDT。所有的GDT都放在cpu_gdt_table数组中,所有GDT的大小和地址都存放在cpu_gdt_descr数组中。
每个GDT包含18个段描述符和14个空的,中间插入14个空的是为了使经常一起访问的描述符能处于同一个32字节的硬件高速缓存行中。
18个段描述符分别是:4个主要段(用户代码段、用户数据段、内核代码段、内核数据段),任务状态段(TTS),包括缺省LDT的段,3个局部线程存储(TLS)段,与高级电源管理(AMP)相关的3个段,与支持即插即用(PnP)功能的BIOS服务程序相关的5个段,被内核用来处理“双重错误”异常的特殊TSS段。
大多数用户态下的linux程序不使用LDT,内核中定义了一个缺省的LDT供大多数进程共享,其描述符存在上述的GDT中。缺省的LDT存放在default_ldt数组中。
4、硬件中的分页
分页单元把所有的RAM分成固定长度的页框(page frame,也叫物理页,主存的一个小块),每个页框包含一个页(page,数据块,可以放在任何页框或磁盘中),页和页框长度一致。
页表:把线性地址映射到物理地址的数据结构称为页表(page table)。页表存放在主存中,并在启用分页单元之前必须由内核对页表进行适当的初始化。
从80386开始,所有的80x86处理器都支持分页,它通过设置cr0寄存器的PG标志启用,如果PG=0,则线性地址就被直接解释成物理地址。
从80386开始,Intel处理器的分页单元处理4KB的页。
32位的线性地址=Directory[10] + Table[10] + Offset[12]
先通过Directory偏移在页目录表中找到页表起始地址,再通过Table偏移在页表中找到块起始地址,再通过Offset偏移在块内找到地址。页目录物理地址存放在控制寄存器cr3中。
页目录和页表具有相同的结构,此模式为二级模式,如果使用一级模式,则页表有2^20个表项,每一项4字节,则每个进程都需要4MB的RAM存放页表,二级模式通过只为进程实际使用的那些虚拟内存区请求页表来减少页表所占用的内存大小。
不管是页目录、页表、页,都是以页框为单位存放。
未完......
第三次学习内存寻址笔记
原文:https://www.cnblogs.com/xjjsk/p/9225681.html