Upgrade to Pro — share decks privately, control downloads, hide ads and more …

その47 日常ソースコードリーディング pgrepの使い方を間違えたのをきっかけにLinuxカーネルのコードを読む

その47 日常ソースコードリーディング pgrepの使い方を間違えたのをきっかけにLinuxカーネルのコードを読む

以下動画のテキストです。
https://youtu.be/n34LCB7Iwig

Satoru Takeuchi
PRO

August 24, 2022
Tweet

More Decks by Satoru Takeuchi

Other Decks in Technology

Transcript

  1. 日常ソースコードリーディング pgrepの使い方を間違えたのをきっかけに Linuxカーネルのコードを読む Aug. 24th, 2022 Satoru Takeuchi twitter: satoru_takeuchi

    1
  2. 日常ソースコードリーディングとは • 日常でソフトウェアの問題に遭遇したのをきっかけにソースコードを読んだ経験を共 有 • 学べること ◦ 人はどんな動機でソースを読むのかという例 ◦ ソースコードリーディングのテクニック

    ◦ 土地勘がないプログラミング言語、ソフトウェアの知識 2
  3. 前提知識: pgrepコマンド • 拡張正規表現にマッチしたコマンドのpidをリスト ◦ ほかにもマッチ条件 (uidとか)はあるが、今回は説明しない • 使用例 $

    cat foo.sh #!/bin/bash sleep infinity $ ./foo.sh & [2] 1086408 $ pgrep "foo\.sh" 1086408 3
  4. 問題発生 foo.shプログラムと同じことをするfoo-bar-baz-hoge-huga.shだとリストが空 $ ./foo-bar-baz-hoge-huga.sh & [2] 1086868 $ pgrep "foo-bar-baz-hoge-huga\.sh"

    $ 4
  5. 仕様を確認 • man pgrepより抜粋 NOTES The process name used for

    matching is limited to the 15 characters present in the output of /proc/pid/stat. Use the -f option to match against the complete command line, /proc/pid/cmdline. 5
  6. 疑問 • この15バイトという制限はどこから来ているのか 6

  7. まずはprocfsのmanを確認 /proc/[pid]/stat Status information about the process. This is used

    by ps(1). It is defined in the kernel source file fs/proc/array.c. … (2) comm %s The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out. 7
  8. 続いてカーネルソースを読む • /proc/<pid>/statの読み出すときに動くコードを見る • カーネルバージョン: 5.4.0-110 • 入り口はfs/proc/array.c. • まずは“stat”で検索するとだいたいの場所がつかめる

    ◦ そこから呼び出し元をたどっていく ◦ 土地勘のあるコードだと素早く読める 8
  9. やること • なぜ15バイトなのかをたしかめる • 具体的にどういう文字列が入るのかをたしかめる 9

  10. やること • なぜ15バイトなのかをたしかめる • 具体的にどういう文字列が入るのかをたしかめる 10

  11. pgrepのマッチ対象文字列の情報源 • プロセス(*1)ごとにあるtask_stuctの中 ◦ 固定長配列で長さが 16バイト ◦ 文字列末尾にはヌル文字が入るので最長 15文字 include/linux/sched.h

    … #define TASK_COMM_LEN 16 … struct task_struct { … char comm[TASK_COMM_LEN]; … 11
  12. やること • なぜ15バイトなのかをたしかめる • 具体的にどういう文字列が入るのかをたしかめる 12

  13. task_struct.commに入る文字列 • 内容は実行ファイルパスの最後の”/”の後の部分 ◦ basenameコマンド実行結果とおなじ void setup_new_exec(struct linux_binprm * bprm)

    { … __set_task_comm(current, kbasename(bprm->filename), true); … include/linux/string.h fs/exec.c static inline const char *kbasename(const char *path) { const char *tail = strrchr(path, '/'); return tail ? tail + 1 : path; } 13
  14. pgrepとpgrep -fのちがい • -fがあると15文字制限がなくなるとかいう単純な話ではない • -fのマッチ対象はコマンドライン全体(C的に言うとargv全部) • 微妙な違い ◦ argv[0]

    != コマンド名 ◦ オプションにもマッチしてしまう 14
  15. task_struct.commの役割 • カーネル内から人間が読めるかたちでコマンド名を得たいときがある ◦ 例: dmesgにOOM killerで殺したプロセスの名前を出す • argvはページアウト(スワップアウト)している可能性がある ◦

    コマンド名参照のたびに毎度ページインさせてられない (そもそもできないこともある ) • かといってクソ長コマンド名をカーネル内にたくさんもつのも嫌 • いい落としどころがtask_struct.comm 物理メモリ argv カーネルのメモリ (ページング非対象) プロセスのメモリ (ページング対象) 15
  16. まとめ • pgrep ◦ デフォルトでカーネルが定義するところのコマンド名にマッチ ◦ コマンド名は/proc/<pid>/statファイルの第二フィールドの両端の ”(”と”)”を取ったもの ◦ プロセス起動に使った実行ファイル名の

    basenameの先頭15バイト ◦ -fオプションをつけると細かいマッチをさせられるが注意が必要 • そのほか ◦ ほんのちょっとしたきっかけでソースコードを読む機会は生まれる ◦ 目的があれば読むのが続く ◦ 土地勘のあるコードだと読むのが楽 ◦ 仕事だともっと厳密にやろうね! (pgrepのソースも見るとか) 16