服務器體系與共享存儲器架構
日期 | 內核版本 | 架構 | 作者 | GitHub | CSDN |
---|---|---|---|---|---|
2016-06-14 | Linux-4.7 | X86 & arm | gatieme | LinuxDeviceDrivers | Linux內存管理 |
#1 前景回顧
#1.1 UMA和NUMA兩種模型
共享存儲型多處理機有兩種模型
-
均勻存儲器存取(Uniform-Memory-Access,簡稱UMA)模型
-
非均勻存儲器存取(Nonuniform-Memory-Access,簡稱NUMA)模型
#1.2 (N)UMA模型中linux內存的機構
非一致存儲器訪問(NUMA)模式下
-
處理器被划分成多個"節點"(node), 每個節點被分配有的本地存儲器空間. 所有節點中的處理器都可以訪問全部的系統物理存儲器,但是訪問本節點內的存儲器所需要的時間,比訪問某些遠程節點內的存儲器所花的時間要少得多
-
內存被分割成多個區域(BANK,也叫"簇"),依據簇與處理器的"距離"不同, 訪問不同簇的代碼也會不同.
##1.3 Linux如何描述物理內存
Linux把物理內存划分為三個層次來管理
層次 | 描述 |
---|---|
存儲節點(Node) | CPU被划分為多個節點(node), 內存則被分簇, 每個CPU對應一個本地物理內存, 即一個CPU-node對應一個內存簇bank,即每個內存簇被認為是一個節點 |
管理區(Zone) | 每個物理內存節點node被划分為多個內存管理區域, 用於表示不同范圍的內存, 內核可以使用不同的映射方式映射物理內存 |
頁面(Page) | 內存被細分為多個頁面幀, 頁面是最基本的頁面分配的單位 | |
-
首先內存被划分為結點. 內存中的每個節點都是由pg_data_t描述,而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, 被稱為高端內存. 是系統中預留的可用內存空間, 不能被內核直接映射.
-
最后頁幀(page frame)代表了系統內存的最小單位, 堆內存中的每個頁都會創建一個struct page的一個實例. 傳統上,把內存視為連續的字節,即內存為字節數組,內存單元的編號(地址)可作為字節數組的索引. 分頁管理時,將若干字節視為一頁,比如4K byte. 此時,內存變成了連續的頁,即內存為頁數組,每一頁物理內存叫頁幀,以頁為單位對內存進行編號,該編號可作為頁數組的索引,又稱為頁幀號.
##1.4 今日內容(頁幀struct page)
分頁單元可以實現把線性地址轉換為物理地址, 為了效率起見, 線性地址被分為固定長度為單位的組, 稱為"頁", 頁內部的線性地址被映射到連續的物理地址. 這樣內核可以指定一個頁的物理地址和其存儲權限, 而不用指定頁所包含的全部線性地址的存儲權限. 分頁單元把所有RAM分為固定長度的頁幀(也叫頁框, 物理頁, 英文page frame). 每一個頁幀包含一個頁(page). 也就是說一個頁幀的長度與一個頁的長度一致. 頁框是主存的一部分, 因此也是一個存儲區域. 簡單來說, 頁是一個數據塊, 可以存放在任何頁框(內存中)或者磁盤(被交換至交換分區)中
我們今天就來詳細講解一下linux下物理頁幀的描述
#2 頁幀
內核把物理頁作為內存管理的基本單位. 盡管處理器的最小可尋址單位通常是字, 但是, 內存管理單元MMU通常以頁為單位進行處理. 因此,從虛擬內存的上來看,頁就是最小單位.
頁幀代表了系統內存的最小單位, 對內存中的每個頁都會創建struct page的一個實例. 內核必須要保證page結構體足夠的小,否則僅struct page就要占用大量的內存.
因為即使在中等程序的內存配置下, 系統的內存同樣會分解為大量的頁. 例如, IA-32系統中標准頁長度為4KB, 在內存大小為384MB時, 大約有100000頁. 就當今的標准而言, 這個容量算不上很大, 但頁的數目已經非常可觀了
因而出於節省內存的考慮,內核要盡力保持struct page盡可能的小. 在典型的系統中, 由於頁的數目巨大, 因此對page結構的小改動, 也可能導致保存所有page實例所需的物理內存暴漲.
頁的廣泛使用, 增加了保持結構長度的難度 : 內存管理的許多部分都使用頁, 用於各種不同的用途. 內核的一部分可能完全依賴於struct page提供的特定信息, 而這部分信息堆內核的其他部分頁可能是完全無用的. 等等.
##2.1 struct page結構
內核用struct page(include/linux/mm_types.h?v=4.7, line 45)結構表示系統中的每個物理頁.
出於節省內存的考慮,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表示的是使用次數;被映射了不一定在使用,但要使用必須先映射。
##2.2 mapping & index
mapping指定了頁幀所在的地址空間, index是頁幀在映射內部的偏移量. 地址空間是一個非常一般的概念. 例如, 可以用在向內存讀取文件時. 地址空間用於將文件的內容與裝載數據的內存區關聯起來. mapping不僅能夠保存一個指針, 而且還能包含一些額外的信息, 用於判斷頁是否屬於未關聯到地址空間的某個匿名內存區.
-
如果mapping = 0,說明該page屬於交換高速緩存頁(swap cache);當需要使用地址空間時會指定交換分區的地址空間swapper_space。
-
如果mapping != 0,第0位bit[0] = 0,說明該page屬於頁緩存或文件映射,mapping指向文件的地址空間address_space。
-
如果mapping != 0,第0位bit[0] != 0,說明該page為匿名映射,mapping指向struct anon_vma對象。
通過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
##2.3 private私有數據指針
private私有數據指針, 由應用場景確定其具體的含義:
-
如果設置了PG_private標志,則private字段指向struct buffer_head
-
如果設置了PG_compound,則指向struct page
-
如果設置了PG_swapcache標志,private存儲了該page在交換分區中對應的位置信息swp_entry_t。
-
如果_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE,說明該page位於伙伴系統,private存儲該伙伴的階
##2.4 lru鏈表頭
最近、最久未使用struct slab結構指針變量
lru:鏈表頭,主要有3個用途:
-
則page處於伙伴系統中時,用於鏈接相同階的伙伴(只使用伙伴中的第一個page的lru即可達到目的)。
-
設置PG_slab, 則page屬於slab,page->lru.next指向page駐留的的緩存的管理結構,page->lru.prec指向保存該page的slab的管理結構。
-
page被用戶態使用或被當做頁緩存使用時,用於將該page連入zone中相應的lru鏈表,供內存回收時使用。
#3 體系結構無關的頁面的狀態flags
頁的不同屬性通過一系列頁標志描述, 存儲在struct page的flag成員中的各個比特位.
struct page { /* First double word block */ unsigned long flags; /* Atomic flags, some possibly updated asynchronously, 描述page的狀態和其他信息 */
這些標識是獨立於體系結構的, 因而無法通過特定於CPU或計算機的信息(該信息保存在頁表中)
##3.1 頁面到管理區和節點的映射
在早期的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的狀態標識 |
如下圖所示
##3.2 內存頁標識pageflags
其中最后一個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的早期實現
- linux-2.6以后的內核中, 很少出現直接用宏定義的標識, 這些標識大多通過enum枚舉常量來定義, 然后__NR_XXXX的形式結束, 正好可以標記出宏參數的個數, 但是在早期的實現中, 這些變量都通過宏來標識
例如我們的page->flags用enum pageflags來定義, 內存管理區類型通過zone_type來定義, 但是這些內容在早期的內核中都是通過宏定義來實現的.
- 其次標識的函數接口也變了, 早期的內核中, 針對每個宏標識都設置了一組test/set/clear, 參見/include/linux/mm.h?v=2.4.37, line 324
形式如下
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的工作方式類似, 該函數會等待與頁面相關的所有待決回寫操作結束, 將頁面包含的數據同步到塊設備為止.
#4 全局頁面數組mem_map
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變量