1 Linux如何描述物理記憶體 Linux把物理記憶體劃分為三個層次來管理 層次| 描述 | 存儲節點(Node) | CPU被劃分為多個節點(node), 記憶體則被分簇, 每個CPU對應一個本地物理記憶體, 即一個CPU node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點 管理區(Zone) ...
1 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. 此時,記憶體變成了連續的頁,即記憶體為頁數組,每一頁物理記憶體叫頁幀,以頁為單位對記憶體進行編號,該編號可作為頁數組的索引,又稱為頁幀號.
2 頁幀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變數