內存管理-之啓動-基於linux3.10


    

內存管理-之啓動-基於linux3.10

分類: linux-內存 149人閱讀 評論(0) 收藏 舉報

Linux內存管理是linux操作系統的子系統之一,是一個非常重要的子系統,這是一個冗雜而又龐大的部分,和網絡子系統的區別在於其和CPU架構和存儲模型是息息相關的。內存管理到底是個什麼意思?這裏借用深入理解linux內核架構那本書對內存管理涵蓋的領域概況:

l  內存中物理頁的管理

l  分配大塊內存的夥伴系統(buddy)

l  分配較小塊內存的系統slab、slub、slob

l  分配非連續內存塊vmalloc

l  進程地址空間

1.1物理內存的佈局何時獲得

         在x86系統下,在系統還是實模式時就調用bios中斷獲取物理內存的佈局了。在arch/x86/boot/main.c函數中的main.c函數會調用detect_memory()函數獲取當前內存佈局。調用BIOS的功能通常稱爲e820,這是因爲使用該獲取內存佈局功能時ax寄存的十六進制值是0xe820。detect_memory()函數定義於arch/x86/boot/memory.c文件中。

  1. <pre name="code" class="cpp">static int detect_memory_e820(void)  
  2. {  
  3.     int count = 0;  
  4.     struct biosregs ireg, oreg;  
  5.     struct e820entry *desc = boot_params.e820_map;  
  6.     static struct e820entry buf; /* static so it is zeroed */  
  7.   
  8.     initregs(&ireg);  
  9.     ireg.ax  = 0xe820;    
  10.     ireg.cx  = sizeof buf;  
  11.     ireg.edx = SMAP;  
  12.     ireg.di  = (size_t)&buf;  
  13.   
  14.     do {  
  15.         intcall(0x15, &ireg, &oreg);  
  16.         ireg.ebx = oreg.ebx; /* for next iteration... */  
  17.   
  18.         /* BIOSes which terminate the chain with CF = 1 as opposed 
  19.            to %ebx = 0 don't always report the SMAP signature on 
  20.            the final, failing, probe. */  
  21.         if (oreg.eflags & X86_EFLAGS_CF)  
  22.             break;  
  23.   
  24.         /* Some BIOSes stop returning SMAP in the middle of 
  25.            the search loop.  We don't know exactly how the BIOS 
  26.            screwed up the map at that point, we might have a 
  27.            partial map, the full map, or complete garbage, so 
  28.            just return failure. */  
  29.         if (oreg.eax != SMAP) {  
  30.             count = 0;  
  31.             break;  
  32.         }  
  33.   
  34.         *desc++ = buf;  
  35.         count++;  
  36.     } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));  
  37.   
  38.     return boot_params.e820_entries = count;  
  39. }  
<pre name="code" class="cpp">static int detect_memory_e820(void)
{
	int count = 0;
	struct biosregs ireg, oreg;
	struct e820entry *desc = boot_params.e820_map;
	static struct e820entry buf; /* static so it is zeroed */

	initregs(&ireg);
	ireg.ax  = 0xe820;  
	ireg.cx  = sizeof buf;
	ireg.edx = SMAP;
	ireg.di  = (size_t)&buf;

	do {
		intcall(0x15, &ireg, &oreg);
		ireg.ebx = oreg.ebx; /* for next iteration... */

		/* BIOSes which terminate the chain with CF = 1 as opposed
		   to %ebx = 0 don't always report the SMAP signature on
		   the final, failing, probe. */
		if (oreg.eflags & X86_EFLAGS_CF)
			break;

		/* Some BIOSes stop returning SMAP in the middle of
		   the search loop.  We don't know exactly how the BIOS
		   screwed up the map at that point, we might have a
		   partial map, the full map, or complete garbage, so
		   just return failure. */
		if (oreg.eax != SMAP) {
			count = 0;
			break;
		}

		*desc++ = buf;
		count++;
	} while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));

	return boot_params.e820_entries = count;
}


該函數的do while語句是一個遍歷語句,其作用是遍歷所有可用的物理內存段,並將物理內存段的個數(count值)記錄在boot_params.e820_entries,boot_params是系統啓動的參數,注意這裏是探測可用的物理內存。

爲了從紛繁的內存管理代碼細節中脫離出來,這裏不會像網絡子系統那部分那樣逐行代碼去分析,而側重功能和管理方法的分析。圖1.1是X86 32bit情況下的內存使用分佈情況。

虛擬內存到物理內存到物理內存的變換是由MMU完成的。內核空間的地址內容

X86虛擬機下dmesg導出的內核虛擬地址拓撲如下:

  1. [    0.000000] virtual kernel memory layout:  
  2. [    0.000000]     fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)  
  3. [    0.000000]     pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)  
  4. [    0.000000]     vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)  
  5. [    0.000000]     lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)  
  6. [    0.000000]       .init : 0xc19b9000 - 0xc1a93000   ( 872 kB)  
  7. [    0.000000]       .data : 0xc1663132 - 0xc19b8200   (3412 kB)  
  8. [    0.000000]       .text : 0xc1000000 - 0xc1663132   (6540 kB)  
[    0.000000] virtual kernel memory layout:
[    0.000000]     fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)
[    0.000000]     pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)
[    0.000000]     vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)
[    0.000000]     lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)
[    0.000000]       .init : 0xc19b9000 - 0xc1a93000   ( 872 kB)
[    0.000000]       .data : 0xc1663132 - 0xc19b8200   (3412 kB)
[    0.000000]       .text : 0xc1000000 - 0xc1663132   (6540 kB)

由這上述信息可以看到,虛擬內存主要分爲lowmen、vmalloc、pkmap(persistentkernel map)、fixmap這四個類型,對應會有它們各自的管理代碼,後面會敘述。

在嵌入式情景下,有略微的差別。下面是一個arm嵌入式系統dmesg導出的內存拓撲:

  1. [    0.000000] Virtual kernel memory layout:  
  2. [    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)  
  3. [    0.000000]     fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)  
  4. [    0.000000]     vmalloc : 0x86800000 - 0xff000000   (1928 MB)  
  5. [    0.000000]     lowmem  : 0x80000000 - 0x86600000   ( 102 MB)  
  6. [    0.000000]     modules : 0x7f000000 - 0x80000000   (  16 MB)  
  7. [    0.000000]       .text : 0x80008000 - 0x805c9f04   (5896 kB)  
  8. [    0.000000]       .init : 0x805ca000 - 0x808cdeac   (3088 kB)  
  9. [    0.000000]       .data : 0x808ce000 - 0x8091e920   ( 323 kB)  
  10. [    0.000000]        .bss : 0x8091e920 - 0x80965828   ( 284 kB)  
