/proc/pid/pagemapの話
前回 /proc/pid/pagemap について簡単に触れたので,実際にpagemapを読み取るときどういうことが起きるか少し追ってみようと思います.
/proc/pid/pagemapに関する操作はカーネルのfs/proc/task_mmu.c
内でproc_pagemap_operations
に定義してあります.
https://github.com/torvalds/linux/blob/v4.11/fs/proc/task_mmu.c#L1463
const struct file_operations proc_pagemap_operations = { .llseek = mem_lseek, /* borrow this */ .read = pagemap_read, .open = pagemap_open, .release = pagemap_release, };
要するに,/proc/pid/pagemapをreadする場合はpagemap_read()
が呼ばれるということです.
pagemap_read()
の処理を抜粋すると以下のようになります.
https://github.com/torvalds/linux/blob/v4.11/fs/proc/task_mmu.c#L1351
static ssize_t pagemap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mm_struct *mm = file->private_data; struct pagemapread pm; struct mm_walk pagemap_walk = {}; unsigned long src; unsigned long svpfn; unsigned long start_vaddr; unsigned long end_vaddr; ... pagemap_walk.pmd_entry = pagemap_pmd_range; pagemap_walk.pte_hole = pagemap_pte_hole; #ifdef CONFIG_HUGETLB_PAGE pagemap_walk.hugetlb_entry = pagemap_hugetlb_range; #endif pagemap_walk.mm = mm; pagemap_walk.private = ± src = *ppos; svpfn = src / PM_ENTRY_BYTES; start_vaddr = svpfn << PAGE_SHIFT; end_vaddr = mm->task_size; ... while (count && (start_vaddr < end_vaddr)) { ... down_read(&mm->mmap_sem); ret = walk_page_range(start_vaddr, end, &pagemap_walk); up_read(&mm->mmap_sem); start_vaddr = end; len = min(count, PM_ENTRY_BYTES * pm.pos); if (copy_to_user(buf, pm.buffer, len)) { ret = -EFAULT; goto out_free; } copied += len; buf += len; count -= len; }
この関数ではread()
のオフセットから求めたい仮想アドレスを計算し(start_vaddr
)その後 walk_page_range()
関数を呼びます.このwalk_page_range()
関数は https://github.com/torvalds/linux/blob/master/mm/pagewalk.c#L283 に定義されており,指定したページ範囲を探索し,必要に応じて設定したコールバック関数を呼ぶ関数です.
今回の場合,基本的には walk_page_range()
=> __walk_page_range()
=> walk_pgd_range()
=> walk_pud_range()
=> walk_pmd_range()
と探索していったのち,walk->pmd_entry()
が呼ばれます.このwalk->pmd_entry()
がpagemap_read()
の前半部分で設定したコールバック関数で,実体はpagemap_pmd_range()
です.
pagemap_pmd_range()
の主要部を抜き出すと以下のようになっています.
https://github.com/torvalds/linux/blob/v4.11/fs/proc/task_mmu.c#L1205
static int pagemap_pmd_range(pmd_t *pmdp, unsigned long addr, unsigned long end, struct mm_walk *walk) { ... orig_pte = pte = pte_offset_map_lock(walk->mm, pmdp, addr, &ptl); for (; addr < end; pte++, addr += PAGE_SIZE) { pagemap_entry_t pme; pme = pte_to_pagemap_entry(pm, vma, addr, *pte); err = add_to_pagemap(addr, &pme, pm); if (err) break; } .... }
また,add_to_pagemap()
は以下のようになっています.
https://github.com/torvalds/linux/blob/v4.11/fs/proc/task_mmu.c#L1121
static int add_to_pagemap(unsigned long addr, pagemap_entry_t *pme, struct pagemapread *pm) { pm->buffer[pm->pos++] = *pme; if (pm->pos >= pm->len) return PM_END_OF_BUFFER; return 0; }
やっていることはpmd
からpte
を求め,そのpte
をpte_to_pagemap_entry
でpagemapの64bitのエントリに変換し,add_to_pagemap
でそれをバッファに保存します.こうして得られたpagemapのエントリを最終的にpagemap_read()
のい中でcopy_to_user()
でコピーしています.
ということで,pagemapをreadするとページテーブルを探索してそれの結果を返すという,そりゃそうだろうという話ですがこうなっているという話でした.