linux物理内存探测

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

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

  1. start_of_setup:  
  2.        .....  
  3. # Jump to C code (should not return)   
  4.     calll   main  
  5.        .....  

跳到boot目录下的main.c文件中

  1. void main(void)  
  2. {  
  3.         ......  
  4.     /* Detect memory layout */  
  5.     detect_memory();/*内存探测函数*/  
  6.     ......  
  7. }  


 

  1. int detect_memory(void)  
  2. {  
  3.     int err = -1;  
  4.   
  5.     if (detect_memory_e820() > 0)  
  6.         err = 0;  
  7.   
  8.     if (!detect_memory_e801())  
  9.         err = 0;  
  10.   
  11.     if (!detect_memory_88())  
  12.         err = 0;  
  13.   
  14.     return err;  
  15. }  

由上面的代码可知,linux内核会分别尝试调用detect_memory_e820()、detcct_memory_e801()、detect_memory_88()获得系统物理内存布局,这3个函数内部其实都会以内联汇编的形式调用bios中断以取得内存信息,该中断调用形式为int 0x15,同时调用前分别把AX寄存器设置为0xe820h、0xe801h、0x88h,关于0x15号中断有兴趣的可以去查询相关手册。下面分析detect_memory_e820()的代码,其它代码基本一样。

  1. #define SMAP    0x534d4150  /* ASCII "SMAP" */   
  2. /*由于历史原因,一些i/o设备也会占据一部分内存 
  3. 物理地址空间,因此系统可以使用的物理内存空 
  4. 间是不连续的,系统内存被分成了很多段,每个段 
  5. 的属性也是不一样的。int 0x15 查询物理内存时每次 
  6. 返回一个内存段的信息,因此要想返回系统中所有 
  7. 的物理内存,我们必须以迭代的方式去查询。 
  8. detect_memory_e820()函数把int 0x15放到一个do-while循环里, 
  9. 每次得到的一个内存段放到struct e820entry里,而 
  10. struct e820entry的结构正是e820返回结果的结构!而像 
  11. 其它启动时获得的结果一样,最终都会被放到 
  12. boot_params里,e820被放到了 boot_params.e820_map。 
  13. */  
  14. static int detect_memory_e820(void)  
  15. {  
  16.     int count = 0;/*用于记录已检测到的物理内存数目*/  
  17.     struct biosregs ireg, oreg;  
  18.     struct e820entry *desc = boot_params.e820_map;  
  19.     static struct e820entry buf; /* static so it is zeroed */  
  20.   
  21.     initregs(&ireg);/*初始化ireg中的相关寄存器*/  
  22.     ireg.ax  = 0xe820;  
  23.     ireg.cx  = sizeof buf;/*e820entry数据结构大小*/  
  24.     ireg.edx = SMAP;/*标识*/  
  25.     ireg.di  = (size_t)&buf;/*int15返回值的存放处*/  
  26.   
  27.     /* 
  28.      * Note: at least one BIOS is known which assumes that the 
  29.      * buffer pointed to by one e820 call is the same one as 
  30.      * the previous call, and only changes modified fields.  Therefore, 
  31.      * we use a temporary buffer and copy the results entry by entry. 
  32.      * 
  33.      * This routine deliberately does not try to account for 
  34.      * ACPI 3+ extended attributes.  This is because there are 
  35.      * BIOSes in the field which report zero for the valid bit for 
  36.      * all ranges, and we don't currently make any use of the 
  37.      * other attribute bits.  Revisit this if we see the extended 
  38.      * attribute bits deployed in a meaningful way in the future. 
  39.      */  
  40.   
  41.     do {  
  42.         /*在执行这条内联汇编语句时输入的参数有: 
  43.         eax寄存器=0xe820 
  44.         dx寄存器=’SMAP’ 
  45.         edi寄存器=desc 
  46.         ebx寄存器=next 
  47.         ecx寄存器=size 
  48.          
  49.         返回给c语言代码的参数有: 
  50.         id=eax寄存器 
  51.         rr=edx寄存器 
  52.         ext=ebx寄存器 
  53.         size=ecx寄存器 
  54.         desc指向的内存地址在执行0x15中断调用时被设置 
  55.         */  
  56.         intcall(0x15, &ireg, &oreg);  
  57.         /*选择下一个*/  
  58.         ireg.ebx = oreg.ebx; /* for next iteration... */  
  59.   
  60.         /* BIOSes which terminate the chain with CF = 1 as opposed 
  61.            to %ebx = 0 don't always report the SMAP signature on 
  62.            the final, failing, probe. */  
  63.         if (oreg.eflags & X86_EFLAGS_CF)  
  64.             break;  
  65.   
  66.         /* Some BIOSes stop returning SMAP in the middle of 
  67.            the search loop.  We don't know exactly how the BIOS 
  68.            screwed up the map at that point, we might have a 
  69.            partial map, the full map, or complete garbage, so 
  70.            just return failure. */  
  71.         if (oreg.eax != SMAP) {  
  72.             count = 0;  
  73.             break;  
  74.         }  
  75.   
  76.         *desc++ = buf;/*将buf赋值给desc*/  
  77.         count++;/*探测数加一*/  
  78.     }   
  79.     while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));  
  80.     /*将内存块数保持到变量中*/  
  81.     return boot_params.e820_entries = count;  
  82. }  