[    0.000000] Virtual kernel memory layout:
[    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
[    0.000000]     fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
[    0.000000]     vmalloc : 0x86800000 - 0xff000000   (1928 MB)
[    0.000000]     lowmem  : 0x80000000 - 0x86600000   ( 102 MB)
[    0.000000]     modules : 0x7f000000 - 0x80000000   (  16 MB)
[    0.000000]       .text : 0x80008000 - 0x805c9f04   (5896 kB)
[    0.000000]       .init : 0x805ca000 - 0x808cdeac   (3088 kB)
[    0.000000]       .data : 0x808ce000 - 0x8091e920   ( 323 kB)
[    0.000000]        .bss : 0x8091e920 - 0x80965828   ( 284 kB)

上述內存拓撲的一個特點是虛擬內存的地址空間可以達到4G而不需要考慮實際物理內存的大小,虛擬內存是內存管理環節中的一環,在認識linux管理模型之前,瞭解CPU架構對內存的影響還是挺有好處的。

1.2 CPU架構

圖1.2 CPU存儲架構

圖1.2可知,按CPU訪問開銷由低到高的存儲類型依次是寄存器、L1cache、L2cache、L3cache以及主存。如果沒有cache,只有register和主存,這就意味着CPU取指令或者數據時,CPU將會等待數據或者指令將需要很長時間,這降低的CPU的利用率。如果和指令相關的操作或者和數據相關操作都存儲在寄存器裏,那麼CPU取指令和數據的開銷幾乎可以忽略不計。

Reg-A、Reg-B:是兩個寄存器組,它們的功能和地位是一樣的,在實現超線程技術時,兩個線程使用不同的寄存器組,這就意味着寄存器內容在線程切換時不需要保護。CPU運算核心訪問它們的週期約爲一個時鐘週期。

L1d和L1i,分別是數據和指令cache,數據和指令分開存放,運算核心訪問其約需4個時鐘週期。通常它的大小分別約32K。

L2cache:第二級緩存,爲L1d和L1i共享,其容量通常是L1d、L1i總和的4倍。兩個CPU有其各自的L2cache。訪問約12cycle。

L3cache:爲第三級緩存,其容量通常是單個L2的128倍,依據情況而不同,訪問週期40cycle。

Cpu運算核心訪問DDR的週期約爲240cycle。

由cpu運算核心訪問cache和DDR的時鐘週期可知,cache在一定程度上緩解了慢速的主存和快速的CPU運算核心不匹配導致的效率變低的問題,但是也帶來了內存管理的複雜性,這種複雜性由芯片設計人員和軟件設計人員共同解決,芯片設計人員提供了TLB和MMU(針對分頁和分段,實模式不會啓用MMU)兩個功能模塊,軟件設計人員需要對這兩個功能模塊以及cache做些配置和管理。

MMU(memory management unit)的主要作用是虛擬地址轉換成實地址。在圖1.1中,虛擬內存到物理內存的轉換就是由MMU完成的。對內核而言,x86的虛擬地址到物理地址的轉換關係是:

虛擬地址 = 物理地址 + PAGE_OFFSET

從上述的關係也可以得到物理地址,x86上還有段地址、線性地址,段地址在arm上沒有類似的概念。

Cache部分的內容還挺多的,其原理可以參。考http://en.wikipedia.org/wiki/CPU_cache

第二章 物理內存管理模型

如何管理常見的4G或者更大的內存條,當前CPU傾向於不再提高CPU的主頻,而朝着多核的方向發展,所以內存模型進一步被複雜化,且分爲一致性內存(UMA)和非一致性內存(NUMA)兩種情況。對於UMA情況,CPU訪問任何一個存儲空間的開銷是等價的,而對於NUMA它們的開銷則是不同的。


圖2.1 UMA和NUMA內存示意

圖2.1是UMA和NUMA的示意,左邊三個CPU訪問內存節點NODE0的開銷是一樣的,而右側訪問NODE0的開銷CPU1是大於CPU2的。在後一種情況下,應該將CPU2需要的指令和數據放在NODE0中,將CPU1 需要的指令和數據放在NODE2中,這樣提高系統的效率。處理器核以及內存的兩種佈局對應兩種內存管理模型,linux將UMA這種模型作爲NUMA這種模型的一個特例,當然混合型的也是NUMA的一個特例,對於混合型,只需看圖2.1右側的NUMA模型,兩顆CPU訪問NODE1的開銷一樣,而訪問其它NODE節點的開銷並不一樣。


圖2.2 內存管理模型

結合圖2.2,分析一下linux內存管理模型,假設圖中左上角那根內存條到各個CPU的開銷是一樣的且整個系統只有這一根內存條,則可以認爲該系統的內存模型退化爲圖2.1中左側的UMA模型,該內存條使用node表示,對應的其管理數據結構是pg_data_t,該node又分爲若干類型的zone,zone的類型是有限的,根據架構不同而略有區別,對於IA-32架構,有ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM以及ZONE_MOVABLE;這對應於圖的中上部。

ZONE_DMA和早期的ISA設備是有關係的;

ZONE_NORMAL指示的是可直接映射到內核段的地址空間;

ZONE_HIGH是超出內核段的物理內存;

ZONE_MOVABLE旨在防止物理內存碎片。

爲了防止內存碎片,將每個zone又分爲五種類型,放大鏡將ZONE_DMA類型的zone進行了放大,其它類型的zone組織內存的方式和其一樣,所以這裏只對ZONE_DMA進行分析,每個zone採用了反碎片技術,這一技術有別於磁盤的碎片整理,後者依賴於文件系統,將零散的小塊合併成連續的大塊,而反碎片技術從源頭上阻止碎片的產生;依據內存使用的方式,將zone分爲unmovable,reclaimable, movale,reserve類型。分別對應於不可移動,可回收,可移動,保留備用(緊急情況下使用)。

ZONE_DMA有16M大小,ZONE_NORMAL上界是896M,如何管理這麼多的且變化的內存,僅僅分成五種類型還是不夠的,linux使用了頁管理技術,每4KB作爲一個進行管理,此外還有一種4MB的大頁,以提高TLB命中的效率。先不考慮大頁,僅僅16M的存儲空間就對應4096頁,對於896M,將有229376個頁,如何高效的管理這些頁?linux採用了夥伴系統方法,這一方法已經包含在圖2.2放大鏡圖像裏了。Buddy system的核心思想是:

根據使用有些情況需要一個頁4KB就可以了,也有需要8KB,依次類推可能需要1M等等,既然如此何不將4KB頁合併成8KB,8KB的頁合併成16KB,16KB合併成32KB,這樣管理也方便。

圖2.2中的0、1…11的意識是指2的0次方、1次方…11次方,2的0次方等於1,表示該鏈表所有員的大小是一個頁4KB,2的1次方等於2,表示該鏈表的所有成員大小是8KB,依次類推,這個組織也可以參看圖2.3,這樣需要多大的內存就到哪個內存鏈表上去取(還要根據zone類型和反碎片類型)。

單單隻有buddy還有一個問題,就是在需要2字節的內存時,去申請一個頁大小的內存4KB,實在有點浪費,所以linux引入slab管理方法,針對嵌入式和服務器又提出了slob和slub管理模型,不過到此可以知道其實內存管理的核心的數據結構是node和zone。

一個具有NUMA的X86-64系統的實例如下:

  1. $ cat /proc/pagetypeinfo   
  2. Page block order: 9  
  3. Pages per block:  512  
  4.   
  5. Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10   
  6. Node    0, zone      DMA, type    Unmovable      1      0      1      0      2      1      1      0      1      0      0   
  7. Node    0, zone      DMA, type  Reclaimable      0      0      0      0      0      0      0      0      0      0      0   
  8. Node    0, zone      DMA, type      Movable      0      0      0      0      0      0      0      0      0      0      3   
  9. Node    0, zone      DMA, type      Reserve      0      0      0      0      0      0      0      0      0      1      0   
  10. Node    0, zone      DMA, type      Isolate      0      0      0      0      0      0      0      0      0      0      0   
  11. Node    0, zone    DMA32, type    Unmovable    276    395    221      1      0      0      0      0      0      0      0   
  12. Node    0, zone    DMA32, type  Reclaimable  27246  21166   8671     70      0      0      0      0      0      0      0   
  13. Node    0, zone    DMA32, type      Movable      3      8     11      0      0      0      0      0      0      0      0   
  14. Node    0, zone    DMA32, type      Reserve      0      0      0     11      1      0      0      0      0      0      0   
  15. Node    0, zone    DMA32, type      Isolate      0      0      0      0      0      0      0      0      0      0      0   
  16. Node    0, zone   Normal, type    Unmovable  10635     67      0      0      0      0      0      0      0      0      0   
  17. Node    0, zone   Normal, type  Reclaimable  28157      2      0      0      0      0      0      0      0      0      0   
  18. Node    0, zone   Normal, type      Movable      0      0      0      0      0      0      0      0      0      0      0   
  19. Node    0, zone   Normal, type      Reserve      0      0      0      9      4      2      1      0      0      1      0   
  20. Node    0, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0   
  21.   
  22. Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate   
  23. Node 0, zone      DMA            1            0            6            1            0   
  24. Node 0, zone    DMA32           89         1103          334            2            0   
  25. Node 0, zone   Normal          225         5583          846            2            0   
  26. Page block order: 9  
  27. Pages per block:  512  
  28.   
  29. Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10   
  30. Node    1, zone   Normal, type    Unmovable   2266   3095    185      0      0      0      0      0      0      0      0   
  31. Node    1, zone   Normal, type  Reclaimable 442807 302949  10893      0      0      0      0      0      0      0      0   
  32. Node    1, zone   Normal, type      Movable      0      1      2      0      0      0      0      0      0      0      0   
  33. Node    1, zone   Normal, type      Reserve      0      0      0      0      1      1      1      1      1      0      0   
  34. Node    1, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0   
  35.   
  36. Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate   
  37. Node 1, zone   Normal          123         7791          276            2            0  
$ cat /proc/pagetypeinfo 
Page block order: 9
Pages per block:  512

Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10 
Node    0, zone      DMA, type    Unmovable      1      0      1      0      2      1      1      0      1      0      0 
Node    0, zone      DMA, type  Reclaimable      0      0      0      0      0      0      0      0      0      0      0 
Node    0, zone      DMA, type      Movable      0      0      0      0      0      0      0      0      0      0      3 
Node    0, zone      DMA, type      Reserve      0      0      0      0      0      0      0      0      0      1      0 
Node    0, zone      DMA, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 
Node    0, zone    DMA32, type    Unmovable    276    395    221      1      0      0      0      0      0      0      0 
Node    0, zone    DMA32, type  Reclaimable  27246  21166   8671     70      0      0      0      0      0      0      0 
Node    0, zone    DMA32, type      Movable      3      8     11      0      0      0      0      0      0      0      0 
Node    0, zone    DMA32, type      Reserve      0      0      0     11      1      0      0      0      0      0      0 
Node    0, zone    DMA32, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 
Node    0, zone   Normal, type    Unmovable  10635     67      0      0      0      0      0      0      0      0      0 
Node    0, zone   Normal, type  Reclaimable  28157      2      0      0      0      0      0      0      0      0      0 
Node    0, zone   Normal, type      Movable      0      0      0      0      0      0      0      0      0      0      0 
Node    0, zone   Normal, type      Reserve      0      0      0      9      4      2      1      0      0      1      0 
Node    0, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 

Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate 
Node 0, zone      DMA            1            0            6            1            0 
Node 0, zone    DMA32           89         1103          334            2            0 
Node 0, zone   Normal          225         5583          846            2            0 
Page block order: 9
Pages per block:  512

Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10 
Node    1, zone   Normal, type    Unmovable   2266   3095    185      0      0      0      0      0      0      0      0 
Node    1, zone   Normal, type  Reclaimable 442807 302949  10893      0      0      0      0      0      0      0      0 
Node    1, zone   Normal, type      Movable      0      1      2      0      0      0      0      0      0      0      0 
Node    1, zone   Normal, type      Reserve      0      0      0      0      1      1      1      1      1      0      0 
Node    1, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 

Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate 
Node 1, zone   Normal          123         7791          276            2            0

2.1 Node

Linux使用pg_data_t管理內存模型,

圖2.3 內存管理數據拓撲

在圖2.1中,根據CPU訪問內存的代價不一樣,將每一個代價節點抽象成一個node,對於NUMA退化爲一個node,需要注意的node節點數並不一定對應於內存條的個數。每一個node節點通常分爲若干的域(zone)。

  1. enum zone_type {  
  2.     ZONE_DMA,  
  3.     ZONE_DMA32,  
  4.     ZONE_NORMAL,  
  5.     ZONE_HIGHMEM,  
  6.     ZONE_MOVABLE,  
  7.     __MAX_NR_ZONES  
  8. };  
enum zone_type {
	ZONE_DMA,
	ZONE_DMA32,
	ZONE_NORMAL,
	ZONE_HIGHMEM,
	ZONE_MOVABLE,
	__MAX_NR_ZONES
};

因爲並不是所有內存都支持DMA操作,所以這裏專門開闢了一個稱爲ZONE_DMA類型的域以支持DMA操作,這塊域大小依賴於體系結構,在i386上其小於16M,這在圖1.1中物理內存有所顯示。

ZONE_DMA32是針對x86架構64爲處理器而準備的,因爲其可以使用的DMA範圍由16M拓展到了4G。

對於i386 ZONE_NORMAL的上限就是896M,是這部分內存是直接映射的。

對於i386 ZONE_HIGHMEM在ZONE_NORMAL之後,這部分的內存頁是動態映射的,主要是因爲頁表項有限,其基本思想是需要高端內存時,申請頁表進行映射,用完釋放再回收頁表。

  1. <include/linux/mmzone.h>  
  2. 700 typedef struct pglist_data {  
  3. /*內存管理類型,ZONE_DMA、ZONE_NORMAL、ZONE_HIGH 
  4.  701     struct zone node_zones[MAX_NR_ZONES]; 
  5. /*每次內存申請會落到zonelist上,zonelist是zone(區)的列表,對於分配內存,第一個區是“全局”的,其它的zone是後備zone,後備zone按優先級遞減排序,這裏的優先級是指訪問的代價,代價越大越靠後。對於UMA,node_zonelists只有一個成員,而對於NUMA,MAX_ZONELISTS的值是2,[0]是有後備zone的zonelist,而[1]沒有後備zone,用於GFP_THISNODE*/  
  6.  702     struct zonelist node_zonelists[MAX_ZONELISTS];  
  7. /*701行實際的node_zones成員數*/  
  8.  703     int nr_zones;  
  9. /*非稀疏內存管理模型時,指向struct page中的第一個頁面,其存放在mem_map數組中*/  
  10.  704 #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */  
  11.  705     struct page *node_mem_map;  
  12. /*Page Cgroup,可看做mem_map的擴展,該結構體用於確定cgroup,LXC用到cgroup和命名空間*/  
  13.  706 #ifdef CONFIG_MEMCG  
  14.  707     struct page_cgroup *node_page_cgroup;  
  15.  708 #endif  
  16.  709 #endif  
  17. /*指向內存引導程序*/  
  18.  710 #ifndef CONFIG_NO_BOOTMEM  
  19.  711     struct bootmem_data *bdata;  
  20.  712 #endif  
  21.  713 #ifdef CONFIG_MEMORY_HOTPLUG  
  22.  714     /* 
  23.  715      * Must be held any time you expect node_start_pfn, node_present_pages 
  24.  716      * or node_spanned_pages stay constant.  Holding this will also 
  25.  717      * guarantee that any pfn_valid() stays that way. 
  26.  718      * 
  27.  719      * Nests above zone->lock and zone->size_seqlock. 
  28.  720      */  
  29.  721     spinlock_t node_size_lock;  
  30.  722 #endif  
  31. /*起始頁幀號,對於UMA,該值是0,對於NUMA,該值隨節點不同而不同,node_start_pfn全局唯一,由頁幀號全局唯一性決定*/  
  32.  723     unsigned long node_start_pfn;  
  33.  724     unsigned long node_present_pages; /* total number of physical pages ,針對該node而言的總數*/  
  34.  725     unsigned long node_spanned_pages; /* total size of physical page 
  35.  726                          range, including holes */  
  36. //具有全局性,對於UMA,該值是1,對於NUMA從0開始計數  
  37.  727     int node_id;  
  38.  728     nodemask_t reclaim_nodes;   /* Nodes allowed to reclaim from */  
  39. /*swap dameon*/  
  40.  729     wait_queue_head_t kswapd_wait;  
  41.  730     wait_queue_head_t pfmemalloc_wait;  
  42.  731     struct task_struct *kswapd; /* Protected by lock_memory_hotplug() */  
  43. /*定義需要釋放的區域長度*/  
  44.  732     int kswapd_max_order;  
  45.  733     enum zone_type classzone_idx;  
  46. 747 } pg_data_t;  
<include/linux/mmzone.h>
700 typedef struct pglist_data {
/*內存管理類型,ZONE_DMA、ZONE_NORMAL、ZONE_HIGH
 701     struct zone node_zones[MAX_NR_ZONES];
/*每次內存申請會落到zonelist上,zonelist是zone(區)的列表,對於分配內存,第一個區是“全局”的,其它的zone是後備zone,後備zone按優先級遞減排序,這裏的優先級是指訪問的代價,代價越大越靠後。對於UMA,node_zonelists只有一個成員,而對於NUMA,MAX_ZONELISTS的值是2,[0]是有後備zone的zonelist,而[1]沒有後備zone,用於GFP_THISNODE*/
 702     struct zonelist node_zonelists[MAX_ZONELISTS];
/*701行實際的node_zones成員數*/
 703     int nr_zones;
/*非稀疏內存管理模型時,指向struct page中的第一個頁面,其存放在mem_map數組中*/
 704 #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
 705     struct page *node_mem_map;
/*Page Cgroup,可看做mem_map的擴展,該結構體用於確定cgroup,LXC用到cgroup和命名空間*/
 706 #ifdef CONFIG_MEMCG
 707     struct page_cgroup *node_page_cgroup;
 708 #endif
 709 #endif
/*指向內存引導程序*/
 710 #ifndef CONFIG_NO_BOOTMEM
 711     struct bootmem_data *bdata;
 712 #endif
 713 #ifdef CONFIG_MEMORY_HOTPLUG
 714     /*
 715      * Must be held any time you expect node_start_pfn, node_present_pages
 716      * or node_spanned_pages stay constant.  Holding this will also
 717      * guarantee that any pfn_valid() stays that way.
 718      *
 719      * Nests above zone->lock and zone->size_seqlock.
 720      */
 721     spinlock_t node_size_lock;
 722 #endif
/*起始頁幀號,對於UMA,該值是0,對於NUMA,該值隨節點不同而不同,node_start_pfn全局唯一,由頁幀號全局唯一性決定*/
 723     unsigned long node_start_pfn;
 724     unsigned long node_present_pages; /* total number of physical pages ,針對該node而言的總數*/
 725     unsigned long node_spanned_pages; /* total size of physical page
 726                          range, including holes */
//具有全局性,對於UMA,該值是1,對於NUMA從0開始計數
 727     int node_id;
 728     nodemask_t reclaim_nodes;   /* Nodes allowed to reclaim from */
/*swap dameon*/
 729     wait_queue_head_t kswapd_wait;
 730     wait_queue_head_t pfmemalloc_wait;
 731     struct task_struct *kswapd; /* Protected by lock_memory_hotplug() */
/*定義需要釋放的區域長度*/
 732     int kswapd_max_order;
 733     enum zone_type classzone_idx;
747 } pg_data_t;

2.2 Zone結構

Zone的類型如下,對於x86, ZONE_HIGH是896M以上的空間,不能直接映射。

  1. enum zone_type {  
  2. #ifdef CONFIG_ZONE_DMA  
  3.     ZONE_DMA,  
  4. #endif  
  5. #ifdef CONFIG_ZONE_DMA32  
  6.     ZONE_DMA32,  
  7. #endif  
  8.     ZONE_NORMAL,  
  9. #ifdef CONFIG_HIGHMEM  
  10.     ZONE_HIGHMEM,  
  11. #endif  
  12.     ZONE_MOVABLE,  
  13.     __MAX_NR_ZONES  
  14. };  
enum zone_type {
#ifdef CONFIG_ZONE_DMA
	ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
	ZONE_DMA32,
#endif
	ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
	ZONE_HIGHMEM,
#endif
	ZONE_MOVABLE,
	__MAX_NR_ZONES
};
zone結構體是
  1. 313 struct zone {  
  2. /*WMARK_MIN,WMARK_LOW,WMARK_HIGH,用於標記各閾值,影響kswap dameon的行爲。 
  3. *WMARK_HIGH:內存不緊張。 
  4. *WMARK_LOW:內存有點緊張,需要將內存頁換到外部存儲設備(硬盤、SSD) 
  5. * WMARK_MIN :內存的最低閾值,這是回收頁壓力增大 
  6. 由*_wmark_pages(zone)宏存取該字段 
  7. */  
  8.  317     unsigned long watermark[NR_WMARK];  
  9.  318   
  10.  319     /* 
  11.  320      * When free pages are below this point, additional steps are taken 
  12.  321      * when reading the number of free pages to avoid per-cpu counter 
  13.  322      * drift allowing watermarks to be breached 
  14.  323      */  
  15.  324     unsigned long percpu_drift_mark;  
  16.  325   
  17.  326     /*每個內存區保留的內存頁數。通過sysctl_lowmem_reserve_ratio sysctl 可以改變。*/  
  18.  334     unsigned long       lowmem_reserve[MAX_NR_ZONES];  
  19.  335   
  20.  336     /* 
  21.  337      * 這是每個zone都會保留的頁,本身和dirty沒什麼關係。 
  22.  339      */  
  23.  340     unsigned long       dirty_balance_reserve;  
  24.  341 /*每個CPU的冷熱頁幀列表,在高速緩存中的頁稱爲“熱”,不在高速緩存中的頁稱爲“冷”*/  
  25. 350     struct per_cpu_pageset __percpu *pageset;  
  26.  351     /* 
  27.  352      * 釋放內存時使用該鎖,防止併發 
  28.  353      */  
  29.  354     spinlock_t      lock;  
  30. /*標誌是否所有也都不可回收*/  
  31.  355     int                     all_unreclaimable;   
  32. /*buddy系統核心數據結構,相見後面*/  
  33. 368     struct free_area    free_area[MAX_ORDER];  
  34. /*對稱多處理器情況下,可能對該結構的不同部分訪問,ZONE_PADDING是按緩存行大小填充對其,這樣併發訪問該結構體時,可以通過訪問兩個緩存行以提高速度*/  
  35. 389     ZONE_PADDING(_pad1_)  
  36.  390   
  37.  391     /*這些是頁回收掃描器使用的字段*/  
  38.  392     spinlock_t      lru_lock;  
  39.  393     struct lruvec       lruvec;  
  40.  394   
  41.  395     unsigned long       pages_scanned;     /* since last reclaim */  
  42.  396     unsigned long       flags;         /* zone flags, see below */  
  43.  397   
  44.  398     /* Zone 統計,percpu_drift_mark會和這裏的統計的空閒也對比,以避免per-CPU計數器漂移*/  
  45.  399     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];  
  46.  400  
  47. 401     /* 
  48.  402      * 該zone的LRU鏈表上的活躍匿名頁和非活躍匿名頁比例 
  49. 404      */  
  50.  405     unsigned int inactive_ratio;  
  51.  406   
  52.  407   
  53.  408     ZONE_PADDING(_pad2_)  
  54.  409     /* Rarely used or read-mostly fields */  
  55.  410   
  56.  411     /* 
  57.  412      * wait_table       -- the array holding the hash table 
  58.  413      * wait_table_hash_nr_entries   -- the size of the hash table array 
  59.  414      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits) 
  60. 434      */  
  61.  435     wait_queue_head_t   * wait_table;  
  62.  436     unsigned long       wait_table_hash_nr_entries;  
  63.  437     unsigned long       wait_table_bits;  
  64.  438   
  65.  439     /* 
  66.  440      * 該node節點所屬的node節點 
  67.  441      */  
  68.  442     struct pglist_data  *zone_pgdat;  
  69.  443     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT ,將物理地址轉換爲起始頁幀號*/  
  70.  444     unsigned long       zone_start_pfn;  
  71.  445   
  72.  446     /* 
  73.  447      * spanned_pages is the total pages spanned by the zone, including 
  74.  448      * holes, which is calculated as: 
  75.  449      *  spanned_pages = zone_end_pfn - zone_start_pfn; 
  76. 450      * 
  77.  451      * present_pages is physical pages existing within the zone, which 
  78.  452      * is calculated as: 
  79.  453      *  present_pages = spanned_pages - absent_pages(pages in holes); 
  80.  454      * 
  81.  455      * managed_pages is present pages managed by the buddy system, which 
  82.  456      * is calculated as (reserved_pages includes pages allocated by the 
  83.  457      * bootmem allocator): 
  84.  458      *  managed_pages = present_pages - reserved_pages; 
  85.  459      * 
  86.  460      * So present_pages may be used by memory hotplug or memory power 
  87.  461      * management logic to figure out unmanaged pages by checking 
  88.  462      * (present_pages - managed_pages). And managed_pages should be used 
  89.  463      * by page allocator and vm scanner to calculate all kinds of watermarks 
  90.  464      * and thresholds. 
  91.  465      * 
  92.  466      * Locking rules: 
  93.  467      * 
  94.  468      * zone_start_pfn and spanned_pages are protected by span_seqlock. 
  95.  469      * It is a seqlock because it has to be read outside of zone->lock, 
  96.  470      * and it is done in the main allocator path.  But, it is written 
  97.  471      * quite infrequently. 
  98.  472      * 
  99.  473      * The span_seq lock is declared along with zone->lock because it is 
  100.  474      * frequently read in proximity to zone->lock.  It's good to 
  101.  475      * give them a chance of being in the same cacheline. 
  102.  476      * 
  103.  477      * Write access to present_pages and managed_pages at runtime should 
  104.  478      * be protected by lock_memory_hotplug()/unlock_memory_hotplug(). 
  105.  479      * Any reader who can't tolerant drift of present_pages and 
  106.  480      * managed_pages should hold memory hotplug lock to get a stable value. 
  107.  481      */  
  108.  482     unsigned long       spanned_pages;  
  109.  483     unsigned long       present_pages;  
  110.  484     unsigned long       managed_pages;  
  111.  485   
  112.  486     /* 
  113.  487      * rarely used fields: 
  114.  488      */  
  115.  489     const char      *name;  
  116.  490 } ____cacheline_internodealigned_in_smp;  
