struct page正是内核用于描述和管理每个物理页框的核心数据结构。由于系统中每个物理页都需要一个对应的struct page实例(例如,在4KB页大小的系统中,16GB内存就需要400万个struct page),其内存占用总量非常可观,维护成本也非常高。因此内核通过大量联合体(Union)来优化其结构大小,同时保证其功能的完整性。这里主要介绍struct page中count/mapcount和flags参数。
flags是页面标志位集合,是内存管理非常重要的部分。
count表示内核中引用该页面的次数;mapcount表示页面被进程映射的个数,对反向映射非常重要。
struct page的设计精髓在于联合体(Union)的大量使用——通过让不同场景下的字段共享内存空间,极大地压缩了单个struct page的大小。这是因为每个物理页都必须对应一个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;#endifstruct {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是一个unsigned long类型的原子变量,每一位代表页面的一个状态标志,是内存管理各模块之间协作的核心,下面是详细解释:
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_EXTENDEDPG_head, /* A head page */PG_tail, /* A tail page */#elsePG_compound, /* A compound page */-------------一个混合页面#endifPG_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_MMUPG_mlocked, /* Page is vma mlocked */-----------表示页面对应的VMA处于locked状态。#endif#ifdef CONFIG_ARCH_USES_PG_UNCACHEDPG_uncached, /* Page has been mapped as uncached */#endif#ifdef CONFIG_MEMORY_FAILUREPG_hwpoison, /* hardware poisoned page. Don't touch */#endif#ifdef CONFIG_TRANSPARENT_HUGEPAGEPG_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中。
_count表示内核中引用该页面的次数。
_count == 0:表示该页面位空闲或即将要被释放。
_count > 0:表示该页面已经被分配切内核正在使用,暂不会被释放。
内核中操作_count的引用技术API有get_page()和put_page()。
```staticinlinevoidget_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);-----------------------------------原子增加引用计数。}```staticinlineintput_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);}
内核还有一对常用的变种宏:
#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。
_mapcount引用计数表示这个页面被进程映射的个数,即已经映射了多少个用户pte也表。
每个用户进程地址空间都有一份独立的页表,有可能出现多个用户进程地址空间同时映射到一个物理页面的情况,RMAP反向映射系统就是利用这个特性来实现的。
_mapcount引用计数主要用于RMAP反响映射系统中。
_mapcount == -1:表示没有pte映射到页面中。
_mapcount == 0:表示只有父进程映射了页面。
匿名页面刚分配时,_mapcount引用计数初始化为0.
voidpage_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) */---------------------设为0if (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计数。
staticinlineunsignedlongcopy_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]++;elserss[MM_FILEPAGES]++;}...}
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.*/staticinlinevoidlock_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)staticinlineinttrylock_page(struct page *page){return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));-----------尝试为page->flags设置PG_locked标志位,并且返回原来标志位的值。所以并不会等待。}
Linux内存管理系列文章:
原作者:ArnoldLu
原文地址:
https://www.cnblogs.com/arnoldlu/p/8335481.html