Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1031139
  • 博文数量: 123
  • 博客积分: 5051
  • 博客等级: 大校
  • 技术积分: 1356
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-14 10:56
文章分类
文章存档

2012年(1)

2011年(21)

2010年(13)

2009年(55)

2008年(33)

分类: LINUX

2009-11-06 20:36:55

本篇文章是对linux内存初始化部分进行的一个小小的总结。本文仅仅代表我个人的分析结果,如果有什么不对的地方,希望各位网友斧正,谢谢!

在这个内核初始化过程中,linux内存的初始活动可以说是最复杂的模块之一,内核中的所有运行程序都是在初始化启动阶段完成的,对于启动部分,本文不会给出太多的解析,但是有一点很有必要,就是给出内核数据在内存中的布局,这里首先给出详细的文字性描述。
在内存中,从第一个页框到_text处由两个部分组成,一个是不可用页框,另外一个是可用页框。_text~_etext部分存放的内核代码,_etext+1~_edata存放的是已初始化的内核数据,_edata+1 ~ _end是未初始化的内核数据。为了更容易地对内存管理有个直观地认识,了解这一点是很有必要的。

接下来就是从start_kernel开始讨论:
内存管理的第一步是通过一个中断来实现的,此中断由函数detect_memory()来调用,在detect_memory()函数中,执行INT 0x15,eax = 0xe820中断,得到的数据存放在boot_params.e820_map中.

下面是boot_params.e820_map的详细定义:

struct e820map {
    __u32 nr_map;
    struct e820entry map[E820_X_MAX]; 
    /* 一般情况下E820_X_MAX的值为128 */
};
struct e820entry {
    __u64 addr;
    __u64 size;
    __u32 type;    
}__attribute__((packed));

addr是存储段的起始地址,size是存储段的大小,type是存储段的类型.
struct boot_params中含有一个struct e820entry类型的数组,长度为128,因为内存可分为不同的段,每个段对应着一个e820map字段,每个段所对应的类型和大小以及起始地址都不一样,boot_params结构体中的e820_entries字段保存的是内存中段的数量。在后面的内存管理初始化过程中将会大量地引用到boot_params这个变量.此变量中的信息对于内核初始化是相当重要的。

setup_arch()
对于初始化内核start_kernel来说,setup_arch()是一个相当重要的函数,此函数实现了对特定体系的初始化。下面将会详细地对setup_arch函数进行说明.
1、setup_arch第一步要做的就是保存head.S初始化的new_cpu_data数据到boot_cpu_data中,boot_cpu_data主要是保存CPU的相关信息。

2、start_kernel内核初始化函数中第一个内存管理函数由setup_memory_map()来实现,setup_memory_map()最终调用default_machine_specific_memory_setup()实现对e820内存图的优化,并根据boot_params中e820_map字段的值来设置变量e820的值。之后将e820的数据保存到e820_saved中,相当于备份。最后打印出内存分布图;

3、调用系统编译生成的数据来初始化init_mm的start_code,end_code,end_data以及.brk等字段,同时也初始化code_resource的start,end字段和data_resource的start,end字段以及bss_resource的start,end字段.

4、接下来,调用e820_end_of_ram_pfn()函数根据e820的数据来获得内存的最大值并由函数e820_end_pfn返回,保存在max_pfn字段中。

5、紧接着,调用find_low_pfn_range()设置一些基本的变量,并设置num_physpages的值;

6、调用init_memory_mapping()函数来初始化内存映射,在函数init_memory_mapping函数中先后调用下面的几个函数来设置内存相关数据:
(因为bootmem此时没有初始化)


find_early_table_space()
kernel_physical_mapping_init()
early_ioremap_page_table_range_init()
load_cr3()
reserve_early()

上面四个函数分别实现了下面的功能:
find_early_table_space所实现的功能是相当重要的,首先是确定了PUD和PMD以及PTE、固定内存映射等所有的选项所使用的内存空间的大小,之后从e820.emap数据字段中寻找到一块能够容纳所有表项的内存段,并设置table_start,table_end,table_top的值,这三个数据相当的重要,因为在后面的页表初始化阶段将会使用到这三个变量.之后两次调用kernel_physical_mapping_init()分别初始化了第一个2MB的页表以及之后所有内存的页表。同样的,early_ioremap_page_table_range_init()也实现了类似的功能。接下来使用load_cr3加载了寄存器cr3。最后,init_memory_mapping返回了所映射页的数量值,并将值保存在max_low_pfn_mapped中,max_pfn_mapped的值和ma_low_pfn_mapped的值相当。

7、接下来,setup_arch()调用initmem_init来初始化内存,这个函数设置了early_node_map,并调用了函数setup_bootmem_allocator()来设置引导启动阶段所涉及到的页映射位。setup_bootmem_allocator()调用find_e820_area函数来寻找一块空闲内存段,之后调用init_bootmem_node
函数设置NODE(0)->bdata字段,设置的值如下所示:


bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));
bdata->node_min_pfn = 0;
bdata->node_low_pfn = max_low_pfn;

并将bdata->node_bootmem_map指向的数据区初始化为全1,最终将after_init_bootmem设置为1。bdata->node_bootmem_map指向的数据为启动阶段用于临时映射的内存区。这个内存区在初始化的后阶段会被应用上。

8、之后,调用paging_init()进行页面初始化.paging_init()分别调用下面的几个函数来实现页面的初始化工作:


pagetable_init();
kmap_init();
sparse_init();
zone_sizes_init();

zone_sizes_init()最终调用free_area_init_nodes()来实现节点空闲区域的初始化:

void __init free_area_init_nodes(unsigned long *max_zone_pfn)
{
    for_each_online_node(nid) {
        free_area_init_node(nid, NULL, find_min_pfn_for_node(nid), NULL);
    }
}

free_area_init_node()函数调用alloc_node_mem_map()来为所有页描述符分配空闲区域,并让NODE_DATA(0)->node_mem_map指向这段内存区域。紧接着调用free_area_init_core()初始化系统中的所有内存管理区。在free_area_init_core()函数内部,内核调用zone_init_free_lists(zone)来初始化所有内存管理区内的空闲链表。在free_area_init_core()函数对每个内存管理区进行初始化的过程中,内核调用memmap_init_zone()初始化了所有的页描述符,下面是具体的初始化:

void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
        unsigned long start_pfn, enum memmap_context context)
{
    struct page *page;
    unsigned long end_pfn = start_pfn + size;
    unsigned long pfn;
    struct zone *z;

    ...
    z = &ZONE_DATA(nid)->node_zones[zone];
    for (pfn = start_pfn; pfn < end_pfn; pfn++) {
        if (context == MEMMAP_EARLY) {
            ...
        }
        page = pfn_to_page(pfn);
        set_page_links(page, zone, nid, pfn);
        mminit_verify_page_links(page, zone, nid, pfn);
        init_page_count(page);
        reset_page_mapcount(page);
        SetPageReserved(page);
        ...
        INIT_LIST_HEAD(&page->lru);
    }
}

到此为止,setup_arch()有关内存管理的内容已经完毕,经过这一番初始化后,内存初始化已经完成了特定系统的基本初始化,接下来,start_kernel开始构建内存管理的经典算法应用:伙伴系统和slab内存管理。

阅读(3278) | 评论(0) | 转发(0) |
0

上一篇:内存探测初探

下一篇:gdb调试

给主人留下些什么吧!~~