313 struct zone {
/*WMARK_MIN,WMARK_LOW,WMARK_HIGH,用於標記各閾值,影響kswap dameon的行爲。
*WMARK_HIGH:內存不緊張。
*WMARK_LOW:內存有點緊張,需要將內存頁換到外部存儲設備(硬盤、SSD)
* WMARK_MIN :內存的最低閾值,這是回收頁壓力增大
由*_wmark_pages(zone)宏存取該字段
*/
 317     unsigned long watermark[NR_WMARK];
 318 
 319     /*
 320      * When free pages are below this point, additional steps are taken
 321      * when reading the number of free pages to avoid per-cpu counter
 322      * drift allowing watermarks to be breached
 323      */
 324     unsigned long percpu_drift_mark;
 325 
 326     /*每個內存區保留的內存頁數。通過sysctl_lowmem_reserve_ratio sysctl 可以改變。*/
 334     unsigned long       lowmem_reserve[MAX_NR_ZONES];
 335 
 336     /*
 337      * 這是每個zone都會保留的頁,本身和dirty沒什麼關係。
 339      */
 340     unsigned long       dirty_balance_reserve;
 341 /*每個CPU的冷熱頁幀列表,在高速緩存中的頁稱爲“熱”,不在高速緩存中的頁稱爲“冷”*/
350     struct per_cpu_pageset __percpu *pageset;
 351     /*
 352      * 釋放內存時使用該鎖,防止併發
 353      */
 354     spinlock_t      lock;
/*標誌是否所有也都不可回收*/
 355     int                     all_unreclaimable; 
/*buddy系統核心數據結構,相見後面*/
368     struct free_area    free_area[MAX_ORDER];
/*對稱多處理器情況下,可能對該結構的不同部分訪問,ZONE_PADDING是按緩存行大小填充對其,這樣併發訪問該結構體時,可以通過訪問兩個緩存行以提高速度*/
389     ZONE_PADDING(_pad1_)
 390 
 391     /*這些是頁回收掃描器使用的字段*/
 392     spinlock_t      lru_lock;
 393     struct lruvec       lruvec;
 394 
 395     unsigned long       pages_scanned;     /* since last reclaim */
 396     unsigned long       flags;         /* zone flags, see below */
 397 
 398     /* Zone 統計,percpu_drift_mark會和這裏的統計的空閒也對比,以避免per-CPU計數器漂移*/
 399     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];
 400
