Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
eBPFを使ったオレオレカーネル拡張入門
Search
Keisuke Nishimura
July 22, 2023
Programming
1
960
eBPFを使ったオレオレカーネル拡張入門
Kernel/VM探検隊@東京 No16
Keisuke Nishimura
July 22, 2023
Tweet
Share
More Decks by Keisuke Nishimura
See All by Keisuke Nishimura
Xenのスケジューラがぜんぜんわからん
mu_mu_mu
0
1.3k
C言語プログラムの構造とほんの少し解釈
mu_mu_mu
3
1.7k
Intel MPK入門
mu_mu_mu
0
710
Other Decks in Programming
See All in Programming
CSC509 Lecture 13
javiergs
PRO
0
250
ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界
shiita0903
1
320
AI駆動開発カンファレンスAutumn2025 _AI駆動開発にはAI駆動品質保証
autifyhq
0
160
問題の見方を変える「システム思考」超入門
panda_program
0
190
Web エンジニアが JavaScript で AI Agent を作る / JSConf JP 2025 sponsor session
izumin5210
3
1.1k
KoogではじめるAIエージェント開発
hiroaki404
1
460
flutter_kaigi_2025.pdf
kyoheig3
1
270
Querying Design System デザインシステムの意思決定を支える構造検索
ikumatadokoro
0
1.1k
知られているようで知られていない JavaScriptの仕様 4選
syumai
0
580
Introducing RemoteCompose: break your UI out of the app sandbox.
camaelon
2
540
PyCon mini 東海 2025「個人ではじめるマルチAIエージェント入門 〜LangChain × LangGraphでアイデアを形にするステップ〜」
komofr
3
940
なぜ強調表示できず ** が表示されるのか — Perlで始まったMarkdownの歴史と日本語文書における課題
kwahiro
11
5.5k
Featured
See All Featured
Thoughts on Productivity
jonyablonski
73
4.9k
GraphQLの誤解/rethinking-graphql
sonatard
73
11k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Why Our Code Smells
bkeepers
PRO
340
57k
Practical Orchestrator
shlominoach
190
11k
Making the Leap to Tech Lead
cromwellryan
135
9.6k
Large-scale JavaScript Application Architecture
addyosmani
514
110k
Code Reviewing Like a Champion
maltzj
527
40k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
249
1.3M
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Statistics for Hackers
jakevdp
799
220k
How to train your dragon (web standard)
notwaldorf
97
6.4k
Transcript
オレオレ カーネル拡張 入門 Kernel/VM 探検隊 Tokyo #16 mumumu / 西村
啓佑 (Keisuke Nishimura) ← Twitter: @mumumu_vm eBPFを使った 2023/07/22
発表内容 - eBPF基礎 - eBPFを使ったカーネルの拡張 - オレオレ カーネル 拡張 2
eBPF 基礎:概要 - カーネル内で動作する VM (In-Kernel VM) - 実行前に検証 =
安全 - 基本的に 特定のイベントをフックして実行 - BPF_PROG_TYPE_XDP - BPF_PROG_TYPE_SK_SKB - BPF_PROG_TYPE_KPROBE - … 3 それぞれを Program Type と呼称
eBPF 基礎:ユースケース例 - モニタリング: カーネル内データを柔軟に参照可能 - 高速パケット処理:コンテキストスイッチ無しで - システムコールのフィルタ:安全かつ効率的な 4
eBPF 基礎:ユースケース例 - モニタリング: カーネル内データを柔軟に参照可能 - 高速パケット処理:コンテキストスイッチ無しで - システムコールのフィルタ:安全かつ効率的な 5
これだけじゃない!
いま、eBPFを使ったカーネルの拡張が熱い! MemcachedのキャッシュをeBPFでカーネルに移植 [NSDI 2021] 6
いま、eBPFを使ったカーネルの拡張が熱い! eBPFでスケジューラのカスタマイズ [SOSP 2021] 7
いま、eBPFを使ったカーネルの拡張が熱い! eBPFを用いたストレージ処理高速化 (e.g. 高速なファイルトラバースとか) [OSDI 2022] 8
いま、eBPFを使ったカーネルの拡張が熱い! eBFPでロック機構をApp. 毎にカスタマイズ [OSDI 2022] 9
10 オレオレ カーネル 拡張 作りたくない? eBPFを使って
eBPFでカーネルを拡張できるおもちゃ作ってみた Program Type: BPF_PROG_OREORE プログラム仕様: - eBPF: u32 を受け取って計算した答えを返却 -
イベント: /sys/oreore/bpf への書き込み - 結果の出力: /sys/oreore/bpf の 読み込み - エラー処理やまともなVerificationは省略 11 ※Linux Kernel: 6.1.38 (最新のLTS)を使用
動作目標 12 sudo ./oreore_loader # bpfプログラムのロード
eBPF の機能追加レシピ 1. 新しい eBPF Program Type を定義 2. アタッチする機能を用意
3. プログラムを実行する関数定義 4. ユーザランドから呼び出し 13
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 14
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例:
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 15
BPF_PROG_TYPEいろんな使われ方をする(8回 def/undef)
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 16
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例:
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 17
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: 追加したいProgram Type @/include/uapi/linux/bpf.h enum bpf_prog_type
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 18
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: 予め作った次の2つの構造体: struct bpf_verifier_ops <このarg> ## _verifier_ops; struct bpf_prog_ops <このarg> ## _prog_ops; を参照する.(詳しくは後述)
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 19
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: prog_ctx_type kern_ctx_type = eBPFからみた = カーネルからみた eBPFプログラムの引数の型 kern_ctx_type prog_ctx_type 変換
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 20
BPF_PROG_TYPE(BPF_PROG_OREORE, bpf_oreore, __u32, u32); 今回の例: prog_ctx_type kern_ctx_type = eBPFからみた = カーネルからみた eBPFプログラムの引数の型 kern_ctx_type prog_ctx_type 変換 今回は自動の変換で事足りるが, 複雑な場合 bpf_verifier_opsの convert_ctx_access() で変換
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 21 bpf_verifier_ops 構造体の設定
(とりあえずこの2つを登録) さっきのマクロ第二引数
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 22 bpf_verifier_ops 構造体の設定
(とりあえずこの2つを登録) 引数addrから4 bytesのみ アクセス可能
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 23 bpf_prog_ops 構造体の設定
(test_run メンバを登録するとデバッグし易い)
#1 新しい eBPF Program Type を定義 ゴール:BPF_PROG_TYPEマクロで宣言 @ /include/linux/bpf_types.h 24
1. enum bpf_prog_type にProg Type 追加 2. bpf_{prog|verifier}_ops 構造体 を定義 3. 適切な引数型で BPF_PROG_TYPE マクロ #1 まとめ:
#2 アタッチする機能を用意 25 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する
bpf システムコールに当該機能を追加 今回の例: ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存
#2 アタッチする機能を用意 26 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する
bpf システムコールに当該機能を追加 今回の例: enum bpf_attach_type (@/include/uapi/linux/bpf.h) に BPF_OREORE_ATTACH を追加 ※ 本来 attach は fd に対して動作するが, 今回は簡単のため,fd 非依存にプログラムを適当なコンテキストに保存
#2 アタッチする機能を用意 27 ゴール:新しいProgram Type に対応するフラグを設定 1.BPF_PROG_TYPEに対応するフラグ BPF_OREORE_ATTACH を追加 2.対応する
bpf システムコールに当該機能を追加 今回の例: /kernel/bpf/syscall.c の以下の関数に追加 - bpf_prog_load_check_attach - attach_type_to_prog_type - bpf_prog_at(de)tach
#2 アタッチする機能を用意 28 ゴール:新しいProgram Type に対応するフラグを設定 bpf_prog_load_check_attach(...) ... + case
BPF_PROG_TYPE_OREORE: + if (expected_attach_type == BPF_OREORE_ATTACH) + return 0; + return -EINVAL; ... attach_type_to_prog_type(...) ... + case BPF_OREORE_ATTACH: + return BPF_PROG_TYPE_OREORE; ...
#2 アタッチする機能を用意 29 ゴール:新しいProgram Type に対応するフラグを設定 +struct bpf_prog __rcu *oreore_prog;
+EXPORT_SYMBOL_GPL(oreore_prog); +int oreore_bpf_prog_attach(struct bpf_prog *prog) +{ + rcu_assign_pointer(oreore_prog, prog); + return 0; +} ... bpf_prog_attach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_attach(prog); + break; ...
#2 アタッチする機能を用意 30 ゴール:新しいProgram Type に対応するフラグを設定 +struct bpf_prog __rcu *oreore_prog;
+EXPORT_SYMBOL_GPL(oreore_prog); +int oreore_bpf_prog_attach(struct bpf_prog *prog) +{ + rcu_assign_pointer(oreore_prog, prog); + return 0; +} ... bpf_prog_attach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_attach(prog); + break; ... この変数をLKM から参照し, progを実行する予定 (本来はfdからfilp を引いて 処理内容を追加)
#2 アタッチする機能を用意 31 ゴール:新しいProgram Type に対応するフラグを設定 +int oreore_bpf_prog_detach() +{ +
rcu_assign_pointer(oreore_prog, NULL); + return 0; +} ... bpf_prog_detach(...) ... + case BPF_PROG_TYPE_OREORE: + ret = oreore_bpf_prog_detach(); + break; ...
#2 アタッチする機能を用意 32 ゴール:新しいProgram Type に対応するフラグを設定 1. enum bpf_attach_type にattach
type を追加 2. bpf システムコールで使われる4つの関数に 1で追加したフラグの設定を追加 #2 まとめ: #1-2 の変更を反映済カーネルをコンパイル・実行
#3 プログラムを実行する関数定義 33 ゴール: bpf_prog_run()の呼び出し (#1-2で作ったカーネル上で実行) LKMを書いて /sys/oreore/bpf への write
から bpf_prog_run() を呼び出し 今回の例: u32 bpf_prog_run(const struct bpf_prog *prog, const void *ctx); • bpf_prog を実行し,その返り値を返却 • その際ctx(#1で定義したkern_ctx_type)が引数
#3 プログラムを実行する関数定義 34 ゴール:bpf_prog_run()の呼び出し sysfs の show/store の ops に登録
(storeの方) attach されているか等 確認・エラー処理必要
#3 プログラムを実行する関数定義 35 ゴール:bpf_prog_run()の呼び出し sysfs の show/store の ops に登録
(show の方)
#3 プログラムを実行する関数定義 36 1. kern_ctx_typeの引数を作成 2. bpf_prog_run()をコール #1-2 の変更を行ったカーネルで実行 #3
まとめ: ゴール:bpf_prog_run()の呼び出し
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 37 1. 入力(u32)に42を足すbpf 命令を錬成(気合い)
2. libbpfの bpf_prog_load() でロード 3. libbpfのbpf_prog_attach() でアタッチ 今回の例:
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 38 入力(u32)に42を足すbpf 命令を錬成(気合い) static
struct bpf_insn insns[] = { // R0 = *(r1+0) BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // R0 += 42 BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 42), // Exit BPF_EXIT_INSN(), };
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 39 入力(u32)に42を足すbpf 命令を錬成(気合い) static
struct bpf_insn insns[] = { // R0 = *(r1+0) BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0), // R0 += 42 BPF_ALU32_IMM(BPF_ADD, BPF_REG_0, 42), // Exit BPF_EXIT_INSN(), }; 本来はtools/bpf/以下を編集し llvm でバイトコード生成 + libbpfのインフラを使用
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 40 libbpf の bpf_prog_load()
でロード
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 41 bpf_prog_load() でロード オプションの指定が必要
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 42 bpf_prog_attach() でアタッチ 本来は,ターゲットの
fd を指定
#4 ユーザランドから登録 ゴール: bpfプログラムのロード & アタッチ 43 1. 対象 bpf
プログラムを錬成 2. bpf_prog_{run|attach}()をコール #1-2 の変更を行ったカーネル上で実行 #4 まとめ:
動作例 44 sudo insmod oreore_mod.ko # #3 /sys/oreore sudo ./oreore_loader
# #4 load/attach
ここから先は... - エラーハンドリング(今回はほぼ無視) - libbpfからのサポートやBTF機構関連の追加 - ヘルパー関数の定義 - アタッチ動作の改善 etc...
45
まとめ - eBPF = In-Kernel VM - eBPF を使ったカーネルの拡張が熱い! -
意外と簡単にeBPFでカーネルを拡張可能! 46
47
Verifier の設定:メモリアクセス 自分の理解が正しければ... check_mem_access()がメモリ系の検証を担当 - is_valid_access() は ctx からのオフセットにへの制約 -
check_mem_region_access() - その他... 48
bpf_verifier_ops
bpf_prog_ops