您的位置:首页 > 运维架构 > Linux

Linux内核---42.arm 内存初始化2

2016-07-09 10:21 351 查看
几个名词:

Page Global Directory (PGD)

Page Upper Directory (PUD)

Page Middle Directory (PMD)

Page Table (PTE)

Page directory 页目录

Page table entry 页表项

 Page Frame Number--> PFN

1. paging_init
setup_arch
    --> paging_init

void __init paging_init(struct machine_desc *mdesc)

{

    void *zero_page;

    //设置memblock.current_limit = lowmem_limit

    memblock_set_current_limit(lowmem_limit);

    build_mem_type_table();        //1.设置memblock.current_limit 

    prepare_page_table();          //2.初始化pgd并清除pgd中所有项除映射实际内存地址的项

    map_lowmem();                  //3.映射lowmem部分,即实际的内存部分

    devicemaps_init(mdesc);        //4.设置memblock.current_limit

    kmap_init();                   //


    top_pmd = pmd_off_k(0xffff0000);

    /* allocate the zero page. */

    zero_page = early_alloc(PAGE_SIZE);

    bootmem_init();                //5.设置memblock.current_limit

    empty_zero_page = virt_to_page(zero_page);

    __flush_dcache_page(NULL, empty_zero_page);

}

2. page_table初始化
setup_arch
    --> paging_init
        --> prepare_page_table
这儿是要把pgd初始化:
初始化的过程并不是把pgd中所有的项全部清0,而是清除pgd中表示虚拟地址0-2048的项,
然后空出表示2048-(2048+mem_size)的项,
再清除表示(2048+mem_size - 4096M)的项.

static inline void prepare_page_table(void)

{   

    //MODULES_VADDR=0x7f000000=2032M, PGDIR_SIZE=0x200000=2048K=2M

    //清除0-2032M (0x0-0x7f000000)的页目录表

    for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)

        pmd_clear(pmd_off_k(addr));

    //清除2032M-2048M (0x7f000000-0x80000000)的页目录表//清除2032M-2048M的页目录表

    for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)

        pmd_clear(pmd_off_k(addr));

    //memory.regions[0].base=0x60000000, size=0x8000000

    end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;

    if (end >= lowmem_limit)

        end = lowmem_limit;

    //清除(2176M-3968M)0x88000000-0xf8000000处的页目录表 (2176-2048=128M)

    for (addr = __phys_to_virt(end); addr < VMALLOC_END; addr += PGDIR_SIZE)

        pmd_clear(pmd_off_k(addr));

}

其中一个pmd可以表示1M的内存空间
A:这个pmd_clear函数为什么要把pmdp的下一个地址即pmdp[1]清除呢?

#define pmd_clear(pmdp)            \

    do {                \

        pmdp[0] = __pmd(0);    \

        pmdp[1] = __pmd(0);    \

        clean_pmd_entry(pmdp);    \

    } while (0)

要理解这个需要知道两点:

a. 因为一个pud可以表示2M的内存即上面的PGDIR_SIZE,而一个pmd可表示1M的内存
b. 这儿的pud和pmd的基地址是一样的,即清pmd时也就是清pud
B: 宏的定义

//下面这两个只是去掉第二个参数,没有多大意义

#define pmd_offset(dir, addr)    ((pmd_t *)(dir))

#define pud_offset(pgd, start)        (pgd)

//pgd_offset_k 就是取得mm->pgd+index的地址

#define pgd_offset_k(addr)    pgd_offset(&init_mm, addr)

#define pgd_offset(mm, addr)    ((mm)->pgd + pgd_index(addr))

#define pgd_index(addr)        ((addr) >> PGDIR_SHIFT)

3.映射lowmem部分,即实际的内存部分
setup_arch
    --> paging_init
        --> map_lowmem
映射的过程说白了其实很简单:
假设要将phy_addr=0x60000000 size=0x08000000的内存映射到virtual_addr=0x80000000处
a. 通过pgd_offset查出virtual_addr在pgd中的地址
b. 将phy_addr写到pgd中去

将虚拟地址virtual_addr=0x80000000 转为物理地址的方法是:
a. 通过pgd_offset查出virtual_addr在pgd中的地址
b. 读出pgd中的内容就是物理地址

static void __init map_lowmem(void)

