start_kernel -> setup_arch 在這個函數中咱們主要看這幾個函數. machine_specific_memory_setup max_low_pfn = setup_memory(); paging_init zone_sizes_init 而後咱們還要看 build_all_zonelists(); mem_init(); //處理內存圖,最後保存在e820中 char * __init machine_specific_memory_setup(void) { ...... who = "BIOS-e820"; // #define E820_MAP_NR (*(char*) (PARAM+E820NR)) // #define E820_MAP ((struct e820entry *) (PARAM+E820MAP)) // 刪除內存構成圖中任何重疊的部分,由於BIOS所報告的內存構成圖可能有重疊 sanitize_e820_map(E820_MAP, &E820_MAP_NR); //調用copy_e820_map()進行實際的拷貝。 //若是操做失敗,建立一個僞內存構成圖,這個僞構成圖有兩部分:0到640K及1M到最大物理內存。 if (copy_e820_map(E820_MAP, E820_MAP_NR) < 0) { ...... e820.nr_map = 0; add_memory_region(0, LOWMEMSIZE(), E820_RAM); add_memory_region(HIGH_MEMORY, mem_size << 10, E820_RAM); } ...... } 這裏要特別對宏E820_MAP進行說明。E820_MAP是個struct e820entry數據結構的指針. 這個數據結構定義在include/i386/e820.h中: struct e820map { int nr_map; struct e820entry { unsigned long long addr; /* start of memory segment */ unsigned long long size; /* size of memory segment */ unsigned long type; /* type of memory segment */ } map[E820MAX]; }; 其中,E820MAX被定義爲128。從這個數據結構的定義能夠看出,每一個e820entry都是對一個物理區間的描述,而且一個物理區間必須是同一類型。若是有一片地址連續的物理內存空間,其一部分是RAM,而另外一部分是ROM,那就要分紅兩個區間。即便同屬RAM,若是其中一部分要保留用於特殊目的,那也屬於不一樣的一個分區。在e820.h文件中定義了4種不一樣的類型: #define E820_RAM 1 #define E820_RESERVED 2 #define E820_ACPI 3 /* usable as RAM once ACPI tables have been read */ #define E820_NVS 4 #define HIGH_MEMORY (1024*1024)
其中E820_NVS表示「Non-Volatile Storage」,即「不揮發」存儲器,包括ROM、EPROM、Flash存儲器等。node
在PC中,對於最初1MB存儲空間的使用是特殊的。開頭640KB(0x0~0x9FFFF爲RAM,從0xA0000開始的空間則用於CGA、EGA、VGA等圖形卡。如今已經不多使用這些圖形卡,可是不論是什麼圖形卡,開機時老是工做於EGA或VGA模式。從0xF0000開始到0xFFFFF,即最高的4KB,就是在EPROM或Flash存儲器中的BIOS。因此,只要有BIOS存在,就至少有兩個區間,若是nr_map小於2,那就必定出錯了。因爲BIOS的存在,原本連續的RAM空間就不連續了。固然,如今已經不存在這樣的存儲結構了。1MB的邊界早已被突破,但由於歷史的緣由,把1MB以上的空間定義爲「HIGH_MEMORY」,這個稱呼一直沿用到如今,因而代碼中的常數HIGH_MEMORY就定義爲「1024´1024」可是,爲了保持兼容,就得留出最初1MB的空間。linux
這個階段初始化後,物理內存中內核映像的分佈如圖所示:ios
* |
圖 內核映象在物理內存中的分佈數組
符號_text對應物理地址0x00100000,表示內核代碼的第一個字節的地址。內核代碼的結束位置用另外一個相似的符號_etext表示。內核數據被分爲兩組:初始化過的數據和未初始化過的數據。初始化過的數據在_etext後開始,在_edata處結束,緊接着是未初始化過的數據,其結束符號爲_end,這也是整個內核映像的結束符號。數據結構
圖中出現的符號是由編譯程序在編譯內核時產生的。你能夠在System.map文件中找到這些符號的線性地址(或叫虛擬地址),System.map是編譯內核之後所建立的。app
調用copy_e820_map()進行實際的拷貝。函數
若是操做失敗,建立一個僞內存構成圖,這個僞構成圖有兩部分:0到640K及1M到最大物理內存。oop
//進行實際拷貝 int __init copy_e820_map(struct e820entry * biosmap, int nr_map) { //若是物理內存區間小於2,那確定出錯。由於BIOS至少和RAM屬於不一樣的物理區間。 if (nr_map < 2) return -1; //從BIOS構成圖中讀出一項 do { unsigned long long start = biosmap->addr; unsigned long long size = biosmap->size; unsigned long long end = start + size; unsigned long type = biosmap->type; if (start > end) return -1; //一些BIOS把640K~1MB之間的區間做爲RAM來用,這是不符合常規的。由於從0xA0000開始的空間用於圖形卡,所以,在內存構成圖中要進行修正。 ////若是一個區的起點在0xA0000 (640K)如下,而終點在1MB之上,就要將這個區間拆開成兩個區間,中間跳過從0xA0000到1MB邊界之間的那一部分。 if (type == E820_RAM) { if (start < 0x100000ULL && end > 0xA0000ULL) { if (start < 0xA0000ULL) add_memory_region(start, 0xA0000ULL-start, type); if (end <= 0x100000ULL) continue; start = 0x100000ULL; size = end - start; } } //這個函數的功能就是在e820中增長一項 add_memory_region(start, size, type); } while (biosmap++, --nr_map); return 0; } 下面來看 #define PFN_UP(x) (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) #define PFN_DOWN(x) ((x) >> PAGE_SHIFT) #define PFN_PHYS(x) ((x) << PAGE_SHIFT) PFN_UP() 和PFN_DOWN()都是將地址x轉換爲頁面號(PFN即Page Frame Number的縮寫),兩者之間的區別爲:PFN_UP()返回大於x的第一個頁面號,而PFN_DOWN()返回小於x的第一個頁面號。宏PFN_PHYS()返回頁面號x的物理地址。 static unsigned long __init setup_memory(void) { //arch/i386/kernel/head.S 中有 //movl %edi,(init_pg_tables_end - __PAGE_OFFSET) 將實際映射到的最後頁表的內存地址放到變量init_pg_tables_end的物理地址 //init_pg_tables_end = 內核保護模式代碼啓始地址(0x100000) + 內核保護模式代碼尺寸 + pg0的1024個4字節頁面描述符號 + 保證第一次分頁設置的頁表尺寸 //(通常還須要若干1024個4字節的頁面描述符號,由內核尺寸決定) + 描述1G內存的位圖尺寸128K字節 + 描述1G內存的頁表空間(1024*4096字節) + //間隔空間(4*4096字節) //初始化最小可用pfn min_low_pfn = PFN_UP(init_pg_tables_end); //在e820 結構中查找最大pfn. 初始化max_pfn find_max_pfn(); //咱們要好好看看這個 max_low_pfn = find_max_low_pfn(); #ifdef CONFIG_HIGHMEM highstart_pfn = highend_pfn = max_pfn; if (max_pfn > max_low_pfn) { highstart_pfn = max_low_pfn; //初始化高端內存開始pfn } printk(KERN_NOTICE "%ldMB HIGHMEM available.\n", pages_to_mb(highend_pfn - highstart_pfn)); #endif printk(KERN_NOTICE "%ldMB LOWMEM available.\n", pages_to_mb(max_low_pfn)); //初始化max_low_pfn的低端內存映射圖bitmap,並對系統啓動時使用一些特殊佔用內存進行保留設置。 setup_bootmem_allocator(); return max_low_pfn; } 首先介紹一些宏定義: #define VMALLOC_RESERVE (unsigned long)(128 << 20) #define MAXMEM (unsigned long)(-PAGE_OFFSET-VMALLOC_RESERVE) #define MAXMEM_PFN PFN_DOWN(MAXMEM) #define MAX_NONPAE_PFN (1 << 20) VMALLOC_RESERVE :爲vmalloc()函數訪問內核空間所保留的內存區,大小爲128MB。 MAXMEM :內核可以直接映射的最大RAM容量,爲1GB-128MB=896MB(-PAGE_OFFSET就等於1GB) MAXMEM_PFN :返回由內核能直接映射的最大物理頁面數。 MAX_NONPAE_PFN :給出在4GB之上第一個頁面的頁面號。當頁面擴充(PAE)功能啓用時,才能訪問4GB以上的內存。 unsigned long __init find_max_low_pfn(void) { unsigned long max_low_pfn; max_low_pfn = max_pfn; if (max_low_pfn > MAXMEM_PFN) { //內存大於896M if (highmem_pages == -1) highmem_pages = max_pfn - MAXMEM_PFN; //初始化高端內存頁數量爲 max_pfn - 896 if (highmem_pages + MAXMEM_PFN < max_pfn) //在調整max_pfn max_pfn = MAXMEM_PFN + highmem_pages; if (highmem_pages + MAXMEM_PFN > max_pfn) { //說明有錯誤發生 printk("only %luMB highmem pages available, ignoring highmem size of %uMB. n", pages_to_mb(max_pfn - MAXMEM_PFN), ages_to_mb(highmem_pages)); highmem_pages = 0; //忽略 } max_low_pfn = MAXMEM_PFN; //初始化爲896M內存的pfn #ifndef CONFIG_HIGHMEM //沒有定義使用高端內存 printk(KERN_WARNING "Warning only %ldMB will be used.\n", MAXMEM>>20); if (max_pfn > MAX_NONPAE_PFN) //內存超過4G printk(KERN_WARNING "Use a PAE enabled kernel.\n"); else printk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n"); max_pfn = MAXMEM_PFN; //最多使用到896M #else //定義 #ifndef CONFIG_X86_PAE //沒有開啓PAE if (max_pfn > MAX_NONPAE_PFN) { max_pfn = MAX_NONPAE_PFN; //只能使用4G內存 printk(KERN_WARNING "Warning only 4GB will be used.\n"); printk(KERN_WARNING "Use a PAE enabled kernel.\n"); } #endif /* !CONFIG_X86_PAE */ #endif /* !CONFIG_HIGHMEM */ //下面說明內存小於896M } else { if (highmem_pages == -1) highmem_pages = 0; //沒有高端內存 #ifdef CONFIG_HIGHMEM if (highmem_pages >= max_pfn) { printk(KERN_ERR "highmem size specified (%uMB) is bigger than pages available (%luMB)!.\n", pages_to_mb(highmem_pages), pages_to_mb(max_pfn)); highmem_pages = 0; } if (highmem_pages) { //有高端內存 if (max_low_pfn-highmem_pages < 64*1024*1024/PAGE_SIZE) { //高端內存的使用致使了常規內存小於64M printk(KERN_ERR "highmem size %uMB results in smaller than 64MB lowmem, ignoring it.\n", pages_to_mb(highmem_pages)); highmem_pages = 0; //忽略 } max_low_pfn -= highmem_pages; //縮小常規內存大小 } #else if (highmem_pages) printk(KERN_ERR "ignoring highmem size on non-highmem kernel!\n"); #endif } return max_low_pfn; } typedef struct bootmem_data { unsigned long node_boot_start; //表示存放bootmem位圖的第一個頁面(即內核映象結束處的第一個頁面) unsigned long node_low_pfn; //表示物理內存的頂點,最高不超過896MB void *node_bootmem_map; //指向bootmem位圖 unsigned long last_offset; //用來存放在前一次分配中所分配的最後一個字節相對於last_pos的位移量 unsigned long last_pos; //用來存放前一次分配的最後一個頁面的頁面號, //這個域用在__alloc_bootmem_core()函數中,經過合併相鄰的內存來減小內部碎片 } bootmem_data_t; 爲了對頁面管理機制做出初步準備,Linux使用了一種叫bootmem分配器(bootmem allocator)的機制,這種機制僅僅用在系統引導時, 它爲整個物理內存創建起一個頁面位圖。這個位圖創建在從min_low_pfn開始的地方,也就是說,內核映象終點_end上方的地方。 這個位圖用來管理低區(例如小於896MB),由於在0到896MB的範圍內,有些頁面可能保留,有些頁面可能有空洞,所以,創建這個 位圖的目的就是要搞清楚哪一些物理頁面是能夠動態分配的。 void __init setup_bootmem_allocator(void) { ...... /* 經過調用init_bootmem()函數,爲物理內存頁面管理機制的創建作初步準備,爲整個物理內存創建起一個頁面位圖。這個位圖創建在從min_low_pfn開始的地方, 也就是說,把內核映像終點_end上方的若干頁面用做物理頁面位圖。在前面的代碼中已經搞清楚了物理內存頂點所在的頁面號爲max_low_pfn,因此物理內存的頁面號必定在0~max_low_pfn之間。但是,在這個範圍內可能有空洞(hole),另外一方面,並非全部的物理內存頁面均可以動態分配。創建這個位圖的目的就是要搞清楚哪一些物理內存頁面能夠動態分配的。 */ bootmap_size = init_bootmem(min_low_pfn, max_low_pfn); //根據e820表將未使用的內存在bdata->node_bootmem_map中對應的位置清0,很簡單 register_bootmem_low_pages(max_low_pfn); //啓始地址是內核啓始指針,尺寸爲從內核開始到內存映射表結束,保留從內核開始到內存映射表結束的內存 //經過使用test_and_set_bit,將保留空間設置爲1 reserve_bootmem(__pa_symbol(_text), (PFN_PHYS(min_low_pfn) + bootmap_size + PAGE_SIZE-1) - __pa_symbol(_text)); reserve_bootmem(0, PAGE_SIZE); //保留啓始4K內存 reserve_ebda_region();保留EBDA開始4K內存 ...... } 在這裏NODE_DATA(0)是全局變量contig_page_data的地址,start是min_low_pfn,pages是max_low_pfn unsigned long __init init_bootmem (unsigned long start, unsigned long pages) { max_low_pfn = pages; min_low_pfn = start; return(init_bootmem_core(NODE_DATA(0), start, 0, pages)); } static unsigned long __init init_bootmem_core (pg_data_t *pgdat, unsigned long mapstart, unsigned long start, unsigned long end) { bootmem_data_t *bdata = pgdat->bdata; unsigned long mapsize = ((end - start)+7)/8; //低端頁面表所佔用尺寸 mapsize = ALIGN(mapsize, sizeof(long)); bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT); //得到min_low_pfn指向的虛擬地址 bdata->node_boot_start = (start << PAGE_SHIFT); // 0 bdata->node_low_pfn = end; //低端內存頁面數目 link_bootmem(bdata); //將這結構插入bdata_list連表 //設置min_low_pfn起內存供低端頁面表佔用的空間初始化爲0xFF memset(bdata->node_bootmem_map, 0xff, mapsize); return mapsize; } 下面咱們看一個很是重要的函數 void __init paging_init(void) { ...... //創建內核頁表 pagetable_init(); //這個最重要,咱們下面看 load_cr3(swapper_pg_dir); //__asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir))); 大約是這樣 //這一句是個宏,它使得轉換旁路緩衝區(TLB)無效。TLB老是要維持幾個最新的虛地址到物理地址的轉換。每當頁目錄改變時,TLB就須要被刷新。 __flush_tlb_all(); //臨時內核映射初始化 kmap_atomic()使用 kmap_init(); } static void __init pagetable_init (void) { unsigned long vaddr; //讓pgd_base (頁目錄基地址) 指向 swapper_pg_dir pgd_t *pgd_base = swapper_pg_dir; #ifdef CONFIG_X86_PAE int i; //若是PAE 被激活, PTRS_PER_PGD就爲4,且變量 swapper_pg_dir 用做頁目錄指針表 for (i = 0; i < PTRS_PER_PGD; i++) set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT)); #endif ...... kernel_physical_mapping_init(pgd_base); //映射核心頁表 ...... //在內存的最高端(4GB - 128MB),有些虛地址直接用在內核資源的某些部分中,這些地址的映射定義在/include/asm-i386/fixmap.h中, //枚舉類型__end_of_fixed_addresses用做索引,宏__fix_to_virt()返回給定索引的虛地址。 //#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT)) // FIXADDR_TOP 0xfffff000 (4G) vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK; //進行保留映射頁的初始化 page_table_range_init(vaddr, 0, pgd_base); //永久映射頁的初始化 permanent_kmaps_init(pgd_base); ...... } //i386體系結構正常使用兩級頁表,若是開啓物理地址擴展PAE那麼使用三級頁表 static void __init kernel_physical_mapping_init(pgd_t *pgd_base) { unsigned long pfn; pgd_t *pgd; pmd_t *pmd; pte_t *pte; int pgd_idx, pmd_idx, pte_ofs; //在給定地址的頁目錄中檢索相應的下標。所以返回0x300 (或 十進制768 ),即內核地址空間開始處的下標。 //所以,pgd如今指向頁目錄表的第768項。 /* #define PGDIR_SHIFT 22 #define PTRS_PER_PGD 1024 開啓PAE #define PGDIR_SHIFT 30 #define PTRS_PER_PGD 4 */ pgd_idx = pgd_index(PAGE_OFFSET); pgd = pgd_base + pgd_idx; //指向768項 pfn = 0; for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) { pmd = one_md_table_init(pgd); if (pfn >= max_low_pfn) continue; //若是是兩級分頁 PTRS_PER_PMD = 1,三級分頁 = 512 啓用了PAE for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) { //計算第pfn個頁框的虛擬地址 unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET; //0xC0000000 if (cpu_has_pse) { //開啓了PSE 4M頁 //計算第pfn個4M頁框結束邊界的虛擬地址 unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1; if (is_kernel_text(address) || is_kernel_text(address2)) set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC)); else set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE)); pfn += PTRS_PER_PTE; // pfn += 1024 } else { //分配一個頁表 pte = one_page_table_init(pmd); //兩級 1024 三級 512 PTRS_PER_PTE for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) { if (is_kernel_text(address)) //addr >= PAGE_OFFSET && addr <= (unsigned long)__init_end 核心正使用 //PAGE_KERNEL_EXEC .. include/asm-i386/pgtable.h //#define __pte(x) ((pte_t) { (x) } ) //#define pfn_pte(pfn, prot) __pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot)) set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC)); else set_pte(pte, pfn_pte(pfn, PAGE_KERNEL)); } } } } } static pmd_t * __init one_md_table_init(pgd_t *pgd) { pud_t *pud; pmd_t *pmd_table; //若是使用了CONFIG_X86_PAE選項,則分配一頁(4K)的內存給頁中間目錄,並在全局目錄中設置它的地址。 //#define pud_offset(pgd, start) (pgd) //#define pmd_offset(pud, address) (pud) //正常兩級分頁,相似這樣 #ifdef CONFIG_X86_PAE pmd_table = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE); set_pgd(pgd, __pgd(__pa(pmd_table) | _PAGE_PRESENT)); pud = pud_offset(pgd, 0); if (pmd_table != pmd_offset(pud, 0)) //從上面的宏能夠看出確定是同樣的 BUG(); #else //不然,沒有頁上級目錄,就把頁上級目錄直接映射到全局頁目錄 pud = pud_offset(pgd, 0); //pud = pgd pmd_table = pmd_offset(pud, 0); // 三級頁表時 pmd_table 爲何是 pgd,pmd_offset 與兩級頁表時不一樣 #endif return pmd_table; } static pte_t * __init one_page_table_init(pmd_t *pmd) { if (pmd_none(*pmd)) { //若是pmd項中爲空,分配一頁用做頁表 pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE); set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE)); if (page_table != pte_offset_kernel(pmd, 0)) BUG(); return page_table; } //返回已經存在的頁表 return pte_offset_kernel(pmd, 0); } static void __init page_table_range_init (unsigned long start, unsigned long end, pgd_t *pgd_base) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; int pgd_idx, pmd_idx; unsigned long vaddr; vaddr = start; pgd_idx = pgd_index(vaddr); //pgd 索引 pmd_idx = pmd_index(vaddr); pgd = pgd_base + pgd_idx; //指向所在pgd項 for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) { if (pgd_none(*pgd)) one_md_table_init(pgd); //已經看過 pud = pud_offset(pgd, vaddr); //取出相應的索引 pmd = pmd_offset(pud, vaddr); for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end); pmd++, pmd_idx++) { if (pmd_none(*pmd)) one_page_table_init(pmd); //已經看過 //兩級頁表 PMD_SIZE (1 << 22) 三級頁表 (1 << 30) vaddr += PMD_SIZE; } pmd_idx = 0; } } static void __init permanent_kmaps_init(pgd_t *pgd_base) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; unsigned long vaddr; //#define FIXADDR_BOOT_START (FIXADDR_TOP - __FIXADDR_BOOT_SIZE) //#define __FIXADDR_BOOT_SIZE (__end_of_fixed_addresses << PAGE_SHIFT) //#define PKMAP_BASE ( (FIXADDR_BOOT_START - PAGE_SIZE*(LAST_PKMAP + 1)) & PMD_MASK ) vaddr = PKMAP_BASE; //LAST_PKMAP 1024 PAE enable 512 page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base); pgd = swapper_pg_dir + pgd_index(vaddr); pud = pud_offset(pgd, vaddr); pmd = pmd_offset(pud, vaddr); pte = pte_offset_kernel(pmd, vaddr); pkmap_page_table = pte; //永久內核映射的開始 kmap 函數使用 } //爲kmap_atomic函數初始化一些參數 static void __init kmap_init(void) { unsigned long kmap_vstart; //臨時內核映射線性地址開始 kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN); //#define kmap_get_fixmap_pte(vaddr) pte_offset_kernel(pmd_offset(pud_offset(pgd_offset_k(vaddr), vaddr), (vaddr)), (vaddr)) //#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address)) //#define pgd_offset_k(address) pgd_offset(&init_mm, address) //init_mm->pgd = swapper_pg_dir :) kmap_pte = kmap_get_fixmap_pte(kmap_vstart); //指向開始的頁表項 kmap_prot = PAGE_KERNEL; } 下面咱們看內存頁是怎樣組織的. 物理內存被劃分爲三個區來管理,它們是ZONE_DMA、ZONE_NORMAL 和ZONE_HIGHMEM。每一個區都用struct zone結構來表示, 定義於include/linux/mmzone.h: struct zone { unsigned long free_pages; //在這個區中現有空閒頁的個數 unsigned long pages_min, pages_low, pages_high; //對這個區最少、較少及最多頁面個數的描述 ...... struct free_area free_area[MAX_ORDER]; //在夥伴分配系統中的位圖數組和頁面鏈表 ...... struct pglist_data *zone_pgdat; //本管理區所在的存儲節點 ...... unsigned long zone_start_pfn; //該管理區的起始pfn unsigned long spanned_pages; //總計頁數量,包含洞 unsigned long present_pages; //總計頁數量,除了洞 char *name; //管理區名字 }; #define ZONE_DMA 0 #define ZONE_DMA32 1 #define ZONE_NORMAL 2 #define ZONE_HIGHMEM 3 #define MAX_NR_ZONES 4 struct free_area { struct list_head free_list; unsigned long nr_free; }; zone結構中的free_area[MAX_ORDER]是一組「空閒區間」鏈表。爲何要定義一組而不是一個空閒隊列呢? 這是由於經常須要成塊地在物理空間分配連續的多個頁面,因此要按塊的大小分別加以管理。 所以,在管理區數據結構中既要有一個隊列來保持一些離散(連續長度爲1)的物理頁面,還要有一個隊列來保持一些 連續長度爲2的頁面塊以及連續長度爲四、8、16、…、直至2 MAX_ORDER(即4M字節)的隊列。 void __init zone_sizes_init(void) { unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0}; ...... //define MAX_DMA_ADDRESS (PAGE_OFFSET+0x1000000) (PAGE_OFFSET + 16M) max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT; low = max_low_pfn; if (low < max_dma) zones_size[ZONE_DMA] = low; else { //創建域 zones_size[ZONE_DMA] = max_dma; zones_size[ZONE_NORMAL] = low - max_dma; #ifdef CONFIG_HIGHMEM zones_size[ZONE_HIGHMEM] = highend_pfn - low; #endif } //這個函數用來初始化內存管理區並建立內存映射表,定義於mm/page_alloc.c中 //NODE_DATA(0) 就是struct pglist_data contig_page_data 變量的宏定義,通常在NUMA中有多個節點,其餘就只有這一個變量. free_area_init(zones_size); //這個函數是 free_area_init_node(0, NODE_DATA(0), zones_size, __pa(PAGE_OFFSET) >> PAGE_SHIFT, NULL);的封裝 } //咱們一個一個看 void __meminit free_area_init_node(int nid, struct pglist_data *pgdat, unsigned long *zones_size, unsigned long node_start_pfn, unsigned long *zholes_size) { pgdat->node_id = nid; // 0 pgdat->node_start_pfn = node_start_pfn; // 也是0 calculate_zone_totalpages(pgdat, zones_size, zholes_size); alloc_node_mem_map(pgdat); free_area_init_core(pgdat, zones_size, zholes_size); } static void __init calculate_zone_totalpages(struct pglist_data *pgdat, unsigned long *zones_size, unsigned long *zholes_size) { unsigned long realtotalpages, totalpages = 0; int i; //總計頁數量 for (i = 0; i < MAX_NR_ZONES; i++) totalpages += zones_size[i]; pgdat->node_spanned_pages = totalpages; //記錄所有頁數量包含洞 realtotalpages = totalpages; if (zholes_size) for (i = 0; i < MAX_NR_ZONES; i++) realtotalpages -= zholes_size[i]; pgdat->node_present_pages = realtotalpages; //如今這是和上面的totalpages同樣,由於這時zholes_size = NULL. } static void __init alloc_node_mem_map(struct pglist_data *pgdat) { ...... if (!pgdat->node_mem_map) { unsigned long size, start, end; struct page *map; //計算頁數量,之因此要這樣是爲了適應NUMA體系結構,若是不是NUMA那麼直接使用pgdat->node_spanned_pages就能夠了 start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1); end = pgdat->node_start_pfn + pgdat->node_spanned_pages; end = ALIGN(end, MAX_ORDER_NR_PAGES); size = (end - start) * sizeof(struct page); map = alloc_remap(pgdat->node_id, size); // ? if (!map) map = alloc_bootmem_node(pgdat, size); //分配size 數量的struct page 內存 pgdat->node_mem_map = map + (pgdat->node_start_pfn - start); //指向位圖的相應位置 } #ifdef CONFIG_FLATMEM if (pgdat == NODE_DATA(0)) mem_map = NODE_DATA(0)->node_mem_map; //mem_map是一個全局變量 #endif ...... } static void __meminit free_area_init_core(struct pglist_data *pgdat, unsigned long *zones_size, unsigned long *zholes_size) { unsigned long j; int nid = pgdat->node_id; unsigned long zone_start_pfn = pgdat->node_start_pfn; int ret; pgdat_resize_init(pgdat); // 調用 spin_lock_init(&pgdat->node_size_lock); pgdat->nr_zones = 0; init_waitqueue_head(&pgdat->kswapd_wait); pgdat->kswapd_max_order = 0; for (j = 0; j < MAX_NR_ZONES; j++) { struct zone *zone = pgdat->node_zones + j; unsigned long size, realsize; realsize = size = zones_size[j]; if (zholes_size) realsize -= zholes_size[j]; //實際大小爲去掉洞的大小 if (j < ZONE_HIGHMEM) nr_kernel_pages += realsize; //全局變量,記錄kernel可使用的頁數量 nr_all_pages += realsize; //全局變量,記錄總共可使用的頁數量,去掉了洞 //下面初始化zone zone->spanned_pages = size; zone->present_pages = realsize; ...... //略過關於 NUMA //static char *zone_names[MAX_NR_ZONES] = { "DMA", "DMA32", "Normal", "HighMem" }; zone->name = zone_names[j]; spin_lock_init(&zone->lock); spin_lock_init(&zone->lru_lock); zone_seqlock_init(zone); zone->zone_pgdat = pgdat; zone->free_pages = 0; zone->prev_priority = DEF_PRIORITY; //初始化zone結構體中的pageset成員,這個成員爲zone中的每cpu變量,其中又分cold和hot下標1和0的頁集合 zone_pcp_init(zone); INIT_LIST_HEAD(&zone->active_list); INIT_LIST_HEAD(&zone->inactive_list); zone->nr_scan_active = 0; zone->nr_scan_inactive = 0; zone->nr_active = 0; zone->nr_inactive = 0; zap_zone_vm_stats(zone); // 實現爲memset(zone->vm_stat, 0, sizeof(zone->vm_stat)); atomic_set(&zone->reclaim_in_progress, 0); if (!size) continue; //初始化 struct zone *zone_table[1 << ZONETABLE_SHIFT] __read_mostly; zonetable_add(zone, nid, j, zone_start_pfn, size); ret = init_currently_empty_zone(zone, zone_start_pfn, size, MEMMAP_EARLY); zone_start_pfn += size; //下一個zone的zone_start_pfn } } __meminit int init_currently_empty_zone(struct zone *zone, unsigned long zone_start_pfn, unsigned long size, enum memmap_context context) { struct pglist_data *pgdat = zone->zone_pgdat; int ret; /*初始化zone中的 wait_queue_head_t * wait_table; unsigned long wait_table_hash_nr_entries; unsigned long wait_table_bits; */ ret = zone_wait_table_init(zone, size); if (ret) return ret; pgdat->nr_zones = zone_idx(zone) + 1; zone->zone_start_pfn = zone_start_pfn; //#define memmap_init(size, nid, zone, start_pfn) memmap_init_zone((size), (nid), (zone), (start_pfn), MEMMAP_EARLY) //#define zone_idx(zone) ((zone) - (zone)->zone_pgdat->node_zones) //計算zone的索引是DMA 0 或其餘 1,2,3等 memmap_init(size, pgdat->node_id, zone_idx(zone), zone_start_pfn); //初始化zone中的free_area zone_init_free_lists(pgdat, zone, zone->spanned_pages); return 0; } 下面咱們看 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; for (pfn = start_pfn; pfn < end_pfn; pfn++) { if (context == MEMMAP_EARLY) { //#define early_pfn_valid(pfn) pfn_valid(pfn) if (!early_pfn_valid(pfn)) continue; if (!early_pfn_in_nid(pfn, nid)) continue; } page = pfn_to_page(pfn); //在 CONFIG_FLATMEM 內存模式 基本上爲 (mem_map + pfn) set_page_links(page, zone, nid, pfn); //用來設置page所屬的節點,zone, node, section, 設置在struct page->flags中. init_page_count(page); //atomic_set(&page->_count, 1); reset_page_mapcount(page); //atomic_set(&(page)->_mapcount, -1); SetPageReserved(page); //設置頁爲保留 INIT_LIST_HEAD(&page->lru); #ifdef WANT_PAGE_VIRTUAL if (!is_highmem_idx(zone)) //不是高端內存 set_page_address(page, __va(pfn << PAGE_SHIFT)); //設置頁的虛擬地址 #endif } } //進一步初始化內存域連表 void __meminit build_all_zonelists(void) { if (system_state == SYSTEM_BOOTING) { //啓動時 //這個函數循環在每一個節點上調用 build_zonelists(NODE_DATA(nid)); 對於無NUMA就一個節點 __build_all_zonelists(0); cpuset_init_current_mems_allowed(); } else { /* we have to stop all cpus to guaranntee there is no user of zonelist */ stop_machine_run(__build_all_zonelists, NULL, NR_CPUS); /* cpuset refresh routine should be here */ } //計算出所有空閒的頁 vm_total_pages = nr_free_pagecache_pages(); //實現爲 return nr_free_zone_pages(gfp_zone(GFP_HIGHUSER)); printk("Built %i zonelists. Total pages: %ld\n", num_online_nodes(), vm_total_pages); } //咱們看無NUMA的 struct zonelist { struct zone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1]; // NULL delimited }; static void __meminit build_zonelists(pg_data_t *pgdat) { int i, j, k, node, local_node; local_node = pgdat->node_id; // 0 for (i = 0; i < GFP_ZONETYPES; i++) { //GPF_ZONETYPES 是 5 struct zonelist *zonelist; //得到節點中指向管理區鏈表的域 zonelist = pgdat->node_zonelists + i; j = 0; //仔細看一下當 i=2 時,連表管理區有所有的zone k = highest_zone(i); //得到一個域類型 例如 ZONE_NORMAL 或 .. 等 j = build_zonelists_node(pgdat, zonelist, j, k); //返回設置了幾個zone ...... //這有幾個循環在不是NUMA的狀況下不執行 zonelist->zones[j] = NULL; //最後一個爲NULL表示結束 } } static int __meminit build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist, int nr_zones, int zone_type) { struct zone *zone; BUG_ON(zone_type > ZONE_HIGHMEM); do { zone = pgdat->node_zones + zone_type; //指向相應類型的域 if (populated_zone(zone)) { //zone有頁存在 #ifndef CONFIG_HIGHMEM BUG_ON(zone_type > ZONE_NORMAL); #endif //若是zone_type爲ZONE_DMA,管理區鏈表zonelist將僅僅包含DMA管理區,若是爲ZONE_HIGHMEM, //則管理區鏈表中就會依次有ZONE_HIGHMEM、 ZONE_NORMAL和ZONE_DMA zonelist->zones[nr_zones++] = zone; //安排好優先順序 check_highest_zone(zone_type); } zone_type--; } while (zone_type >= 0); return nr_zones; } static unsigned int nr_free_zone_pages(int offset) { pg_data_t *pgdat = NODE_DATA(numa_node_id()); unsigned int sum = 0; struct zonelist *zonelist = pgdat->node_zonelists + offset; struct zone **zonep = zonelist->zones; struct zone *zone; //計算出所有的空閒頁 for (zone = *zonep++; zone; zone = *zonep++) { unsigned long size = zone->present_pages; unsigned long high = zone->pages_high; if (size > high) sum += size - high; } return sum; } 最後內存初始化,釋放前邊標誌爲保留的全部頁面 void __init mem_init(void) { extern int ppro_with_ram_bug(void); // 檢測pentium是不是有bug的cpu int codesize, reservedpages, datasize, initsize; int tmp; int bad_ppro; #ifdef CONFIG_FLATMEM if (!mem_map) BUG(); #endif bad_ppro = ppro_with_ram_bug(); #ifdef CONFIG_HIGHMEM //確認fixmap和kmap映射範圍沒有重疊 if (PKMAP_BASE+LAST_PKMAP*PAGE_SIZE >= FIXADDR_START) { printk(KERN_ERR "fixmap and kmap areas overlap - this will crash\n"); printk(KERN_ERR "pkstart: %lxh pkend: %lxh fixstart %lxh\n", PKMAP_BASE, PKMAP_BASE+LAST_PKMAP*PAGE_SIZE, FIXADDR_START); BUG(); } #endif set_max_mapnr_init(); //設置num_physpages 和 (也許有)max_mapnr 全局變量 #ifdef CONFIG_HIGHMEM high_memory = (void *) __va(highstart_pfn * PAGE_SIZE - 1) + 1; //設置高端內存開始虛擬地址 #else high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1) + 1; #endif //實現爲 return(free_all_bootmem_core(NODE_DATA(0))); totalram_pages += free_all_bootmem(); //根據頁面位圖釋放內存中全部可供動態分配的頁面 reservedpages = 0; for (tmp = 0; tmp < max_low_pfn; tmp++) //在e820中尋找 if (page_is_ram(tmp) && PageReserved(pfn_to_page(tmp))) reservedpages++; //計算保留頁 //我認爲下面這行應該和上面的循環顛倒一下,而後 max_low_pfn 應該爲num_physpages. 這樣能夠統計全部的保留頁 set_highmem_pages_init(bad_ppro);//初始化高端頁面 //計算內核各個部分的大小 codesize = (unsigned long) &_etext - (unsigned long) &_text; datasize = (unsigned long) &_edata - (unsigned long) &_etext; initsize = (unsigned long) &__init_end - (unsigned long) &__init_begin; kclist_add(&kcore_mem, __va(0), max_low_pfn << PAGE_SHIFT); //#define VMALLOC_OFFSET (8*1024*1024) // 8M gap //#define VMALLOC_START (((unsigned long) high_memory + vmalloc_earlyreserve + 2*VMALLOC_OFFSET-1) & ~(VMALLOC_OFFSET-1)) //vmalloc_earlyreserve 在非discontig內存模式下爲0 kclist_add(&kcore_vmalloc, (void *)VMALLOC_START, VMALLOC_END-VMALLOC_START); //打印一些統計信息 //nr_free_pages 函數核心就是 // for_each_zone(zone) 循環全部zone // sum += zone->free_pages; //統計全部空閒頁數量,前面釋放頁調用過free_page()函數 printk(KERN_INFO "Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data, %dk init, %ldk highmem)\n", (unsigned long) nr_free_pages() << (PAGE_SHIFT-10), num_physpages << (PAGE_SHIFT-10), codesize >> 10, reservedpages << (PAGE_SHIFT-10), datasize >> 10, initsize >> 10, (unsigned long) (totalhigh_pages << (PAGE_SHIFT-10)) ); ...... if (boot_cpu_data.wp_works_ok < 0) test_wp_bit(); //檢查cpu是否支持寫保護位,不支持就提示用戶從新編譯內核. #ifndef CONFIG_SMP //由startup_32( )函數建立的物理內存前8MB的恆等映射用來完成內核的初始化階段. //當這種映射再也不必要時,內核調用這函數清除對應的頁表項 zap_low_mappings(); #endif } static void __init set_max_mapnr_init(void) { #ifdef CONFIG_HIGHMEM num_physpages = highend_pfn; #else num_physpages = max_low_pfn; #endif #ifdef CONFIG_FLATMEM max_mapnr = num_physpages; #endif } static unsigned long __init free_all_bootmem_core(pg_data_t *pgdat) { struct page *page; unsigned long pfn; bootmem_data_t *bdata = pgdat->bdata; unsigned long i, count, total = 0; unsigned long idx; unsigned long *map; int gofast = 0; pfn = bdata->node_boot_start >> PAGE_SHIFT; //存放bootmem位圖的第一個頁面 idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT); //總計須要釋放的內存頁數量, node_low_pfn 最高到896M的pfn map = bdata->node_bootmem_map; //節點內存位圖 if (bdata->node_boot_start == 0 || ffs(bdata->node_boot_start) - PAGE_SHIFT > ffs(BITS_PER_LONG)) gofast = 1; for (i = 0; i < idx; ) { //沒有使用頁相應位是0,取反後爲1 unsigned long v = ~map[i / BITS_PER_LONG]; //#define BITS_PER_LONG 32 if (gofast && v == ~0UL) { //v爲0xFFFFFFFF,即連續32個頁面都沒有使用 int order; page = pfn_to_page(pfn); count += BITS_PER_LONG; order = ffs(BITS_PER_LONG) - 1; __free_pages_bootmem(page, order); i += BITS_PER_LONG; page += BITS_PER_LONG; } else if (v) { unsigned long m; page = pfn_to_page(pfn); for (m = 1; m && i < idx; m<<=1, page++, i++) { if (v & m) { //相應位爲1,沒有使用 count++; __free_pages_bootmem(page, 0); //釋放頁 } } } else { i+=BITS_PER_LONG; } pfn += BITS_PER_LONG; } total += count; page = virt_to_page(bdata->node_bootmem_map); //釋放位圖頁面 count = 0; //一個字節8位,每一位表明一個pfn,下面 / 8 在 / PAGE_SIZE 就是換算使用了多少個頁面 for (i = 0; i < ((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/8 + PAGE_SIZE-1)/PAGE_SIZE; i++,page++) { count++; __free_pages_bootmem(page, 0); } total += count; bdata->node_bootmem_map = NULL; return total; } void fastcall __init __free_pages_bootmem(struct page *page, unsigned int order) { if (order == 0) { __ClearPageReserved(page); //清除頁的保留標誌 set_page_count(page, 0); //atomic_set(&page->_count, 0); set_page_refcounted(page); //set_page_count(page, 1); __free_page(page); //釋放這頁 } else { int loop; prefetchw(page); //預取 for (loop = 0; loop < BITS_PER_LONG; loop++) { struct page *p = &page[loop]; if (loop + 1 < BITS_PER_LONG) prefetchw(p + 1); __ClearPageReserved(p); set_page_count(p, 0); } set_page_refcounted(page); __free_pages(page, order); } } mem_init-> static void __init set_highmem_pages_init(int bad_ppro) { int pfn; for (pfn = highstart_pfn; pfn < highend_pfn; pfn++) add_one_highpage_init(pfn_to_page(pfn), pfn, bad_ppro); totalram_pages += totalhigh_pages; } void __init add_one_highpage_init(struct page *page, int pfn, int bad_ppro) { if (page_is_ram(pfn) && !(bad_ppro && page_kills_ppro(pfn))) { ClearPageReserved(page); //清除保留標誌 free_new_highpage(page); } else SetPageReserved(page); //設置保留標誌,此頁不能使用 } static void __meminit free_new_highpage(struct page *page) { init_page_count(page); //atomic_set(&page->_count, 1); __free_page(page); totalhigh_pages++; //統計高端內存頁數量 } mem_init-> void kclist_add(struct kcore_list *new, void *addr, size_t size) { new->addr = (unsigned long)addr; new->size = size; write_lock(&kclist_lock); new->next = kclist; kclist = new; write_unlock(&kclist_lock); }