蓝盟IT小贴士,来喽!
分阶段内存管理
在16位的8086时代,CPU为了能够寻址16位以上的地址能够表示的最大空间(64KB ),引入了分段寄存器。
通过将内存空间分割为几个段,并使用段基地址段内的偏移量访问内存,现在可以访问1MB的内存空间
此时,段寄存器有4个,分别指不同的段。
cs:代码段
ds:数据段
ss:堆栈段
es :扩展段
此时,段寄存器中存储有基于段的地址。 请注意。 地址。
从ip寄存器读出指令时,实际上是cs:ip,从sp寄存器访问堆栈时,实际上是ss:sp。
当我看到网上很多文章介绍段存储器,介绍段寄存器的时候,就停在这里了,但实际上进入32位时代后情况发生了很大的变化,只说上面的部分其实会误导很多人。
变化1 :
在32位时代,分段寄存器中增加了fs、gs两个。 这两个段寄存器有特殊的用途。
备选方案2 :
存储在段寄存器中的不是基于段的地址,而是称为段选择器的地址。 请注意。 所有的变化都从这里开始。
段寄存器为16位宽,本来这16位是物理内存地址,现在变成了这样的结构。
实际上,当前的级寄存器存储有编号。 几号? 表中表项的编号,该表可以是全局描述符表GDT,也可以是本地描述符表LDT。
那到底是哪个表? 由段选择器的倒数第三位决定。 如果这个位数为0,则为GDT,否则为LDT。
那这两个表是什么? 表里装的是什么? 你怎么指定地址?
这两个表中的表条目称为段描述符,用于描述内存段的信息,包括段的基地址、最大长度和访问属性。 这个长度如下所示。
CPU中分别添加了指向两个表(gdtr和ldtr )的寄存器。
在寻址时,CPU首先基于段寄存器的号码,通过gdtr或ldtr来GDT/LDT检索对应的段描述符,然后检索该段的基地址,最后结合段内的偏移并记录
也就是说,在16位模式下,段寄存器中是直接地址,相当于指针,但变为32位时则成为句柄或辅助指针。
基于分页的内存管理
与分级内存管理相比,分级内存管理可能更熟悉。
操作系统以“页”为单位将内存空间分为许多页。 此页面的大小默认为4KB。 每个进程都有一个虚拟的、完整的地址空间,进程使用的页面映射到实际的物理内存。 程序使用的地址是虚拟地址,CPU在运行时自动翻译为实际的物理地址。既然要翻译,就需要有记录虚拟地址和物理地址映射关系的地方,只有基于这种关系才能完成翻译。
这种映射关系由页面表完成。
页表用于记录虚拟内存页与物理内存页之间的映射关系,每个页表条目都记录页的映射关系。 但是,由于进程的地址空间很大,计算所需的页表条目也非常多。 实际上,进程地址空间中的许多页没有实际使用,也没有映射关系是徒劳的。
为了解决这个问题,CPU引入了多级页表机制。 32位通常是2级页面表。 如下所示。
将虚拟地址分为页面目录索引、页面表索引、页面内偏移量三个段。
如果线程切换时同时发生进程切换,则CPU中的CR3寄存器将加载当前进程的页面目录地址。
寻址时,通过CR3在等级1按下表索页,最终找到对应的物理存储器页,结合页内的偏移值,实现最终的存储器寻址。
现代操作系统的实际情况
一旦学习了这两种内存管理方式,很多人就会变得无知。
目前,操作系统使用的是什么方式? 好像是寻呼,为什么好像还有分段寄存器? 到底怎么样了呢?
结论是,答案是组合了分割寻呼的存储器管理方式
首先,明确前提是非常重要的。 无论是分区还是分页,这都是x86架构CPU的内存管理机制,这两者同时存在。 在保护模式下,操作系统并不是二者择一的。
既然是同时存在的,为什么现在翻译内存地址的时候,在分页的故事中,很少谈分段的事情呢?
这一切都是因为一个原因。 操作系统通过巧妙的设置“屏蔽”了该段的存在。
接下来分析一下OS是怎么实现那个的。 彻底弄清楚背后猫的厌倦!
分段寄存器
让我们从段寄存器开始。 在windows7的32位系统上使用调试器(我使用的WinDbg )自由调试程序。 真的,自由地使用记事本、浏览器和Word调试谁想看。
让我们在插队的上下文中看看。 程序运行期间,段寄存器里有什么?
让我们来看看几个主要的段寄存器的内容。
cs: 001b
ds: 0023
ss: 0023
es: 0023
PS:的结果可能因Windows版本而异,但这并不重要,也不影响问题分析。
只有0x001b和0x0023两个值。 如前所述,这不是地址,而是段选择器。 让我们根据段选择器的格式展开,看看这两个值指向哪个段描述符。十六进制: 001b
二进制: 0000000000011 0 11
段编号: 3
表型: GDT
权限级别: Ring3
十六进制数: 0023
二进制: 00000000100 11
段编号: 4
表型: GDT
权限级别: Ring3
也就是说,cs段是指GDT的第三个条目,其他三个寄存器是指GDT的第四个条目。
接下来,让我们来看看这个神秘的GDT到底是什么。 虽然很多人学习了内存管理,但是可能还没有见过实际的GDT中包含了什么样的数据。
GDT位于操作系统的内核地址空间中,在Windows中有两种显示方法。 一个是Windbg,另一个是一些ARK工具,我会选择在这里使用PChunter这个神器进行显示。
如上所述,GDT中的表项是段描述符,这是一种相对复杂的数据格式。 幸运的是,这个神器解析了段描述符,使用表格字段进行了展示,所以我们看起来很轻松。
不说多余的话,让我们来看看这个神秘的GDT :
请注意第三个表项目和第四个表项目哦。 看看那些基地址。 都是0x00000000。
再看看它们的极限值,都是0x000FFFFF。 请注意,此限制的单位不是字节,而是第——页。 如果该值乘以页面大小4KB,则为0xFFFFF000。 也就是说,该段的上限为0xFFFFF000页,如果添加该页的大小,则为0xFFFFFFFF
所以,重点来了! 看到了吗? GDT的第三和第四个表项中介绍的这两个段的基地址为0x00000000,整个段的大小为0xFFFFFFFF。 这意味着什么? 也就是说,整个进程的地址空间实际上是段。
也就是说,进程的代码段、数据段、堆栈段、扩展段这4个段全部重叠,并且整个进程地址空间共计4GB为一个段。
虽然说是段子,但实际上等于没有分开。 除此之外,段的基地址都是0,进行地址翻译时,段是否存在没有太大变化。
一句话总结,操作系统被这样划分,实际上就是将该划分虚构了!
以上是Windows的情况,让我们再来看看Linux的情况。
使用GDB自由调试ELF32的可执行文件,使用info r命令确认寄存器的状况。
段寄存器有0x23和0x2b两种。十六进制数: 0023
二进制: 00000000100 11
段编号: 4
表型: GDT
权限级别: Ring3
十六进制: 002B
二进制: 0000000000101 0 11
段编号: 5
表型: GDT
权限级别: Ring3
因为在Linux上没有找到可以直接用什么命令和工具看GDT的方法(如果记得的话请一定要告诉我哦),所以我去源代码中寻找了答案:
看到了吗? 这两部分中描述的段与Windows相同,基地址为0,大小为4GB。
Windows和Linux都选择了这样将CPU架空的分段存储器管理机构。
但是,这两种操作系统都是这种情况,但并不意味着分段机制没有完全使用,要知道CPU的任务管理TSS还需要使用。
文/上海蓝盟 IT外包专家