KVM TLB Shootdown Preemption メモ

KVM FORUM 2018 で発表されたTowards a more Scalable KVM Hypervisorについてのメモ。 [PATCH v8 0/4] KVM: X86: Add Paravirt TLB Shootdown ゲストOSはOSレベルの同期機構であるTLB shoot downやRCUの処理において、ホストOSのスケジューラの影響を受ける。ベアメタル環境であればすぐに完了する操作であっても、仮想環境上ではその遅延を無視することができなくなる。 TLB(Translatoin Lookaside buffer)は仮想メモリアドレスと物理メモリアドレスのマッピングをキャッシュするために使われる。あるCPUがそのマッピングを切り替えたとき、その以外のCPUでTLBをflushしなかればならない。これを TLB shoot down と呼ぶ。 モダンなOSでは、TLB shoot down はパフォーマンスクリティカルな箇所として位置づけされ、この処理によって遅延が発生しないようチューニングされている。TLBのflushはすぐさま完了するという前提で、IPI(Inter Processor Interrupt)で実装されている。Remote CPUのTLB flushの完了は busy wait で待っているため、ベアメタル環境とは相性が良い。一方で、仮想環境ではvCPUが別のゲストに横取りされたり、実行がブロックされたり、ということが起きるため、長時間にわたって busy wait が継続することになる。 この問題は、準仮想化TLB shoot down によって解決できる。準仮想化TLB shoot down では、動作していないvCPUに対する操作を遅延させておいて、次回そのvCPUが起動するときに、KVMがTLB flushを担当する。特にオーバコミットされた環境で顕著なパフォーマンス向上が見られる。 ゲストとホストどちらからも参照できるメモリ領域に「あるvCPUが preempt されたかどうか」を示すフラグを用意しておく。pv_mmu_ops.flush_tlb_others関数は、アクティブなvCPUに対してはIPI経由でTLB Flushの通知を送り、そうでないvCPUに対してはKVM_VCPU_FLUSH_TLBフラグを付与しておく。その後、KVMはKVM_VCPU_FLUSH_TLBフラグがついているvCPUを起動する際に、INVVPIDを発行する。VM数が増えるに従ってこのチューニングの効果が顕著に現れる。

July 1, 2020

Linux Observability with BPF 読書メモ

