内存是怎么映射到物理地址空间的?内存是连续分布的吗?( 二 )


服务器上Interleaving更是不可或缺 , 它的粒度更细 , 可以达到数十bytes层级的interleave , 它和内存的其他特性 , 如类似磁盘阵列RAID的内存spare, mirror特性 , 构成了复杂异常的内存映射系统 。在BIOS里面 , 台式机/笔记本内存映射相对简单 , 只有一个大表和数十个寄存器;而在服务器BIOS中 , 有数个相互关联的大表和寄存器阵列来解码(decode)内存的请求 , 代码的硬件逻辑也是相当复杂 。关于它 , 我会有一篇专栏文章讨论地址译码和地址反向解码 , 详细内容那里再说 , 这里只需要知道 , 物理内存分布在各个DIMM上就够了 。
物理地址到内存单元的反推BIOS实际上一手导演的内存的分配 , 它当然可以从任何物理地址反推回内存的单元地址 。我们可以用下面一组数据来唯一确定某个内存单元:
Channel #;DIMM #; Rank #;Bank #;Row #;Column #在内存分配表缺失的情况下 , BIOS甚至可以通过它填过的寄存器重建这个映射表 。但实际上BIOS并不希望一般用户知道这些信息 , 因为有安全性问题 。
暴露内存信息容易招来内存侧信道攻击(Side Channel) , 比较有名的有Row hammer攻击 。简单的来说它是通过反复写某个内存单元 , 借助内存的特性 , 希望影响相邻Row/Column的内容 。详细内容可以参考这里:
内存不刷新会怎样?有趣的内存物理攻击和旁路攻击
有些情况确实需要知道这些信息 , 就是内存出错的时候 。和大家想象的不同 , 内存是会出错的 。尤其云服务器中内存的出错是十分频繁的 。出错起来也千奇百怪 , 开始可能是偶尔的随机错误 , 经过ECC等校正后 , 就再也不会复现;而有时是某个Bit总是出错 , 进而慢慢的整个row、column或者相邻的cell开始出错 , 从可以纠正的错误变成不可修正的错误 , 导致服务器必须停机 。这时候就必须知道哪个内存坏了 , 进而换掉它 。BIOS的报错是通过WHEA:
报告给操作系统 , 但这个信息里面只有物理地址 , 如何才能知道是哪个内存单元坏了呢?在linux上面可以通过edca(参考资料4) , 有编程经验的同学可以通过edca的程序接口(参考资料3) , 可以得到更加丰富的信息 。
如何关掉Interleaving对内存有特殊需求的朋友 , 如果希望内存连续 , 可以在BIOS里面关闭所有的Interleaving来达成这个目标:

内存是怎么映射到物理地址空间的?内存是连续分布的吗?

文章插图
 
注意是所有的 。之后可以通过SMBIOS来看到内存分布信息(dmidecode) 。
结论BIOS作为内存的大管家 , 也负责内存的分配和映射memory map 。它会把这些信息通过E820, GetMemoryMap函数和SMBIOS传递给操作系统 。操作系统在此基础上再建立页表 , 产生虚拟地址 。

【内存是怎么映射到物理地址空间的?内存是连续分布的吗?】


推荐阅读