页面的换出 kswapd 和 kreclaimd
内核线程 kswapd 和 kreclaimd 的启动
static int __init kswapd_init(void) { printk("Starting kswapd v1.8\n"); swap_setup(); kernel_thread(kswapd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); kernel_thread(kreclaimd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); return 0; } module_init(kswapd_init)
0) 这是一个内建模块。在系统初始化调用。
1) 启动了两个内核线程 kswapd 和 kreclaimd 。
2) swap_setup 根据物理内存大小设置page_cluster。这个是从磁盘读block时候的预读参数。
void __init swap_setup(void) { if (num_physpages < ((16 * 1024 * 1024) >> PAGE_SHIFT)) page_cluster = 2; else if (num_physpages < ((32 * 1024 * 1024) >> PAGE_SHIFT)) page_cluster = 3; else page_cluster = 4; }
kswap 内核线程
kswap的主流程
int kswapd(void *unused) { struct task_struct *tsk = current; tsk->session = 1; tsk->pgrp = 1; strcpy(tsk->comm, "kswapd"); sigfillset(&tsk->blocked); kswapd_task = tsk; tsk->flags |= PF_MEMALLOC; for (;;) { static int recalc = 0; if (inactive_shortage() || free_shortage()) { int wait = 0; if (waitqueue_active(&kswapd_done)) wait = 1; do_try_to_free_pages(GFP_KSWAPD, wait); } refill_inactive_scan(6, 0); if (time_after(jiffies, recalc + HZ)) { recalc = jiffies; recalculate_vm_stats(); } wake_up_all(&kswapd_done); run_task_queue(&tq_disk); if (!free_shortage() || !inactive_shortage()) { interruptible_sleep_on_timeout(&kswapd_wait, HZ); } else if (out_of_memory()) { oom_kill(); } } }
1) 设置进程的flags为 PF_MEMALLOC。这个标记了kswapd是一个内存管理者的角色,有权使用最后一点‘血本’内存。同时也为了避免递归。
2) 每次循环的末尾都会进入 interruptible_sleep_on_timeout,睡眠HZ。HZ是系统1秒内产生多少次中断,已经编译就不能更改。
也就是1秒醒来一次。但是也会被中断提前醒来。
3) 每次 kswapd 主要干两件事情。
kswap主流程 之 页面缺口的判断 inactive_shortage
if (inactive_shortage() || free_shortage()) { int wait = 0; if (waitqueue_active(&kswapd_done)) wait = 1; do_try_to_free_pages(GFP_KSWAPD, wait); }
0) 计算是否缺少不活跃的页面,或者缺少空闲页。
1) 不活跃页面是alloc_pages的潜在页面,如果不够需要释放出来。
int inactive_shortage(void) { int shortage = 0; shortage += freepages.high; shortage += inactive_target; shortage -= nr_free_pages(); shortage -= nr_inactive_clean_pages(); shortage -= nr_inactive_dirty_pages; if (shortage > 0) return shortage; return 0; }
1) 缺少的空闲页面数 = freepages.high + inactive_target - 系统目前的free_pages - 系统目前有的空闲页 - 系统目前的不活跃脏页
kswap主流程 之 释放空闲页 do_try_to_free_pages
static int do_try_to_free_pages(unsigned int gfp_mask, int user) { int ret = 0; if (free_shortage() || nr_inactive_dirty_pages > nr_free_pages() + nr_inactive_clean_pages()) ret += page_launder(gfp_mask, user); if (free_shortage() || inactive_shortage()) { shrink_dcache_memory(6, gfp_mask); shrink_icache_memory(6, gfp_mask); ret += refill_inactive(gfp_mask, user); } else { kmem_cache_reap(gfp_mask); ret = 1; } return ret; }
1) 先试着清洗不活跃脏页面 page_launder。
2) 如果还缺少不活跃页面,则开始从buffer和cache中释放。
do_try_to_free_pages 之 清洗页面 page_launder
int page_launder(int gfp_mask, int sync) { dirty_page_rescan: spin_lock(&pagemap_lru_lock); maxscan = nr_inactive_dirty_pages; while ((page_lru = inactive_dirty_list.prev) != &inactive_dirty_list && maxscan-- > 0) { page = list_entry(page_lru, struct page, lru); if (PageDirty(page)) { int (*writepage)(struct page *) = page->mapping->a_ops->writepage; int result; if (!writepage) goto page_active; if (!launder_loop) { list_del(page_lru); list_add(page_lru, &inactive_dirty_list); UnlockPage(page); continue; } ClearPageDirty(page); page_cache_get(page); spin_unlock(&pagemap_lru_lock); result = writepage(page); page_cache_release(page); spin_lock(&pagemap_lru_lock); if (result != 1) continue; set_page_dirty(page); goto page_active; } } }
1) 页面清洗从 inactive_dirty_list中依次扫面,找到可以清洗的页面。
2) 清洗动作由 writepage完成。
3) 做两次扫描,为什么呢?
do_try_to_free_pages 之 swap文件系统和swap进程
page_launder之后,页面还是短缺
这个时候还是缺页面,则进行下面4个暴力的回收
0) shrink_dcache_memory(6, gfp_mask);
1) shrink_icache_memory(6, gfp_mask);
2) ret += refill_inactive(gfp_mask, user);
3) kmem_cache_reap(gfp_mask);
0) 和 1) 和文件系统相关,先来看 2)refill_inactive
refill_inactive
主要做两件事情:
0) 扫描 active_list,期望从中发现不用了的,没有及时放入 inactive_dirty_list中的页面。
1) swap_out, 找一个进程。
refill_inactive 之 swap_out
static int swap_out(unsigned int priority, int gfp_mask) { int counter; int __ret = 0; counter = (nr_threads << SWAP_SHIFT) >> priority; if (counter < 1) counter = 1; for (; counter >= 0; counter--) { struct list_head *p; unsigned long max_cnt = 0; struct mm_struct *best = NULL; int assign = 0; int found_task = 0; select: spin_lock(&mmlist_lock); p = init_mm.mmlist.next; for (; p != &init_mm.mmlist; p = p->next) { struct mm_struct *mm = list_entry(p, struct mm_struct, mmlist); if (mm->rss <= 0) continue; found_task++; if (assign == 1) { mm->swap_cnt = (mm->rss >> SWAP_SHIFT); if (mm->swap_cnt < SWAP_MIN) mm->swap_cnt = SWAP_MIN; } if (mm->swap_cnt > max_cnt) { max_cnt = mm->swap_cnt; best = mm; } } if (best) atomic_inc(&best->mm_users); spin_unlock(&mmlist_lock); if (!best) { if (!assign && found_task > 0) { assign = 1; goto select; } break; } else { __ret = swap_out_mm(best, gfp_mask); mmput(best); break; } } return __ret; }
1) swap_out的目标是选择一个合适的进程然后进行swap。
2) 所有的进程组织成了一个双向链表,而1号进程是init_mm,所有从init_mm下一个进程开始找合适的进程。
3) 什么是合适的进程,有两个判断的阶段。
rss 和 swap_cnt。
开始swap_cnt就是rss的值,然后每次swap掉一个页面都swap_cnt--,然后当swap_cnt都是0的时候,又开始一轮赋值。
这样保证了大的rss优先被选出来,同时又保证所有的进程依次每选中。
refill_inactive 之 swap_out_mm
static int swap_out_mm(struct mm_struct * mm, int gfp_mask) { int result = 0; unsigned long address; struct vm_area_struct* vma; spin_lock(&mm->page_table_lock); address = mm->swap_address; vma = find_vma(mm, address); if (vma) { if (address < vma->vm_start) address = vma->vm_start; for (;;) { result = swap_out_vma(mm, vma, address, gfp_mask); if (result) goto out_unlock; vma = vma->vm_next; if (!vma) break; address = vma->vm_start; } } mm->swap_address = 0; mm->swap_cnt = 0; out_unlock: spin_unlock(&mm->page_table_lock); return result; }
1) address = mm->swap_address;
对找到的合适被swap的进程,从上一次被swap的逻辑地址接着swap。
2) vma = find_vma(mm, address);
从address找到对应的vma。
3) result = swap_out_vma(mm, vma, address, gfp_mask);
循环遍历vma,直到swap成功一次为止。
refill_inactive 之 swap_out_vma
static int swap_out_vma(struct mm_struct * mm, struct vm_area_struct * vma, unsigned long address, int gfp_mask) { pgd_t *pgdir; unsigned long end; if (vma->vm_flags & (VM_LOCKED|VM_RESERVED)) return 0; pgdir = pgd_offset(mm, address); end = vma->vm_end; if (address >= end) BUG(); do { int result = swap_out_pgd(mm, vma, pgdir, address, end, gfp_mask); if (result) return result; address = (address + PGDIR_SIZE) & PGDIR_MASK; pgdir++; } while (address && (address < end)); return 0; }
1) vma->vm_flags & (VM_LOCKED|VM_RESERVED)
如果vma是被lock了或者vm_reserved了,不进行swap。
2) pgdir = pgd_offset(mm, address);
计算出address对应的pgdir
3) 一个vma可能包含多个pgd项,循环这个vma对应的pgdir,直到成功的swap一个页面为止。
refill_inactive 之 swap_out_pgd
static inline int swap_out_pgd(struct mm_struct * mm, struct vm_area_struct * vma, pgd_t *dir, unsigned long address, unsigned long end, int gfp_mask) { pmd_t * pmd; unsigned long pgd_end; if (pgd_none(*dir)) return 0; if (pgd_bad(*dir)) { pgd_ERROR(*dir); pgd_clear(dir); return 0; } pmd = pmd_offset(dir, address); pgd_end = (address + PGDIR_SIZE) & PGDIR_MASK; if (pgd_end && (end > pgd_end)) end = pgd_end; do { int result = swap_out_pmd(mm, vma, pmd, address, end, gfp_mask); if (result) return result; address = (address + PMD_SIZE) & PMD_MASK; pmd++; } while (address && (address < end)); return 0; }
1) pmd = pmd_offset(dir, address);
由pgdir和address计算出pmd的开始和结束。
2) 循环尝试swap所有的pmd,直到成功。
refill_inactive 之 swap_out_pmd
static inline int swap_out_pmd(struct mm_struct * mm, struct vm_area_struct * vma, pmd_t *dir, unsigned long address, unsigned long end, int gfp_mask) { pte_t * pte; unsigned long pmd_end; if (pmd_none(*dir)) return 0; if (pmd_bad(*dir)) { pmd_ERROR(*dir); pmd_clear(dir); return 0; } pte = pte_offset(dir, address); pmd_end = (address + PMD_SIZE) & PMD_MASK; if (end > pmd_end) end = pmd_end; do { int result; mm->swap_address = address + PAGE_SIZE; result = try_to_swap_out(mm, vma, address, pte, gfp_mask); if (result) return result; address += PAGE_SIZE; pte++; } while (address && (address < end)); return 0; }
1) pte = pte_offset(dir, address);
由pmd和address计算出pte的起始。
2) 循环尝试swap所有的pte。
refill_inactive 之 try_to_swap_out
static int try_to_swap_out(struct mm_struct * mm, struct vm_area_struct* vma, unsigned long address, pte_t * page_table, int gfp_mask) { if (!pte_present(pte)) goto out_failed; page = pte_page(pte); if ((!VALID_PAGE(page)) || PageReserved(page)) goto out_failed; if (ptep_test_and_clear_young(page_table)) { age_page_up(page); goto out_failed; } flush_cache_page(vma, address); entry = get_swap_page(); add_to_swap_cache(page, entry); set_page_dirty(page); }
1) 开始尝试swap一个真正的页面了。
2) pte_present(pte)
首先判断这个页面是否已经配swap出去了。
3) page = pte_page(pte);
由pte找到page结构的指针
4) ptep_test_and_clear_young(page_table)
是否是一个刚刚被访问过了的年轻页面。
5) flush_cache_page(vma, address);
6) entry = get_swap_page();
从 swap_info 中申请一个swap项。
7) add_to_swap_cache(page, entry);
kswap最终的动作只是把一个进程的页面从页式映射中‘断开’,然后把这个页面加入到swap_cache。并设置为脏页。
加入到 active_list 中。
kreclaimd 内核线程
kreclaimd 的主流程
这个内核线程是在需要的时候被动的被唤醒,把一个zone里的inactive_clean_list的页面转移到free里面。
int kreclaimd(void *unused) { struct task_struct *tsk = current; pg_data_t *pgdat; tsk->session = 1; tsk->pgrp = 1; strcpy(tsk->comm, "kreclaimd"); sigfillset(&tsk->blocked); current->flags |= PF_MEMALLOC; while (1) { interruptible_sleep_on(&kreclaimd_wait); pgdat = pgdat_list; do { int i; for(i = 0; i < MAX_NR_ZONES; i++) { zone_t *zone = pgdat->node_zones + i; if (!zone->size) continue; while (zone->free_pages < zone->pages_low) { struct page * page; page = reclaim_page(zone); if (!page) break; __free_page(page); } } pgdat = pgdat->node_next; } while (pgdat); } }
1) 设置当前task的属性: session, pgrp, task->comm, tsk->blocked。
2) 设置 PF_MEMALLOC。这个标记了kreclaimd是一个内存管理者的角色。
3) interruptible_sleep_on(&kreclaimd_wait);
主循环不主动的干活,而是等待page_alloc.c::__alloc_pages()分配页面不够用的时候,唤醒这个线程。
4) 遍历NUMA的pgdat_list链表。
5) zone_t *zone = pgdat->node_zones + i;
在每个node的结构pg_data中遍历所有的zone。
6) while(zone->free_pages < zone->pages_low)
在每个zone里,如果当前zone的free_pages数比low还低的话,就开始relcaim.
kreclaimd 之 relaim_page(zone)
while ((page_lru = zone->inactive_clean_list.prev) != &zone->inactive_clean_list && maxscan--) { page = list_entry(page_lru, struct page, lru); if (!PageInactiveClean(page)) { printk("VM: reclaim_page, wrong page on list.\n"); list_del(page_lru); page->zone->inactive_clean_pages--; continue; } if (PageTestandClearReferenced(page) || page->age > 0 || (!page->buffers && page_count(page) > 1)) { del_page_from_inactive_clean_list(page); add_page_to_active_list(page); continue; } if (page->buffers || PageDirty(page) || TryLockPage(page)) { del_page_from_inactive_clean_list(page); add_page_to_inactive_dirty_list(page); continue; } if (PageSwapCache(page)) { __delete_from_swap_cache(page); goto found_page; } if (page->mapping) { __remove_inode_page(page); goto found_page; } }
1) 遍历zone的 inactive_clean_list,找到一个页面就返回。
2) if (page->buffers || PageDirty(page) || TryLockPage(page))
如果是脏页,把这个页面添加到 inactive_dirty_list中。
3) __delete_from_swap_cache(page);
把page从 cache 中摘除掉。
4) __remove_inode_page(page);
把page从 inode 中摘除。