Linux Observability with BPFを読んだので、メモしておく。 今月、Brendan Gregg氏のBPF本も出るので、 それも読みたいな。 BPFを使うことで、カーネルのイベントをフックして安全にコードを実行できる そのコードがシステムを破壊したりクラッシュさせたりすることが無いよう、BPF側で検証してくれる カーネルモジュールと異なり、BPFプログラムはカーネル再コンパイルの必要がない BPFコードが検証された後、BPFバイトコードは機械命令にJITされる BPFプログラムは、bpf syscall によってBPF VMへとロードされる 2014年前半にAlexei Starovoitov氏がeBPFを導入した 過去のBPFでは2つの32bit registerのみが使えたが、eBPFでは10個の64bit registerまで使える 2014年6月にはeBPFはユーザスペースにも拡張された BPFプログラムタイプ:トレーシングとネットワーキングに大きく分類できる Socket Filter:カーネルに最初に入ったプログラムタイプ。観測目的のみ Kprobe:kprobe ハンドラとしてBPFプログラムを動かす。関数に入るときと出る時が、それぞれSEC(kprobe/sys_exec)とSEC(kretprobe/sys_exec)に対応する Tracepoint:事前にカーネル側で定義されたtracepointに対してBPFプログラムを動かす。/sys/kernel/debug/tracing/eventsで一覧を確認できる XDP:パケット着信時に、早い段階でBPFプログラムを動かす。 Perf:perf eventに対してBPFプログラムを動かす。perfがデータを吐き出す度にBPFプログラムが実行される Cgroup Socket:そのcgroup内のすべてのプロセスにアタッチされる Socket Option:Facebookはデータセンター内の接続において、RTOs(recovery time objectives)の制御に、これを使っている。 Socket Map:BPFでロードバランサを実装するときに使う。CilliumやFacebook Katranはこれを使っている。 BFP Vefirier CVE-2017-16995 のように、過去BFPをバイパスしてカーネルメモリをバイパスできる脆弱性があった DFSで、そのプログラムが終了すること、危険なコードパスが存在しないことを検証する 無限ループを弾くために、すべてのループを禁止している。ループ許可については、本書を書いている時点では、まだ提案の段階。 命令数は4096に制限されている bpf syscall の log_*を設定すれば、検証結果を確認できる BPFプログラムは tail calls によって、他のBPFプログラムを呼び出すことができる 呼び出し時にすべてのコンテキストは失われるので、何らかの方法で情報を共有する必要がある BPFマップ 直接bpf syscall でBPFマップを作成することができる bpf_map_createヘルパ関数を使うのが簡単 bpf_map_update_elem のシグネチャは、カーネル側bpf/bpf_helpers.hとユーザ側tools/lib/bpf/bpf.hで異なるので注意する エラー番号でsteerror(errno)で文字列にすると便利 bpf_map_get_next_keyはそのほかのヘルパ関数と違って、ユーザ側でのみ使えるので注意する array, hash, cgroup storage maps については、スピンロックがあるので、並行アクセス可能 array map は要素数だけ事前にメモリが確保されて、ゼロで初期化される。グローバルな変数確保のために使う? LRUハッシュやLRM(Longest Prefix Match)Trie mapもある Linux 4.4 からマップとBPFプログラムを仮想FSから扱えるよう2つのsyscallが追加された Tracing kprobes/kretprobes:stableでないABIなので、事前にprobe対象の関数シグネチャを調べておく必要がある。Linuxバージョンごとに変わる可能性がある BPFプログラムのcontextはプログラムによって変わる トレーシングポイントAPIはLinuxバージョンごとに互換性がある。/sys/kernel/debug/tracing/eventsで確認できる USDTs(user statically defined tracepoints):ユーザプログラムの静的なトレースポイント BPFTool 開発中が活発なので、Linux src からコンパイルする bpftool featureで何が利用できるか確認できる もしJITが無効ならecho 1 > /proc/sys/net/core/bpf_jit_enableで有効にできる bpftool map show や bpftool prog show でBPFプログラムやBPFマップの一覧を確認できる batch fileをバージョン管理しておくのがオススメ BPFTrace BPFの高レベルDSL awkのような感じでBEGINとENDと実際のトレーシング部分という構成 BPFマップを自動的に作ってくれるので便利 kubectl-trace BPFTraceプログラムをkubernetes jobとして実行する eBPF Exported BPFトレーシング結果をPrometheusに転送。Cloudflareで使われている XDP cotextとして渡されるxdp_buffは、sk_buffを簡単にしたもの。 3つの動作モードがある Native XDP:ドライバから出てすぐのところでBPFプログラムを動かす。git grep -l XDP_SETUP_PROG drive/で、対応するNICか確認する。 Offloaded XDP:git grep -l XDP_SETUP_PROG_HW drivers/で対応するNICを調べる。BPFプログラムをNICへオフロードする。 Generic XDP:開発者向けのテストモード。 XDPはユニットテストができる ユースケース Sysdig では troubleshooting tool を eBPF でOSSとして開発している Flowmill では データセンターネットワークの監視ツールを開発している。CPUオーばヘッドは0.1%~0.25%程度。 # SHELL # BPFプログラムをELFバイナリへコンパイル clang -O2 -target bpf -c bpf_program.c -o bpf_program.o # BPF用仮想FSのマウント mount -t bpf /sys/fs/bpf /sys/fs/bpf # USDTの確認 tplist -l ./hello_usdt # スタックトレースの取得とflamegraphの作成 ./profiler.py `pgrep -nx go` > /tmp/profile.out ./flamegraph.pl /tmp/profile.out > /tmp/flamegraph.svg # kubectl-traceの実行例 kubectl trace run pod/pod_identifier -n application_name -e <<PROGRAM uretprobe:/proc/$container_pid/exe:"main.main" { printf("exit: %d\n", retval) } PROGRAM # XDP BPFプログラムのロード # native mode がダメなら、generic mode で動く。強制することもできる ip link set dev eth0 xdp obj program.o sec mysection // C // bpf syscall でBPFマップを作成 int fd = bpf(BPF_MAP_REATE, &my_map, sizeof(my_map)); // bpf map 一覧取得 int next_key, lookup_key; = -1; while (bpf_map_get_next\key(map_data[0].fd, &lookup_key, &next_key) == 0) { printf("The next key in the map: %d\n", next_key); lookup_key = next_key; } # BCC (python) from bcc import BPF # kprobesの例 bpf_source = """ int do_sys_execve(struct pt_regs *ctx, void filename, void argv, void envp) { char comm[16]; bpf_get_current_comm(&comm, sizeof(comm)); bpf_trace_printk("executing program: %s", comm); return 0; } """ bpf = BPF(text = bpf_source) execve_function = bpf.get_syscall_fnname("execve") bpf.attach_kprobe(event = execve_function, fn_name = "do_sys_execve") bpf.trace_print() # tracepointの例 bpf_source = """ int trace_bpf_prog_load(void ctx) { char comm[16]; bpf_get_current_comm(&comm, sizeof(comm)); bpf_trace_printk("%s is loading a BPF program", comm); return 0; } """ bpf = BPF(text = bpf_source) bpf.attach_tracepoint(tp = "bpf:bpf_prog_load", fn_name = "trace_bpf_prog_load") bpf.trace_print() # uprobesの例 bpf_source = """ int trace_go_main(struct pt_regs *ctx) { u64 pid = bpf_get_current_pid_tgid(); bpf_trace_printk("New hello-bpf process running with PID: %d", pid); } """ bpf = BPF(text = bpf_source) bpf.attach_uprobe(name = "hello-bpf", sym = "main.main", fn_name = "trace_go_main") bpf.trace_print() # USDTの例 from bcc import BPF, USDT bpf_source = """ #include <uapi/linux/ptrace.h> int trace_binary_exec(struct pt_regs *ctx) { u64 pid = bpf_get_current_pid_tgid(); bpf_trace_printk("New hello_usdt process running with PID: %d", pid); } """ usdt = USDT(path = "./hello_usdt") usdt.enable_probe(probe = "probe-main", fn_name = "trace_binary_exec") bpf = BPF(text = bpf_source, usdt = usdt) bpf.trace_print() # BPFTrace のDSL # bpftrace /tmp/examble.bt で実行 BEGIN { printf("starting BPFTrace program\n") } kprobe:do_sys_open { printf("opening file descriptor: %s\n", str(arg1)) @opens[str(arg1)] = count() } END { printf("exiting BPFTrace program\n") }