401     /*
 402      * 該zone的LRU鏈表上的活躍匿名頁和非活躍匿名頁比例
404      */
 405     unsigned int inactive_ratio;
 406 
 407 
 408     ZONE_PADDING(_pad2_)
 409     /* Rarely used or read-mostly fields */
 410 
 411     /*
 412      * wait_table       -- the array holding the hash table
 413      * wait_table_hash_nr_entries   -- the size of the hash table array
 414      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits)
434      */
 435     wait_queue_head_t   * wait_table;
 436     unsigned long       wait_table_hash_nr_entries;
 437     unsigned long       wait_table_bits;
 438 
 439     /*
 440      * 該node節點所屬的node節點
 441      */
 442     struct pglist_data  *zone_pgdat;
 443     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT ,將物理地址轉換爲起始頁幀號*/
 444     unsigned long       zone_start_pfn;
 445 
 446     /*
 447      * spanned_pages is the total pages spanned by the zone, including
 448      * holes, which is calculated as:
 449      *  spanned_pages = zone_end_pfn - zone_start_pfn;
450      *
 451      * present_pages is physical pages existing within the zone, which
 452      * is calculated as:
 453      *  present_pages = spanned_pages - absent_pages(pages in holes);
 454      *
 455      * managed_pages is present pages managed by the buddy system, which
 456      * is calculated as (reserved_pages includes pages allocated by the
 457      * bootmem allocator):
 458      *  managed_pages = present_pages - reserved_pages;
 459      *
 460      * So present_pages may be used by memory hotplug or memory power
 461      * management logic to figure out unmanaged pages by checking
 462      * (present_pages - managed_pages). And managed_pages should be used
 463      * by page allocator and vm scanner to calculate all kinds of watermarks
 464      * and thresholds.
 465      *
 466      * Locking rules:
 467      *
 468      * zone_start_pfn and spanned_pages are protected by span_seqlock.
 469      * It is a seqlock because it has to be read outside of zone->lock,
 470      * and it is done in the main allocator path.  But, it is written
 471      * quite infrequently.
 472      *
 473      * The span_seq lock is declared along with zone->lock because it is
 474      * frequently read in proximity to zone->lock.  It's good to
 475      * give them a chance of being in the same cacheline.
 476      *
 477      * Write access to present_pages and managed_pages at runtime should
 478      * be protected by lock_memory_hotplug()/unlock_memory_hotplug().
 479      * Any reader who can't tolerant drift of present_pages and
 480      * managed_pages should hold memory hotplug lock to get a stable value.
 481      */
 482     unsigned long       spanned_pages;
 483     unsigned long       present_pages;
 484     unsigned long       managed_pages;
 485 
 486     /*
 487      * rarely used fields:
 488      */
 489     const char      *name;
 490 } ____cacheline_internodealigned_in_smp;

2.3 page結構

