1. x86 平台
E820如何从硬件获得内存布局
背景
在操作系统开始管理内存之前,首先要获取物理内存的信息,比如共有多少物理地址是可用的? 有哪些物理地址是被ACPI(Advanced Configuration and Power Interface)数据使用? 这些信息从何而来?
e820就是BIOS像x86架构(包括x86_64)上的操作系统引导程序提供物理内存信息的功能。当请求BIOS中断号15H,并且置操作码AX=E820H的时候,BIOS就会向调用者报告可用的物理地址区间等信息,e820由此得名。
什么是E820?
E820是基于X86的计算机系统的BIOS将内存映射报告给操作系统或引导加载程序的便利工具。通过将AX寄存器设置为十六进制值E820,可以通过INT15H调用对其进行访问,报告哪些内存地址范围可用,哪些保留给BIOS使用。"BIOS-e820"通常是启动Linux内核报告的第一件事,可以通过dmesg命令看到它。使用dmesg可以看到相关的信息:
hacher@reaper:~# dmesg | grep BIOS-e820
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009bbff] usable #01
[ 0.000000] BIOS-e820: [mem 0x000000000009bc00-0x000000000009ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000bb4f2fff] usable #02
[ 0.000000] BIOS-e820: [mem 0x00000000bb4f3000-0x00000000bb532fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb533000-0x00000000bb543fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb544000-0x00000000bb554fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb555000-0x00000000bb55dfff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb55e000-0x00000000bb565fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb566000-0x00000000bb57ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb580000-0x00000000bb580fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb581000-0x00000000bb58ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb590000-0x00000000bb594fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb595000-0x00000000bb59bfff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb59c000-0x00000000bb59cfff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb59d000-0x00000000bb59ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb5a0000-0x00000000bb5a2fff] ACPI data
[ 0.000000] BIOS-e820: [mem 0x00000000bb5a3000-0x00000000bb5a7fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb5a8000-0x00000000bb5b5fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb5b6000-0x00000000bb5cbfff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb5cc000-0x00000000bb5cdfff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb5ce000-0x00000000bfffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fed1b000-0x00000000fed1ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000ffa00000-0x00000000ffbfffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000ffe00000-0x00000000ffffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x00000002bfffffff] usable #03
其中usable的区间就是实际被映射到物理内存上的地址空间,上面标注出来的{#01,#02,#03}区间,就是我主要的可用的物理地址区间。
表示
Usable:表示已经被映射到物理内存的物理地址。
Reserved:表示这些区间是没有被映射到任何地方,不能当作RAM来使用,但是Kernel可决定将该区间映射到其他地方,比如PCI设备。通过读取/proc/iomem可以查看物理地址空间的映射情况,可以知道这些reserved的空间,是如何进一步分配给不同的设备来使用的。
ACPI data:表示映射到用来存放ACPI数据的RAM空间,操作系统应该将ACPI Table读入到这个区间内。
ACPI NVS:表示映射到用来存放ACPI数据的非易失性存储空间,操作系统不能使用。
Unusable:表示检测到发生错误的物理内存。这个在上面例子里没有,因为比较少见。
随着内核的启动,内核还会修改E820的信息{#11,#12,#13}。
hacher@reaper:~# dmesg | grep e820
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009bbff] usable
[ 0.000000] BIOS-e820: [mem 0x000000000009bc00-0x000000000009ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000bb4f2fff] usable
[ 0.000000] BIOS-e820: [mem 0x00000000bb4f3000-0x00000000bb532fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb533000-0x00000000bb543fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb544000-0x00000000bb554fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb555000-0x00000000bb55dfff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb55e000-0x00000000bb565fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb566000-0x00000000bb57ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb580000-0x00000000bb580fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb581000-0x00000000bb58ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb590000-0x00000000bb594fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb595000-0x00000000bb59bfff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb59c000-0x00000000bb59cfff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb59d000-0x00000000bb59ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb5a0000-0x00000000bb5a2fff] ACPI data
[ 0.000000] BIOS-e820: [mem 0x00000000bb5a3000-0x00000000bb5a7fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb5a8000-0x00000000bb5b5fff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb5b6000-0x00000000bb5cbfff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000bb5cc000-0x00000000bb5cdfff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000bb5ce000-0x00000000bfffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fed1b000-0x00000000fed1ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000ffa00000-0x00000000ffbfffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000ffe00000-0x00000000ffffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x00000002bfffffff] usable
[ 0.001803] e820: update [mem 0x00000000-0x00000fff] usable ==> reserved #11
[ 0.001805] e820: remove [mem 0x000a0000-0x000fffff] usable #12
[ 0.002844] e820: update [mem 0xbc000000-0xffffffff] usable ==> reserved #13
[ 0.449865] e820: reserve RAM buffer [mem 0x0009bc00-0x0009ffff]
[ 0.449867] e820: reserve RAM buffer [mem 0xbb4f3000-0xbbffffff]
e820是和BIOS的一个中断相关的,具体说是int 0x15。之所以叫e820是因为在用这个中断时ax必须是0xe820。这个中断的作用是得到系统的内存布局。因为系统内存会有很多段,每段的类型属性也不一样,所以这个查询是“迭代式”的,每次求得一个段。
我们看内核源代码。主要涉及两个文件:arch/x86/boot/memory.c和arch/x86/kernel/e820_32.c。我们 已经很幸运了,这部分代码已经用C重写过了。你可能会奇怪,启动调用e820时我们还在实模式,怎么能用C呢?答案是,这里用的是16位的C。gcc早已 经支持.code16 gcc模式了。
看detect_memory_e820()函数,里面就是e820的本质。它把int 0x15放到一个do-while循环里,每次得到的一个内存段放到struct e820entry里,而struct e820entry的结构正是e820返回结果的结构!而像其它启动时获得的结果一样,最终都会被放到boot_params里,e820被放到了 boot_params.e820_map。
如果你对struct e820entry还有疑问,你可以看一下arch/x86/kernel/e820_32.c::print_memory_map(),看看里面是怎么使用它的。
当然了,在arch/x86/boot/memory.c里,你还会看到另外两个利用int 0x15查询内存的函数,不过用途不一样了。
附:
boot_params结构体定义,其中E820MAX定义为128:
struct e820entry {
__u64 addr;
__u64 size;
__u32 type;
} __attribute__((packed));
struct boot_params {
struct screen_info screen_info;
struct apm_bios_info apm_bios_info;
__u8 _pad2[12];
struct ist_info ist_info;
__u8 _pad3[16];
__u8 hd0_info[16];
__u8 hd1_info[16];
struct sys_desc_table sys_desc_table;
__u8 _pad4[144];
struct edid_info edid_info;
struct efi_info efi_info;
......
} __attribute__((packed));
通过bios获取系统内存布局代码如下:
static int detect_memory_e820(void){ ... }
这个函数执行完毕后,boot_params.e820_map就含有了系统内存布局图。
函数关键部分解释如下:
获取启动参数 boot_params里的 e820_map数组首地址。
通过中断0x15调用bios例程获得一个内存段的信息,这条语句是按照AT&T的汇编语法格式写的,具体语法可以查看相关资料。
2. ARM 平台