{

    struct memblock_region *reg;

    for_each_memblock(memory, reg) {

        //start=0x60000000, end=0x68000000

        phys_addr_t start = reg->base;

        phys_addr_t end = start + reg->size;

        struct map_desc map;

        if (end > lowmem_limit)

            end = lowmem_limit;

        if (start >= end)

            break;

             

        map.pfn = __phys_to_pfn(start);       //map.pfn=0x60000

        map.virtual = __phys_to_virt(start);  //把实际内存0x60000000映射到0x80000000处

        map.length = end - start;             //映射的长度是0x08000000

        map.type = MT_MEMORY;                 //映射的类型是MEMORY

        create_mapping(&map);                 //映射的主函数

    }

}

3.1 对lowmem部分进行映射
setup_arch
    --> paging_init
        --> map_lowmem
            --> create_mapping

static void __init create_mapping(struct map_desc *md)

{

    unsigned long addr, length, end;

    phys_addr_t phys;

    const struct mem_type *type;

    pgd_t *pgd;

    type = &mem_types[md->type];

    addr = md->virtual & PAGE_MASK;     //addr=0x80000000

    phys = __pfn_to_phys(md->pfn);      //phys=0x60000000

    length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));  //length=0x08000000

    pgd = pgd_offset_k(addr);           //pgd=0x80006000,pgd是虚地址addr所在页目录表的地址 

    end = addr + length;                //end=0x88000000

    do {

        unsigned long next = pgd_addr_end(addr, end);    //下一个页目录表所映射的地址

        alloc_init_pud(pgd, addr, next, phys, type);

        phys += next - addr;

        addr = next;

    } while (pgd++, addr != end);

}

3.1.1 映射
setup_arch
    --> paging_init
        --> map_lowmem
            --> create_mapping
                 --> alloc_init_pud
pgd 页目录表的基地址
addr 要映射的虚地址
phy  要映射的虚地址的物理地址
end 要映射的虚地址+偏移(PUD_SIZE)

static void alloc_init_pud(pgd_t *pgd, unsigned
long addr, unsigned long end,

    unsigned long phys, const struct mem_type *type)

{

    pud_t *pud = pud_offset(pgd, addr);      //虚地址addr在页目录表的地址

    unsigned long next;

    do {

        next = pud_addr_end(addr, end);      //next=addr+PUD_SIZE

        alloc_init_section(pud, addr, next, phys, type);

        phys += next - addr;

    } while (pud++, addr = next, addr != end);

}

3.1.1.1 真正的映射过程
setup_arch
    --> paging_init
        --> map_lowmem
            --> create_mapping
                 --> alloc_init_pud
                      --> alloc_init_section 

#ifndef pud_addr_end

#define pud_addr_end(addr, end)                        \

({    unsigned long __boundary = ((addr) + PUD_SIZE) & PUD_MASK;    \

    (__boundary - 1 < (end) - 1)? __boundary: (end);        \

})

#endif

pud: 虚地址addr在页目录表的地址

addr: 要映射的虚地址

end:  addr+PUD_SIZE

phys: 虚地址addr的物理地址

static void __init alloc_init_section(pud_t *pud, unsigned long addr,

                 unsigned long end, phys_addr_t phys, const struct
mem_type *type)

{

    //#define pmd_offset(dir, addr)    ((pmd_t *)(dir))

    pmd_t *pmd = pmd_offset(pud, addr);

    if (((addr | end | phys) & ~SECTION_MASK) == 0) {

        pmd_t *p = pmd;

        if (addr & SECTION_SIZE)

            pmd++;

        //SECTION_SIZE=1<<20而PUD_SIZE=1<<21,所以下面要执行两次

        do {

           //真正的映射这就这一句,很简单嘛:

           //把要映射的地址加上标志填充到pgd中

            *pmd = __pmd(phys | type->prot_sect); 
     

            phys += SECTION_SIZE;

        } while (pmd++, addr += SECTION_SIZE, addr != end);

        flush_pmd_entry(p);

    } else {   //接下来会用到,不过map_lowmem时不执行 

        alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);

    }

}

4. devicemaps_init
会调用memblock_alloc_base从物理内存的顶端分配一些内存

static void __init devicemaps_init(struct machine_desc *mdesc)

