linux物理内存探测

linux在被bootloader加载到内存后, cpu最初执行的linux内核代码是/header.S文件中的start_of_setup函数,这个函数在做了一些准备工作后会跳转到boot目下文件main.c的main函数执行,在这个main函数中我们可以第一次看到与内存管理相关的代码,这段代码调用detect_memeory()函数检测系统物理内存

在header.S中执行下面汇编代码:

start_of_setup: ..... # Jump to C code (should not return) calll main .....跳到boot目录下的main.c文件中
void main(void) { ...... /* Detect memory layout */ detect_memory();/*内存探测函数*/ ...... }
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; }由上面的代码可知,linux内核会分别尝试调用detect_memory_e820()、detcct_memory_e801()、detect_memory_88()获得系统物理内存布局,这3个函数内部其实都会以内联汇编的形式调用bios中断以取得内存信息,该中断调用形式为int 0x15,同时调用前分别把AX寄存器设置为0xe820h、0xe801h、0x88h,关于0x15号中断有兴趣的可以去查询相关手册。下面分析detect_memory_e820()的代码,其它代码基本一样。

#define SMAP 0x534d4150 /* ASCII "SMAP" */ /*由于历史原因,一些i/o设备也会占据一部分内存 物理地址空间,因此系统可以使用的物理内存空 间是不连续的,系统内存被分成了很多段,每个段 的属性也是不一样的。int 0x15 查询物理内存时每次 返回一个内存段的信息,因此要想返回系统中所有 的物理内存,我们必须以迭代的方式去查询。 detect_memory_e820()函数把int 0x15放到一个do-while循环里, 每次得到的一个内存段放到struct e820entry里,而 struct e820entry的结构正是e820返回结果的结构!而像 其它启动时获得的结果一样,最终都会被放到 boot_params里,e820被放到了 boot_params.e820_map。 */ 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中的相关寄存器*/ ireg.ax = 0xe820; ireg.cx = sizeof buf;/*e820entry数据结构大小*/ ireg.edx = SMAP;/*标识*/ ireg.di = (size_t)&buf;/*int15返回值的存放处*/ /* * 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 { /*在执行这条内联汇编语句时输入的参数有: eax寄存器=0xe820 dx寄存器=’SMAP’ edi寄存器=desc ebx寄存器=next ecx寄存器=size 返回给c语言代码的参数有: id=eax寄存器 rr=edx寄存器 ext=ebx寄存器 size=ecx寄存器 desc指向的内存地址在执行0x15中断调用时被设置 */ 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;/*将buf赋值给desc*/ count++;/*探测数加一*/ } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map)); /*将内存块数保持到变量中*/ return boot_params.e820_entries = count; } 其中存放中断返回值得结构如下

struct e820entry { __u64 addr; /* start of memory segment */ __u64 size; /* size of memory segment */ __u32 type; /* type of memory segment */ } __attribute__((packed));在内核初始化跳入start_kernel函数后执行以下初始化

start_kernel()->setup_arch()->setup_memory_map()

/*调用x86_init.resources.memory_setup()实现对e820内存图的优化, 将e820中得值保存在e820_saved中,打印内存图 */ void __init setup_memory_map(void) { char *who; /*调用x86体系下的memory_setup函数*/ who = x86_init.resources.memory_setup(); /*保存到e820_saved中*/ memcpy(&e820_saved, &e820, sizeof(struct e820map)); printk(KERN_INFO "BIOS-provided physical RAM map:\n"); /*打印输出*/ e820_print_map(who); }

x86_init.c中定义了x86下的memory_setup函数

struct x86_init_ops x86_init __initdata = { .resources = { …… .memory_setup = default_machine_specific_memory_setup, }, …… };char *__init default_machine_specific_memory_setup(void) { char *who = "BIOS-e820"; u32 new_nr; /* * Try to copy the BIOS-supplied E820-map. * * Otherwise fake a memory map; one section from 0k->640k, * the next section from 1mb->appropriate_mem_k */ new_nr = boot_params.e820_entries; /*将重叠的去除*/ sanitize_e820_map(boot_params.e820_map, ARRAY_SIZE(boot_params.e820_map), &new_nr); /*去掉重叠的部分后得到的内存个数*/ boot_params.e820_entries = new_nr; /*将其赋值到全局变量e820中,小于0时,为出错处理*/ if (append_e820_map(boot_params.e820_map, boot_params.e820_entries) < 0) { …… } /* In case someone cares... */ return who; }

append_e820_map调用__append_e820_map实现

static int __init __append_e820_map(struct e820entry *biosmap, int nr_map) { while (nr_map) {/*循环nr_map次调用,添加内存块到e820*/ u64 start = biosmap->addr; u64 size = biosmap->size; u64 end = start + size; u32 type = biosmap->type; /* Overflow in 64 bits? Ignore the memory map. */ if (start > end) return -1; /*添加函数*/ e820_add_region(start, size, type); biosmap++; nr_map--; } return 0; }
void __init e820_add_region(u64 start, u64 size, int type) { __e820_add_region(&e820, start, size, type); }

e820为e820map结构

struct e820map { __u32 nr_map; struct e820entry map[E820_X_MAX]; };

其中E820_X_MAX大小为128.

tatic void __init __e820_add_region(struct e820map *e820x, u64 start, u64 size, int type) { int x = e820x->nr_map; if (x >= ARRAY_SIZE(e820x->map)) { printk(KERN_ERR "Ooops! Too many entries in the memory map!\n"); return; }到这里,物理内存就已经从BIOS中读出来存放到全局变量e820中,e820是linux内核中用于建立内存管理框架的基础。在后面我们会看到,建立初始化节点、管理区会用到他。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值