本文采用 Linux 内核 v3.10 版本 x86_64 架构
一、内存探测接口
x86 处理器通过 int 15h
BIOS 中断 获取系统内存布局,其中 15h
是中断号。根据 AX 寄存器的值不同,主要有 3 种常见的方式:0xE820
,0xE801
,0x88
。
其中,0xE820 是主探测接口,0xE801
、0x88
作为 0xE820
接口的补充。
INT 15h, AX=E820h - Query System Address Map
该接口返回已安装的内存映射以及为 BIOS 保留的物理内存区域。每次调用该 API,只会返回一段物理内存的信息,信息中会指示内存类型。为了获取完整的内存映射,需要多次调用该接口。
Input:
Register | Contents | Description |
---|---|---|
EAX | Function Code | E820h |
EBX | Continuation | Contains the "continuation value" to get the next run of physical memory. This is the value returned by a previous call to this routine. If this is the first call, EBX must contain zero. |
ES:DI | Buffer Pointer | Pointer to an Address Range Descriptor tructure which the BIOS is to fill in. |
ECX | Buffer Size | The length in bytes of the structure passed to the BIOS. The BIOS will fill in at most ECX bytes of the structure or however much of the structure the BIOS implements. The minimum size which must be supported by both the BIOS and the caller is 20 bytes. Future implementations may extend this structure. |
EDX | Signature | 'SMAP' - Used by the BIOS to verify the caller is requesting the system map information to be returned in ES:DI. |
该接口通过寄存器传参,共有 5 个参数:
- EAX -- 功能码,E820h
- EBX -- 首次调用时,必须设置为 0。当调用后,EBX 中包含下次运行的物理地址,如果该值为 0,说明完成探测。
- ES:DI -- Buffer 指针,BIOS 会将探测结果填充到指针指向的 Buffer 中。
- ECX -- 以字节为单位的 Buffer 大小,最小为 20 字节。
- EDX -- 签名,ASCII 码 “SMAP”,用来验证调用者。
Output:
Register | Contents | Description |
---|---|---|
CF | Carry Flag | Non-Carry - indicates no error |
EAX | Signature | 'SMAP' - Signature to verify correct BIOS revision. |
ES:DI | Buffer Pointer | Returned Address Range Descriptor pointer. Same value as on input. |
ECX | Buffer Size | Number of bytes returned by the BIOS in the address range descriptor. The minimum size structure returned by the BIOS is 20 bytes. |
EBX | Continuation | Contains the continuation value to get the next address descriptor. The actual significance of the continuation value is up to the discretion of the BIOS. The caller must pass the continuation value unchanged as input to the next iteration of the E820 call in order to get the next Address Range Descriptor. A return value of zero means that this is the last descriptor. Note that the BIOS indicate that the last valid descriptor has been returned by either returning a zero as the continuation value, or by returning carry. |
接口的输出结果,也保存在 5 个寄存器中:
- CF -- 状态寄存器 EFLAFS 的 CF 标志位用来指示请求是否出错。当 CF 为 0 是,指示未发生错误。
- EAX -- 签名,ASCII 码 “SMAP”。
- ES:DI -- Buffer 指针。与输入一致。
- ECX -- Buffer 大小。BIOS 返回数据的大小。
- EBX -- 指示是否需要继续查询。当该值为 0 时,表示已查询到最后一段内存。
探测到的内存信息会保存到 buffer 中,其格式如下:
- Base address -- 表示内存段的基地址,64 位。
- Length -- 该段内存的长度,64 位。
- Type -- 内存类型,32 位。
内存类型包括以下几种:
- Type 1: Usable (normal) RAM
- Type 2: Reserved - unusable
- Type 3: ACPI reclaimable memory
- Type 4: ACPI NVS memory
- Type 5: Area containing bad memory
其中,ACPI reclaimable memory(ACPI 可回收内存)中保存着 ACPI 表(ACPI tables),当 ACPI 表使用完成后,这部分内存就是可用的,所以被称为可回收内存。在分配物理内存时,内存类型 2、4、5(Reserved,ACPI Non-Volatile-Sleeping,bad )不应被分配。
由于该接口返回的是未排序的内存列表,可能包含不可用的或重叠的内存区域,所以需要对该列表进行后期处理。
INT 15h, AX=E801h - Get Memory Size for Large Configurations
该接口最初是为 EISA(Extended Industry Standard Architecture) 服务器定义的,能够报告最高 4 GB 的 RAM。
Input:
Register | Contents | Description |
---|---|---|
AX | Function Code | E801h |
Output:
Register | Contents | Description |
---|---|---|
CF | Carry Flag | Non-Carry - indicates no error |
AX | Extended 1 | Number of contiguous KB between 1 and 16 MB, maximum 0x3C00 = 15 MB. |
BX | Extended 2 | Number of contiguous 64 KB blocks between 16 MB and 4 GB. |
CX | Configured 1 | Number of contiguous KB between 1 and 16 MB, maximum 0x3C00 = 15 MB. |
DX | Configured 2 | Number of contiguous 64 KB blocks between 16 MB and 4 GB. |
输出结果保存在 5 个寄存器中:
- CF -- 指示查询是否出错。当 CF 为 0 时,指示未发生错误。
- AX -- 在 1MB 到 16 MB 之间的内存,以 KB 为单位。最大为 0x3C00,即 15 MB 内存。
- BX -- 在 16 MB 到 4 GB 之间的内存,以 64 KB 为单位。
- CX -- 同 AX。
- DX -- 同 BX。
为什么 AX 的最大值是 0x3C00 ?这是由于历史原因导致的。在 80286 时代,ISA 总线由 8 位扩展到了 24 位,24 位的地址线最大寻址空间为 16M,而15 MB ~ 16 MB 的空间要用于 ISA 设备的内存映射,不能自由使用,这段内存也被称为 "ISA Memory Hole" 。
INT 15h, AH=88h - Get Extended Memory Size
这是一个比较原始的接口。 该接口返回 1 MB 以上的连续内存值,但是由于返回的是 16 位值(以 KB 为单位),因此其能返回的最大值会略低于 64 MB。也就是说,该接口会返回 1MB ~ 64 MB 之间的内存。
Input:
Register | Contents | Description |
---|---|---|
AH | Function Code | 88h |
Output:
Register | Contents | Description |
---|---|---|
CF | Carry Flag | Non-Carry - indicates no error |
AX | Memory Count | Number of contiguous KB above 1 MB. |
注:
1、以上三个接口,详见 INT 15h, AX=E820h 。
2、关于 x86 内存映射,详见 Memory Map (x86)。
3、关于内存类型,详见 Address Range Types。
二、源码解析
2.1 数据结构
2.1.1 e820entry
// file: arch/x86/include/uapi/asm/e820.h
struct e820entry {
__u64 addr; /* start of memory segment */
__u64 size; /* size of memory segment */
__u32 type; /* type of memory segment */
} __attribute__((packed));
e820entry
结构体表示探测到的内存段信息,大小为 20 字节,包含 3 个字段:
- addr -- 内存段的起始地址
- size -- 内存段的大小
- type -- 内存段的类型
其中,type 有以下几种类型:
// file: arch/x86/include/uapi/asm/e820.h
#define E820_RAM 1
#define E820_RESERVED 2
#define E820_ACPI 3
#define E820_NVS 4
#define E820_UNUSABLE 5
2.1.2 biosregs
// file: arch/x86/boot/boot.h
/* bioscall.c */
struct biosregs {
union {
struct {
u32 edi;
u32 esi;
u32 ebp;
u32 _esp;
u32 ebx;
u32 edx;
u32 ecx;
u32 eax;
u32 _fsgs;
u32 _dses;
u32 eflags;
};
struct {
u16 di, hdi;
u16 si, hsi;
u16 bp, hbp;
u16 _sp, _hsp;
u16 bx, hbx;
u16 dx, hdx;
u16 cx, hcx;
u16 ax, hax;
u16 gs, fs;
u16 es, ds;
u16 flags, hflags;
};
struct {
u8 dil, dih, edi2, edi3;
u8 sil, sih, esi2, esi3;
u8 bpl, bph, ebp2, ebp3;
u8 _spl, _sph, _esp2, _esp3;
u8 bl, bh, ebx2, ebx3;
u8 dl, dh, edx2, edx3;
u8 cl, ch, ecx2, ecx3;
u8 al, ah, eax2, eax3;
};
};
};
biosregs
用于保存 BIOS 中断调用前后各寄存器的状态。其内部是一个联合体,表示不同大小的寄存器内容。
2.1.3 boot_params
// file: arch/x86/include/uapi/asm/bootparam.h
/* The so-called "zeropage" */
struct boot_params {
...
__u32 alt_mem_k; /* 0x1e0 */
...
__u8 e820_entries; /* 0x1e8 */
...
struct e820entry e820_map[E820MAX]; /* 0x2d0 */
...
} __attribute__((packed));
宏 E820MAX
扩展为 128,其定义如下:
#define E820MAX 128 /* number of entries in E820MAP */
boot_params
就是所谓的 "zeropage",其中保存着启动参数。
2.2 内存探测的实现
x86 架构下,通过 detect_memory()
函数来完成内存探测。
// file: arch/x86/boot/main.c
void main(void)
{
...
/* Detect memory layout */
detect_memory();
...
}
在 detect_memory()
函数中,依次调用了 detect_memory_e820()
,detect_memory_e801()
以及 detect_memory_88()
函数,每个函数对应着上文介绍的一种接口协议。
// file: arch/x86/boot/memory.c
int detect_memory(void)
{
int err = -1;
if (detect_memory_e820() > 0)
err = 0;
if (!detect_memory_e801())
err = 0;
if (!detect_memory_88())
err = 0;
return err;
}
2.2.1 detect_memory_e820()
// file: arch/x86/boot/memory.c
static int detect_memory_e820(void)
{
int count = 0;
struct biosregs ireg, oreg;
struct e820entry *desc = boot_params.e820_map;
static struct e820entry buf; /* static so it is zeroed */
initregs(&ireg);
ireg.ax = 0xe820;
ireg.cx = sizeof buf;
ireg.edx = SMAP;
ireg.di = (size_t)&buf;
/*
* Note: at least one BIOS is known which assumes that the
* buffer pointed to by one e820 call is the same one as
* the previous call, and only changes modified fields. Therefore,
* we use a temporary buffer and copy the results entry by entry.
*
* This routine deliberately does not try to account for
* ACPI 3+ extended attributes. This is because there are
* BIOSes in the field which report zero for the valid bit for
* all ranges, and we don't currently make any use of the
* other attribute bits. Revisit this if we see the extended
* attribute bits deployed in a meaningful way in the future.
*/
do {
intcall(0x15, &ireg, &oreg);
ireg.ebx = oreg.ebx; /* for next iteration... */
/* BIOSes which terminate the chain with CF = 1 as opposed
to %ebx = 0 don't always report the SMAP signature on
the final, failing, probe. */
if (oreg.eflags & X86_EFLAGS_CF)
break;
/* Some BIOSes stop returning SMAP in the middle of
the search loop. We don't know exactly how the BIOS
screwed up the map at that point, we might have a
partial map, the full map, or complete garbage, so
just return failure. */
if (oreg.eax != SMAP) {
count = 0;
break;
}
*desc++ = buf;
count++;
} while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));
return boot_params.e820_entries = count;
}
在函数开始,声明了一些变量。其中 count
用来存储探测到的内存段的数量;ireg
和 oreg
分别表示输入寄存器状态和输出寄存器状态,它们都是 biosregs
结构体类型,该结构体里包含了 BIOS 中断用到的不同寄存器;desc
是 e820entry
结构体类型的指针,指向 boot_params.e820_map
,boot_params.e820_map
是一个数组,用来保存探测到的内存段信息;buf
是一个静态变量,用来临时保存每一轮探测到的内存段信息。
boot_params
是结构体 boot_params
的同名变量,__attribute__((aligned(16))
指示该变量是 16 字节对齐的。
// file: arch/x86/boot/main.c
struct boot_params boot_params __attribute__((aligned(16)));
接下来调用 initregs()
函数对 biosregs
类型入参进行初始化。
2.2.2 initregs()
// file: arch/x86/boot/regs.c
void initregs(struct biosregs *reg)
{
memset(reg, 0, sizeof *reg);
reg->eflags |= X86_EFLAGS_CF;
reg->ds = ds();
reg->es = ds();
reg->fs = fs();
reg->gs = gs();
}
函数内部,先调用 memset()
函数,将 biosregs
结构体填充为 0;然后填充相应的寄存器。
其中 ds()
函数获取到当前 %ds
寄存器的值,该函数定义如下:
// file: arch/x86/boot/boot.h
static inline u16 ds(void)
{
u16 seg;
asm("movw %%ds,%0" : "=rm" (seg));
return seg;
}
通过内联汇编,使用 movw
指令将 %ds
寄存的值拷贝到变量 seg
中,并将其返回。
fs()
函数及 gs()
函数的实现与 ds()
函数类似:
// file: arch/x86/boot/boot.h
static inline u16 fs(void)
{
u16 seg;
asm volatile("movw %%fs,%0" : "=rm" (seg));
return seg;
}
static inline u16 gs(void)
{
u16 seg;
asm volatile("movw %%gs,%0" : "=rm" (seg));
return seg;
}
接下来,根据 e820 协议的要求,将功能号(0xe820)、 buffer 大小、签名("SMAP")以及 buffer 地址分别封装到 ireg
变量中。
其中,宏 SMAP
是 ASCII 码 "SMAP" 的十六进制表示,其定义如下:
// file: arch/x86/boot/memory.c
#define SMAP 0x534d4150 /* ASCII "SMAP" */
然后是一个 do-while 循环。先是调用 intcall()
函数,执行 BIOS 中断,其中 0x15
是中断号,&ireg
是入参地址,&oreg
用于保存输出结果。
接下来需要判断请求是否出错。输出中的 ebx
寄存器用来判断探测是否完成,当 ebx
为 0 时,说明是最后一个内存段,探测结束。当输出中 eflags
中的 CF 位被置位时,说明探测出错了。当输出中的签名出错时,将 count
设置为 0,并跳出循环。如果一切正常,将探测到的内存段信息保存到boot_params.e820_map
中。
当探测结果中的 ebx
寄存器不为 0 且 count
小于数组 boot_params.e820_map
的大小时,循环继续。
宏 ARRAY_SIZE()
计算数组的成员数量,其定义如下:
// file: arch/x86/boot/boot.h
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
intcall()
函数的实现见下文 2.2.5 小节。
2.2.3 detect_memory_e801()
// file: arch/x86/boot/memory.c
static int detect_memory_e801(void)
{
struct biosregs ireg, oreg;
initregs(&ireg);
ireg.ax = 0xe801;
intcall(0x15, &ireg, &oreg);
if (oreg.eflags & X86_EFLAGS_CF)
return -1;
/* Do we really need to do this? */
if (oreg.cx || oreg.dx) {
oreg.ax = oreg.cx;
oreg.bx = oreg.dx;
}
if (oreg.ax > 15*1024) {
return -1; /* Bogus! */
} else if (oreg.ax == 15*1024) {
boot_params.alt_mem_k = (oreg.bx << 6) + oreg.ax;
} else {
/*
* This ignores memory above 16MB if we have a memory
* hole there. If someone actually finds a machine
* with a memory hole at 16MB and no support for
* 0E820h they should probably generate a fake e820
* map.
*/
boot_params.alt_mem_k = oreg.ax;
}
return 0;
}
根据 e801 接口协议,如果输出结果中的 eflags
寄存器中的 CF 标志为 0,指示一切正常;否则,指示执行出错。所以在执行 BIOS 中断后,首先检查 eflags 寄存器的 CF 位是否置位,如果置位,返回 -1
。
接下来,如果输出中的 cx
或 dx
寄存器有值,则使用 cx
和 dx
中的值。
由于 ax
的最大值为 15MB,如果大于 15MB,说明出错了,返回 -1
;如果 ax
等于 15MB,说明内存大于 16MB,需要计算 bx
寄存器中的内存。由于 bx
中的内存单位为 64KB,所以左移 6 位转换成 KB,再加上 ax
的值,就得到以 KB 为单位的总内存大小,并将结果保存到 boot_params.alt_mem_k
中;如果 ax
小于 15 MB,说明内存不超过 16 MB,ax
的值就是内存总大小。
2.2.4 detect_memory_88()
// file: arch/x86/boot/memory.c
static int detect_memory_88(void)
{
struct biosregs ireg, oreg;
initregs(&ireg);
ireg.ah = 0x88;
intcall(0x15, &ireg, &oreg);
boot_params.screen_info.ext_mem_k = oreg.ax;
return -(oreg.eflags & X86_EFLAGS_CF); /* 0 or -1 */
}
0x88 接口探测的是 1MB ~ 64 MB 之间的内存。探测完成后,将结果保存到 boot_params.screen_info.ext_mem_k
中。
2.2.5 intcall()
intcall()
函数是对 BIOS 中断调用的封装,该函数使用汇编语言实现。函数原型如下:
// file: arch/x86/boot/boot.h
void intcall(u8 int_no, const struct biosregs *ireg, struct biosregs *oreg);
由于内核构建时,arch/x86/boot/Makefile
文件中的 KBUILD_CFLAGS
使用了gcc 选项 -mregparm=3
,所以函数的前 3 个参数会通过 ax
、dx
即 cx
寄存器传递。对于 intcall()
函数来说:
- ax -- 中断号 int_no
- dx -- 入参地址 ireg
- cx -- 结果输出地址 oreg
// file: arch/x86/boot/bioscall.S
/*
* "Glove box" for BIOS calls. Avoids the constant problems with BIOSes
* touching registers they shouldn't be.
*/
.code16gcc
.text
.globl intcall
.type intcall, @function
intcall:
/* Self-modify the INT instruction. Ugly, but works. */
cmpb %al, 3f
je 1f
movb %al, 3f
jmp 1f /* Synchronize pipeline */
1:
/* Save state */
pushfl
pushw %fs
pushw %gs
pushal
/* Copy input state to stack frame */
subw $44, %sp
movw %dx, %si
movw %sp, %di
movw $11, %cx
rep; movsd
/* Pop full state from the stack */
popal
popw %gs
popw %fs
popw %es
popw %ds
popfl
/* Actual INT */
.byte 0xcd /* INT opcode */
3: .byte 0
/* Push full state to the stack */
pushfl
pushw %ds
pushw %es
pushw %fs
pushw %gs
pushal
/* Re-establish C environment invariants */
cld
movzwl %sp, %esp
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
/* Copy output state from stack frame */
movw 68(%esp), %di /* Original %cx == 3rd argument */
andw %di, %di
jz 4f
movw %sp, %si
movw $11, %cx
rep; movsd
4: addw $44, %sp
/* Restore state and return */
popal
popw %gs
popw %fs
popfl
retl
.size intcall, .-intcall
在上述汇编代码中,许多指令都带有后缀,不同的后缀指示不同的操作大小:
- b -- 操作数为 8 位,如 cmpb,movb。
- w -- 操作数为 16 位,如 movw,pushw。
- l -- 操作数为 32 位,如 pushfl,pushal。
- q -- 操作数为 64 位
intcall()
函数执行过程如下。
/* Self-modify the INT instruction. Ugly, but works. */
cmpb %al, 3f
je 1f
movb %al, 3f
jmp 1f /* Synchronize pipeline */
函数开始,首先比较中断号与标签 3f
处的值,如果相等,直接跳转到标签 1f
处执行。标签后缀 f
表示向前(forward)寻找该标签。中断号保存在寄存器 %ax
中,由于是 8 位数字,所以实际使用的是 %ax
寄存器的低 8 位,即 %al
。标签 3f
处保存着上一次调用 intcall()
函数时的中断号。如果不相等,则把中断号保存到标签 3f
处,并跳转到标签 1f
处执行。标签 3 处使用 .byte
指令保留了一个字节的空间。
/* Save state */
pushfl
pushw %fs
pushw %gs
pushal
接下来,保存当前进程状态,将各寄存器压栈。先是使用pushfl
指令将 eflags
寄存器压入栈中;接着将 %fs
和 %gs
寄存器入栈;最后使用 pushal
指令将所有的通用寄存器压入栈中,由于指令后缀为 l
,所以保存的是 32 位的寄存器,入栈顺序为 %eax
、 %ecx
、 %edx
、 %ebx
、 %esp
、 %ebp
、 %esi
以及 %edi
。
此时栈中布局如下图所示:
/* Copy input state to stack frame */
subw $44, %sp
movw %dx, %si
movw %sp, %di
movw $11, %cx
rep; movsd
接下来,将入参中的寄存器值拷贝到栈中。由于 biosregs
结构体大小为 44 字节,所以先将栈指针向下移动 44,给数据腾出空间。 接下使用 movsd
指令执行字符串拷贝,该指令会从地址 %ds:%si
拷贝双字(32位)到地址 %ds:%si
处。所以,需要把源数据地址 %dx
,目标数据地址 %sp
分别拷贝到 %si
、%di
寄存器。由于是批量拷贝,所以在 movsd
指令前使用了前缀指令 rep
,表示重复操作,重复的次数通过 %cx
寄存器指定,当 %cx
为 0 时,拷贝停止。由于总共要拷贝 44 字节,每次拷贝 4 字节(32位),所以要拷贝 11 次。
拷贝完成后,栈中布局如下图所示:
/* Pop full state from the stack */
popal
popw %gs
popw %fs
popw %es
popw %ds
popfl
然后,将入参中的寄存器值从栈中弹出到对应的寄存器中,准备执行 BIOS 中断。
/* Actual INT */
.byte 0xcd /* INT opcode */
3: .byte 0
再接着,会执行 BIOS 中断。当中断指令 int
的操作数为 8 位立即数时,其指令码为 0xcd
;标签 3 处保存着中断号。
/* Push full state to the stack */
pushfl
pushw %ds
pushw %es
pushw %fs
pushw %gs
pushal
中断执行后,中断函数会修改寄存器的状态,将各寄存器状态保存到栈中。这些寄存器状态会作为执行结果,保存到 oreg
中。
/* Re-establish C environment invariants */
cld
movzwl %sp, %esp
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
重建 C 环境。
/* Copy output state from stack frame */
movw 68(%esp), %di /* Original %cx == 3rd argument */
andw %di, %di
jz 4f
movw %sp, %si
movw $11, %cx
rep; movsd
4: addw $44, %sp
接下来,将运行结果保存到输出地址中。68(%esp)
处保存着原始的 %cx
寄存器值,即 intcall()
函数的第 3 个参数,用于保存输出结果。使用 rep; movsd
指令将结果从栈拷贝到指定地址。此时,源地址为栈指针 %sp
,目标地址为原始 %cx
的值,即 68(%esp)
,所以将它们分别拷贝到 %si
及 %di
寄存器。需要拷贝 11 次,所以将 %cx
设置为 11。拷贝完成后,将栈指针 %sp
增加 44,释放掉这部分空间。如果原始 %cx
值为 0,说明参数有错误,直接跳转到标签 4 处执行。
/* Restore state and return */
popal
popw %gs
popw %fs
popfl
retl
最后,恢复各寄存器状态并返回。
2.2.6 setup_memory_map() -- 设置并打印内存信息
// file: arch/x86/kernel/e820.c
void __init setup_memory_map(void)
{
char *who;
who = x86_init.resources.memory_setup();
memcpy(&e820_saved, &e820, sizeof(struct e820map));
printk(KERN_INFO "e820: BIOS-provided physical RAM map:\n");
e820_print_map(who);
}
首先,调用 x86_init.resources.memory_setup()
函数设置内存。x86_init
是 x86_init_ops
类型的结构体,表示平台相关的初始化函数,定义在 arch/x86/kernel/x86_init.c
文件中。
// file: arch/x86/kernel/x86_init.c
/*
* The platform setup functions are preset with the default functions
* for standard PC hardware.
*/
struct x86_init_ops x86_init __initdata = {
.resources = {
.probe_roms = probe_roms,
.reserve_resources = reserve_standard_io_resources,
.memory_setup = default_machine_specific_memory_setup,
},
...
}
可以看到,实际调用的是 default_machine_specific_memory_setup()
函数,该函数会净化我们通过 detect_memory()
探测到的内存段,即消除重叠的、无效的内存段。
最后,打印出 e820 相关信息。e820_print_map()
函数会打印出协议类型("BIOS-e820" / "BIOS-88" / "BIOS-e801"),内存段的起始和结束地址以及类型。
// file: arch/x86/kernel/e820.c
void __init e820_print_map(char *who)
{
int i;
for (i = 0; i < e820.nr_map; i++) {
printk(KERN_INFO "%s: [mem %#018Lx-%#018Lx] ", who,
(unsigned long long) e820.map[i].addr,
(unsigned long long)
(e820.map[i].addr + e820.map[i].size - 1));
e820_print_type(e820.map[i].type);
printk(KERN_CONT "\n");
}
}
e820_print_type()
函数通过 switch-case
语句,根据不同类型输出对应的字符串。
// file: arch/x86/kernel/e820.c
static void __init e820_print_type(u32 type)
{
switch (type) {
case E820_RAM:
case E820_RESERVED_KERN:
printk(KERN_CONT "usable");
break;
case E820_RESERVED:
printk(KERN_CONT "reserved");
break;
case E820_ACPI:
printk(KERN_CONT "ACPI data");
break;
case E820_NVS:
printk(KERN_CONT "ACPI NVS");
break;
case E820_UNUSABLE:
printk(KERN_CONT "unusable");
break;
default:
printk(KERN_CONT "type %u", type);
break;
}
}
可以通过 dmesg
命令,查看 e820
输出信息
# dmesg|grep -i e820
[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000dffeffff] usable
[ 0.000000] BIOS-e820: [mem 0x00000000dfff0000-0x00000000dfffffff] ACPI data
[ 0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000011fffffff] usable
...
...
三、参考资料
2、e820
5、"INT 15h, AX=E820h - Query System Address Map"
7、ISA
8、EISA
11、gcc参数 -mregparm=num