我去,又又又被内存坑了( 二 )


文章图片
也就是说 , 在16位模式下 , 段寄存器中直接就是一个地址 , 相当于一个指针 , 而到了32位下 , 则变成了一个句柄 , 或者说二级指针了 。
分页式内存管理
相比分段式内存管理 , 可能大家对分页式内存管理要熟悉的多 。
操作系统将内存空间按照“页”为单位划分了很多页面 , 这个页的大小默认是4KB(当然可以改的) , 各进程拥有虚拟的完整的地址空间 , 进程中使用到的页面会映射到真实的物理内存上 , 程序中使用的地址是虚拟地址 , CPU在运行时自动将其翻译成真实的物理地址 。
我去,又又又被内存坑了
文章图片
既然要翻译 , 那就得有地方记录虚拟地址和物理地址的映射关系 , 只有根据这个关系 , 才能完成翻译 。
这个映射关系 , 是通过页表来完成的 。
页表是用来记录虚拟内存页面和物理内存页面之间的映射关系的 , 每一个页表项记录一个页面的映射关系 。 但进程的地址空间很大 , 这样算下来需要的页表项的数量也会非常多 。 而实际上进程地址空间中很多页面都没有真正使用 , 也就没有映射关系 , 这样是一种浪费 。
为了解决这个问题 , CPU引入了多级页表的机制 , 在32位下一般是2级页表 , 像下面这样:
我去,又又又被内存坑了
文章图片
将虚拟地址划分了三段:页目录索引、页表索引、页内偏移 。
线程切换时 , 如果同时发生了进程切换 , CPU中的CR3寄存器将会加载当前进程的页目录地址 。
在寻址的时候 , 通过CR3 , 一级一级按表索页 , 最终找到对应的物理内存页面 , 再结合页面内的偏移值 , 实现最终的内存寻址 。
现代操作系统实际情况
我去,又又又被内存坑了】学完了这两种内存管理方式 , 很多人就要懵了:
现在操作系统到底用的哪种方式?好像是分页 , 但为什么段寄存器好像还是有 , 到底是怎么一回事?
先说结论 , 答案就是:分段+分页相结合的内存管理方式
首先要明确一个前提 , 这一点非常非常重要:无论是分段还是分页 , 这都是x86架构CPU的内存管理机制 , 这俩是同时存在的(保护模式下) , 并不是让操作系统二选一!
既然是同时存在的 , 那为什么现在将内存地址翻译时 , 都是讲分页 , 而很少谈到分段呢?
这一切的一切 , 都是因为一个原因:操作系统通过巧妙的设置 , ‘屏蔽’了段的存在 。
操作系统怎么做到这一点的 , 接下来我们就来分析一下 , 彻底弄清楚背后的猫腻!
段寄存器
让我们从段寄存器出发 , 在Win732位系统上 , 使用调试器(我用的WinDbg)随意调试一个程序 , 真的 , 随意 , 记事本、浏览器、Word , 你看上谁就调试谁 。
在中断的上下文中看一下 , 程序在执行时 , 段寄存器里面到底装了啥?
我去,又又又被内存坑了
文章图片
来看下几个主要的段寄存器的内容:
cs:001bds:0023ss:0023es:0023PS:可能不同版本的Windows上面的结果不一样 , 但这不重要 , 不影响我们分析问题 。
只有0x001b和0x0023两个值 , 前面我们说了 , 这不是一个地址 , 而是一个段选择子 , 按照段选择子的格式展开来看一下这两个值指向的是哪个段描述符:
十六进制:001b二进制:0000000000011011段序号:3表类型:GDT特权级:Ring3十六进制:0023二进制:0000000000100011段序号:4表类型:GDT特权级:Ring3也就是说 , cs段指向的是GDT中的第3个表项 , 其他三个寄存器指向的是GDT中的第4个表项 。