プロセス毎のswap使用量を計測するパッチを読む その7
「プロセス毎のswap使用量を計測するパッチを読む その6」の続き。
カーネルコードを追える部分、追えない部分が分かってきたので、残りのパッチエントリは簡単に見て最後とします。
@@ -648,11 +653,11 @@ static int copy_pte_range(struct mm_stru pte_t *src_pte, *dst_pte; spinlock_t *src_ptl, *dst_ptl; int progress = 0; - int rss[2]; + int rss[3]; swp_entry_t entry = (swp_entry_t){0}; again: - rss[1] = rss[0] = 0; + rss[2] = rss[1] = rss[0] = 0; dst_pte = pte_alloc_map_lock(dst_mm, dst_pmd, addr, &dst_ptl); if (!dst_pte) return -ENOMEM; @@ -688,7 +693,7 @@ again: arch_leave_lazy_mmu_mode(); spin_unlock(src_ptl); pte_unmap_nested(orig_src_pte); - add_mm_rss(dst_mm, rss[0], rss[1]); + add_mm_rss(dst_mm, rss[0], rss[1], rss[2]); pte_unmap_unlock(orig_dst_pte, dst_ptl); cond_resched();
patchがあてられているcopy_pte_range関数は、これまで見てきたcopy_one_pte関数を呼び出しています。この関数は名前のとおり一定範囲内のページテーブルエントリをコピーする関数です。パッチの内容としては、swap領域のカウントを保持できるようにmm_structにメンバを追加したことに対応し、rssの配列を拡張してswap領域のカウントを格納する領域を確保、swap領域のカウントを加算(copy_one_pte関数)し、add_mm_rss関数で保存する、という流れになります。
@@ -818,6 +823,7 @@ static unsigned long zap_pte_range(struc spinlock_t *ptl; int file_rss = 0; int anon_rss = 0; + int swap_usage = 0; pte = pte_offset_map_lock(mm, pmd, addr, &ptl); arch_enter_lazy_mmu_mode(); @@ -887,13 +893,18 @@ static unsigned long zap_pte_range(struc if (pte_file(ptent)) { if (unlikely(!(vma->vm_flags & VM_NONLINEAR))) print_bad_pte(vma, addr, ptent, NULL); - } else if - (unlikely(!free_swap_and_cache(pte_to_swp_entry(ptent)))) - print_bad_pte(vma, addr, ptent, NULL); + } else { + swp_entry_t ent = pte_to_swp_entry(ptent); + + if (!non_swap_entry(ent)) + swap_usage--; + if (unlikely(!free_swap_and_cache(ent))) + print_bad_pte(vma, addr, ptent, NULL); + } pte_clear_not_present_full(mm, addr, pte, tlb->fullmm); } while (pte++, addr += PAGE_SIZE, (addr != end && *zap_work > 0)); - add_mm_rss(mm, file_rss, anon_rss); + add_mm_rss(mm, file_rss, anon_rss, swap_usage); arch_leave_lazy_mmu_mode(); pte_unmap_unlock(pte - 1, ptl); |<< zap_pte_range関数にあてられたパッチを見てみます。この関数は、一定範囲内のページテーブルエントリをクリアしているようです。クリアされるページテーブルエントリがswap領域を指している場合、swap領域のカウントを減算し、add_mm_rss関数で保存しています。 >|c| @@ -2595,6 +2606,7 @@ static int do_swap_page(struct mm_struct */ inc_mm_counter(mm, anon_rss); + dec_mm_counter(mm, swap_usage); pte = mk_pte(page, vma->vm_page_prot); if ((flags & FAULT_FLAG_WRITE) && reuse_swap_page(page)) { pte = maybe_mkwrite(pte_mkdirty(pte), vma);
do_swap_page関数は、「Linuxカーネル解析室」のp.234に記述があり、スワップイン時に呼ばれる関数とのこと。ここでは元々無名マッピング領域のカウントを加算していますが、同時にswap領域のカウントが減算されています。
Index: mm-test-kernel/mm/swapfile.c =================================================================== --- mm-test-kernel.orig/mm/swapfile.c +++ mm-test-kernel/mm/swapfile.c @@ -837,6 +837,7 @@ static int unuse_pte(struct vm_area_stru } inc_mm_counter(vma->vm_mm, anon_rss); + dec_mm_counter(vma->vm_mm, swap_usage); get_page(page); set_pte_at(vma->vm_mm, addr, pte, pte_mkold(mk_pte(page, vma->vm_page_prot)));
mm/memory.cの次はmm/swapfile.cです。
unuse_pte関数ですが、SYSCALL_DEFINE1(swapoff)→try_to_unuse→unuse_mm→unuse_vma→unuse_pud_range→unuse_pmd_range→unuse_pte_range→unuse_pteという流れで呼ばれていました。
mm/swapfile.c
/* * No need to decide whether this PTE shares the swap entry with others, * just let do_wp_page work it out if a write is requested later - to * force COW, vm_page_prot omits write permission from any private vma. */ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd, unsigned long addr, swp_entry_t entry, struct page *page) { struct mem_cgroup *ptr = NULL; spinlock_t *ptl; pte_t *pte; int ret = 1; if (mem_cgroup_try_charge_swapin(vma->vm_mm, page, GFP_KERNEL, &ptr)) { ret = -ENOMEM; goto out_nolock; } pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl); if (unlikely(!pte_same(*pte, swp_entry_to_pte(entry)))) { if (ret > 0) mem_cgroup_cancel_charge_swapin(ptr); ret = 0; goto out; } inc_mm_counter(vma->vm_mm, anon_rss); dec_mm_counter(vma->vm_mm, swap_usage); get_page(page); set_pte_at(vma->vm_mm, addr, pte, pte_mkold(mk_pte(page, vma->vm_page_prot))); page_add_anon_rmap(page, vma, addr); mem_cgroup_commit_charge_swapin(page, ptr); swap_free(entry); /* * Move the page to the active list so it is not * immediately swapped out again after swapon. */ activate_page(page); out: pte_unmap_unlock(pte, ptl); out_nolock: return ret; }
swapoff時に、swap領域のページを無名マッピングに戻してswap領域側のページテーブルエントリを解放しているようです。通常はswapoffすることがないと思うので、あまり気にしなくても良いのではないかと思います。
Index: mm-test-kernel/fs/proc/task_mmu.c =================================================================== --- mm-test-kernel.orig/fs/proc/task_mmu.c +++ mm-test-kernel/fs/proc/task_mmu.c @@ -17,7 +17,7 @@ void task_mem(struct seq_file *m, struct mm_struct *mm) { unsigned long data, text, lib; - unsigned long hiwater_vm, total_vm, hiwater_rss, total_rss; + unsigned long hiwater_vm, total_vm, hiwater_rss, total_rss, swap; /* * Note: to minimize their overhead, mm maintains hiwater_vm and @@ -36,6 +36,7 @@ void task_mem(struct seq_file *m, struct data = mm->total_vm - mm->shared_vm - mm->stack_vm; text = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK)) >> 10; lib = (mm->exec_vm << (PAGE_SHIFT-10)) - text; + swap = get_mm_counter(mm, swap_usage); seq_printf(m, "VmPeak:\t%8lu kB\n" "VmSize:\t%8lu kB\n" @@ -46,7 +47,8 @@ void task_mem(struct seq_file *m, struct "VmStk:\t%8lu kB\n" "VmExe:\t%8lu kB\n" "VmLib:\t%8lu kB\n" - "VmPTE:\t%8lu kB\n", + "VmPTE:\t%8lu kB\n" + "VmSwap:\t%8lu kB\n", hiwater_vm << (PAGE_SHIFT-10), (total_vm - mm->reserved_vm) << (PAGE_SHIFT-10), mm->locked_vm << (PAGE_SHIFT-10), @@ -54,7 +56,8 @@ void task_mem(struct seq_file *m, struct total_rss << (PAGE_SHIFT-10), data << (PAGE_SHIFT-10), mm->stack_vm << (PAGE_SHIFT-10), text, lib, - (PTRS_PER_PTE*sizeof(pte_t)*mm->nr_ptes) >> 10); + (PTRS_PER_PTE*sizeof(pte_t)*mm->nr_ptes) >> 10, + swap << (PAGE_SHIFT - 10)); } unsigned long task_vsize(struct mm_struct *mm)
/proc/(pid)/statusに出力する部分です。patchの内容はswap領域を出力する箇所のみ。PAGE_SHIFTは12なのでswap*4(kb-ページサイズ)を表示しています。
Index: mm-test-kernel/mm/rmap.c =================================================================== --- mm-test-kernel.orig/mm/rmap.c +++ mm-test-kernel/mm/rmap.c @@ -834,6 +834,7 @@ static int try_to_unmap_one(struct page spin_unlock(&mmlist_lock); } dec_mm_counter(mm, anon_rss); + inc_mm_counter(mm, swap_usage); } else if (PAGE_MIGRATION) { /* * Store the pfn of the page in a special migration
try_to_unmap_one関数は、以下のように呼び出されています。
shrink_list→shrink_inactive_list→shrink_page_list→try_to_unmap→try_to_unmap_anon、try_to_unmap_file→try_to_unmap_one
「Linuxカーネル解析室」のp.245を見るとshrink_listはページの回収処理の一部という扱いになっているので、try_to_unmap_one関数の中でスワップアウトするようです。
mm/rmap.c
if (PageHWPoison(page) && !(flags & TTU_IGNORE_HWPOISON)) { if (PageAnon(page)) dec_mm_counter(mm, anon_rss); else dec_mm_counter(mm, file_rss); set_pte_at(mm, address, pte, swp_entry_to_pte(make_hwpoison_entry(page))); } 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)) list_add(&mm->mmlist, &init_mm.mmlist); spin_unlock(&mmlist_lock); } dec_mm_counter(mm, anon_rss); inc_mm_counter(mm, swap_usage); } 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); entry = make_migration_entry(page, pte_write(pteval)); } set_pte_at(mm, address, pte, swp_entry_to_pte(entry)); BUG_ON(pte_file(*pte)); } else if (PAGE_MIGRATION && (TTU_ACTION(flags) == TTU_MIGRATION)) { /* Establish migration entry for a file page */ swp_entry_t entry; entry = make_migration_entry(page, pte_write(pteval)); set_pte_at(mm, address, pte, swp_entry_to_pte(entry)); } else dec_mm_counter(mm, file_rss); page_remove_rmap(page); page_cache_release(page); out_unmap: pte_unmap_unlock(pte, ptl); out: return ret; }
ページがスワップキャッシュページの場合、スワップ領域を加算しています。この後に呼ぶpage_remove_rmap関数、page_cache_release関数によってページが解放されるので、スワップキャッシュページではなくなり、純粋にスワップアウトした状態になるのでしょう。
まとめ
スワップ領域の使用頻度について、以下のポイントで操作が行われていました。
- 加算
- 減算
参考文献:
Linuxカーネル2.6解読室 | |
ソフトバンククリエイティブ 2006-11-18 売り上げランキング : 63555 おすすめ平均 Linuxカーネル全般についてじっくり学べる これを理解できなくてもがっかりするな 細かい割りに、肝心な疑問点がわからない Amazonで詳しく見る by G-Tools |