每一個物理頁都有一個struct page與之關聯,以跟蹤頁使用情況,

  1. 41 struct page {  
  2.  42     /* First double word block */  
  3.  43     unsigned long flags;      
  4.  45     struct address_space *mapping;    
  5.  52     /* Second double word */  
  6.  53     struct {  
  7.  54         union {  
  8.  55             pgoff_t index;      /* 在映射頁的偏移*/  
  9.  56             void *freelist;     /* slub/slob 第一個空閒對象 */  
  10.  57             bool pfmemalloc;    /*如果該標識由頁分配器設置,則ALLOC_NO_WATERMARKS也被設置並且空閒內存量不滿足low水印,這意味着內存有點緊張,調用者必須確保該頁用於釋放其它頁之用。 
  11. 66         }; 
  12.  67  
  13.  68         union { 
  14. 74             /* 
  15.  75              * Keep _count separate from slub cmpxchg_double data. 
  16.  76              * As the rest of the double word is protected by 
  17.  77              * slab_lock but _count is not. 
  18.  78              */  
  19.  79             unsigned counters;  
  20. 82             struct {  
  21.  83   
  22.  84                 union {  
  23. 86                      // 頁表中指向該頁的頁表入口項(PTE)數,也被用作複合頁的尾部頁引用計數  
  24. 101                     atomic_t _mapcount;  
  25. 102   
  26. 103                     struct { /* SLUB */  
  27. 104                         unsigned inuse:16; //在使用的slub對象  
  28. 105                         unsigned objects:15;//slub對象數  
  29. 106                         unsigned frozen:1;  
  30. 107                     };  
  31. 108                     int units;  /* SLOB */  
  32. 109                 };  
  33. 110                 atomic_t _count;        /*使用計數器,爲0則說明沒有被內核引用,將可能被釋放*/  
  34. 111             };  
  35. 112         };  
  36. 113     };  
  37. 114   
  38. 115     /* Third double word block */  
  39. 116     union {  
  40. 117         struct list_head lru;   /* 換出頁列表,由zone->lru_lock 鎖保護 */  
  41. 120         struct {        /* slub per cpu partial pages */  
  42. 121             struct page *next;  /* Next partial slab */  
  43. 126             short int pages;  
  44. 127             short int pobjects;  
  45. 129         };  
  46. 130   
  47. 131         struct list_head list;  /* slobs list of pages */  
  48. 132         struct slab *slab_page; /* slab fields */  
  49. 133     };  
  50. 134   
  51. 135     /* Remainder is not double word aligned */  
  52. 136     union {  
  53. 137         unsigned long private;      /*映射私有,用途自定,通常如果設置PagePrivate 標誌,則用於buffer_heads,如果設置PageSwapCache,則用於swp_entry_t,如果設置PG_buddy,則用於夥伴系統*/  
  54. 147         struct kmem_cache *slab_cache;  /* SL[AU]B: Pointer to slab */  
  55. 148         struct page *first_page;    /* Compound tail pages */  
  56. 149     };  
41 struct page {
 42     /* First double word block */
 43     unsigned long flags;    
 45     struct address_space *mapping;  
 52     /* Second double word */
 53     struct {
 54         union {
 55             pgoff_t index;      /* 在映射頁的偏移*/
 56             void *freelist;     /* slub/slob 第一個空閒對象 */
 57             bool pfmemalloc;    /*如果該標識由頁分配器設置,則ALLOC_NO_WATERMARKS也被設置並且空閒內存量不滿足low水印,這意味着內存有點緊張,調用者必須確保該頁用於釋放其它頁之用。
66         };
 67 
 68         union {
74             /*
 75              * Keep _count separate from slub cmpxchg_double data.
 76              * As the rest of the double word is protected by
 77              * slab_lock but _count is not.
 78              */
 79             unsigned counters;
82             struct {
 83 
 84                 union {
86                      // 頁表中指向該頁的頁表入口項(PTE)數,也被用作複合頁的尾部頁引用計數
101                     atomic_t _mapcount;
102 
103                     struct { /* SLUB */
104                         unsigned inuse:16; //在使用的slub對象
105                         unsigned objects:15;//slub對象數
106                         unsigned frozen:1;
107                     };
108                     int units;  /* SLOB */
109                 };
110                 atomic_t _count;        /*使用計數器,爲0則說明沒有被內核引用,將可能被釋放*/
111             };
112         };
113     };
114 
115     /* Third double word block */
116     union {
117         struct list_head lru;   /* 換出頁列表,由zone->lru_lock 鎖保護 */
120         struct {        /* slub per cpu partial pages */
121             struct page *next;  /* Next partial slab */
126             short int pages;
127             short int pobjects;
129         };
130 
131         struct list_head list;  /* slobs list of pages */
132         struct slab *slab_page; /* slab fields */
133     };
134 
135     /* Remainder is not double word aligned */
136     union {
137         unsigned long private;      /*映射私有,用途自定,通常如果設置PagePrivate 標誌,則用於buffer_heads,如果設置PageSwapCache,則用於swp_entry_t,如果設置PG_buddy,則用於夥伴系統*/
147         struct kmem_cache *slab_cache;  /* SL[AU]B: Pointer to slab */
148         struct page *first_page;    /* Compound tail pages */
149     };

flags相關的定義在include/linux/page-flags.h文件中,flag用於指示該頁是否被鎖住、是否是髒頁,是否是活動的等,該文件中還定義了一些存取該變量的宏和函數,該標誌在某些情況下是可能發生異步更新的。

*mapping如果其最低比特是0,則其指向inode(信息節點,文件系統相關)地址空間或者是NULL。如果映射成匿名內存,最低比特被置位並且其指向anon_vma對象。

第三章內存初始化

在使用內存時,需要知道內存的一些信息,比如物理內存到底有多大?內存是不是分節點的(訪問的代價不一樣)?啓用分頁情況下的頁表設置情況?內存相關的初始化工作在start_kernel函數完成,下列列出了內存相關的函數。

圖3.0.1內存初始化相關函數調用

Build_all_zonelists的作用是處理系統node間zone的備用關係,即如果有node0,node1,node2,如果node0中的某個類型的zone申請內存,發現內存不足,去該節點的其它類型zone也沒有,這是向node1還是node2申請,這就牽涉到node1 的開銷和node2的開銷大小以及去哪個備用zone申請,該函數即完成此工作。

Mm_init停用boot內存分配器,啓用夥伴內存管理。

進入setup_arch函數,不論是arm還是x86,映入眼簾的是_text,_etext,_edata,_end,__bss_stop這些變量。這些變量定義在arch/x86/kernel/vmlinux.lds.S文件,類似的將x86=換成arm也能看到arm的連接腳本里對這些變量的定義,這些變量的值在鏈接時才確定,具體來看,有一個System.map文件,通常和vmlinx在同一個文件夾,在System.map文件裏可以找到上述變量的定義。該文件有如下幾行,隨編譯結果不同而有區別:

  1. c1000000 T _text  
  2. c1000000 T startup_32  
  3. c10000e0 t bad_subarch  
  4. c10000e0 W lguest_entry  
  5. c10000e0 W xen_entry  
  6. c10000e4 T start_cpu0  
  7. c10000f8 T _stext  
c1000000 T _text
c1000000 T startup_32
c10000e0 t bad_subarch
c10000e0 W lguest_entry
c10000e0 W xen_entry
c10000e4 T start_cpu0
c10000f8 T _stext

c1000000即爲3G+16M,這從一個方面驗證了內核的代碼段確實是從3G+16M開始的,這裏的地址是虛擬地址。對於內核在物理內存中的分佈情況,可以看/proc/iomem文件,該文件的一部分內容摘錄如下:

  1. 00000000-00000fff : reserved  
  2. 00001000-0009efff : System RAM  
  3. 0009f000-0009ffff : reserved  
  4. 000a0000-000bffff : PCI Bus 0000:00  
  5.   000a0000-000bffff : Video RAM area  
  6. 000c0000-000c7fff : Video ROM  
  7. 000ca000-000cbfff : reserved  
  8.   000ca000-000cafff : Adapter ROM  
  9. 00100000-7fedffff : System RAM  
  10.   01000000-01663131 : Kernel code  
  11.   01663132-019b81ff : Kernel data  
  12.   01a9b000-01b81fff : Kernel bss  
00000000-00000fff : reserved
00001000-0009efff : System RAM
0009f000-0009ffff : reserved
000a0000-000bffff : PCI Bus 0000:00
  000a0000-000bffff : Video RAM area
000c0000-000c7fff : Video ROM
000ca000-000cbfff : reserved
  000ca000-000cafff : Adapter ROM
00100000-7fedffff : System RAM
  01000000-01663131 : Kernel code
  01663132-019b81ff : Kernel data
  01a9b000-01b81fff : Kernel bss

從上面可以看到內核代碼從1M地方開始存放,前4k作爲一個單獨的頁,其後的640KBIOS和顯卡會使用這段區域,從640K到1M是ROM區域,所以linux內核選擇從1M開始處連續存放。Setup_arch主要完成以下工作:


圖3.0.2 和內存相關的內存初始化

頁表初始化會在setup_arch函數完成,kernel_physical_mapping_init用於虛擬地址到物理地址的映射,和頁表息息相關,不過在分析頁表初始化過程之前先來點linux分頁基礎,至於分段arm上並不存在這一概念,所以這裏直接略過了。

3.1 Linux分頁

分頁單元將線性地址轉換成物理地址,該分頁單元將檢查訪問的物理頁是否有效,無效會產生缺頁異常通知操作系統。X86上當CR0的PG位被置位時,則啓用分頁功能,否則,線性地址就是物理地址。現在的Linux採用了四級分頁模型,四級頁目錄分別是:

           頁全局目錄(PageGlobal Directory)

           頁上級目錄(PageUpper Directory)

           頁中間目錄(PageMiddle Directory)

頁表(Page Table)

X86-64採用了四級頁表,啓用PAE特性的x86-32也此採用了四級頁表,對於未啓用PAE特性的32位系統,看一個三級頁表映射到物理地址的過程:

32比特按頁目錄項10bit,頁表10bit,頁內偏移12bit,總比特數是10+10+12=32,符合32位系統劃分,一個頁的大小是4KB,要在這4KB大小裏任意尋址,則需要12bit的地址,這正是頁內偏移爲12bit的原因。線性地址到物理地址的轉換分爲兩步,首先根據線性地址高10比特找到頁目錄項,然後根據頁目錄項和線性地址的中間10比特找到頁表,最後的12bit用於在頁表中尋址,這一過程如下:

圖3.1.1 80x86處理器分頁

假設一個線性地址範圍是0x2000_0000到0x2003_ffff,該地址對應的空間是用戶空間,內核從3G開始,這一段包括64(0x2003_ffff-0x2000_0000+1 = 0x4_0000; 0x4_0000/1024=256KB;256KB/4KB=64)個頁,

頁目錄項是線性地址最高10比特,對應的就是0x080,和CR3(不同進程CR3裏的值不同)裏的基地址值相加得到頁表項,同理得到的頁目錄項作爲頁表的基地址,取線性地址接下來的10比特和頁表項相加得到頁的基地址,根據偏移量找到對應的比特,這就是這個查找過程。四級過程類似。

內存容量的物理限制源於芯片的地址總線和數據總線的位寬,從奔騰pro開始,芯片的數據總線從32位被擴展到36比特,這樣實際可以訪問的物理內存由先前的4GB擴展到64GB。而這一特性正是PAE特性。另外一個PSE(Physical Address Extension)從奔三引入。對於64比特系統,IA64採用了三級分頁(不包括頁內偏移),而x86_64使用了四級分頁技術。

對於32位系統,通過將頁上級目錄和頁中間目錄設置成0,實際上加0等於什麼也沒有加。但是爲了兼容32位和64位系統頁上級目錄和頁中間目錄還是一直存在的。

線性地址被劃分成如下部分,定義在<pgtable-3level-types.h>/<pgtable-2level-types.h>;

PAGE_SHIFT:12比特,用於找到頁表起始處。

PMD_SHIFT:頁內偏移和頁表的總比特數,用於找到頁中間目錄項,當未啓用PAE時,其值爲22(12位頁內偏移以及10位頁表項),當PAE啓動時其值爲21(12位頁內偏移以及9位頁表項)。

PUD_SHIFT:用於查找頁上級目錄,x86上,其值等於PMD_SHIFT。

PGDIR_SHIFT:頁全局目錄的起始地址處,未啓用PAE時PGDIR_SHIFT的值是22,啓用PAE時其值時30(12比特頁內偏移,9比特頁表長度,9比特頁中間目錄長度)。

PTRS_PER_PTE、PTRS_PER_PMD、PTRS_PER_PUD、PTRS_PER_PGD用於計算各入口項的總數。當PAE未啓用時,它們值爲1024、1、1以及1024,而啓用PAE特性時,值爲512,512,1和4。

3.2 Setup_arch

接着圖3.0.2,在系統啓動時PAE特性並未啓用,所以頁表退化爲兩級頁表,臨時頁全局目錄存放在swapper_pg_dir裏,swapper_pg_dir實際存放在bss段,在鏈接時纔會確定其地址,但是該變量的值可以看編譯生成的System.map文件。

  1. c19ff000 B __bss_start  
  2. c19ff000 R __smp_locks_end  
  3. c19ff000 b initial_pg_pmd  
  4. c1a00000 b initial_pg_fixmap  
  5. c1a01000 B empty_zero_page  
  6. c1a02000 B swapper_pg_dir  
c19ff000 B __bss_start
c19ff000 R __smp_locks_end
c19ff000 b initial_pg_pmd
c1a00000 b initial_pg_fixmap
c1a01000 B empty_zero_page
c1a02000 B swapper_pg_dir

未啓用PAE時,PAGE_SHIFT:12;PMD_SHIFT:22;PGDIR_SHIFT:22;

圖3.2.1 和內存相關的內存初始化

Init_mem_mapping映射低端內存區,首先映射ISA區域而不管ISA區是否存在內存空洞,然後映射剩下的內存區,max_low_pfn記錄的就是低端內存區的最後一個頁幀號,根據其大小,按照4MB對其的方式,循環映射完低端內存。而early_ioremap_page_table_range_init負責固定內存映射,圖1.1中的fixmap部分,load_cr3操作啓用分頁機制,而flush操作是保持cache的一致性。

3.2.1 低端內存映射初始化

圖3.2.2init_mem_mapping流程

 

由於ISA以及其它區域建立映射過程類似,這裏剖析ISA區域的映射過程:

init_memory_mapping(0,ISA_END_ADDRESS); // #define ISA_END_ADDRESS              0x100000

建立物理內存的直接映射。

//mr[0].start = 0; mr[0].end = 256 << 12; mr[0].page_size_mask =0, 對應於該函數的三個參數。

  1. unsigned long __init  
  2. kernel_physical_mapping_init(unsigned long start,  
  3.                  unsigned long end,  
  4.                  unsigned long page_size_mask)  
  5. {  
  6.     int use_pse = page_size_mask == (1<<PG_LEVEL_2M);  
  7.     unsigned long last_map_addr = end;  
  8.     unsigned long start_pfn, end_pfn;  
  9.     pgd_t *pgd_base = swapper_pg_dir;  
  10.     int pgd_idx, pmd_idx, pte_ofs;  
  11.     unsigned long pfn;  
  12.     pgd_t *pgd;  
  13.     pmd_t *pmd;  
  14.     pte_t *pte;  
  15.     unsigned pages_2m, pages_4k;  
  16.     int mapping_iter;  
  17.   
  18.     start_pfn = start >> PAGE_SHIFT; //起始頁幀號0  
  19.     end_pfn = end >> PAGE_SHIFT; //結束的頁幀號256  
  20.   
  21.     mapping_iter = 1;  
  22.   
  23. repeat:  
  24.     pages_2m = pages_4k = 0;  
  25.     pfn = start_pfn; //起始頁幀號保存  
  26. //頁全局目錄索引,PAGE_OFFSET是0Xc000_0000,對應pgd_idx是0xc00即768,其意義是內核在全局目錄項映射的第一個項索引是第768項。  
  27.     pgd_idx = pgd_index((pfn<<PAGE_SHIFT) + PAGE_OFFSET);   
  28. //頁全局目錄項基地址+索引  
  29.     pgd = pgd_base + pgd_idx;  
  30. //知道頁全局目錄項有1024項,這裏要對這1024項進行初始化。  
  31.     for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {  
  32.         pmd = one_md_table_init(pgd); //對於未啓用PAE機制,則pmd項只有一項,  
  33.         pmd_idx = 0; //PAE未啓用時,該pmd項的索引值是0,唯一的。  
  34.         for (; pmd_idx < PTRS_PER_PMD && pfn < end_pfn;  
  35.              pmd++, pmd_idx++) {  
  36.             unsigned int addr = pfn * PAGE_SIZE + PAGE_OFFSET; //該頁幀對應的線性地址,偏移量是3G。  
  37.   
  38.             pte = one_page_table_init(pmd);//獲得一個頁表項。  
  39.   
  40.             pte_ofs = pte_index((pfn<<PAGE_SHIFT) + PAGE_OFFSET);//獲得頁表項的索引。  
  41.             pte += pte_ofs;  
  42. //遍歷頁表  
  43.             for (; pte_ofs < PTRS_PER_PTE && pfn < end_pfn;  
  44.                  pte++, pfn++, pte_ofs++, addr += PAGE_SIZE) {  
  45.                 pgprot_t prot = PAGE_KERNEL;  
  46.                 /* 
  47.                  * first pass will use the same initial 
  48.                  * identity mapping attribute. 
  49.                  */  
  50.                 pgprot_t init_prot = __pgprot(PTE_IDENT_ATTR);  
  51.   
  52.                 if (is_kernel_text(addr)) //內核代碼段,則具有可執行權限  
  53.                     prot = PAGE_KERNEL_EXEC;  
  54.   
  55.                 pages_4k++; //記錄初始化的4KB頁數量  
  56. /將pte和pfn關聯/  
  57.                 if (mapping_iter == 1) {  
  58.                     set_pte(pte, pfn_pte(pfn, init_prot));  
  59.                     last_map_addr = (pfn << PAGE_SHIFT) + PAGE_SIZE;  
  60.                 } else  
  61.                     set_pte(pte, pfn_pte(pfn, prot));  
  62.             }  
  63.         }  
  64.     }  
  65.     if (mapping_iter == 1) {  
  66.         /* 
  67.          * update direct mapping page count only in the first 
  68.          * iteration. 
  69.          */  
  70.         update_page_count(PG_LEVEL_2M, pages_2m);  
  71.         update_page_count(PG_LEVEL_4K, pages_4k);  
  72.   
  73.         /* 
  74.          * local global flush tlb, which will flush the previous 
  75.          * mappings present in both small and large page TLB's. 
  76.          */  
  77.         __flush_tlb_all();  
  78.   
  79.         /* 
  80.          * Second iteration will set the actual desired PTE attributes. 
  81.          */  
  82.         mapping_iter = 2;  
  83.         goto repeat;  
  84.     }  
  85.     return last_map_addr;  
  86. }  
unsigned long __init
kernel_physical_mapping_init(unsigned long start,
			     unsigned long end,
			     unsigned long page_size_mask)
{
	int use_pse = page_size_mask == (1<<PG_LEVEL_2M);
	unsigned long last_map_addr = end;
	unsigned long start_pfn, end_pfn;
	pgd_t *pgd_base = swapper_pg_dir;
	int pgd_idx, pmd_idx, pte_ofs;
	unsigned long pfn;
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;
	unsigned pages_2m, pages_4k;
	int mapping_iter;

	start_pfn = start >> PAGE_SHIFT; //起始頁幀號0
	end_pfn = end >> PAGE_SHIFT; //結束的頁幀號256

	mapping_iter = 1;

repeat:
	pages_2m = pages_4k = 0;
	pfn = start_pfn; //起始頁幀號保存
//頁全局目錄索引,PAGE_OFFSET是0Xc000_0000,對應pgd_idx是0xc00即768,其意義是內核在全局目錄項映射的第一個項索引是第768項。
	pgd_idx = pgd_index((pfn<<PAGE_SHIFT) + PAGE_OFFSET); 
//頁全局目錄項基地址+索引
	pgd = pgd_base + pgd_idx;
//知道頁全局目錄項有1024項,這裏要對這1024項進行初始化。
	for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
		pmd = one_md_table_init(pgd); //對於未啓用PAE機制,則pmd項只有一項,
		pmd_idx = 0; //PAE未啓用時,該pmd項的索引值是0,唯一的。
		for (; pmd_idx < PTRS_PER_PMD && pfn < end_pfn;
		     pmd++, pmd_idx++) {
			unsigned int addr = pfn * PAGE_SIZE + PAGE_OFFSET; //該頁幀對應的線性地址,偏移量是3G。

			pte = one_page_table_init(pmd);//獲得一個頁表項。

			pte_ofs = pte_index((pfn<<PAGE_SHIFT) + PAGE_OFFSET);//獲得頁表項的索引。
			pte += pte_ofs;
//遍歷頁表
			for (; pte_ofs < PTRS_PER_PTE && pfn < end_pfn;
			     pte++, pfn++, pte_ofs++, addr += PAGE_SIZE) {
				pgprot_t prot = PAGE_KERNEL;
				/*
				 * first pass will use the same initial
				 * identity mapping attribute.
				 */
				pgprot_t init_prot = __pgprot(PTE_IDENT_ATTR);

				if (is_kernel_text(addr)) //內核代碼段,則具有可執行權限
					prot = PAGE_KERNEL_EXEC;

				pages_4k++; //記錄初始化的4KB頁數量
/將pte和pfn關聯/
				if (mapping_iter == 1) {
					set_pte(pte, pfn_pte(pfn, init_prot));
					last_map_addr = (pfn << PAGE_SHIFT) + PAGE_SIZE;
				} else
					set_pte(pte, pfn_pte(pfn, prot));
			}
		}
	}
	if (mapping_iter == 1) {
		/*
		 * update direct mapping page count only in the first
		 * iteration.
		 */
		update_page_count(PG_LEVEL_2M, pages_2m);
		update_page_count(PG_LEVEL_4K, pages_4k);

		/*
		 * local global flush tlb, which will flush the previous
		 * mappings present in both small and large page TLB's.
		 */
		__flush_tlb_all();

		/*
		 * Second iteration will set the actual desired PTE attributes.
		 */
		mapping_iter = 2;
		goto repeat;
	}
	return last_map_addr;
}

3.2.2高端內區存固定映射初始化

高端內存域的初始化過程和低端內存初始化類似,但是隻分配了頁表,對應的PTE項並沒有被初始化,初始化工作留到set_fixmap()函數建立相關頁表和物理內存的關聯。固定映射區分爲幾種索引類型,索引類型由枚舉變量enum fixed_addresses定義,該初始化工作就是初始化這段區域。

圖3.3.3

一個索引暫用一個4KB的頁框,固定映射區的結束地址是FIXADDR_TOP,即0xfffff000(4G-4K),見圖1.1。

  1. static void __init  
  2. page_table_range_init(unsigned long start, unsigned long end, pgd_t *pgd_base)  
  3. {  
  4.     int pgd_idx, pmd_idx;  
  5.     unsigned long vaddr;  
  6.     pgd_t *pgd;  
  7.     pmd_t *pmd;  
  8.     pte_t *pte = NULL;  
  9.     unsigned long count = page_table_range_init_count(start, end);  
  10.     void *adr = NULL;  
  11.   
  12.     if (count)  
  13.         adr = alloc_low_pages(count);  
  14.   
  15.     vaddr = start;  
  16.     pgd_idx = pgd_index(vaddr);  
  17.     pmd_idx = pmd_index(vaddr);  
  18.     pgd = pgd_base + pgd_idx;   
  19. //遍歷固定映射區,建立頁表項  
  20.     for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {  
  21.         pmd = one_md_table_init(pgd);  
  22.         pmd = pmd + pmd_index(vaddr);  
  23.         for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end);  
  24.                             pmd++, pmd_idx++) {  
  25. //建立PTE項,並檢查vaddr是否對應內核臨時映射區,若是則重新申請一個頁表來保存PTE項。  
  26.             pte = page_table_kmap_check(one_page_table_init(pmd),  
  27.                             pmd, vaddr, pte, &adr);  
  28.   
  29.             vaddr += PMD_SIZE;  
  30.         }  
  31.         pmd_idx = 0;  
  32.     }  
  33. }  
