Linux把物理內存劃分爲三個層次來管理node
層次 | 描述 |
---|---|
存儲節點(Node) | CPU被劃分爲多個節點(node), 內存則被分簇, 每一個CPU對應一個本地物理內存, 即一個CPU-node對應一個內存簇bank,即每一個內存簇被認爲是一個節點 |
管理區(Zone) | 每一個物理內存節點node被劃分爲多個內存管理區域, 用於表示不一樣範圍的內存, 內核可使用不一樣的映射方式映射物理內存 |
頁面(Page) | 內存被細分爲多個頁面幀, 頁面是最基本的頁面分配的單位 |
pg_data_t
由struct pglist_data
定義而來, 該數據結構定義在include/linux/mmzone.h, line 615, 每一個結點關聯到系統中的一個處理器, 內核中表示爲pg_data_t的實例. 系統中每一個節點被連接到一個以NULL結尾的pgdat_list鏈表中<而其中的每一個節點利用pg_data_tnode_next
字段連接到下一節.而對於PC這種UMA結構的機器來講, 只使用了一個成爲contig_page_data的靜態pg_data_t結構.struct zone_struct
描述, 其被定義爲zone_t, 用以表示內存的某個範圍, 低端範圍的16MB被描述爲ZONE_DMA, 某些工業標準體系結構中的(ISA)設備須要用到它, 而後是可直接映射到內核的普通內存域ZONE_NORMAL,最後是超出了內核段的物理地址域ZONE_HIGHMEM, 被稱爲高端內存. 是系統中預留的可用內存空間, 不能被內核直接映射.分頁單元能夠實現把線性地址轉換爲物理地址, 爲了效率起見, 線性地址被分爲固定長度爲單位的組, 稱爲」頁」, 頁內部的線性地址被映射到連續的物理地址. 這樣內核能夠指定一個頁的物理地址和其存儲權限, 而不用指定頁所包含的所有線性地址的存儲權限.linux
分頁單元把全部RAM分爲固定長度的頁幀(也叫頁框, 物理頁, 英文page frame). 每個頁幀包含一個頁(page). 也就是說一個頁幀的長度與一個頁的長度一致. 頁框是主存的一部分, 所以也是一個存儲區域. 簡單來講, 頁是一個數據塊, 能夠存放在任何頁框(內存中)或者磁盤(被交換至交換分區)中算法
咱們今天就來詳細講解一下linux下物理頁幀的描述c#
內核把物理頁做爲內存管理的基本單位. 儘管處理器的最小可尋址單位一般是字, 可是, 內存管理單元MMU一般以頁爲單位進行處理. 所以,從虛擬內存的上來看,頁就是最小單位.數組
頁幀表明了系統內存的最小單位, 對內存中的每一個頁都會建立struct page的一個實例. 內核必需要保證page結構體足夠的小,不然僅struct page就要佔用大量的內存.緩存
由於即便在中等程序的內存配置下, 系統的內存一樣會分解爲大量的頁. 例如, IA-32系統中標準頁長度爲4KB, 在內存大小爲384MB時, 大約有100000頁. 就當今的標準而言, 這個容量算不上很大, 但頁的數目已經很是可觀了數據結構
於是出於節省內存的考慮,內核要盡力保持struct page儘量的小. 在典型的系統中, 因爲頁的數目巨大, 所以對page結構的小改動, 也可能致使保存全部page實例所需的物理內存暴漲.app
頁的普遍使用, 增長了保持結構長度的難度 : 內存管理的許多部分都使用頁, 用於各類不一樣的用途. 內核的一部分可能徹底依賴於struct page提供的特定信息, 而這部分信息堆內核的其餘部分頁多是徹底無用的. 等等.dom
內核用struct page(include/linux/mm_types.h?v=4.7, line 45)結構表示系統中的每一個物理頁.electron
出於節省內存的考慮,struct page中使用了大量的聯合體union.
/* * Each physical page in the system has a struct page associated with * it to keep track of whatever it is we are using the page for at the * moment. Note that we have no way to track which tasks are using * a page, though if it is a pagecache page, rmap structures can tell us * who is mapping it. * * The objects in struct page are organized in double word blocks in * order to allows us to use atomic double word operations on portions * of struct page. That is currently only used by slub but the arrangement * allows the use of atomic double word operations on the flags/mapping * and lru list pointers also. */ struct page { /* First double word block */ unsigned long flags; /* Atomic flags, some possibly updated asynchronously 描述page的狀態和其餘信息 */ union { 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. */ void *s_mem; /* slab first object */ atomic_t compound_mapcount; /* first tail page */ /* page_deferred_list().next -- second tail page */ }; /* Second double word */ struct { union { pgoff_t index; /* Our offset within mapping. 在映射的虛擬空間(vma_area)內的偏移; 一個文件可能只映射一部分,假設映射了1M的空間, index指的是在1M空間內的偏移,而不是在整個文件內的偏移。 */ void *freelist; /* sl[aou]b first free object */ /* page_deferred_list().prev -- second tail page */ }; union { #if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \ defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE) /* Used for cmpxchg_double in slub */ unsigned long counters; #else /* * Keep _refcount separate from slub cmpxchg_double * data. As the rest of the double word is protected by * slab_lock but _refcount is not. */ unsigned counters; #endif struct { union { /* * Count of ptes mapped in mms, to show * when page is mapped & limit reverse * map searches. * 頁映射計數器 */ atomic_t _mapcount; struct { /* SLUB */ unsigned inuse:16; unsigned objects:15; unsigned frozen:1; }; int units; /* SLOB */ }; /* * Usage count, *USE WRAPPER FUNCTION* * when manual accounting. See page_ref.h * 頁引用計數器 */ atomic_t _refcount; }; unsigned int active; /* SLAB */ }; }; /* * Third double word block * * WARNING: bit 0 of the first word encode PageTail(). That means * the rest users of the storage space MUST NOT use the bit to * avoid collision and false-positive PageTail(). */ union { struct list_head lru; /* Pageout list, eg. active_list * protected by zone->lru_lock ! * Can be used as a generic list * by the page owner. */ struct dev_pagemap *pgmap; /* ZONE_DEVICE pages are never on an * lru or handled by a slab * allocator, this points to the * hosting device page map. */ struct { /* slub per cpu partial pages */ struct page *next; /* Next partial slab */ #ifdef CONFIG_64BIT int pages; /* Nr of partial slabs left */ int pobjects; /* Approximate # of objects */ #else short int pages; short int pobjects; #endif }; struct rcu_head rcu_head; /* Used by SLAB * when destroying via RCU */ /* Tail pages of compound page */ struct { unsigned long compound_head; /* If bit zero is set */ /* First tail page only */ #ifdef CONFIG_64BIT /* * On 64 bit system we have enough space in struct page * to encode compound_dtor and compound_order with * unsigned int. It can help compiler generate better or * smaller code on some archtectures. */ unsigned int compound_dtor; unsigned int compound_order; #else unsigned short int compound_dtor; unsigned short int compound_order; #endif }; #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS struct { unsigned long __pad; /* do not overlay pmd_huge_pte * with compound_head to avoid * possible bit 0 collision. */ pgtable_t pmd_huge_pte; /* protected by page->ptl */ }; #endif }; /* Remainder is not double word aligned */ union { unsigned long private; /* Mapping-private opaque data: * usually used for buffer_heads * if PagePrivate set; used for * swp_entry_t if PageSwapCache; * indicates order in the buddy * system if PG_buddy is set. * 私有數據指針,由應用場景肯定其具體的含義 */ #if USE_SPLIT_PTE_PTLOCKS #if ALLOC_SPLIT_PTLOCKS spinlock_t *ptl; #else spinlock_t ptl; #endif #endif struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */ }; #ifdef CONFIG_MEMCG struct mem_cgroup *mem_cgroup; #endif /* * 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 */ #ifdef CONFIG_KMEMCHECK /* * kmemcheck wants to track the status of each byte in a page; this * is a pointer to such a status block. NULL if not tracked. */ void *shadow; #endif #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS int _last_cpupid; #endif } /* * The struct page can be forced to be double word aligned so that atomic ops * on double words work. The SLUB allocator can make use of such a feature. */ #ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE __aligned(2 * sizeof(unsigned long)) #endif ;
字段 | 描述 |
---|---|
flag | 用來存放頁的狀態,每一位表明一種狀態,因此至少能夠同時表示出32中不一樣的狀態,這些狀態定義在linux/page-flags.h中 |
virtual | 對於若是物理內存能夠直接映射內核的系統, 咱們能夠之間映射出虛擬地址與物理地址的管理, 可是對於須要使用高端內存區域的頁, 即沒法直接映射到內核的虛擬地址空間, 所以須要用virtual保存該頁的虛擬地址 |
_refcount | 引用計數,表示內核中引用該page的次數, 若是要操做該page, 引用計數會+1, 操做完成-1. 當該值爲0時, 表示沒有引用該page的位置,因此該page能夠被解除映射,這每每在內存回收時是有用的 |
_mapcount | 被頁表映射的次數,也就是說該page同時被多少個進程共享。初始值爲-1,若是隻被一個進程的頁表映射了,該值爲0. 若是該page處於夥伴系統中,該值爲PAGE_BUDDY_MAPCOUNT_VALUE(-128),內核經過判斷該值是否爲PAGE_BUDDY_MAPCOUNT_VALUE來肯定該page是否屬於夥伴系統 |
index | 在映射的虛擬空間(vma_area)內的偏移;一個文件可能只映射一部分,假設映射了1M的空間,index指的是在1M空間內的偏移,而不是在整個文件內的偏移 |
private | 私有數據指針,由應用場景肯定其具體的含義 |
lru | 鏈表頭,用於在各類鏈表上維護該頁, 以便於按頁將不一樣類別分組, 主要有3個用途: 夥伴算法, slab分配器, 被用戶態使用或被當作頁緩存使用 |
mapping | 指向與該頁相關的address_space對象 |
index | 頁幀在映射內部的偏移量 |
注意區分_count和_mapcount,_mapcount表示的是映射次數,而_count表示的是使用次數;被映射了不必定在使用,但要使用必須先映射。
mapping指定了頁幀所在的地址空間, index是頁幀在映射內部的偏移量. 地址空間是一個很是通常的概念. 例如, 能夠用在向內存讀取文件時. 地址空間用於將文件的內容與裝載數據的內存區關聯起來. mapping不只可以保存一個指針, 並且還能包含一些額外的信息, 用於判斷頁是否屬於未關聯到地址空間的某個匿名內存區.
經過mapping恢復anon_vma的方法:anon_vma = (struct anon_vma *)(mapping - PAGE_MAPPING_ANON)。
pgoff_t index是該頁描述結構在地址空間radix樹page_tree中的對象索引號即頁號, 表示該頁在vm_file中的偏移頁數, 其類型pgoff_t被定義爲unsigned long即一個機器字長.
/* * The type of an index into the pagecache. */ #define pgoff_t unsigned long
private私有數據指針, 由應用場景肯定其具體的含義:
最近、最久未使用struct slab結構指針變量
lru:鏈表頭,主要有3個用途:
頁的不一樣屬性經過一系列頁標誌描述, 存儲在struct page的flag成員中的各個比特位.
struct page { /* First double word block */ unsigned long flags; /* Atomic flags, some possibly updated asynchronously, 描述page的狀態和其餘信息 */
這些標識是獨立於體系結構的, 於是沒法經過特定於CPU或計算機的信息(該信息保存在頁表中)
在早期的linux-2.4.18的內核中, struct page存儲有一個指向對應管理區的指針page->zone, 可是該這hi真在吼吼被認爲是一種浪費, 由於若是有成千上萬的這樣的struct page存在, 那麼即便是很小的指針也會消耗大量的內存空間.
所以在後來linux-2.4.x的更新中, 刪除了這個字段, 取而代之的是page->flags的最高ZONE_SHIFT位和NODE_SHIFT位, 存儲了其所在zone和node在內存區域表zone_table的編號索引.
那麼內核在初始化內存管理區時, 首先創建管理區表zone_table. 參見mm/page_alloc.c?v=2.4.37, line 38
/* * * The zone_table array is used to look up the address of the * struct zone corresponding to a given zone number (ZONE_DMA, * ZONE_NORMAL, or ZONE_HIGHMEM). */ zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES]; EXPORT_SYMBOL(zone_table);
MAX_NR_ZONES是一個節點中所能包容納的管理區的最大數, 如3個, 定義在include/linux/mmzone.h?v=2.4.37, line 25, 與zone區域的類型(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM)定義在一塊兒. 固然這時候咱們這些標識都是經過宏的方式來實現的, 而不是現在的枚舉類型
MAX_NR_NODES是能夠存在的節點的最大數.
函數EXPORT_SYMBOL使得內核的變量或者函數能夠被載入的模塊(好比咱們的驅動模塊)所訪問.
該表處理起來就像一個多維數組, 在函數free_area_init_core中, 一個節點的全部頁面都會被初始化.
內核提供了page_zone經過頁面查找其對應的內存區域zone_t, 頁提供了set_page_zone接口, 而查找到了zone後, 能夠經過 其struct pglist_data *zone_pgdat
直接獲取其所在node信息
/* * The zone field is never updated after free_area_init_core() * sets it, so none of the operations on it need to be atomic. */ #define NODE_SHIFT 4 #define ZONE_SHIFT (BITS_PER_LONG - 8) struct zone_struct; extern struct zone_struct *zone_table[]; static inline zone_t *page_zone(struct page *page) { return zone_table[page->flags >> ZONE_SHIFT]; } static inline void set_page_zone(struct page *page, unsigned long zone_num) { page->flags &= ~(~0UL << ZONE_SHIFT); page->flags |= zone_num << ZONE_SHIFT; }
然後來的內核(至今linux-4.7)中, 這些必要的標識(ZONE_DMA等)都是經過枚舉類型實現的(ZONE_DMA等用enum zone_type定義), 而後zone_table也被移除, 參照[PATCH] zone table removal miss merge
所以內核提供了新的思路, 參見include/linux/mm.h?v4.7, line 907
static inline struct zone *page_zone(const struct page *page) { return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)]; } static inline void set_page_zone(struct page *page, enum zone_type zone) { page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT); page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT; } static inline void set_page_node(struct page *page, unsigned long node) { page->flags &= ~(NODES_MASK << NODES_PGSHIFT); page->flags |= (node & NODES_MASK) << NODES_PGSHIFT; }
其中NODE_DATA使用了全局的node表進行索引.
在UMA結構的機器中, 只有一個node結點即contig_page_data, 此時NODE_DATA直接指向了全局的contig_page_data, 而與node的編號nid無關, 參照include/linux/mmzone.h?v=4.7, line 858, 其中全局惟一的cnode結點ontig_page_data定義在mm/nobootmem.c?v=4.7, line 27
#ifndef CONFIG_NEED_MULTIPLE_NODES extern struct pglist_data contig_page_data; #define NODE_DATA(nid) (&contig_page_data) #define NODE_MEM_MAP(nid) mem_map else /* ...... */ #endif
而對於NUMA結構的系統中, 全部的node都存儲在node_data數組中, NODE_DATA直接經過node編號索引便可, 參見NODE_DATA的定義
extern struct pglist_data *node_data[]; #define NODE_DATA(nid) (node_data[(nid)])
那麼page的flags標識主要分爲4部分,其中標誌位flag向高位增加, 其他位字段向低位增加,中間存在空閒位
字段 | 描述 |
---|---|
section | 主要用於稀疏內存模型SPARSEMEM,可忽略 |
node | NUMA節點號, 標識該page屬於哪個節點 |
zone | 內存域標誌,標識該page屬於哪個zone |
flag | page的狀態標識 |
以下圖所示
其中最後一個flag用於標識page的狀態, 這些狀態由枚舉常量enum pageflags
定義, 定義在include/linux/page-flags.h?v=4.7, line 74. 經常使用的有以下狀態
enum pageflags { PG_locked, /* Page is locked. Don't touch. */ PG_error, PG_referenced, PG_uptodate, PG_dirty, PG_lru, PG_active, PG_slab, PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/ PG_arch_1, PG_reserved, PG_private, /* If pagecache, has fs-private data */ PG_private_2, /* If pagecache, has fs aux data */ PG_writeback, /* Page is under writeback */ PG_head, /* A head page */ PG_swapcache, /* Swap page: swp_entry_t in private */ PG_mappedtodisk, /* Has blocks allocated on-disk */ PG_reclaim, /* To be reclaimed asap */ PG_swapbacked, /* Page is backed by RAM/swap */ PG_unevictable, /* Page is "unevictable" */ #ifdef CONFIG_MMU PG_mlocked, /* Page is vma mlocked */ #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED PG_uncached, /* Page has been mapped as uncached */ #endif #ifdef CONFIG_MEMORY_FAILURE PG_hwpoison, /* hardware poisoned page. Don't touch */ #endif #if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT) PG_young, PG_idle, #endif __NR_PAGEFLAGS, /* Filesystems */ PG_checked = PG_owner_priv_1, /* Two page bits are conscripted by FS-Cache to maintain local caching * state. These bits are set on pages belonging to the netfs's inodes * when those inodes are being locally cached. */ PG_fscache = PG_private_2, /* page backed by cache */ /* XEN */ /* Pinned in Xen as a read-only pagetable page. */ PG_pinned = PG_owner_priv_1, /* Pinned as part of domain save (see xen_mm_pin_all()). */ PG_savepinned = PG_dirty, /* Has a grant mapping of another (foreign) domain's page. */ PG_foreign = PG_owner_priv_1, /* SLOB */ PG_slob_free = PG_private, /* Compound pages. Stored in first tail page's flags */ PG_double_map = PG_private_2, };
頁面狀態 | 描述 |
---|---|
PG_locked | 指定了頁是否被鎖定, 若是該比特未被置位, 說明有使用者正在操做該page, 則內核的其餘部分不容許訪問該頁, 這能夠防止內存管理出現競態條件 |
PG_error | 若是涉及該page的I/O操做發生了錯誤, 則該位被設置 |
PG_referenced | 表示page剛剛被訪問過 |
PG_uptodate | 表示page的數據已經與後備存儲器是同步的, 即頁的數據已經從塊設備讀取,且沒有出錯,數據是最新的 |
PG_dirty | 與後備存儲器中的數據相比,該page的內容已經被修改. 出於性能能的考慮,頁並不在每次改變後當即回寫, 所以內核須要使用該標識來代表頁面中的數據已經改變, 應該在稍後刷出 |
PG_lru | 表示該page處於LRU鏈表上, 這有助於實現頁面的回收和切換. 內核使用兩個最近最少使用(least recently used-LRU)鏈表來區別活動和不活動頁. 若是頁在其中一個鏈表中, 則該位被設置 |
PG_active | page處於inactive LRU鏈表, PG_active和PG_referenced一塊兒控制該page的活躍程度,這在內存回收時將會很是有用 當位於LRU active_list鏈表上的頁面該位被設置, 並在頁面移除時清除該位, 它標記了頁面是否處於活動狀態 |
PG_slab | 該page屬於slab分配器 |
PG_onwer_priv_1 | |
PG_arch_1 | 直接從代碼中引用, PG_arch_1是一個體繫結構相關的頁面狀態位, 通常的代碼保證了在第一次禁圖頁面高速緩存時, 該位被清除. 這使得體系結構能夠延遲到頁面被某個進程映射後, 才能夠D-Cache刷盤 |
PG_reserved | 設置該標誌,防止該page被交換到swap |
PG_private | 若是page中的private成員非空,則須要設置該標誌, 用於I/O的頁可以使用該字段將頁細分爲多核緩衝區 |
PG_private_2 | |
PG_writeback | page中的數據正在被回寫到後備存儲器 |
PG_head | |
PG_swapcache | 表示該page處於swap cache中 |
PG_mappedtodisk | 表示page中的數據在後備存儲器中有對應 |
PG_reclaim | 表示該page要被回收。當PFRA決定要回收某個page後,須要設置該標誌 |
PG_swapbacked | 該page的後備存儲器是swap |
PG_unevictable | 該page被鎖住,不能交換,並會出如今LRU_UNEVICTABLE鏈表中,它包括的幾種page:ramdisk或ramfs使用的頁, shm_locked、mlock鎖定的頁 |
PG_mlocked | 該page在vma中被鎖定,通常是經過系統調用mlock()鎖定了一段內存 |
PG_uncached | |
PG_hwpoison | |
PG_young | |
PG_idle |
內核中提供了一些標準宏,用來檢查、操做某些特定的比特位,這些宏定義在include/linux/page-flags.h?v=4.7, line 183
#define TESTPAGEFLAG(uname, lname, policy) #define SETPAGEFLAG(uname, lname, policy) #define CLEARPAGEFLAG(uname, lname, policy)
關於page flags的早期實現
例如咱們的page->flags用enum pageflags來定義, 內存管理區類型經過zone_type來定義, 可是這些內容在早期的內核中都是經過宏定義來實現的.
形式以下
PageXXX(page):檢查page是否設置了PG_XXX位 SetPageXXX(page):設置page的PG_XXX位 ClearPageXXX(page):清除page的PG_XXX位 TestSetPageXXX(page):設置page的PG_XXX位,並返回原值 TestClearPageXXX(page):清除page的PG_XXX位,並返回原值
不少狀況下, 須要等待頁的狀態改變, 而後才能恢復工做. 所以內核提供了兩個輔助函數
http://lxr.free-electrons.com/source/include/linux/pagemap.h?v=4.7#L495 /* * Wait for a page to be unlocked. * * This must be called with the caller "holding" the page, * ie with increased "page->count" so that the page won't * go away during the wait.. */ static inline void wait_on_page_locked(struct page *page) // http://lxr.free-electrons.com/source/include/linux/pagemap.h?v=4.7#L504 /* * Wait for a page to complete writeback */ static inline void wait_on_page_writeback(struct page *page)
假定內核的一部分在等待一個被鎖定的頁面, 直至頁面被解鎖. wait_on_page_locked提供了該功能. 在頁面被鎖定的狀況下, 調用該函數, 內核將進入睡眠. 而在頁面解鎖後, 睡眠進程會被自動喚醒並繼續工做
wait_on_page_writeback的工做方式相似, 該函數會等待與頁面相關的全部待決回寫操做結束, 將頁面包含的數據同步到塊設備爲止.
mem_map是一個struct page的數組,管理着系統中全部的物理內存頁面。在系統啓動的過程當中,建立和分配mem_map的內存區域, mem_map定義在mm/page_alloc.c?v=4.7, line 6691
#ifndef CONFIG_NEED_MULTIPLE_NODES /* use the per-pgdat data instead for discontigmem - mbligh */ unsigned long max_mapnr; struct page *mem_map; EXPORT_SYMBOL(max_mapnr); EXPORT_SYMBOL(mem_map); #endif
UMA體系結構中, free_area_init函數在系統惟一的struct node對象contig_page_data中node_mem_map成員賦值給全局的mem_map變量