{

    struct map_desc map;

    unsigned long addr;

    //没有具体看early_alloc干了什么,不过从打印信息看,它的作用是:

    //在物理地址的最高处分配了一个page的内存: vectors_page=0x87fff000

    vectors_page = early_alloc(PAGE_SIZE);             //为中断向量分配一个page的内存

    //清除pgd映射了虚拟地址VAMLLOC_END-4G的项

    //addr每次步进2M,直到溢出addr=0才结束

    for (addr = VMALLOC_END; addr; addr += PGDIR_SIZE)

        pmd_clear(pmd_off_k(addr));

    map.pfn = __phys_to_pfn(virt_to_phys(vectors_page));

    map.virtual = 0xffff0000;                   //把中断向量表映射到0xffff0000处   

    map.length = PAGE_SIZE;                     //中断向量表要映射的长度是1个page
4K

    map.type = MT_HIGH_VECTORS;                 //映射的类型是
MT_HIGH_VECTORS

    create_mapping(&map);

    if (mdesc->map_io)

        mdesc->map_io();

    

    local_flush_tlb_all();

    flush_cache_all();

}

5. bootmem_init
setup_arch
    --> paging_init
        --> bootmem_init

void __init bootmem_init(void)

{

    unsigned long min, max_low, max_high;

    max_low = max_high = 0;

    //内存开始0x60000000-0x68000000,其页帧号的起始0x60000-0x68000

    //页帧号就是把地址 >> PAGE_SHIFT

    find_limits(&min, &max_low, &max_high);    //5.1
获取页帧号的起始地址

    arm_bootmem_init(min, max_low);            //5.2
初始化bootmem

    arm_memory_present();                      //空 

    sparse_init();                             //空        

    arm_bootmem_free(min, max_low, max_high);  //5.3
初始化内存结点

    high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1; 

    max_low_pfn = max_low - PHYS_PFN_OFFSET;

    max_pfn = max_high - PHYS_PFN_OFFSET;      //将一些值保存在全局变量中

}

5.1 find_limits  获取页帧号的起始地址
setup_arch
    --> paging_init
        --> bootmem_init
               -->
find_limits

static void __init find_limits(unsigned long *min, unsigned
long *max_low, unsigned long *max_high)

{

    struct meminfo *mi = &meminfo;

    int i;

    *min = -1UL;

    *max_low = *max_high = 0;

    for_each_bank (i, mi) {

        struct membank *bank = &mi->bank[i];

        unsigned long start, end;

        //函数bank_pfn_start即把地址bank >>
PAGE_SHIFT

        start = bank_pfn_start(bank);  //内存开始bank_start=0x60000000,转为页帧号start=0x60000

        end = bank_pfn_end(bank);      //内存结束bank_end=0x68000000,转为页帧号end=0x68000

        if (*min > start)

            *min = start;

        if (*max_high < end)

            *max_high = end;

        if (bank->highmem)

            continue;

        if (*max_low < end)

            *max_low = end;

    }    //最后min=0x60000, max_low=0x68000, max_high=0x68000

}

5.2 初始化bootmem
setup_arch
    --> paging_init
        --> bootmem_init
               --> arm_bootmem_init

static void __init arm_bootmem_init(unsigned long start_pfn, 
unsigned long end_pfn)

{

    struct memblock_region *reg;

    unsigned int boot_pages;

    phys_addr_t bitmap;

    pg_data_t *pgdat;

    //页帧号的起始:start_pfn=0x60000, end_pfn=0x68000

    //所以共有页0x8000个,可以用0x8000/8=0x1000个Byte的位图来表示

    //0x1000正好需要1个page来表示

    boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);   //5.2.1

    

    //为位图分配内存:
物理地址bitmap=0x67ffa000,

   //这儿为什么是0x67ffa000呢?因为memblock_alloc_base这个函数先从物理地址的结束0x68000000处开始分配内存,

   //然后依次递减,这个过程好像类似于压栈

    bitmap = memblock_alloc_base(boot_pages << PAGE_SHIFT, L1_CACHE_BYTES, __pfn_to_phys(end_pfn));   

    node_set_online(0);        //空函数

    pgdat = NODE_DATA(0);      //获取pglist_data结构体contig_page_data的地址

    init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);  //5.2.2初始化内存位图

    for_each_memblock(memory, reg) {

        unsigned long start = memblock_region_memory_base_pfn(reg);

        unsigned long end = memblock_region_memory_end_pfn(reg);

        if (end >= end_pfn)

            end = end_pfn;

        if (start >= end)

            break;

        //对物理内存在位图中置0,代表free

        free_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT);         

    }

   

    for_each_memblock(reserved, reg) {

        unsigned long start = memblock_region_reserved_base_pfn(reg);

        unsigned long end = memblock_region_reserved_end_pfn(reg);

        if (end >= end_pfn)

            end = end_pfn;

        if (start >= end)

            break;

        //对reserve的memory在内存位图中置1

        reserve_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT, BOOTMEM_DEFAULT);

    }

}

