Linux內存管理 (11)page引用計數


專題:Linux內存管理專題

關鍵詞:struct page、_count、_mapcount、PG_locked/PG_referenced/PG_active/PG_dirty等。

 

Linux的內存管理是以頁展開的,struct page非常重要,同時其維護成本也非常高。

這里主要介紹struct page中_count/_mapcount和flags參數。

 flags是頁面標志位集合,是內存管理非常重要的部分。

_count表示內核中引用該頁面的次數;_mapcount表示頁面被進程映射的個數,對反向映射非常重要。

1. struct page數據結構

struct page大量使用聯合體union來優化其結構大小,因為每個物理頁面都需要一個struct page數據結構,因此管理成本很高。

 

/*
 * 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.--------------------------------------------------------------我們無法知道那個進程在使用一個頁面,但是可以通過RMAP相關結構體知道誰映射到了此頁面。
 *
 * 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 */
    union {
        struct address_space *mapping;    /* If low bit clear, points to----------表示頁面所指向的地址空間,低兩位用於判斷是匿名映射還是KSM頁面。位1表示匿名頁面,位2表示KSM頁面。
                         * 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 */---------------------------用於slab分配器,slab中第一個對象的開始地址,和mapping共同占用一個字的存儲空間。
    };

    /* Second double word */
    struct {
        union {
            pgoff_t index;        /* Our offset within mapping. */
            void *freelist;        /* sl[aou]b first free object */
            bool pfmemalloc;    /* If set by the page allocator,
                         * ALLOC_NO_WATERMARKS was set
                         * and the low watermark was not
                         * met implying that the system
                         * is under some pressure. The
                         * caller should try ensure
                         * this page is only used to
                         * free other pages.
                         */
        };

        union {
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
    defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
            /* Used for cmpxchg_double in slub */
            unsigned long counters;
#else
            /*
             * Keep _count separate from slub cmpxchg_double data.
             * As the rest of the double word is protected by
             * slab_lock but _count is not.
             */
            unsigned counters;
#endif

            struct {

                union {
                    /*
                     * Count of ptes mapped in
                     * mms, to show when page is
                     * mapped & limit reverse map
                     * searches.
                     *
                     * Used also for tail pages
                     * refcounting instead of
                     * _count. Tail pages cannot
                     * be mapped and keeping the
                     * tail page _count zero at
                     * all times guarantees
                     * get_page_unless_zero() will
                     * never succeed on tail
                     * pages.
                     */
                    atomic_t _mapcount;

                    struct { /* SLUB */
                        unsigned inuse:16;
                        unsigned objects:15;
                        unsigned frozen:1;
                    };
                    int units;    /* SLOB */
                };
                atomic_t _count; /* Usage count, see below. */
            };
            unsigned int active;    /* SLAB */
        };
    };
...
}

 

 flags是頁面的重要標志位,下面是詳細解釋:

enum pageflags {
    PG_locked,        /* Page is locked. Don't touch. */---表示頁面已經上鎖了。如果該比特位置位,說明頁面已經被鎖定;內存管理其他模塊不能訪問這個頁面,以防發生競爭。
    PG_error,----------------------------------------------頁面操作過程中發生錯誤會設置該位。
    PG_referenced,-----------------------------------------控制頁面活躍程度,在kswapd頁面回收中使用。
    PG_uptodate,-------------------------------------------表示頁面的數據已經從塊設備成功讀取。
    PG_dirty,----------------------------------------------表示頁面內容發生改變,頁面為臟,頁面內容被改寫后還沒有和外部存儲器進行同步操作。
    PG_lru,------------------------------------------------表示頁面加入了LRU鏈表,內核使用LRU鏈表管理活躍和不活躍頁面。
    PG_active,---------------------------------------------控制頁面活躍成都,在kswapd頁面回收中使用。
    PG_slab,-----------------------------------------------用於slab分配器
    PG_owner_priv_1,    /* Owner use. If pagecache, fs may use*/--頁面的所有者使用,如果是page cache頁面,文件系統可能使用。
    PG_arch_1,---------------------------------------------與體系結構相關的頁面狀態位。
    PG_reserved,-------------------------------------------表示該頁不可被換出。
    PG_private,        /* If pagecache, has fs-private data */--表示該頁是有效的,。如果頁面是page cache,那么包含一些文件系統相關的數據信息。
    PG_private_2,        /* If pagecache, has fs aux data */----如果是page cache,可能包含fs aux data。
    PG_writeback,        /* Page is under writeback */----表示頁面的內容正在向塊設備進行回寫。
#ifdef CONFIG_PAGEFLAGS_EXTENDED
    PG_head,        /* A head page */
    PG_tail,        /* A tail page */
#else
    PG_compound,        /* A compound page */-------------一個混合頁面
#endif
    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 */---------頁面具有swap緩存功能,通常匿名頁面才可以寫回swap分區。
    PG_unevictable,        /* Page is "unevictable"  */----表示頁面不可回收。
#ifdef CONFIG_MMU
    PG_mlocked,        /* Page is vma mlocked */-----------表示頁面對應的VMA處於locked狀態。
#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
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
    PG_compound_lock,
#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,
}

 

