プロセス毎の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関数によってページが解放されるので、スワップキャッシュページではなくなり、純粋にスワップアウトした状態になるのでしょう。

まとめ

スワップ領域の使用頻度について、以下のポイントで操作が行われていました。

  • 加算
    • スワップアウト時
    • fork等によってページテーブルエントリがコピーされる際、コピーされるエントリがスワップ領域を指していた場合
  • 減算
    • スワップイン時
    • ページテーブルエントリクリア時に、クリアされるエントリがスワップ領域を指していた場合
    • swapoffが呼ばれ、swap領域が解放される時

参考文献:

Linuxカーネル2.6解読室
Linuxカーネル2.6解読室
ソフトバンククリエイティブ 2006-11-18
売り上げランキング : 63555

おすすめ平均 star
starLinuxカーネル全般についてじっくり学べる
starこれを理解できなくてもがっかりするな
star細かい割りに、肝心な疑問点がわからない

Amazonで詳しく見る
by G-Tools