天天看點

[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

linux核心版本:linux4.9.115(arm64)

文章目錄

  • 1.bootmem_init()
  • 2.arm64_memory_present
  • 3.sparse_init
  • 4.zone_sizes_init
    • free_area_init_node
      • free_area_init_core

arm64_memblock_init()完成了memblock的初始化然後通過paging_init建立隻能用于核心的頁表開始初始化分頁機制。分頁機制完成後核心則需要通過bootmem_init函數開始進行記憶體基本資料結構的初始化工作且bootmem_init函數就是建立buddy記憶體管理方案的關鍵一步。本章主要分析arm 64架構的bootmem_init函數。(在進入分析函數前需要讀者需要預先了解PFN,NUMA和uma,linux三種記憶體模型這3方面的知識:http://www.wowotech.net/memory_management/memory_model.html。)

1.bootmem_init()

void __init bootmem_init(void)  
{  
    unsigned long min, max;  
    //擷取最大,最小頁号;此處求min和max的方法貌似舍棄掉了不足一個page大小的實體記憶體  
    min = PFN_UP(memblock_start_of_DRAM());  
    max = PFN_DOWN(memblock_end_of_DRAM());  
   /*
    *可以看到如果使能CONFIG_MEMTEST,并且傳遞的command line 中保護 memtest 關鍵字的話, 
    *則核心會對沒有使用的free memory做memtest,通過相關算法檢測出存在問題的dram, 
    *将這些dram過reserve_bad_mem 保留起來不使用,進而保證系統能正常boot 
    */  
    early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);  
  
    max_pfn = max_low_pfn = max;  
    //若支援且使能numa則做一些numa相關的初始化工作  
    arm64_numa_init();   
    /*
     *若配置了CONFIG_SPARSEMEM則通過arm64_memory_present()進行支援sparse memory模型相關數		  
     *據結構的初始化工作,未配置則跳過該函數 
     */  
    arm64_memory_present();  
  
    sparse_init();  
    zone_sizes_init(min, max);  
  
    memblock_dump_all();  
}  
           

2.arm64_memory_present

[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

1.arm64_memory_present函數調用關系圖

若核心配置了CONFIG_SPARSEMEM,調用該函數周遊memblock中所有的memory region,每個region劃分為1G大小的section,并設定section對應的結構體struct mem_section的section_mem_map成員 ,主要是設定present位和nid位(section大小可以配置,對于arm64往往section大小為1G)。具體實作細節如下所示

  1. 1 arm64_memory_present
    static void __init arm64_memory_present(void)  
    {  
        struct memblock_region *reg;  
           /*針對每一個memblock中的memory type的region,調用memory_present函數配置設定memory region中每個
            *section的struct *mem_section結構體的實體空間,并将nid放在mem_section結構體的section_mem_map
            *成員中然後置位section_mem_map的present位.表示該mem_section有對應的實體記憶體了,可以進行存儲該
            *section中所有頁的struct page結構體實體空間的配置設定.需要注意的是:
    		* (1)此函數并沒進行struct page結構體對應實體空間的配置設定的操作
    		* (2)nid除了存儲在ms->section_mem_map中,還會存儲在全局靜态數組section_to_node_table中,目
    		*      的是通過struct page結構體指針找到對應的nid。
    		*/
        for_each_memblock(memory, reg) {  
            int nid = memblock_get_region_node(reg);  
      
            memory_present(nid, memblock_region_memory_base_pfn(reg),  
                    memblock_region_memory_end_pfn(reg));  
        }  
    }  
               
  2. memory_present
    void __init memory_present(int nid, unsigned long start, unsigned long end)  
    {  
        unsigned long pfn;  
        //保留起始pfn對應的 section num,section内的偏移mask為0  
        start &= PAGE_SECTION_MASK;  
        // 對實體位址範圍進行檢查  
        mminit_validate_memmodel_limits(&start, &end);  
        //以section為機關建立mem_section結構體的記憶體空間,并不配置設定 struct page所占記憶體空間  
        for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {  
            //擷取pfn對應的section編号,高18位  
            unsigned long section = pfn_to_section_nr(pfn);  
            struct mem_section *ms;  
           /* 
            *根據section編号,找出标号屬于全局靜态指針數組mem_section中的哪個root section,并判斷該
            *root section是否存在.如果mem_section[root]對應的指針不存在,需要配置設定一個大小為1個page的記憶體空
            *間(類型為struct mem_section *)。然後将該位址指向對應的mem_section[root] 
            */  
            sparse_index_init(section, nid);  
            /* 
             *給全局靜态指針數組section_to_node_table指派,數組的大小為系統中SECTION的數量。該數組元素是
             *sectio區域對應的記憶體節點号node id,索引index是section num.對于arm64架構由
             *于不支援NUMA是以nid都為0。作用是後續通過struct page結構體指針找到對應的nid.
             */  
            set_section_nid(section, nid); 
    		/*
    		 *如果section對應的靜态指針數組mem_section的root不存在傳回NULL,存在則取該root指向記憶體空間對應
    		 *的偏移例如section=15,SECTION_PER_ROOT=4,則指派mem_section[3]+3,即是mem_section[3][3] 
             */  
            ms = __nr_to_section(section);  
            /* 
             *首次初始化時,ms對應的section_mem_map是NULL,此時将對應的nid放在ms->section_mem_map特定的位
             *置,同時把ms->section_mem_map的第一位置為1,表示該mem_section num對應的mem_section區域為
             *present,但是此時*該pfn對應的struct page結構體記憶體空間還未配置設定: 
             *(1)sparse_encode_early_nid(nid) ---> nid << SECTION_NID_SHIFT 
             *(2)SECTION_MARKED_PRESENT     1<<0 
    		 */  
            if (!ms->section_mem_map)  
                ms->section_mem_map = sparse_encode_early_nid(nid) |  
                                SECTION_MARKED_PRESENT;  
        }  
    }  
               
  3. sparse_index_init
    static int __meminit sparse_index_init(unsigned long section_nr, int nid)  
    {  
        //根據section num,首先判斷此section屬于哪個root section  
        unsigned long root = SECTION_NR_TO_ROOT(section_nr);  
        struct mem_section *section;  
        //判斷此root section是否存在,存在則傳回-EEXIST  
        if (mem_section[root])  
            return -EEXIST;  
        /*若root section不存在則配置設定一個大小為1個page的記憶體空間,當配置設定成功則将該記憶體基位址指
    	 *針指向靜态定義的mem_section指針數組(位址類型轉化為struct mem_section *)配置設定記憶體通
    	 *過memblock_virt_alloc_node函數在對應節點上配置設定1page大小實體記憶體并傳回虛拟位址 
         */  
        section = sparse_index_alloc(nid);  
        if (!section)  
            return -ENOMEM;  
          
        mem_section[root] = section;  
      
        return 0;  
    }  
               

3.sparse_init

該函數主要做兩件事

    1. 給所有的理論存在的section記憶體區域配置設定一個unsigned long **usemap_map指針數組,類似于:unsigned long usemap_map [L1][L2],其中L1表示記憶體中最大的section 的個數即是NR_MEM_SECTIONS,L2表示每個section中有多少個page block。頁塊(page block)的的大小為2^(MAX_ORDER-1)個page。usemap_map指向的元素是一個實體記憶體空間首位址映射的虛拟位址,該記憶體空間存儲着section中每個page block的位圖資料該資料大小為4bit,與記憶體的回收機制相關:4bits=3bits Migrate + 1bit Skip。
    2. 給所有present的section配置設定實體空間用于存儲section的每個頁塊位圖資訊,并将其虛拟位址填充在usemap_map對應的位置,非present section在數組中對應的元素指向NULL。
  1. 周遊所有present section,然後将其映射到vmemmap區域空間。

Ps:下面代碼核心未配置CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER且配置了CONFIG_SPARSEMEM_VMEMMAP,若是其它配置配置設定方式可能出現差異

[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

2.Sparse_init函數調用關系圖

//mm/spase.c
void __init sparse_init(void)  
{  
    unsigned long pnum;  
    struct page *map;  
    unsigned long *usemap;  
    unsigned long **usemap_map;  
    int size;    
    /* see include/linux/mmzone.h 'struct mem_section' definition */  
    BUILD_BUG_ON(!is_power_of_2(sizeof(struct mem_section)));  
  
    /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */  
    set_pageblock_order();  
  
    /*NR_MEM_SECTIONS是理論是最大的section的數量*/  
    size = sizeof(unsigned long *) * NR_MEM_SECTIONS;  
    /*
	 *通過memblock子產品配置設定usemap_map指向的實體記憶體并傳回虛拟位址,實際上這裡為所有可能存在的section配置設定了	
	 *unsigned long * 類型的指針數組 
     */  
    usemap_map = memblock_virt_alloc(size, 0);  
    if (!usemap_map)  
        panic("can not allocate usemap_map\n");  
    /* 
     *分别為記憶體各個節點的所有section在usemap_map中對應的位置進行指派: 
     *(1)為所有節點中每個present的section配置設定實體空間用于存儲各個section的位圖資料,其中同一個節點中所有
     *     section 位圖資訊存儲的實體記憶體配置設定是通過memblock按節點統一配置設定的,并傳回記憶體空間基位址映射的虛拟地		   址。 
	 *(2)循環找出每個節點的所有present section在指針數組usemap_map對的應位置,然後把其虛拟位址賦給它。其中	
	 *	   sparse_early_usemaps_alloc_node函數用于處理單個節點所有present section相關記憶體配置設定和虛拟位址指派工      *     作,而alloc_usemap_and_memmap是循環調用sparse_early_usemaps_alloc_node處理每個節點中的所有的
	 *     present section 
     */  
    alloc_usemap_and_memmap(sparse_early_usemaps_alloc_node,  
                            (void *)usemap_map);   
    /* 
	 *(1)先給每個線上的section的struct section結構體中的pageblock_flags元素指派。
	 *(2)由于還未對線上的section中所有頁的struct page結構體配置設定存儲空間,是以接着給每個線上的section配置設定了一段	 *     實體空間用于存儲struct page,同時實體位址映射的虛拟位址位于vmemmap區域中,可以通過pfn進行索引擷取。
	 *(3)最後将section基位址pfn對應的struct page的虛拟位址經過相關編碼工作,指派給struct section結構體中的	
	 *     section_mem_map元素
	 */  
    for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {  
        if (!present_section_nr(pnum))  
            continue;  
  
        usemap = usemap_map[pnum];  
        if (!usemap)  
            continue;  
  
        /* 
         *上面已經判定了該section為present,此處為該section的所有page的struct page結構體執行個體配置設定實際的虛拟空間
         *和對應的實體空間,map為虛拟空間的的基位址
		 */  
        map = sparse_early_mem_map_alloc(pnum);  
        if (!map)  
            continue;  
        //給目前section對應的結構體成員指派  
        sparse_init_one_section(__nr_to_section(pnum), pnum, map,  
                                usemap);  
    }  
  
    vmemmap_populate_print_last();  
  
    memblock_free_early(__pa(usemap_map), size);  
}  
           
  1. sparse_early_usemaps_alloc_node:該函數處理單個節點所有線上的section的所有page_block位圖存儲空間的配置設定,最後将傳回的虛拟位址指派給usemap_map[pnum],後面具體指派時可以通過section的編号擷取該虛拟位址中:
    static void __init sparse_early_usemaps_alloc_node(void *data,  
                     unsigned long pnum_begin,  
                     unsigned long pnum_end,  
                     unsigned long usemap_count, int nodeid)  
    {  
        void *usemap;  
        unsigned long pnum;  
        unsigned long **usemap_map = (unsigned long **)data;  
       //section的pageblock bitmap大小  
        int size = usemap_size();  
      //動态配置設定目前節點上所有section的位圖存儲的實體空間,傳回該空間首位址的虛拟位址  
        usemap = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nodeid),  
                                  size * usemap_count);  
        if (!usemap) {  
            pr_warn("%s: allocation failed\n", __func__);  
            return;  
        }  
        /* 
         *每個節點中所有section對應的位圖資訊存儲在一個對應節點下的一段記憶體空間中,下面是把pnum_begin到
         *pnum_end-1範圍内的所有present section找出來,然後将上面給其配置設定的位圖存儲空間的虛拟位址填充在指針
         *數組usemap_map對應的位置。pnum_begin到pnum_end-1表示目前節點中所有的section num 
         */  
        for (pnum = pnum_begin; pnum < pnum_end; pnum++) {  
            //跳過目前節點的非present section  
            if (!present_section_nr(pnum))  
                continue;  
            usemap_map[pnum] = usemap;  
            //跳轉到下一個present section位圖資訊存儲的虛拟位址  
            usemap += size;  
            check_usemap_section_nr(nodeid, usemap_map[pnum]);  
        }  
    }  
               
  2. alloc_usemap_and_memmap:該函數循環中的每一次疊代處理同一個節點中所有present的section。通過調用alloc_fun函數給目前節點的所有線上section配置設定對應的實體空間,用于存儲該對應section的相關資訊。最後把實體空間基位址的虛拟位址賦給usemap_map[pnum],通過section的num号進行索引:
    static void __init alloc_usemap_and_memmap(void (*alloc_func)  
                        (void *, unsigned long, unsigned long,  
                        unsigned long, int), void *data)  
    {  
        unsigned long pnum;  
        unsigned long map_count;  
        int nodeid_begin = 0;  
        unsigned long pnum_begin = 0;  
        //周遊所有的section,找到第一個present的section  
        for (pnum = 0; pnum < NR_MEM_SECTIONS; pnum++) {  
            struct mem_section *ms;  
      
            if (!present_section_nr(pnum))  
                continue;  
            ms = __nr_to_section(pnum);  
            nodeid_begin = sparse_early_nid(ms);  
            pnum_begin = pnum;  
            break;  
        }  
        //記錄目前(nodeid_begin)節點上有多少個presentsection  
        map_count = 1;  
        //一個循環處理一個節點的所有preset section的實體空間配置設定,和虛拟的指派操作  
        for (pnum = pnum_begin + 1; pnum < NR_MEM_SECTIONS; pnum++) {  
            struct mem_section *ms;  
            int nodeid;  
            //跳過非present的section  
            if (!present_section_nr(pnum))  
                continue;  
            ms = __nr_to_section(pnum);  
            nodeid = sparse_early_nid(ms);  
            /*
              *此處結合for循環統計在nodeid_begin節點上的present section個數。 
             */  
            if (nodeid == nodeid_begin) {  
                map_count++;  
                continue;  
            }  
            /*每個循環到達此處代表該節點的present section的數量統計已經結束,且該節點線上的section的
             *section num在範圍(pnum_begin和pnum - 1)中(該範圍中仍然有非present的section),alloc_func
             *指向的函數是給目前節點中所有線上的section配置設定相關實體位址空間,配置設定後傳回物虛拟位址空間的基地
             *址。并将基準位址放在數組data對應的位置(data用于存儲指針的數組,長度為系統總section個數) 
             */  
            alloc_func(data, pnum_begin, pnum,  
                            map_count, nodeid_begin);  
            //為下一個節點處理初始化資料  
            nodeid_begin = nodeid;  
            pnum_begin = pnum;  
            map_count = 1;  
        }  
        /* ok, last chunk */  
        alloc_func(data, pnum_begin, NR_MEM_SECTIONS,  
                            map_count, nodeid_begin);  
    }  
               
    Ps:上面函數(2)循環調用函數(1)來達到處理每個節點的目的
  3. sparse_early_mem_maps_alloc:該函數最終作用是給每個線上的section配置設定一段實體空間用于存儲該section中每個頁的struct page,并将該實體空間映的位址與vmemmap區域對應的虛拟位址建立映射關系。最後傳回基位址的虛拟位址用于後續給struct section的section_mem_map指派(就是将section的section_mem_map映射到vmemmap區域):
    static struct page __init *sparse_early_mem_map_alloc(unsigned long pnum)  
    {  
        struct page *map;  
        struct mem_section *ms = __nr_to_section(pnum);  
        int nid = sparse_early_nid(ms);  
        map = sparse_mem_map_populate(pnum, nid);  
        if (map)  
            return map;  
      
        pr_err("%s: sparsemem memory map backing failed some memory will not be available\n",  
               __func__);  
        ms->section_mem_map = 0;  
        return NULL;  
    }  
               
    sparse_mem_map_populate:**該函數有兩條不同的線由核心配置CONFIG_SPARSEMEM_VMEMMAP控制:
    1. 若沒有配置CONFIG_SPARSEMEM_VMEMMAP,則每個線上的section中所有page的struct page存儲的實體位址空間和虛拟位址都由memblock子產品配置設定,先申請實體空間再傳回其對應的虛拟位址
    2. 若配置了CONFIG_SPARSEMEM_VMEMMAP,則先通過過pfn_to_page在vmemmap區域擷取存儲該section的所有頁面對應的“struct page”結構體執行個體的虛拟位址,然後再通過vmemmap_populate函數配置設定對應的實體位址空間,并将上面獲得的虛拟位址和新配置設定實體内容空間的實體位址在轉換表和頁表中建立映射關系。以後核心就可以通過核心虛拟位址空間的Vmemmap區間的虛拟位址來回去到實體頁對應的struct page資料(1.先用實體頁框号為索引,在Vmemmap區間擷取到實體頁struct page描述符的虛拟位址(pfn_to_page),2.然後将通過擷取到的虛拟位址通過MMU,就能回去到該頁的實體位址,也就能通路到struct page描述符對應的資料):
      1. 由于VMEMMAP的虛拟位址并未在轉換頁表和頁表中建立過與實體位址的映射關系,是以vmemmap_populate先是通過虛拟位址找到其在轉換頁表中對應的entry的位置,并填寫entry,然後通過memblock配置設定一個頁大小的實體記憶體空間,用新配置設定頁的實體頁框号(pfn)來填pte頁表中對應的entry。(注意新配置設定的實體頁用來存該section中每個頁面描述符struct page,且新頁的實體空間在目前節點上配置設定)
    本文分析的是配置了CONFIG_SPARSEMEM_VMEMMAP這種情況代碼如下:
    struct page * __meminit sparse_mem_map_populate(unsigned long pnum, int nid)  
    {  
        unsigned long start;  
        unsigned long end;  
        struct page *map;  
        /* 
         *配置設定虛拟位址空間,若定義了CONFIG_SPARSEMEM_VMEMMAP,該虛拟位址空間連續 
         *#define PAGES_PER_SECTION       (1UL << PFN_SECTION_SHIFT) 
         *#define pfn_to_page __pfn_to_page 
         *#define __pfn_to_page(pfn)    (vmemmap + (pfn)) 
         *#define vmemmap    ((struct page *)VMEMMAP_START - (memstart_addr >> PAGE_SHIFT)
         *#define VMEMMAP_START     (PAGE_OFFSET - VMEMMAP_SIZE) 
         *#define VMEMMAP_SIZE (UL(1) << (VA_BITS - PAGE_SHIFT- 1 + STRUCT_PAGE_MAX_SHIFT)) 		   */  
        map = pfn_to_page(pnum * PAGES_PER_SECTION);  
        start = (unsigned long)map;  
        end = (unsigned long)(map + PAGES_PER_SECTION);  
        /* 
         *将配置設定的虛拟位址空間映射到實際的實體記憶體上 
         *(1)先将根據虛拟位址把對應的資訊填寫到所有轉換頁表(PGD=&init_mm,PUD,PMD)對應的entry上,因為
         *   VMEMMAP區域虛拟位址頁表還未映射是以其虛拟位址對應的頁表和轉換頁表上對應的entry還為0. 
         *(2)對于pte頁表,先找到虛拟位址對應的entry,然後通過memblock子產品在目前節點上配置設定一個page的實體頁,
         *   最後用剛配置設定實體頁的pfn和該頁的權限資訊去填充對應的pte entry。 
         *在建立虛拟得知和實體位址的映射關系時,pgd頁表就是以前初始化建立的swapper_pg_dir頁表(實體位址在
         *ttbr0寄存器中)
         */  
        if (vmemmap_populate(start, end, nid))  
            return NULL;  
      
        return map;  
    } 
               
  4. sparse_init_one_section:該函數給目前section的struct mem_section結構體的section_mem_map和page_block_flags指派。其中section_mem_map是裡面存儲了多個section資訊:(a)section中所頁的struct page存儲空間基位址的虛拟位址。(b)section的present資訊。(c)section的map資訊 (d)section首個pfn資訊。(e)section的nid資訊;page_block_flags代表的是section區域中所有pageblock的位圖資料存儲空間基位址的虛拟位址
    static int __meminit sparse_init_one_section(struct mem_section *ms,  
            unsigned long pnum, struct page *mem_map,  
            unsigned long *pageblock_bitmap)  
    {  
        if (!present_section(ms))  
            return -EINVAL;  
        //~SECTION_MAP_MASK: 11  
        ms->section_mem_map &= ~SECTION_MAP_MASK;  
    /*
     * Subtle, we encode the real pfn into the mem_map such that
     *the identity pfn - section_mem_map will return the actual
     *physical page frame number.
     *mem_map指向的是存儲ms區域中所有頁的struct page執行個體的實體空間基位址對應的虛拟位址。
     *mem_map不是所有位都用來存儲虛拟位址。第一位用來表示該section是否present,而第二位表示
     *該section是否map過(該map是指從section到page的map),第3位上幾位用于儲存nid資料: 
     * (1)因為mem_map虛拟位址的低PAGE_SHIFT位全為0,是以mem_map理論上低PAGE_SHIFT位都能用來存儲其它資訊。 
     *      若需要使用mem_map作為虛拟位址時,隻需将該mem_map與相關MASK做一個與運算即可獲得實際的虛拟位址。 
     * (2)由于mem_map虛拟位址隻能通路到存儲ms區域第一個pfn對應的struct page執行個體的實體空間,并不能知道該
     *      stuct page代表哪一個pfn。于是此處用sparse_encode_mem_map将實際的pfn編碼到mem_map中去下面是編
     *      碼和解碼過程: 
     *     (a)編碼:先通過section_nr_to_pfn(pnum)擷取到ms區域的首個pfn。然後mem_map=mem_map-pfn.用
     *          sparse_encode_mem_map(mem_map,pnum)實作上述操作 
     *      (b)解碼:後續需要mem_map擷取ms第一個pfn指向的struct page執行個體,隻需通過
     *	       vaddr=sparse_decode_mem_map(mem_map,pnum),vaddr就是struct page執行個體的對應的虛拟位址,實際
     *         的pfn=vaddr-mem_map将mem_map經過位運算和編碼操 作後将值賦給ms的section_mem_map成員 
     */ 
        ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum) |  
                                SECTION_HAS_MEM_MAP;  
        //将ms的每個pageblock位圖表的虛拟位址指派給pageblock_flags成員  
        ms->pageblock_flags = pageblock_bitmap;  
      
        return 1;  
    
    }  
               