其中存放中断返回值得结构如下

  1. struct e820entry {  
  2.     __u64 addr; /* start of memory segment */  
  3.     __u64 size; /* size of memory segment */  
  4.     __u32 type; /* type of memory segment */  
  5. } __attribute__((packed));  

在内核初始化跳入start_kernel函数后执行以下初始化

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

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

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

  1. struct x86_init_ops x86_init __initdata = {  
  2.   
  3.     .resources = {  
  4.         ……  
  5.         .memory_setup       = default_machine_specific_memory_setup,  
  6.     },  
  7.         ……  
  8. };  
  1. char *__init default_machine_specific_memory_setup(void)  
  2. {  
  3.     char *who = "BIOS-e820";  
  4.     u32 new_nr;  
  5.     /* 
  6.      * Try to copy the BIOS-supplied E820-map. 
  7.      * 
  8.      * Otherwise fake a memory map; one section from 0k->640k, 
  9.      * the next section from 1mb->appropriate_mem_k 
  10.      */  
  11.     new_nr = boot_params.e820_entries;  
  12.     /*将重叠的去除*/  
  13.     sanitize_e820_map(boot_params.e820_map,  
  14.             ARRAY_SIZE(boot_params.e820_map),  
  15.             &new_nr);  
  16.     /*去掉重叠的部分后得到的内存个数*/  
  17.     boot_params.e820_entries = new_nr;  
  18.     /*将其赋值到全局变量e820中,小于0时,为出错处理*/  
  19.     if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)  
  20.       < 0) {  
  21.         ……  
  22.     }  
  23.   
  24.     /* In case someone cares... */  
  25.     return who;  
  26. }  

append_e820_map调用__append_e820_map实现

  1. static int __init __append_e820_map(struct e820entry *biosmap, int nr_map)  
  2. {  
  3.     while (nr_map) {/*循环nr_map次调用,添加内存块到e820*/  
  4.         u64 start = biosmap->addr;  
  5.         u64 size = biosmap->size;  
  6.         u64 end = start + size;  
  7.         u32 type = biosmap->type;  
  8.         /* Overflow in 64 bits? Ignore the memory map. */  
  9.         if (start > end)  
  10.             return -1;  
  11.         /*添加函数*/  
  12.         e820_add_region(start, size, type);  
  13.         biosmap++;  
  14.         nr_map--;  
  15.     }  
  16.     return 0;  
  17. }  


 

  1. void __init e820_add_region(u64 start, u64 size, int type)  
  2. {  
  3.     __e820_add_region(&e820, start, size, type);  
  4. }  

e820e820map结构

  1. struct e820map {  
  2.     __u32 nr_map;  
  3.     struct e820entry map[E820_X_MAX];  
  4. };  

其中E820_X_MAX大小为128.

  1. tatic void __init __e820_add_region(struct e820map *e820x, u64 start, u64 size,  
  2.                      int type)  
  3. {  
  4.     int x = e820x->nr_map;  
  5.   
  6.     if (x >= ARRAY_SIZE(e820x->map)) {  
  7.         printk(KERN_ERR "Ooops! Too many entries in the memory map!\n");  
  8.         return;  
  9.     }  

到这里,物理内存就已经从BIOS中读出来存放到全局变量e820中,e820是linux内核中用于建立内存管理框架的基础。在后面我们会看到,建立初始化节点、管理区会用到他。

posted on 2013-04-03 09:33  疯子123  阅读(641)  评论(0编辑  收藏  举报

导航