static void __init
page_table_range_init(unsigned long start, unsigned long end, pgd_t *pgd_base)
{
	int pgd_idx, pmd_idx;
	unsigned long vaddr;
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte = NULL;
	unsigned long count = page_table_range_init_count(start, end);
	void *adr = NULL;

	if (count)
		adr = alloc_low_pages(count);

	vaddr = start;
	pgd_idx = pgd_index(vaddr);
	pmd_idx = pmd_index(vaddr);
	pgd = pgd_base + pgd_idx; 
//遍歷固定映射區,建立頁表項
	for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {
		pmd = one_md_table_init(pgd);
		pmd = pmd + pmd_index(vaddr);
		for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end);
							pmd++, pmd_idx++) {
//建立PTE項,並檢查vaddr是否對應內核臨時映射區,若是則重新申請一個頁表來保存PTE項。
			pte = page_table_kmap_check(one_page_table_init(pmd),
						    pmd, vaddr, pte, &adr);

			vaddr += PMD_SIZE;
		}
		pmd_idx = 0;
	}
}

3.2.3 persistent 內存初始化

圖1.1中只剩下的是persistent memory初始化了。這一工作由圖3.2.1中的paging_init完成。該函數還完成了解除虛擬內核0地址頁的映射關係,這就是NULL指針所在的區域,用於異常捕捉。

  1. <arch/x86/mm/init_32.c>  
  2. void __init paging_init(void)  
  3. {  
  4.     pagetable_init();  
  5.   
  6.     __flush_tlb_all();  
  7.   
  8.     kmap_init();  
  9.   
  10.     /* 
  11.      * NOTE: at this point the bootmem allocator is fully available. 
  12.      */  
  13.     olpc_dt_build_devicetree();  
  14.     sparse_memory_present_with_active_regions(MAX_NUMNODES);  
  15.     sparse_init();  
  16.     zone_sizes_init();  
  17. }  
