內存管理-之啓動-基於linux3.10
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文件中。
- <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;
- }
<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導出的內核虛擬地址拓撲如下:
- [ 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)
[ 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導出的內存拓撲:
- [ 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)
[ 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系統的實例如下:
- $ 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
$ 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)。
- enum zone_type {
- ZONE_DMA,
- ZONE_DMA32,
- ZONE_NORMAL,
- ZONE_HIGHMEM,
- ZONE_MOVABLE,
- __MAX_NR_ZONES
- };
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之後,這部分的內存頁是動態映射的,主要是因爲頁表項有限,其基本思想是需要高端內存時,申請頁表進行映射,用完釋放再回收頁表。
- <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;
<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以上的空間,不能直接映射。
- 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
- };
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結構體是- 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;
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與之關聯,以跟蹤頁使用情況,
- 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 };
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文件裏可以找到上述變量的定義。該文件有如下幾行,隨編譯結果不同而有區別:
- 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 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文件,該文件的一部分內容摘錄如下:
- 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
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文件。
- 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
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, 對應於該函數的三個參數。
- 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;
- }
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。
- 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;
- }
- }
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指針所在的區域,用於異常捕捉。
- <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();
- }
<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完成。
- 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;
- }
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函數還是很好理解的。
- 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 }
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文件定義了兩個局部全局變量:
- static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];
- 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的起止邊界頁幀號。
- 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 }
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;
- 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 }
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函數。
- 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;
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出來的信息,就在該函數打印的。
- [ 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)
[ 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內存池初始化。
至此內存初始化流程結束。