1. 程式人生 > >Linux記憶體描述之記憶體頁面page--Linux記憶體管理(四)

Linux記憶體描述之記憶體頁面page--Linux記憶體管理(四)

1 Linux如何描述實體記憶體

Linux把實體記憶體劃分為三個層次來管理

層次 描述
儲存節點(Node) CPU被劃分為多個節點(node), 記憶體則被分簇, 每個CPU對應一個本地實體記憶體, 即一個CPU-node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點
管理區(Zone) 每個實體記憶體節點node被劃分為多個記憶體管理區域, 用於表示不同範圍的記憶體, 核心可以使用不同的對映方式對映實體記憶體
頁面(Page) 記憶體被細分為多個頁面幀, 頁面是最基本的頁面分配的單位 
  • 首先記憶體被劃分為結點. 記憶體中的每個節點都是由pg_data_t描述,而pg_data_tstruct 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不僅能夠儲存一個指標, 而且還能包含一些額外的資訊, 用於判斷頁是否屬於未關聯到地址空間的某個匿名記憶體區.

  1. 如果mapping = 0,說明該page屬於交換快取記憶體頁(swap cache);當需要使用地址空間時會指定交換分割槽的地址空間swapper_space。
  2. 如果mapping != 0,第0位bit[0] = 0,說明該page屬於頁快取或檔案對映,mapping指向檔案的地址空間address_space。
  3. 如果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私有資料指標, 由應用場景確定其具體的含義:

  1. 如果設定了PG_private標誌,則private欄位指向struct buffer_head
  2. 如果設定了PG_compound,則指向struct page
  3. 如果設定了PG_swapcache標誌,private儲存了該page在交換分割槽中對應的位置資訊swp_entry_t。
  4. 如果_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE,說明該page位於夥伴系統,private儲存該夥伴的階

2.4 lru連結串列頭

最近、最久未使用struct slab結構指標變數

lru:連結串列頭,主要有3個用途:

  1. 則page處於夥伴系統中時,用於連結相同階的夥伴(只使用夥伴中的第一個page的lru即可達到目的)。
  2. 設定PG_slab, 則page屬於slab,page->lru.next指向page駐留的的快取的管理結構,page->lru.prec指向儲存該page的slab的管理結構。
  3. 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來定義, 但是這些內容在早期的核心中都是通過巨集定義來實現的.

形式如下

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變數