December 14, 2019

TP-Link Archer T3U AC1300をLinuxから使う

USB接続の無線LAN子機 TP-Link Archer T3U AC1300 を、Thinkpad X1 Carbon 2015(Fedora release 29 (Twenty Nine)、linux 5.3.6-100.fc29.x86_64)から使えるようにしたので手順を残しておく。 # 公式HPではドライバが配布されていなかったので rtl88x2bu を使った git clone https://github.com/cilynx/rtl88x2bu cd rtl88x2bu/ git rev-parse HEAD # 962cd6b1660d3dae996f0bde545f641372c28e12 VER=$(sed -n 's/\PACKAGE_VERSION="\(.*\)"/\1/p' dkms.conf) sudo rsync -rvhP ./ /usr/src/rtl88x2bu-${VER} # /usr/src/<module_name>_<module_version> に移動させて dkms で管理する sudo dkms add -m rtl88x2bu -v ${VER} sudo dkms build -m rtl88x2bu -v ${VER} sudo dkms install -m rtl88x2bu -v ${VER} sudo dkms status sudo modprobe 88x2bu 2種類の子機(オンボードの子機とTP-Linkの子機)を同一のネットワークにつなぐと、metric によって優先順位が決まる。 metric の値を見ると、オンボードの子機側を優先していた(値が小さい)ので入れ替えた。 ip route sudo nmcli con mod <connection name> ipv4.route-metric 599 sudo systemctl restart NetworkManager 追記: Fedora 30、31にアップデートしたあとも特に問題なかった。 ...

November 9, 2019