經過上述函數的處理,最終核心展現出sparese memory記憶體模型的示意圖如圖3所示:

[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

3.Sparse記憶體模型映射關系示意圖

若核心配置了CONFIG_SPARSEMEM_VMEMMAP,這表示核心通過vmemmap能夠在pfn和page執行個體位址之間快速轉換,它多用于具有較大vmalloc區域的64位系統。此時多了一層特殊的映射關系,模型示意圖可參考圖4:

[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

4.Sparse_vmemmap記憶體模型映射關系示意圖

4.zone_sizes_init

在學習該函數前需要需要先學習兩個關鍵結構體struct pglist_data和strcut zone,以及兩結構體間關系(前面已經做了詳細介紹)。

該函數主要是對各個節點對應的struct pglist_data結構體成員進行初始化,同時也對其所屬的所有zone區域對應的結構體stuct zone的成員進行初始化和對zone區域中所有有效頁對應的struct page中的成員進行初始化。函數實作細節如圖5所示。(圖五流程分析針對的系統具有arm64架構,sparse記憶體模型,UMA和VMEMMAP這4個條件)

[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

5.zone_sizes_init實作流程

下面代碼隻分析了uma,情況下的實作細節對于numa以後有機會分析分析。下面看具體代碼實作細節:

static void __init zone_sizes_init(unsigned long min, unsigned long max)  
{  
    struct memblock_region *reg;  
    unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];  
    unsigned long max_dma = min;  
    //數組記憶體區域初始化為0  
    memset(zone_size, 0, sizeof(zone_size));  
  
    /* 4GB maximum for 32-bit only capable devices */  
#ifdef CONFIG_ZONE_DMA  
    max_dma = PFN_DOWN(arm64_dma_phys_limit);  
    /*計算DMA大小,此處的大小隻是記憶體的跨度,并不是實際大小,因為記憶體中可能有空洞。從該區域起始位置起将4G的記憶體分
    *配給DMA域(arm64)。當總的記憶體小于4g,則所有記憶體空間都用作DMA記憶體域,此時NORMAL域 0 
	*/  
    zone_size[ZONE_DMA] = max_dma - min;  
#endif  
    //同上,其中NORMAL值可能為0  
    zone_size[ZONE_NORMAL] = max - max_dma;  
    /*下面主要是周遊memblock中的所有memory region,分别計算出DMA和NORMAL記憶體區域的空洞大小,存儲在zhole_size數
    *組中。空洞存在于region和region之間 
    */  
    memcpy(zhole_size, zone_size, sizeof(zhole_size));  
    for_each_memblock(memory, reg) {  
        unsigned long start = memblock_region_memory_base_pfn(reg);  
        unsigned long end = memblock_region_memory_end_pfn(reg);  
 
        if (start >= max)  
            continue;  
  
#ifdef CONFIG_ZONE_DMA  
        if (start < max_dma) {  
            unsigned long dma_end = min(end, max_dma);  
            zhole_size[ZONE_DMA] -= dma_end - start;  
        }  
#endif  
        if (end > max_dma) {  
            unsigned long normal_end = min(end, max);  
            unsigned long normal_start = max(start, max_dma);  
            zhole_size[ZONE_NORMAL] -= normal_end - normal_start;  
        }  
    }  
    //初始化記憶體節點,unam系統隻有一個記憶體節點--->contig_page_data  
    free_area_init_node(0, zone_size, min, zhole_size);  
}  
           

free_area_init_node

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);  
    unsigned long start_pfn = 0;  
    unsigned long end_pfn = 0;  
  
    /* pg_data_t should be reset to zero when it's allocated */  
    WARN_ON(pgdat->nr_zones || pgdat->kswapd_classzone_idx);  
    //若為uma系統隻有一個節點,nid為0  
    pgdat->node_id = nid;  
    pgdat->node_start_pfn = node_start_pfn;  
    pgdat->per_cpu_nodestats = NULL;  
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP  
    get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);  
    pr_info("Initmem setup node %d [mem %#018Lx-%#018Lx]\n", nid,  
        (u64)start_pfn << PAGE_SHIFT,  
        end_pfn ? ((u64)end_pfn << PAGE_SHIFT) - 1 : 0);  