5.2.1 计算出整个内存位图所占的字节数
setup_arch
    --> paging_init
        --> bootmem_init
               --> arm_bootmem_init
                   --> bootmem_bootmap_pages

unsigned long __init bootmem_bootmap_pages(unsigned long pages)

{

    unsigned long bytes = bootmap_bytes(pages);  

    return PAGE_ALIGN(bytes) >> PAGE_SHIFT;     //将表示整个位图需要的字节数,转为page

}

static unsigned long __init bootmap_bytes(unsigned long pages)

{

    unsigned long bytes = (pages + 7) / 8;       //位图中1个page占1位,那么表示整个位图需要的字节数

    return ALIGN(bytes, sizeof(long));

}

5.2.2 初始化内存位图
setup_arch
    --> paging_init
        --> bootmem_init
               --> arm_bootmem_init
                   --> init_bootmem_node
                        --> init_bootmem_core
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);
pgdat 是 获取pglist_data结构体contig_page_data的地址
freepfn 是内存位图的页帧
start_pfn与end_pfn是内存的起始页帧

unsigned long __init init_bootmem_node(pg_data_t *pgdat, unsigned
long freepfn,

                unsigned long startpfn, unsigned long endpfn)

{

    return init_bootmem_core(pgdat->bdata, freepfn, startpfn, endpfn);

}

static unsigned long __init init_bootmem_core(bootmem_data_t *bdata,

    unsigned long mapstart, unsigned long start, unsigned long end)

{

    unsigned long mapsize;

    mminit_validate_memmodel_limits(&start, &end);    //空函数

    bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));  //将内存位图的首地址由物理地址转为虚地址

    bdata->node_min_pfn = start;                      //

    bdata->node_low_pfn = end;                        //

    link_bootmem(bdata);                              //链表 

    mapsize = bootmap_bytes(end - start); //上次计算的时储存整个位图需要的页数,这次只计算位图的大小size=0x1000     

    memset(bdata->node_bootmem_map, 0xff, mapsize);   //将整个位图初始化为0xff

    return mapsize;

}

list_add_tail只执行这一句

static void __init link_bootmem(bootmem_data_t *bdata)

{

    struct list_head *iter;

    list_for_each(iter, &bdata_list) {

        bootmem_data_t *ent;

        ent = list_entry(iter, bootmem_data_t, list);

        if (bdata->node_min_pfn < ent->node_min_pfn)

            break;

    }

    list_add_tail(&bdata->list, iter);          //只执行了这一句

}

5.3 初始化内存结点
setup_arch
    --> paging_init
        --> bootmem_init
               --> arm_bootmem_free

static void __init arm_bootmem_free(unsigned long min, unsigned long max_low, unsigned
long max_high)

{

    //min=0x60000, max_low=0x68000, max_high=0x68000, MAX_NR_ZONES=2

    unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];

    struct memblock_region *reg;

    memset(zone_size, 0, sizeof(zone_size));

    zone_size[0] = max_low - min;
          //zone_size[0]=0x8000

    memcpy(zhole_size, zone_size, sizeof(zhole_size));

    for_each_memblock(memory, reg) {

        unsigned long start = memblock_region_memory_base_pfn(reg); //start=0x60000

        unsigned long end = memblock_region_memory_end_pfn(reg);    //end=0x68000

        if (start < max_low) {

            unsigned long low_end = min(end, max_low);

            zhole_size[0] -= low_end - start;  //zhole_size[0]=0x0,说明没有留下空洞hole

        }

    }

    free_area_init_node(0, zone_size, min, zhole_size);   //初始化结点中所有内存区

}

void __paginginit free_area_init_node(int nid, unsigned
long *zones_size,

        unsigned long node_start_pfn, unsigned long *zholes_size)

{

    pg_data_t *pgdat = NODE_DATA(nid);   
//contig_page_data的基地址

    pgdat->node_id = nid;

    pgdat->node_start_pfn = node_start_pfn;

    calculate_node_totalpages(pgdat, zones_size, zholes_size);

    alloc_node_mem_map(pgdat);

    free_area_init_core(pgdat, zones_size, zholes_size);

}

分析不下去了,需要再好好看一下-->    memblock_alloc_base 等
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: