linux在被bootloader加载到内存后, cpu最初执行的linux内核代码是/header.S文件中的start_of_setup函数,这个函数在做了一些准备工作后会跳转到boot目下文件main.c的main函数执行,在这个main函数中我们可以第一次看到与内存管理相关的代码,这段代码调用detect_memeory()函数检测系统物理内存
在header.S中执行下面汇编代码:
[cpp] view plain copy print ?
- start_of_setup:
- .....
- # Jump to C code (should not return)
- calll main
- .....
跳到boot目录下的main.c文件中
[cpp] view plain copy print ?
- void main(void)
- {
- ......
-
- detect_memory();
- ......
- }
[cpp] view plain copy print ?
- 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()的代码,其它代码基本一样。
[cpp] view plain copy print ?
- #define SMAP 0x534d4150 /* ASCII "SMAP" */
-
-
-
-
-
-
-
-
-
-
-
-
- static int detect_memory_e820(void)
- {
- int count = 0;
- struct biosregs ireg, oreg;
- struct e820entry *desc = boot_params.e820_map;
- static struct e820entry buf;
-
- initregs(&ireg);
- ireg.ax = 0xe820;
- ireg.cx = sizeof buf;
- ireg.edx = SMAP;
- ireg.di = (size_t)&buf;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- do {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- intcall(0x15, &ireg, &oreg);
-
- ireg.ebx = oreg.ebx;
-
-
-
-
- if (oreg.eflags & X86_EFLAGS_CF)
- break;
-
-
-
-
-
-
- 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;
- }
其中存放中断返回值得结构如下
[cpp] view plain copy print ?
- struct e820entry {
- __u64 addr;
- __u64 size;
- __u32 type;
- } __attribute__((packed));
在内核初始化跳入start_kernel函数后执行以下初始化
start_kernel()->setup_arch()->setup_memory_map()
[cpp] view plain copy print ?
-
-
-
- void __init setup_memory_map(void)
- {
- char *who;
-
- who = x86_init.resources.memory_setup();
-
- 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函数
[cpp] view plain copy print ?
- struct x86_init_ops x86_init __initdata = {
-
- .resources = {
- ……
- .memory_setup = default_machine_specific_memory_setup,
- },
- ……
- };
[cpp] view plain copy print ?
- char *__init default_machine_specific_memory_setup(void)
- {
- char *who = "BIOS-e820";
- u32 new_nr;
-
-
-
-
-
-
- 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;
-
- if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)
- < 0) {
- ……
- }
-
-
- return who;
- }
append_e820_map调用__append_e820_map实现
[cpp] view plain copy print ?
- static int __init __append_e820_map(struct e820entry *biosmap, int nr_map)
- {
- while (nr_map) {
- u64 start = biosmap->addr;
- u64 size = biosmap->size;
- u64 end = start + size;
- u32 type = biosmap->type;
-
- if (start > end)
- return -1;
-
- e820_add_region(start, size, type);
- biosmap++;
- nr_map--;
- }
- return 0;
- }
[cpp] view plain copy print ?
- void __init e820_add_region(u64 start, u64 size, int type)
- {
- __e820_add_region(&e820, start, size, type);
- }
e820为e820map结构
[cpp] view plain copy print ?
- struct e820map {
- __u32 nr_map;
- struct e820entry map[E820_X_MAX];
- };
其中E820_X_MAX大小为128.
[cpp] view plain copy print ?
- 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内核中用于建立内存管理框架的基础。在后面我们会看到,建立初始化节点、管理区会用到他。