zoukankan      html  css  js  c++  java
  • 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标志位,并且返回原来标志位的值。所以并不会等待。
    }
  • 相关阅读:
    省常中模拟 Test4
    省常中模拟 Test3 Day1
    省常中模拟 Test3 Day2
    省常中模拟 Test1 Day1
    树型动态规划练习总结
    noip2010提高组题解
    noip2003提高组题解
    noip2009提高组题解
    noip2004提高组题解
    noip2002提高组题解
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/8335481.html
Copyright © 2011-2022 走看看