頁面的換出 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 中摘除。