linux 初始化内存管理_Linux内存管理第二章 -- Describing Physical Memory
首先來描述幾個名詞:
3. node:NUMA架構下的每一個內存區域稱作是node,node用struct pglist_data來描述。所有的node在Linux內核中用一個全局鏈表pgdat_list關聯起來,pg_data_t->pgdat_next指向下一個區域。
4. zone:每一個node下面的內存又被劃分為多個小區域,每個區域稱作是zone.zone一般分為三個:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM.Linux中使用struct zone來描述一個zone。
5. page:Linux內核對每一個物理內存頁框的描述。
node和zone的關系如下圖:
Nodes
Linux內核中每一個node由struct pglist_data來進行描述。其定義如下<include/linux/mmzone.h>,下面來介紹下主要成員的作用。
typedef struct pglist_data {struct zone node_zones[MAX_NR_ZONES];struct zonelist node_zonelists[GFP_ZONETYPES];int nr_zones;struct page *node_mem_map;struct bootmem_data *bdata;unsigned long node_start_pfn;unsigned long node_present_pages; /* total number of physical pages */unsigned long node_spanned_pages; /* total size of physical page range, including holes */int node_id;struct pglist_data *pgdat_next;wait_queue_head_t kswapd_wait;struct task_struct *kswapd; } pg_data_t;node_zones:當前node中的zones:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM
node_zonelists:該數組決定了當前node中分配物理內存時,選擇zone的順序.該數組的初始化由mm/page_alloc.c中的build_zonelists()來完成。build_zonelists()由free_area_init_core()調用觸發。一般狀況下當從ZONE_HIGHMEM中分配內存失敗時,會再嘗試從ZONE_NORMAL或者ZONE_DMA中分配。
node_mem_map:該指針表示當前node中的物理頁框數字中的第一個頁框描述符struct page的地址。在內核中有些地方也可以直接使用全局數組mem_map來訪問獲取node的第一個page地址,實現node_mem_map相同的功能。
node_start_paddr:當前node的物理地址的起始地址。該字段類型是一個unsigned long型,有的時候可能會超長,一個比較好的頒發是在此字段中中記錄物理頁框號(Page Frame Number,PFN)。PFN = page_phys_addr >> PAGE_SHIFT
node_start_mapnr:當前node中起始物理地址在全局數組mem_map中的偏移。
node_size:當前node中page的總數。
系統中維護的所有node都在一個全局列表pgdat_list中。該list由init_bootmem_core()函數初始化。
Zones
Linux內核中每一個zone由struct zone結構來描述。該結構主要使用來統計page的使用和釋放信息。其定義如下,接下來再看下重點成員:
struct zone {spinlock_t lock;unsigned long free_pages;unsigned long pages_min, pages_low, pages_high;unsigned long protection[MAX_NR_ZONES];ZONE_PADDING(_pad1_)spinlock_t lru_lock; struct list_head active_list;struct list_head inactive_list;unsigned long nr_scan_active;unsigned long nr_scan_inactive;unsigned long nr_active;unsigned long nr_inactive;int all_unreclaimable; /* All pages pinned */unsigned long pages_scanned; /* since last reclaim */ZONE_PADDING(_pad2_)int temp_priority;int prev_priority;struct free_area free_area[MAX_ORDER];wait_queue_head_t * wait_table;unsigned long wait_table_size;unsigned long wait_table_bits;ZONE_PADDING(_pad3_)struct per_cpu_pageset pageset[NR_CPUS];struct pglist_data *zone_pgdat;struct page *zone_mem_map;/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */unsigned long zone_start_pfn;char *name;unsigned long spanned_pages; /* total size, including holes */unsigned long present_pages; /* amount of memory (excluding holes) */ };- free_pages:當前zone中free page的總個數
- pages_min,pages_low,page_high:該zone中的水位線,具體在下一小節進行說明。
- free_area:buddy allocator使用的free area bitmaps
- wait_table:等待某個page被釋放的進程的等待隊列鏈表。這個隊列對于wait_on_page()和unlock_page()函數來說非常重要。
- wait_table_size:wait_table中等待隊列的個數。
- wait_table_bits:wait_table_size == (1 << wait_table_bits)
- zone_pgdat:指向當前zone所屬的node
- zone_mem_map:當前zone中的第一個page的地址,該page輸入全局數組mem_map
- size:當前zone中pages 的個數
Zone初始化
當內核的頁表有page_init()建立好之后,就開會初始化zone。每種硬件架構執行zone初始化的時候可能稍有不同,但是最終目標是相同的。每種架構初始化zone的流程中的最終目標是決定傳入什么參數值到UMA架構的初始化函數free_area_init()或者NUMA架構的初始化函數free_area_init_node()中。下面來看下參數的含義:
void __init free_area_init_node(int nid, struct pglist_data *pgdat,unsigned long *zones_size, unsigned long node_start_pfn,unsigned long *zholes_size) {............ }- nid:要被初始化的zone所屬的node的邏輯標識符。
- pgdat:要被初始化成zone的node
- zones_size:包含每個zone的page的個數的數組
- node_start_pfn:當前node的起始PFN
- zholes_size:包含每個zone中memory holes的個數的數組。
無論是free_area_init()還是free_area_init_node()都會調用free_area_init_core()來真正初始化每個struct zone中的成員。在做zone初始化的時候,系統無法知道每個zone中有多少可用的page,這些信息知道boot memory allocator退休之后才會知道。
mem_map初始化
mem_map是在系統啟動階段被創建。在NUMA系統中,全局數組mem_map被當做是一個虛擬的數組其起始位置為PAGE_OFFSET(一般就是指虛擬地址3G的位置)。系統中的每個node初始化的過程中調用free_area_init_node()來為這個虛擬數組分配填充mem_map的一部分內容。而在UMA系統中,因為只有一個node,所以其node為全局變量contig_page_data,而全局變量mem_map就是這個contig_page_data中的ode_mem_map。
對于UMA架構來說,核心函數free_area_core()將為當前初始化的node分配一個本地的lmem_map,lmem_map是從boot memory allocator中使用alloc_bootmem_node()來進行分配。該新分配的lmem_map將會成為全局的mem_map。但NUMA系統中與此有稍微不同。
對于NUMA架構來說,核心函數free_area_core()將從node節點自己的內存中分配lmem_map,而全部變量mem_map不會顯式的分配,而是將其設定為PAGE_OFFSET的虛擬地址上,其被當做是一個虛擬的數組。而lmem_map被存儲在node中的node_mem_map,pg_data_t->node_mem_map,其在虛擬數組mem_map的某個位置上。當node和zone被初始化完整之后,mem_map將會被當成一個真正的數組了。
Pages
page定義
系統中的每一個物理頁框都有一個相應地struct page,其用來持續跟蹤物理頁框的狀態。其定義如下:
struct page {page_flags_t flags; /* Atomic flags, some possibly updated asynchronously */atomic_t _count; /* Usage count, see below. */atomic_t _mapcount; /* Count of ptes mapped in mms,to show when page is mapped & limit reverse map searches. unsigned long private; /* Mapping-private opaque data:usually used for buffer_heads if PagePrivate set;/* used for swp_entry_t if PageSwapCache */struct address_space *mapping; /* If low bit clear, points to inode address_space, or NULL.* If page mapped as anonymous memory, low bit is set, and it points to anon_vma object:* see PAGE_MAPPING_ANON below.*/pgoff_t index; /* Our offset within mapping. */struct list_head lru;/* Pageout list, eg. active_list protected by zone->lru_lock !*//* On machines where all RAM is mapped into kernel address space,* we can simply calculate the virtual address. On machines with* highmem some memory is mapped into kernel virtual memory* dynamically, so we need a place to store that address.* Note that this field could be 16 bits on x86 ... ;)** Architectures with slow multiplication can define* WANT_PAGE_VIRTUAL in asm/page.h */ #if defined(WANT_PAGE_VIRTUAL)void *virtual; /* Kernel virtual address (NULL if not kmapped, ie. highmem) */ #endif /* WANT_PAGE_VIRTUAL */ };- mapping:當文件或者設備有內存映射,那么文件的inode中有一個成員address_space,如果該page屬于這個文件那么mapping字段將指向這個address_space。
- lru:根據page的替換策略,active_list或者inactive_list中的pages都可能被換出。該字段是active_list或者inacvtive_list的頭。
- virtual:通常來講只有ZONE_NORMAL中的物理內存才會被內核直接映射到虛擬地址空間,而ZONE_HIGHMEM中的物理內存需要調用kmap()來講物理地址轉成虛擬地址,通常只有固定數目的pages被kmap()轉換,如果當前page被kmap映射了,則virtual字段記錄它的虛擬地址。
- index:mapping中偏移。
- flags:該字段表示描述page狀態的標志位
- count:當前page正在被使用的次數。當count為0時,該page可能被釋放。只要大于0,那么當前被一個或者多個進程或者內核在使用中。
| bit名稱 | 描述 |
PG_active | 當一個page在avtive_list LRU中時,該bit被置位。從active_list LRU中被刪除時,該bit被清零。該bit標記page當前的熱度
PG_arch_1 | 該bit位依據不同的架構而不同的page狀態位。通常當一個page第一次整體進入page cache的時候該位被清零
PG_checked | 僅僅被Ext2文件系統使用
PG_dirty|該標記位1時表示該page需要被刷新到磁盤上。磁盤文件對應的page被寫后不會立即刷新到磁盤,該bit為來保證dirty page在刷新之前不被釋放
PG_error|在磁盤IO過程中發生錯誤,該bit為被置位
PG_fs_1|該bit位保留給文件系統自己用。當前只有NFS用該bit位來表示當前page是否同遠端server同步
PG_highmem|高端內存的pages不能被kernel直接映射,因此在mem_init()的時候high memory page該bit位被置位
PG_launder|系統一般會先將該bit置位然后再調用writepage()函數,當系統想換出一個page,在掃描時如果發現一個page的該bit為被置位且PG_locked被置位,則系統會等待IO完成。
PG_locked|當進行磁盤IO時該bit為必須置位,IO完成,該bit為清零
PG_lru|如果一個page在active_list或者inactive_list中時,該bit為置位
PG_referenced|當一個page被映射并且通過映射或者哈希表有訪問,則該bit為置位,其主要用作LRU list更新過程中。
PG_slab|如果page正在被slab allocator使用,該bit為置位
PG_skip|某些架構中用該bit為來標記跳過虛擬地址空間中沒有對應物理內存的部分
PG_unused|該標記位目前沒有使用
PG_uptodate|當一個page從磁盤中讀入內容時沒有報錯,該bit為置位
### 映射 Pages 到 Zones
在kernel2.4中,struct page中有個struct zone的指針,用來表示page輸入哪個zone,這是一種相當浪費的行為。因為當有幾千個page的時候,這一個小小的指針也會耗費很多的memory。在kernel2.6中,struct page中的zone字段被刪除了,取而代之的是使用page->flags中的高ZONE_SHIFT(x86下是8)位來決定page屬于哪個zone。
首先創建一個zone_table,mm/page_alloc.c:
/** Used by page_zone() to look up the address of the struct zone whose* id is encoded in the upper bits of page->flags*/ struct zone *zone_table[1 << (ZONES_SHIFT + NODES_SHIFT)]; EXPORT_SYMBOL(zone_table);然后調用set_page_zone設置zone的ID。
static inline void set_page_zone(struct page *page, unsigned long nodezone_num) {page->flags &= ~(~0UL << NODEZONE_SHIFT);page->flags |= nodezone_num << NODEZONE_SHIFT; }高端內存--High Memory
為什么要支持高端內存?
因為kernel所能使用的虛擬地址空間是有限的(一般只ZONE_NORMAL)。所以kernel需要支持High Memory的概念。在x86 32位系統中,對于高端內存來說有兩個閾值:4GB,64GB
4GB:因為物理地址是32位,最大4GB。所以虛擬地址也是32位即虛擬地址空間是0 ~~ 4GB。由于內核只能使用虛擬地址3G ~ 4G這一個G的虛擬地址,而這一個G的虛擬地址大部分被kernel從ZONE_NORMAL和ZONE_DMA直接線性映射所占用,如果系統想要訪問更多的物理內存就得從ZONE_HIGMEM中獲取物理頁框再調用kmap()將其映射到虛擬地址空間,從而讓高端物理內存可以。
64GB:Intel發明了PAE技術(Page Address Extension)讓32位系統可以使用更多的物理內存。即 $2^{36}$ = 64GB
PAE理論上讓處理器可以訪問64GB物理內存,但是實際上32位Linux系統中的進程無法訪問那么多物理內存因為虛擬地址空間仍然是4GB。
再者來說,PAE不允許內核自己使用如此多的內存。一個struct page占用44字節,而struct page所占用的的虛擬內從空間是在內核可用的虛擬地址空間內。因此描述1GB的物理內存,所需要的struct page就要占用11MB的虛擬地址空間,16GB的物理內存描述就要占用176MB的虛擬地址空間,這會使得內核所使用的虛擬地址空間更加緊張。
因此在32位系統中要訪問更多的物理內存,就得內核就得支持高端內存的概念。
所謂高端內存就是指將內核現行映射完成之后還有多余的物理內存,通過動態映射的方式將物理地址轉換為虛擬地址,從而使得這部分物理地址可用。這也僅僅是臨時可用,一個進程也不能一次malloc 3G以上的內存使用。
高端內存具體如何使用?
例如socket中的buffer就是使用高端內存,比如網絡上來了大量數據,內核先調用kmap()將一些page映射成虛擬地址,然后將網絡數據copy到物理內存,然后調用kunmap()解除這段虛擬地址到物理地址的映射,此時內核記錄的是struct page的地址來描述這些buff在哪里。
當上層應用要讀取socket的數據的時候,kernel再次調用kmap()將之前存放網絡數據的page再次映射成虛擬地址,讓上層應用將這些數據copy出去。
總結
以上是生活随笔為你收集整理的linux 初始化内存管理_Linux内存管理第二章 -- Describing Physical Memory的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .tcl文件_TCL电视如何安装第三方软
- 下一篇: 苹果备忘录怎么调字体大小_苹果手机备忘录