<arch/x86/mm/init_32.c>
void __init paging_init(void)
{
	pagetable_init();

	__flush_tlb_all();

	kmap_init();

	/*
	 * NOTE: at this point the bootmem allocator is fully available.
	 */
	olpc_dt_build_devicetree();
	sparse_memory_present_with_active_regions(MAX_NUMNODES);
	sparse_init();
	zone_sizes_init();
}

Persistent memory頁表在pagetable_init函數分配,映射在kmap_init完成。

  1. static void __init permanent_kmaps_init(pgd_t *pgd_base)  
  2. {  
  3.     unsigned long vaddr;  
  4.     pgd_t *pgd;  
  5.     pud_t *pud;  
  6.     pmd_t *pmd;  
  7.     pte_t *pte;  
  8.   
  9.     vaddr = PKMAP_BASE;  
  10. //爲persistent 內存分配頁表  
  11.     page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);  
  12.   
  13.     pgd = swapper_pg_dir + pgd_index(vaddr);  
  14.     pud = pud_offset(pgd, vaddr);  
  15.     pmd = pmd_offset(pud, vaddr);  
  16.     pte = pte_offset_kernel(pmd, vaddr);  
  17.     pkmap_page_table = pte; //persistent頁表項保存  
  18. }  
  19.   
  20.   
  21. static void __init kmap_init(void)  
  22. {  
  23.     unsigned long kmap_vstart;  
  24.   
  25.     /* 
  26.      * Cache the first kmap pte: 
  27.      */  
  28.     kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);//persistent memory的物理地址到虛擬地址轉換  
  29.     kmap_pte = kmap_get_fixmap_pte(kmap_vstart); //獲得固定映射區的臨時映射頁表項的起始項  
  30.   
  31.     kmap_prot = PAGE_KERNEL;  
  32. }  
static void __init permanent_kmaps_init(pgd_t *pgd_base)
{
	unsigned long vaddr;
	pgd_t *pgd;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;

	vaddr = PKMAP_BASE;
//爲persistent 內存分配頁表
	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; //persistent頁表項保存
}


static void __init kmap_init(void)
{
	unsigned long kmap_vstart;

	/*
	 * Cache the first kmap pte:
	 */
	kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);//persistent memory的物理地址到虛擬地址轉換
	kmap_pte = kmap_get_fixmap_pte(kmap_vstart); //獲得固定映射區的臨時映射頁表項的起始項

	kmap_prot = PAGE_KERNEL;
}

3.3 per-CPU area 初始化

在圖3.0.1中,setup_per_cpu_areas用於初始化per-CPU區域,將.data.percpu中的數據拷貝到每個cpu的數據段,在SMP情況下,per-CPU可以提高併發性,是免鎖算法的一種,有利於提高系統性能,一個經典的應用是:

網卡接收到數據包存在若干個隊列中,隊列個數和CPU的個數是一樣的,這樣多個CPU可以併發訪問各自的接收隊列而不需要鎖。

3.4 節點(node)和域(zone)初始化

在圖3.2.1中,提到了native_pagetable_init函數,該函數將剩下頁表的映射工作完成。


圖3.3 節點初始化

對於node、zone和page的關係,從圖2.2可以看出,這裏涉及帶代碼分析如何完成這種內存管理拓撲的。zone_sizes_init函數還是很好理解的。

  1. 573 void __init zone_sizes_init(void)  
  2. 574 {  
  3. 575     unsigned long max_zone_pfns[MAX_NR_ZONES];  
  4. 576   
  5. 577     memset(max_zone_pfns, 0, sizeof(max_zone_pfns));  
  6. 578   
  7. 579 #ifdef CONFIG_ZONE_DMA  
  8. 580     max_zone_pfns[ZONE_DMA]     = MAX_DMA_PFN; // MAX_DMA_PFN=16M  
  9. 581 #endif  
  10. 582 #ifdef CONFIG_ZONE_DMA32//ia32未定義  
  11. 583     max_zone_pfns[ZONE_DMA32]   = MAX_DMA32_PFN;  
  12. 584 #endif  
  13. 585     max_zone_pfns[ZONE_NORMAL]  = max_low_pfn; //max_low_pfn低端頁幀的上限  
  14. 586 #ifdef CONFIG_HIGHMEM  
  15. 587     max_zone_pfns[ZONE_HIGHMEM] = max_pfn; //最大頁幀號,用於高端內存  
  16. 588 #endif  
  17. 589   
  18. 590     free_area_init_nodes(max_zone_pfns);  
  19. 591 }  
573 void __init zone_sizes_init(void)
574 {
575     unsigned long max_zone_pfns[MAX_NR_ZONES];
576 
577     memset(max_zone_pfns, 0, sizeof(max_zone_pfns));
578 
579 #ifdef CONFIG_ZONE_DMA
580     max_zone_pfns[ZONE_DMA]     = MAX_DMA_PFN; // MAX_DMA_PFN=16M
581 #endif
582 #ifdef CONFIG_ZONE_DMA32//ia32未定義
583     max_zone_pfns[ZONE_DMA32]   = MAX_DMA32_PFN;
584 #endif
585     max_zone_pfns[ZONE_NORMAL]  = max_low_pfn; //max_low_pfn低端頁幀的上限
586 #ifdef CONFIG_HIGHMEM
587     max_zone_pfns[ZONE_HIGHMEM] = max_pfn; //最大頁幀號,用於高端內存
588 #endif
589 
590     free_area_init_nodes(max_zone_pfns);
591 }

free_area_init_nodes的參數是zone數組,其作用是初始化每一個node節點,

在mm/page_alloc.c文件定義了兩個局部全局變量:

  1. static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];  
  2. static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];  
static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];
static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];

這兩個變量用於標記每一個zone的起止邊界頁幀號。

  1. 5043 void __init free_area_init_nodes(unsigned long *max_zone_pfn)  
  2. 5044 {  
  3. 5045     unsigned long start_pfn, end_pfn;  
  4. 5046     int i, nid;  
  5. 5047   
  6. 5048     /* 5048~5068確定每一個zone類型的起止頁幀號 */  
  7. 5049     memset(arch_zone_lowest_possible_pfn, 0,  
  8. 5050                 sizeof(arch_zone_lowest_possible_pfn));  
  9. 5051     memset(arch_zone_highest_possible_pfn, 0,  
  10. 5052                 sizeof(arch_zone_highest_possible_pfn));  
  11. 5053     arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();  
  12. 5054     arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];  
  13. 5055     for (i = 1; i < MAX_NR_ZONES; i++) {  
  14. 5056         if (i == ZONE_MOVABLE)  
  15. 5057             continue;  
  16. 5058         arch_zone_lowest_possible_pfn[i] =  
  17. 5059             arch_zone_highest_possible_pfn[i-1];  
  18. 5060         arch_zone_highest_possible_pfn[i] =  
  19. 5061             max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]);  
  20. 5062     }  
  21. 5063     arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;  
  22. 5064     arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;  
  23. 5065   
  24. 5066     /* Find the PFNs that ZONE_MOVABLE begins at in each node */  
  25. 5067     memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));  
  26. 5068     find_zone_movable_pfns_for_nodes();  
  27. 5100     /*初始化每一個node */  
  28. //遍歷每一個node  
  29. 5103     for_each_online_node(nid) {  
  30. /**定義於include/linux/mmzone.h,對於單一節點,NODE_DATA定義如下: 
  31. #define NODE_DATA(nid)      (&contig_page_data), 5104行的node節點 
  32. */  
  33. 5104         pg_data_t *pgdat = NODE_DATA(nid);  
  34. 5105         free_area_init_node(nid, NULL,  
  35. 5106                 find_min_pfn_for_node(nid), NULL);  
  36. 5107   
  37. 5108         /* 如果node上存在頁,則將其設置該node有內存狀態爲N_MEMORY,表示該node上有可用的內存。 */  
  38. 5109         if (pgdat->node_present_pages)  
  39. 5110             node_set_state(nid, N_MEMORY);  
  40. 5111         check_for_memory(pgdat, nid);  
  41. 5112     }  
  42. 5113 }  
5043 void __init free_area_init_nodes(unsigned long *max_zone_pfn)
5044 {
5045     unsigned long start_pfn, end_pfn;
5046     int i, nid;
5047 
5048     /* 5048~5068確定每一個zone類型的起止頁幀號 */
5049     memset(arch_zone_lowest_possible_pfn, 0,
5050                 sizeof(arch_zone_lowest_possible_pfn));
5051     memset(arch_zone_highest_possible_pfn, 0,
5052                 sizeof(arch_zone_highest_possible_pfn));
5053     arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();
5054     arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];
5055     for (i = 1; i < MAX_NR_ZONES; i++) {
5056         if (i == ZONE_MOVABLE)
5057             continue;
5058         arch_zone_lowest_possible_pfn[i] =
5059             arch_zone_highest_possible_pfn[i-1];
5060         arch_zone_highest_possible_pfn[i] =
5061             max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]);
5062     }
5063     arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;
5064     arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
5065 
5066     /* Find the PFNs that ZONE_MOVABLE begins at in each node */
5067     memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
5068     find_zone_movable_pfns_for_nodes();
5100     /*初始化每一個node */
//遍歷每一個node
5103     for_each_online_node(nid) {
/**定義於include/linux/mmzone.h,對於單一節點,NODE_DATA定義如下:
#define NODE_DATA(nid)		(&contig_page_data), 5104行的node節點
*/
5104         pg_data_t *pgdat = NODE_DATA(nid);
5105         free_area_init_node(nid, NULL,
5106                 find_min_pfn_for_node(nid), NULL);
5107 
5108         /* 如果node上存在頁,則將其設置該node有內存狀態爲N_MEMORY,表示該node上有可用的內存。 */
5109         if (pgdat->node_present_pages)
5110             node_set_state(nid, N_MEMORY);
5111         check_for_memory(pgdat, nid);
5112     }
5113 }

