请求调页机制,只要用户态进程继续执行,他们就能获得页框,然而,请求调页没有办法强制进程释放不再使用的页框。因此,迟早所有空闲内存将被分配给进程和高速缓存,Linux内核的页面回收算法(PFRA)采取从用户进程和内核高速缓存“窃取”页框的办法不从伙伴系统的空闲块列表。
选择目标页
页类型 |
说明 |
回收操作 |
不可回收页 |
空闲页(包含子伙伴系统列表中) |
不允许也无需回收 |
可回收页 |
用户太地址空间的匿名页 |
将页的内容保存在交换区 |
可同步页 |
用户态地址空间的映射页 |
必要时,与磁盘镜像同步这些页 |
可丢弃页 |
内存高速缓存中的未使用页(如slab分配器高速缓存) |
无需操作 |
进行页面回收的时机
字段含义
名称 |
字段描述 |
pages_min |
区域的预留页面数目,如果空闲物理页面的数目低于 pages_min,那么系统的压力会比较大,此时,内存区域中急需空闲的物理页面,页面回收的需求非常紧迫。 |
pages_low |
控制进行页面回收的最小阈值,如果空闲物理页面的数目低于 pages_low,那么操作系统内核会开始进行页面回收。 |
pages_high |
控制进行页面回收的最大阈值,如果空闲物理页面的数目多于 pages_high,则内存区域的状态是理想的。 |
PFRA设计
设计总则
- 首先释放“无害”页,即必须线回收没有被任何进程使用的磁盘与内存高速缓存中的页;
- 将用户态进程和所有页定为可回首页,FPRA必须能够窃得人任何用户态进程页,包括匿名页。这样,睡眠较长时间的进程将逐渐失去所有页;
- 同时取消引用一个共享页的所有页表项的映射,就可以回收该共享页;
- 只回收“未用”页,使用LRU算法。Linux使用每个页表项中的访问标志位,在页被访问时,该标志位由银奖自动置位;而且,页年龄由页描述符在链表(两个不同的链表之一)中的位置来表示。
- 谨慎选择检查高速缓存的顺序;
- 基于页年龄的变化排序;
- 区别对待不同状态的页;
反向映射
基于对象的反向映射的实现
数据结构
struct page { atomic_t _mapcount;">union { …… struct { …… struct address_space *mapping; };">
/*检查页是否为匿名页,低位为1时为匿名页*/static inline int PageAnon(struct page *page){ return ((unsigned long)page->mapping &">0;">
struct anon_vma { spinlock_t lock;">struct list_head head; };">
匿名页的面向对象反向映射如下图:
?
struct address_space { …… struct prio_tree_root i_mmap; …… }
struct vm_area_struct { struct mm_struct * vm_mm;…… union { struct { struct list_head list; void *parent;">struct vm_area_struct *head; } vm_set;">struct raw_prio_tree_node prio_tree_node; } shared;">struct list_head anon_vma_node; struct anon_vma *anon_vma; };">
vm_set.list 和 vm_set.head
?
反向映射实现
实现函数 try_to_unmap() 的关键代码流程图
?
static int try_to_unmap_anon(struct page *page, enum ttu_flags flags){ struct anon_vma *anon_vma; struct vm_area_struct *vma; unsigned int mlocked = 0;">int ret = SWAP_AGAIN;">int unlock = TTU_ACTION(flags) == TTU_MUNLOCK;">if (MLOCK_PAGES &&">/* default for try_to_munlock() */ /*如果该页面为匿名映射,返回该页面对应的匿名结构*/ anon_vma = page_lock_anon_vma(page);">if (!anon_vma) return ret;">/*这里可以看出,vma的anon_vma_node字段链接到 anon_vma的head字段*/ /*扫描线性区描述符的anon_vma链表*/ list_for_each_entry(vma, &anon_vma->head, anon_vma_node) { if (MLOCK_PAGES &&">if (!((vma->vm_flags &">continue;">/* must visit all unlocked vmas */ ret = SWAP_MLOCK;">/* saw at least one mlocked vma */ } else { /*对anon_vma链表中的每一个vma线性区描述符 调用该函数*/ ret = try_to_unmap_one(page, vma, flags);">if (ret == SWAP_FAIL || !page_mapped(page)) break;">if (ret == SWAP_MLOCK) { mlocked = try_to_mlock_page(page, vma);">if (mlocked) break;">/* stop if actually mlocked page */ } } page_unlock_anon_vma(anon_vma);">if (mlocked) ret = SWAP_MLOCK;">/* actually mlocked the page */ else if (ret == SWAP_MLOCK) ret = SWAP_AGAIN;">/* saw VM_LOCKED vma */ return ret;">/* * Subfunctions of try_to_unmap: try_to_unmap_one called * repeatedly from either try_to_unmap_anon or try_to_unmap_file. */ /** *page是一个指向目标页描述符的指针;">static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma, enum ttu_flags flags){ struct mm_struct *mm = vma->vm_mm; unsigned long address;">pte_t *pte;">pte_t pteval;">spinlock_t *ptl;">int ret = SWAP_AGAIN;">/*计算出待回收页的线性地址*/ address = vma_address(page, vma);">if (address == -EFAULT) goto out;">/*获取线性地址对应的页表项地址*/ pte = page_check_address(page, mm, address, &ptl, 0);">if (!pte) goto out;">/* * If the page is mlock()d, we cannot swap it out. * If it's recently referenced (perhaps page_referenced * skipped over this mm) then we should reactivate it. */ /*下面为判断是否可以被回收*/ if (!(flags &">if (vma->vm_flags &">goto out_unmap;">if (!(flags &">if (ptep_clear_flush_young_notify(vma, address, pte)) { ret = SWAP_FAIL;">goto out_unmap;">/* Nuke the page table entry. */ flush_cache_page(vma, address, page_to_pfn(page));">/*更新页表项并冲刷相应的TLB*/ pteval = ptep_clear_flush_notify(vma, address, pte);">/* Move the dirty bit to the physical page now the pte is gone. */ if (pte_dirty(pteval))/*如果是脏页面,置位PG_dirty*/ set_page_dirty(page);">/* Update high watermark before we lower rss */ /*更新mm的hiwater_rss*/ update_hiwater_rss(mm);">if (PageHWPoison(page) &&">if (PageAnon(page)) dec_mm_counter(mm, anon_rss);">else dec_mm_counter(mm, file_rss);">else if (PageAnon(page)) {/*如果是匿名页*/ swp_entry_t entry = { .val = page_private(page) };">if (PageSwapCache(page)) { /* * Store the swap location in the pte. * See handle_pte_fault() ... */ /*保存换出位置*/ swap_duplicate(entry);">if (list_empty(&mm->mmlist)) { spin_lock(&mmlist_lock);">if (list_empty(&mm->mmlist)) /*添加到init_mm的相应链表,从这里可以 看出mm->mmlist为交换用的链表*/ list_add(&mm->mmlist, &init_mm.mmlist);">else if (PAGE_MIGRATION) { /* * Store the pfn of the page in a special migration * pte. do_swap_page() will wait until the migration * pte is removed and then restart fault handling. */ BUG_ON(TTU_ACTION(flags) != TTU_MIGRATION);">else if (PAGE_MIGRATION &&">/* Establish migration entry for a file page */ swp_entry_t entry;">else dec_mm_counter(mm, file_rss);">/*断开页表项和物理页面的关系*/ page_remove_rmap(page);">/*释放所分配的缓存*/ page_cache_release(page);">return ret;">
PFRA具体实现
LRU 链表
struct zone { …… spinlock_t lru_lock;">struct list_head active_list; struct list_head inactive_list; unsigned long nr_active;">unsigned long nr_inactive;">
- lru_lock:active_list 和 inactive_list 使用的自旋锁。
- active_list:管理内存区域中处于活跃状态的页面。
- inactive_list:管理内存区域中处于不活跃状态的页面。
- nr_active:active_list 链表上的页面数目。
- nr_inactive:inactive_list 链表上的页面数目。
如何在两个LRU 链表之间移动页面
- 如果页面被认为是活跃的,则将该页的 PG_active 置位;否则,不置位。
- 当页面被访问时,检查该页的 PG_referenced 位,若未被置位,则置位之;若发现该页的 PG_referenced 已经被置位了,则意味着该页经常被访问,这时,若该页在 inactive 链表上,则置位其 PG_active 位,将其移动到 active 链表上去,并清除其 PG_referenced 位的设置;如果页面的 PG_referenced 位被置位了一段时间后,该页面没有被再次访问,那么 Linux 操作系统会清除该页面的 PG_referenced 位,因为这意味着这个页面最近这段时间都没有被访问。
- PG_referenced 位同样也可以用于页面从 active 链表移动到 inactive 链表。对于某个在 active 链表上的页面来说,其 PG_active 位被置位,如果 PG_referenced 位未被置位,给定一段时间之后,该页面如果还是没有被访问,那么该页面会被清除其 PG_active 位,挪到 inactive 链表上去。
- mark_page_accessed():当一个页面被访问时,则调用该函数相应地修改 PG_active 和 PG_referenced。
- page_referenced():当操作系统进行页面回收时,每扫描到一个页面,就会调用该函数设置页面的 PG_referenced 位。如果一个页面的 PG_referenced 位被置位,但是在一定时间内该页面没有被再次访问,那么该页面的 PG_referenced 位会被清除。
- activate_page():该函数将页面放到 active 链表上去。
- shrink_active_list():该函数将页面移动到 inactive 链表上去。
LRU 缓存
声明:本站部分文章内容及图片转载于互联 、内容不代表本站观点,如有内容涉及侵权,请您立即联系本站处理,非常感谢!