#else  
    start_pfn = node_start_pfn;  
#endif  
    /* 
     *(1)給節點中各個記憶體的zone的zone_start_pfn成員,spanned_pages成員和present_pages成員指派 
	 *(2)給節點的node_spanned_pages和node_present_pages成員指派 
     */  
    calculate_node_totalpages(pgdat, start_pfn, end_pfn,  
                  zones_size, zholes_size);  
    //針對FLAT記憶體模型  
    alloc_node_mem_map(pgdat);  
#ifdef CONFIG_FLAT_NODE_MEM_MAP  
    printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx\n",  
        nid, (unsigned long)pgdat,  
        (unsigned long)pgdat->node_mem_map);  
#endif  
  
    reset_deferred_meminit(pgdat);  
    /*初始化pgdata相關成員,主要是初始化該節點中的每個zone對應的struct zone結構體成員和zone中每個頁struct page
     *結構的的相關成員 
	 */  
    free_area_init_core(pgdat);  
}
           

free_area_init_core

該函數内部有很多宏控制的結構體成員初始化,本文此處進行了省略有興趣的同學可以通過核心源碼自己進行分析。

static void __paginginit free_area_init_core(struct pglist_data *pgdat)  
{  
    enum zone_type j;  
    int nid = pgdat->node_id;  
    int ret;  
    /*初始化pg_dat_t結構體的相關成員;前面是初始化其内部的鎖和隊列*/  
    //給node_size_lock自旋鎖加鎖(CONFIG_MEMORY_HOTPLUG配置才有效)  
    pgdat_resize_init(pgdat); 
#ifdef CONFIG_NUMA_BALANCING  
    spin_lock_init(&pgdat->numabalancing_migrate_lock);  
    pgdat->numabalancing_migrate_nr_pages = 0;  
    pgdat->numabalancing_migrate_next_window = jiffies;  
#endif  
#ifdef CONFIG_TRANSPARENT_HUGEPAGE  
    spin_lock_init(&pgdat->split_queue_lock);  
    INIT_LIST_HEAD(&pgdat->split_queue);  
    pgdat->split_queue_len = 0;  
#endif  
    init_waitqueue_head(&pgdat->kswapd_wait);  
    init_waitqueue_head(&pgdat->pfmemalloc_wait);  
#ifdef CONFIG_COMPACTION  
    init_waitqueue_head(&pgdat->kcompactd_wait);  
#endif
    pgdat_page_ext_init(pgdat);  
    spin_lock_init(&pgdat->lru_lock);  
    lruvec_init(node_lruvec(pgdat));  
    //初始化該節點中的每個zone結構體,并對zone管理的每個記憶體區域進行初始化  
    for (j = 0; j < MAX_NR_ZONES; j++) {  
        struct zone *zone = pgdat->node_zones + j;  
        unsigned long size, realsize, freesize, memmap_pages;  
        unsigned long zone_start_pfn = zone->zone_start_pfn;  
  
        size = zone->spanned_pages;  
        realsize = freesize = zone->present_pages;  
  
        /* 
         * 通過zone的spanned_pages和present_pages計算出管理該zone所需的struct page 
         * 結構體所占據的頁的個數memmap_pages 
         */  
        memmap_pages = calc_memmap_size(size, realsize);  
        //非高端記憶體域,freesize的大小要減去memmap_pages  
        if (!is_highmem_idx(j)) {  
            if (freesize >= memmap_pages) {  
                freesize -= memmap_pages;  
                if (memmap_pages)  
                    printk(KERN_DEBUG  
                           "  %s zone: %lu pages used for memmap\n",  
                           zone_names[j], memmap_pages);  
            } else  
                pr_warn("  %s zone: %lu pages exceeds freesize %lu\n",  
                    zone_names[j], memmap_pages, freesize);  
        }  
  
        /*第一個記憶體域DMA,要預留一段記憶體給DMA使用。這是某些外設的要求如果系統沒有需求可以不用預留*/  
        if (j == 0 && freesize > dma_reserve) {  
            freesize -= dma_reserve;  
            printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",  
                    zone_names[0], dma_reserve);  
        }  
        /*對于非高端記憶體域,可用的page數都會計入nr_kernel_pages,即所有zone中可用的page數都會計入其中*/  
        if (!is_highmem_idx(j))  
            nr_kernel_pages += freesize;  
          /*對于高端記憶體域當nr_kernel_pages大小充足的情況下,會預留一段記憶體空間用來存儲高端記憶體的page結構體,該段
           *記憶體空間在NORMAL處(猜測),而高端記憶體本身并不會計入nr_kernel_pages.注意arm64架構不存在高端記憶體.
		   */  
  
        else if (nr_kernel_pages > memmap_pages * 2)  
            nr_kernel_pages -= memmap_pages;  
        /*所有zone的freesize都會累加計入nr_all_pages.由上可知在沒有高端記憶體域的時候其大小和nr_kernel_pages一緻
         */  
        nr_all_pages += freesize;  
  
         /* 
          *對于非高端記憶體域zone的managed_pages表示可用的page個數,高端記憶體域則是其實際page個數
          */  
        zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;  
#ifdef CONFIG_NUMA  
        zone->node = nid;  
#endif  
        zone->name = zone_names[j];  
        zone->zone_pgdat = pgdat;  
        /* &zone->lock:Primarily protects free_area */   
        spin_lock_init(&zone->lock);  
        zone_seqlock_init(zone);  
        zone_pcp_init(zone);  
  
        if (!size)  
            continue;  
        //初始化頁塊的order預設是MAX_ORDER - 1  
        set_pageblock_order();  
         /*(1)若是SPARSEMEM記憶體模型該函數不做處理因為sparese_init已經對記憶體塊的位圖資料做了相關處理
		  *(2)若不是稀疏記憶體模型則同過memblock子產品配置設定記憶體空間給該zone的記憶體塊的位圖資料提供存儲空間傳回虛拟地
		  *址,将值賦給zone的pageblock_flags成員
		  */  
        setup_usemap(pgdat, zone, zone_start_pfn, size);  
        //初始化zone的free_area,夥伴系統的核心  
        ret = init_currently_empty_zone(zone, zone_start_pfn, size);  
        BUG_ON(ret);  
        /*通過PFN頁框号找到對應的struct page結構體,然後将該結構體進行初始化,并設定位MIGRATE_MOVABLE标志表示為
         *可移動 
         */  
        memmap_init(size, nid, j, zone_start_pfn);  
    }  
}  
           

上述代碼步驟:

  1. 初始化struct pglist_data内部使用的鎖和隊列
  2. 周遊zone的各個區域
    1. 根據zone的spanned_pages和present pages計算出zone存儲區struct page結構體需要的實體記憶體大小memmap_pages(機關頁)
    2. 計算各個zone的freeszie
    3. 循環統計出全局變量nr_kernel_pages和nr_all_pages。圖6表明這兩個全局參數同節點頁面的關系(為了該圖的通用性以arm32系統為例,因為具有HIGEMEM)。
    4. 初始化zone的各類鎖等變量
    5. 循環周遊zone區域,通過pfn以page大小為機關找出zone區域的每個struct page結構體,對結構體成員進行初始化,并設定MIGRATE_MOVABLE表明可以移動。
[核心記憶體] [arm64] 記憶體初始化4---bootm_init1.bootmem_init()2.arm64_memory_present3.sparse_init4.zone_sizes_init

6.nr_kernel_pages和nr_all_pages參數與節點記憶體關系圖(arm32)

到此bootmem_init完成了實體記憶體的架構的的出示化工作:Node,Zone,page fram和其對應結構體struct page中的各個成員的初始化工作

繼續閱讀