イントロ
ペパボ社内 Slack で Linux の CoW = Copy On Write について、 id:ryuichi1208 id:udzura とディスカッションして盛り上がっていた。カーネル内で CoW を処理する関数を追えないか? という話があがったので、調べてみた次第。
( なぜ CoW の話が出てきたのか / どんなことをディスカッションしてたのかは id:ryuichi1208 がまとめくれるかも? )
結論
CoW を観察するには do_wp_pageを観察するといいみたい
wiki.bit-hive.com
( いつもお世話になっております )
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[])
{
int pages = 100;
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);
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());
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
github.com
実験
こんな感じで観察できました。とりあえず このエントリはここまで
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 処理するのは これなのだろう」と思い込んでしまった。検証コードが期待したように動作せずハマってしまった。
参考
cstmize.hatenablog.jp
linuxjm.osdn.jp
qiita.com
qiita.com
lore.kernel.org