5015行的free_area_init_node第一個參數是node(節點)ID,第二個參數在zone的大小,第三個參數的是該zone的其實頁幀號,最後一個參數是該zone的空洞大小,在3.10版本。Node的核心初始化函數是free_area_init_core;

  1. 4592 static void __paginginit free_area_init_core(struct pglist_data *pgdat,  
  2. 4593         unsigned long *zones_size, unsigned long *zholes_size)  
  3. 4594 {  
  4. 4595     enum zone_type j;  
  5. 4596     int nid = pgdat->node_id; //節點ID號  
  6. 4597     unsigned long zone_start_pfn = pgdat->node_start_pfn; //該node的起始頁幀號  
  7. 4598     int ret;  
  8. 4599   
  9. 4600     pgdat_resize_init(pgdat); //熱插拔情況內存量可能會變化,重新計算  
  10. 4606     init_waitqueue_head(&pgdat->kswapd_wait); //換頁守護進程隊列頭初始化。  
  11. 4607     init_waitqueue_head(&pgdat->pfmemalloc_wait);//盡最大努力分配內存隊列頭初始化。  
  12. 4608     pgdat_page_cgroup_init(pgdat); //node節點頁cgroup初始化  
  13. 4609 //迭代每一個zone  
  14. 4610     for (j = 0; j < MAX_NR_ZONES; j++) {  
  15. 4611         struct zone *zone = pgdat->node_zones + j;  
  16. 4612         unsigned long size, realsize, freesize, memmap_pages;  
  17. 4613 //j類型的zone的頁總數,可能包含空洞  
  18. 4614         size = zone_spanned_pages_in_node(nid, j, zones_size);  
  19. 4615         realsize = freesize = size - zone_absent_pages_in_node(nid, j, //realsize是真實長度,減去了hole  
  20. 4616                                 zholes_size);  
  21. 4617   
  22. 4618         /*調整頁,將其按4KB邊界對其,影響水印值。 
  23. 4623         memmap_pages = calc_memmap_size(size, realsize); 
  24. 4635         /*DMA預留內存保留,不會計入到zone裏 */  
  25. 4636         if (j == 0 && freesize > dma_reserve) {  
  26. 4637             freesize -= dma_reserve;  
  27. 4638             printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",  
  28. 4639                     zone_names[0], dma_reserve);  
  29. 4640         }  
  30. // nr_kernel_pages記錄了DMA和NORMAL類型的頁數,對不是高端內存情況需要加上。如果內核頁足夠多,需要進行調整,內核頁的物理地址範圍小於896M。  
  31. 4642         if (!is_highmem_idx(j))  
  32. 4643             nr_kernel_pages += freesize;  
  33. 4644         /* Charge for highmem memmap if there are enough kernel pages */  
  34. 4645         else if (nr_kernel_pages > memmap_pages * 2)  
  35. 4646             nr_kernel_pages -= memmap_pages;  
  36. 4647         nr_all_pages += freesize;  
  37. 4648 //4649到4667初始化zone的相關成員  
  38. 4649         zone->spanned_pages = size;  
  39. 4650         zone->present_pages = realsize;  
  40. 4656         zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;  
  41. 4663         zone->name = zone_names[j];  
  42. 4664         spin_lock_init(&zone->lock);  
  43. 4665         spin_lock_init(&zone->lru_lock);  
  44. 4666         zone_seqlock_init(zone);  
  45. 4667         zone->zone_pgdat = pgdat;  
  46. 4668   
  47. 4669         zone_pcp_init(zone); //zone的per-CPU成員初始化  
  48. 4670         lruvec_init(&zone->lruvec);  
  49. 4671         if (!size)  
  50. 4672             continue;  
  51. 4673   
  52. //夥伴系統的各階初始化再次完成,從0階到11階  
  53. 4676         ret = init_currently_empty_zone(zone, zone_start_pfn,  
  54. 4677                         size, MEMMAP_EARLY);  
  55. 4678         BUG_ON(ret);  
  56. 4679         memmap_init(size, nid, j, zone_start_pfn); //處理內存域中的page實例,將其標記爲可移動的。  
  57. 4680         zone_start_pfn += size;  
  58. 4681     }  
  59. 4682 }  
4592 static void __paginginit free_area_init_core(struct pglist_data *pgdat,
4593         unsigned long *zones_size, unsigned long *zholes_size)
4594 {
4595     enum zone_type j;
4596     int nid = pgdat->node_id; //節點ID號
4597     unsigned long zone_start_pfn = pgdat->node_start_pfn; //該node的起始頁幀號
4598     int ret;
4599 
4600     pgdat_resize_init(pgdat); //熱插拔情況內存量可能會變化,重新計算
4606     init_waitqueue_head(&pgdat->kswapd_wait); //換頁守護進程隊列頭初始化。
4607     init_waitqueue_head(&pgdat->pfmemalloc_wait);//盡最大努力分配內存隊列頭初始化。
4608     pgdat_page_cgroup_init(pgdat); //node節點頁cgroup初始化
4609 //迭代每一個zone
4610     for (j = 0; j < MAX_NR_ZONES; j++) {
4611         struct zone *zone = pgdat->node_zones + j;
4612         unsigned long size, realsize, freesize, memmap_pages;
4613 //j類型的zone的頁總數,可能包含空洞
4614         size = zone_spanned_pages_in_node(nid, j, zones_size);
4615         realsize = freesize = size - zone_absent_pages_in_node(nid, j, //realsize是真實長度,減去了hole
4616                                 zholes_size);
4617 
4618         /*調整頁,將其按4KB邊界對其,影響水印值。
4623         memmap_pages = calc_memmap_size(size, realsize);
4635         /*DMA預留內存保留,不會計入到zone裏 */
4636         if (j == 0 && freesize > dma_reserve) {
4637             freesize -= dma_reserve;
4638             printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",
4639                     zone_names[0], dma_reserve);
4640         }
// nr_kernel_pages記錄了DMA和NORMAL類型的頁數,對不是高端內存情況需要加上。如果內核頁足夠多,需要進行調整,內核頁的物理地址範圍小於896M。
4642         if (!is_highmem_idx(j))
4643             nr_kernel_pages += freesize;
4644         /* Charge for highmem memmap if there are enough kernel pages */
4645         else if (nr_kernel_pages > memmap_pages * 2)
4646             nr_kernel_pages -= memmap_pages;
4647         nr_all_pages += freesize;
4648 //4649到4667初始化zone的相關成員
4649         zone->spanned_pages = size;
4650         zone->present_pages = realsize;
4656         zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
4663         zone->name = zone_names[j];
4664         spin_lock_init(&zone->lock);
4665         spin_lock_init(&zone->lru_lock);
4666         zone_seqlock_init(zone);
4667         zone->zone_pgdat = pgdat;
4668 
4669         zone_pcp_init(zone); //zone的per-CPU成員初始化
4670         lruvec_init(&zone->lruvec);
4671         if (!size)
4672             continue;
4673 
//夥伴系統的各階初始化再次完成,從0階到11階
4676         ret = init_currently_empty_zone(zone, zone_start_pfn,
4677                         size, MEMMAP_EARLY);
4678         BUG_ON(ret);
4679         memmap_init(size, nid, j, zone_start_pfn); //處理內存域中的page實例,將其標記爲可移動的。
4680         zone_start_pfn += size;
4681     }
4682 }

3.5 啓用內核內存分配器

依然還是圖3.0.1,這次是mm_init函數。

  1. static void __init mm_init(void)  
  2. {  
  3.     /* 
  4.      * page_cgroup requires contiguous pages, 
  5.      * bigger than MAX_ORDER unless SPARSEMEM. 
  6.      */  
  7.     page_cgroup_init_flatmem();  
  8.     mem_init();  
  9.     kmem_cache_init();  
  10.     percpu_init_late();  
  11.     pgtable_cache_init();  
  12.     vmalloc_init();  
  13. }  
  14.   
  15.   
  16. 740 void __init mem_init(void)  
  17. 741 {  
  18. 742     int codesize, reservedpages, datasize, initsize;  
  19. 743     int tmp;  
  20. 761     /* this will put all low memory onto the freelists */  
  21. 762     totalram_pages += free_all_bootmem(); //釋放boot內存分配器  
  22. 763   
  23. 764     reservedpages = 0;  
  24. 765     for (tmp = 0; tmp < max_low_pfn; tmp++)  
  25. 769         if (page_is_ram(tmp) && PageReserved(pfn_to_page(tmp)))  
  26. 770             reservedpages++;  
  27. 771   
  28. 772     after_bootmem = 1;  
  29. 773   
  30. 774     codesize =  (unsigned long) &_etext - (unsigned long) &_text; //內核代碼段  
  31. 775     datasize =  (unsigned long) &_edata - (unsigned long) &_etext; //  
  32. 776     initsize =  (unsigned long) &__init_end - (unsigned long) &__init_begin;  
static void __init mm_init(void)
{
	/*
	 * page_cgroup requires contiguous pages,
	 * bigger than MAX_ORDER unless SPARSEMEM.
	 */
	page_cgroup_init_flatmem();
	mem_init();
	kmem_cache_init();
	percpu_init_late();
	pgtable_cache_init();
	vmalloc_init();
}


740 void __init mem_init(void)
741 {
742     int codesize, reservedpages, datasize, initsize;
743     int tmp;
761     /* this will put all low memory onto the freelists */
762     totalram_pages += free_all_bootmem(); //釋放boot內存分配器
763 
764     reservedpages = 0;
765     for (tmp = 0; tmp < max_low_pfn; tmp++)
769         if (page_is_ram(tmp) && PageReserved(pfn_to_page(tmp)))
770             reservedpages++;
771 
772     after_bootmem = 1;
773 
774     codesize =  (unsigned long) &_etext - (unsigned long) &_text; //內核代碼段
775     datasize =  (unsigned long) &_edata - (unsigned long) &_etext; //
776     initsize =  (unsigned long) &__init_end - (unsigned long) &__init_begin;

下列dmesg出來的信息,就在該函數打印的。

  1. [    0.000000] Memory: 2044624K/2096632K available (6539K kernel code, 640K rwda  
  2. ta, 2764K rodata, 872K init, 924K bss, 52008K reserved, 1183624K highmem)  
  3. [    0.000000] virtual kernel memory layout:  
  4. [    0.000000]     fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)  
  5. [    0.000000]     pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)  
  6. [    0.000000]     vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)  
  7. [    0.000000]     lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)  
  8. [    0.000000]       .init : 0xc19b9000 - 0xc1a93000   ( 872 kB)  
  9. [    0.000000]       .data : 0xc1663132 - 0xc19b8200   (3412 kB)  
  10. [    0.000000]       .text : 0xc1000000 - 0xc1663132   (6540 kB)  
[    0.000000] Memory: 2044624K/2096632K available (6539K kernel code, 640K rwda
ta, 2764K rodata, 872K init, 924K bss, 52008K reserved, 1183624K highmem)
[    0.000000] virtual kernel memory layout:
[    0.000000]     fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)
[    0.000000]     pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)
[    0.000000]     vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)
[    0.000000]     lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)
[    0.000000]       .init : 0xc19b9000 - 0xc1a93000   ( 872 kB)
[    0.000000]       .data : 0xc1663132 - 0xc19b8200   (3412 kB)
[    0.000000]       .text : 0xc1000000 - 0xc1663132   (6540 kB)

kmem_cache_init初始化cache,vmalloc內存池初始化。

 至此內存初始化流程結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章