e820与kernel物理内存映射

我们都对操作系统如何管理内存有一定的了解,然而,在操作系统开始管理内存之前,首先要获取物理内存的信息,比如一共有多少物理地址是可用的,有哪些物理地址是被ACPI(Advanced Configuration and Power Interface)数据使用,这些信息从何而来呢?

e820就是BIOS像x86架构(包括x86_64)上的操作系统引导程序提供物理内存信息的功能。当请求BIOS中断号15H,并且置操作码AX=E820H的时候,BIOS就会向调用者报告可用的物理地址区间等信息,e820由此得名。

Linux内核也通过这种机制来获得物理地址信息,使用dmesg可以看到相关的信息:

[    0.000000] BIOS-provided physical RAM map:
[    0.000000]  BIOS-e820: 0000000000000000 - 000000000009e800 (usable)
[    0.000000]  BIOS-e820: 000000000009e800 - 00000000000a0000 (reserved)
[    0.000000]  BIOS-e820: 00000000000e0000 - 0000000000100000 (reserved)
[    0.000000]  BIOS-e820: 0000000000100000 - 0000000020000000 (usable)  #511MB
[    0.000000]  BIOS-e820: 0000000020000000 - 0000000020200000 (reserved)
[    0.000000]  BIOS-e820: 0000000020200000 - 0000000040000000 (usable)  #510MB
[    0.000000]  BIOS-e820: 0000000040000000 - 0000000040200000 (reserved)
[    0.000000]  BIOS-e820: 0000000040200000 - 00000000aac0d000 (usable)  #1706MB
[    0.000000]  BIOS-e820: 00000000aac0d000 - 00000000aad8e000 (reserved)
[    0.000000]  BIOS-e820: 00000000aad8e000 - 00000000aad95000 (usable)
[    0.000000]  BIOS-e820: 00000000aad95000 - 00000000aad96000 (reserved)
[    0.000000]  BIOS-e820: 00000000aad96000 - 00000000aad97000 (usable)
[    0.000000]  BIOS-e820: 00000000aad97000 - 00000000aadb8000 (reserved)
[    0.000000]  BIOS-e820: 00000000aadb8000 - 00000000aadc6000 (usable)
[    0.000000]  BIOS-e820: 00000000aadc6000 - 00000000aade8000 (reserved)
[    0.000000]  BIOS-e820: 00000000aade8000 - 00000000aaf23000 (usable)
[    0.000000]  BIOS-e820: 00000000aaf23000 - 00000000aafe8000 (ACPI NVS)
[    0.000000]  BIOS-e820: 00000000aafe8000 - 00000000aaffd000 (usable)
[    0.000000]  BIOS-e820: 00000000aaffd000 - 00000000ab000000 (ACPI data)
[    0.000000]  BIOS-e820: 00000000ab000000 - 00000000b0000000 (reserved)
[    0.000000]  BIOS-e820: 00000000e0000000 - 00000000e4000000 (reserved)
[    0.000000]  BIOS-e820: 00000000fec00000 - 00000000fec01000 (reserved)
[    0.000000]  BIOS-e820: 00000000fed10000 - 00000000fed14000 (reserved)
[    0.000000]  BIOS-e820: 00000000fed18000 - 00000000fed1a000 (reserved)
[    0.000000]  BIOS-e820: 00000000fed1c000 - 00000000fed20000 (reserved)
[    0.000000]  BIOS-e820: 00000000fee00000 - 00000000fee01000 (reserved)
[    0.000000]  BIOS-e820: 00000000ff980000 - 00000000ffc00000 (reserved)
[    0.000000]  BIOS-e820: 00000000ffd80000 - 0000000100000000 (reserved)
[    0.000000]  BIOS-e820: 0000000100000000 - 000000014f800000 (usable)  #1272MB

上面是我在自己计算机上得到的数据,其中usable的区间就是实际被映射到物理内存上的地址空间,上面标注出来的四个区间,就是我主要的四个可用的物理地址区间了,大约4GB。

  • Usable:已经被映射到物理内存的物理地址。
  • Reserved:这些区间是没有被映射到任何地方,不能当作RAM来使用,但是kernel可以决定将这些区间映射到其他地方,比如PCI设备。通过检查/proc/iomem这个虚拟文件,就可以知道这些reserved的空间,是如何进一步分配给不同的设备来使用了。
  • ACPI data:映射到用来存放ACPI数据的RAM空间,操作系统应该将ACPI Table读入到这个区间内。
  • ACPI NVS:映射到用来存放ACPI数据的非易失性存储空间,操作系统不能使用。
  • Unusable:表示检测到发生错误的物理内存。这个在上面例子里没有,因为比较少见。

内核读到这些信息后,将其保存在e820map结构体中,有两个副本,一个符号名叫e820,还有一个符号名叫e820_saved。具体的数据结构可以参考arch/x86/include/asm/e820.h。随着内核的启动,内核还会修改e820的信息。

[    0.000000] e820 update range: 0000000000000000 - 0000000000010000 (usable) ==> (reserved)
[    0.000000] e820 remove range: 00000000000a0000 - 0000000000100000 (usable)
[    0.000000] e820 update range: 00000000ab000000 - 0000000100000000 (usable) ==> (reserved)

比如在我的系统上发生了这样三次e820的改动,这些改动已经与BIOS没有任何关系了,只是内核自己通过修改自己的内存数据结构,来改变自己对内存区间的使用。

还有一个典型的例子是当内核启动参数中有置顶memmap这样的参数项时,内核会修改指定的usable的区间为reserved,这样内核就不能将虚拟地址映射到这些物理地址空间了,换言之就是不能使用这块物理内存了(其实通过ioremap还是可以将其映射到内核的虚拟地址空间的,但是这些行为都是自己控制的而不会受到其他程序的干扰)。这样当我们需要自己管理某段物理内存而不希望内核干预时就很有用处,比如基于物理内存的文件系统,就可以这样实现。

内核中还提供了一系列操作e820数据结构的函数,函数声明都在arch/x86/include/asm/e820.h,相应的定义都在arch/x86/kernel/e820.c中。文章开头看到的这段信息,就是由void __init e820_print_map(char *who)来打印的。

在这个过程中,e820_saved始终保持原始的状态不变,以便查询BIOS提供的真实映射信息,不过内核目前好像没有使用这个数据结构。我曾经有使用过它,用来检查某个物理地址是否确实是物理内存。