內核定義了一些宏,用於檢查頁面是否設置了某個特定標志位,或者設置、清空某個標志位。

這些宏的定義在page-flags.h中:

#define PAGEFLAG(uname, lname) TESTPAGEFLAG(uname, lname)        \
    SETPAGEFLAG(uname, lname) CLEARPAGEFLAG(uname, lname)


#define TESTPAGEFLAG(uname, lname)                    \
static inline int Page##uname(const struct page *page)            \
            { return test_bit(PG_##lname, &page->flags); }

#define SETPAGEFLAG(uname, lname)                    \
static inline void SetPage##uname(struct page *page)            \
            { set_bit(PG_##lname, &page->flags); }

#define CLEARPAGEFLAG(uname, lname)                    \
static inline void ClearPage##uname(struct page *page)            \
            { clear_bit(PG_##lname, &page->flags); }

 

以PG_lru為例:

PageLRU:檢查頁面是否設置了PG_lru表志位。

SetPageLRU:設置頁中的PG_lru標志位。

ClearPageLRU:清除液中的PG_lry標志位。

 

flags處理存放上述標志位之外,還存放了page對應的zone信息。通過set_page_zone講zone信息設置到page->flags中。

 

2. _count和_mapcount的區別

2.1 _count解釋

 _count表示內核中引用該頁面的次數。

_count == 0:表示該頁面位空閑或即將要被釋放。

_count > 0:表示該頁面已經被分配切內核正在使用,暫不會被釋放。

內核中操作_count的引用技術API有get_page()和put_page()。

static inline void get_page(struct page *page)
{
    if (unlikely(PageTail(page)))
        if (likely(__get_page_tail(page)))
            return;
    /*
     * Getting a normal page or the head of a compound page
     * requires to already have an elevated page->_count.
     */
    VM_BUG_ON_PAGE(atomic_read(&page->_count) <= 0, page);-------判斷頁面_count值不能小於等於0,因為伙伴系統分配好的頁面初始值位1。
    atomic_inc(&page->_count);-----------------------------------原子增加引用計數。
}

  static inline int put_page_testzero(struct page *page)
  {
      VM_BUG_ON_PAGE(atomic_read(&page->_count) == 0, page);-----_count不能為0,如果為0,說明這頁面已經被釋放了。
      return atomic_dec_and_test(&page->_count);
  }

void put_page(struct page *page)
{
    if (unlikely(PageCompound(page)))
        put_compound_page(page);
    else if (put_page_testzero(page))------------------------如果減1之后等於0,就會釋放頁面。
        __put_single_page(page);-----------------------------釋放頁面
}

 內核還有一對常用的變種宏:

#define page_cache_get(page)        get_page(page)
#define page_cache_release(page)    put_page(page)

 _count常用於內核中跟蹤page頁面的使用情況,常見的用法有:

(1)分配頁面時_count引用計數會變成1。

分配頁面函數alloc_pages()在成功分配頁面后,_count引用計數應該為0,由set_page_refcounter()設置。

/*
 * Turn a non-refcounted page (->_count == 0) into refcounted with
 * a count of one.
 */
static inline void set_page_refcounted(struct page *page)
{
    VM_BUG_ON_PAGE(PageTail(page), page);
    VM_BUG_ON_PAGE(atomic_read(&page->_count), page);
    set_page_count(page, 1);
}

 

(2)加入LRU鏈表時,page會被kswapd內核線程使用,因此_count引用計數會加1。

以malloc()為用戶程序分配內存為例,發生缺頁中斷后do_anonymous_page()函數成功分配出來一個頁面,在設置硬件PTE之前,調用lru_cache_add()函數把這個匿名頁面添加到LRU鏈表中,在這個過程中,使用page_cache_get()宏來增加_count引用計數。

static void __lru_cache_add(struct page *page)
{
    struct pagevec *pvec = &get_cpu_var(lru_add_pvec);

    page_cache_get(page);---------------------增加計數 if (!pagevec_space(pvec))
        __pagevec_lru_add(pvec);
    pagevec_add(pvec, page);
    put_cpu_var(lru_add_pvec);
}

 

(3)被映射到其他用戶進程pte時,_count引用計數會加1。

子進程在被創建時共享父進程地址空間,設置父進程的pte頁表項內容到子進程中並增加該頁面的_count計數。

(4)頁面的private中私有數據。

對於PG_swapable頁面,__add_to_swap_cache函數會增加_count引用計數。

對於PG_private頁面,主要在block模塊的buffer_head中引用。

(5)內核對頁面進行操作等關鍵路徑上也會使_count引用計數加1。

 

 2.2 _mapcount解釋

 _mapcount引用計數表示這個頁面被進程映射的個數,即已經映射了多少個用戶pte也表。

每個用戶進程地址空間都有一份獨立的頁表,有可能出現多個用戶進程地址空間同時映射到一個物理頁面的情況,RMAP反向映射系統就是利用這個特性來實現的。

_mapcount引用計數主要用於RMAP反響映射系統中。

_mapcount == -1:表示沒有pte映射到頁面中。

_mapcount == 0:表示只有父進程映射了頁面。

匿名頁面剛分配時,_mapcount引用計數初始化為0.

void page_add_new_anon_rmap(struct page *page,
    struct vm_area_struct *vma, unsigned long address)
{
    VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
    SetPageSwapBacked(page);
    atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) */---------------------設為0
    if (PageTransHuge(page))
        __inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
    __mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
            hpage_nr_pages(page));
    __page_set_anon_rmap(page, vma, address, 1);
}

 

_mapcount > 0:表示除了父進程外還有其他進程映射了這個頁面。

 設置父進程pte頁表項內容到子進程中並增加該頁面的_mapcount計數。

static inline unsigned long
copy_one_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
        pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *vma,
        unsigned long addr, int *rss)
{
...
    page = vm_normal_page(vma, addr, pte);
    if (page) {
        get_page(page);--------------------------增加_count計數
        page_dup_rmap(page);---------------------增加_mapcount計數 if (PageAnon(page))
            rss[MM_ANONPAGES]++;
        else
            rss[MM_FILEPAGES]++;
    }
...
}

 

 

3. 頁面所PG_locked

PG_locked用於設置頁面鎖,有兩個函數用於申請頁面鎖:lock_page()和trylock_page()。

lock_page()用於申請頁面鎖,如果頁面鎖被其他進程占用,那么睡眠等待。

trylock_page()也同樣檢查PG_locked位,但是不等待。如果頁面的PG_locked置位,則返回false,表明有其他進程已經鎖住了頁面;返回true表示獲取鎖成功。

int __sched
__wait_on_bit_lock(wait_queue_head_t *wq, struct wait_bit_queue *q,wait_on_bit_lock()------使用原子位操作,試着去置位,若已經置位,則任務被掛起,直到調用wake_up_bit()喚醒,等待的線程。可以被wake_up_bit喚醒。
            wait_bit_action_f *action, unsigned mode)
{
    do {
        int ret;

        prepare_to_wait_exclusive(wq, &q->wait, mode);
        if (!test_bit(q->key.bit_nr, q->key.flags))
            continue;
        ret = action(&q->key);
        if (!ret)
            continue;
        abort_exclusive_wait(wq, &q->wait, mode, &q->key);
        return ret;
    } while (test_and_set_bit(q->key.bit_nr, q->key.flags));
    finish_wait(wq, &q->wait);
    return 0;
}


void __lock_page(struct page *page)
{
    DEFINE_WAIT_BIT(wait, &page->flags, PG_locked);-----------------------------定義在哪位上等待。

    __wait_on_bit_lock(page_waitqueue(page), &wait, bit_wait_io,
                            TASK_UNINTERRUPTIBLE);
}

/*
 * lock_page may only be called if we have the page's inode pinned.
 */
static inline void lock_page(struct page *page)
{
    might_sleep();
    if (!trylock_page(page))---------------------------------------------------如果原page->flags已經被置PG_locked,則調用__lock_page進行等待使用者釋放。
        __lock_page(page);
}


#define test_and_set_bit_lock(nr, addr)    test_and_set_bit(nr, addr)

static inline int trylock_page(struct page *page)
{
    return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));-----------嘗試為page->flags設置PG_locked標志位,並且返回原來標志位的值。所以並不會等待。
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM