イントロ
ペパボ社内 Slack で Linux の CoW = Copy On Write について、 id:ryuichi1208 id:udzura とディスカッションして盛り上がっていた。カーネル内で CoW を処理する関数を追えないか? という話があがったので、調べてみた次第。
( なぜ CoW の話が出てきたのか / どんなことをディスカッションしてたのかは id:ryuichi1208 がまとめくれるかも? )
結論
CoW を観察するには do_wp_pageを観察するといいみたい
( いつもお世話になっております )
do_wp_page のソース
https://elixir.bootlin.com/linux/v5.11.22/source/mm/memory.c#L3085
検証環境
Vagrant で用意した
$script = <<-SCRIPT sudo apt-get update && apt-get install -y build-essential bpftrace bpfcc-tools linux-headers-$(uname -r) SCRIPT Vagrant.configure("2") do |config| config.vm.box = "bento/ubuntu-21.04" config.vm.provision "shell", inline: $script end
検証コード
以下の検証コードを用意した
- 親プロセスで mmap(2) して minor page fault を起こしておく
- 親プロセスが fork(2) する
- 子プロセスが 1 のメモリに書き込みして minor page fault を起こす
- カーネル内で do_wp_page で CoW が実行される
を期待する動作とする。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, char *argv[]) { /* CoW を起こしたいページ数 */ int pages = 100; /* getconf PAGESIZE */ size_t page_size = 4096; /* 観察しやすいようにアドレスを固定する */ char *p1 = (char*)mmap((void *)0x100000000000, page_size * pages, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); if (p1 == MAP_FAILED) { perror("failed to mmap"); exit(1); } printf("mmap: %p\n", p1); /* 親プロセス: minor page fault */ for (int i = 0; i < pages; i++) { p1[i * page_size] = 'p'; } pid_t pid = fork(); if (pid == -1) { perror("failed to fork"); exit(1); } else if (pid == 0) { printf("child pid:%d\n", getpid()); /* 子プロセス: minor page fault -> cow -> do_wp_page */ for (int i = 0; i < pages; i++) { printf("cow address: %p\n", &p1[i * page_size]); p1[i * page_size] = 'c'; sleep(1); } } else { printf("parent pid:%d\n", getpid()); waitpid(pid, NULL, 0); } }
観察
bpftrace で do_wp_page をトレースする
kprobe:do_wp_page /comm == "cow"/ { printf("do_wp_page > pid:%d comm:%s address:%p\n", pid, comm, ((struct vm_fault *)arg0)->address) }
- その他プロセスの CoW が邪魔しないよう
comm
でフィルタする - struct vm_fault の address で fault を起こした仮想アドレスが取り出せる
- printf して検証コードと付き合わせて確認する
単に do_wp_page が呼び出されているかどうかだけ確かめるなら perf-tools なんかでも OK
実験
こんな感じで観察できました。とりあえず このエントリはここまで
perf-tools の場合
perf-tools の functrace だと ↓ みたいにトレースできる
$ sudo ./bin/functrace do_wp_page cow-25935 [000] .... 24930.105521: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24930.105522: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24930.105534: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24931.122937: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24932.158144: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24933.165625: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24934.167118: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24935.168714: do_wp_page <-handle_pte_fault cow-25935 [000] .... 24936.177183: do_wp_page <-handle_pte_fault
その他
最初は do_cow_fault() を調べていたのだが、こちらは anonymous ページでなく file backed な ページの CoW を処理する関数ぽい? (まだ調べてないので宿題)
memory.c - mm/memory.c - Linux source code (v5.11.22) - Bootlin
名前にまんま cow
が含まれているので、「Copy on Write 処理するのは これなのだろう」と思い込んでしまった。検証コードが期待したように動作せずハマってしまった。