TT Lab
はじめる
学ぶ 学習パス コース

オペレーティングシステム

プロセスを目で見る

TT Labで続きを見る

目標

OSの教科書で読んだことを実際に自分の手で作ってみます。ゾンビ、孤児、仮想メモリ、 開いているファイルなど、すべてこのPodの中で数行あれば作れます。

ここで学ぶことは、あとで障害を調べるときにそのまま役立ちます。「ディスクが空かない」、 「メモリをなぜこんなに使っているのか」、「Podが終了しない」への答えは、すべてここにあります。

見る場所

Linuxはカーネルの状態をファイルとして見せてくれます。

ps -eo pid,ppid,stat,comm     # 프로세스 목록
ls /proc/<PID>/task           # 그 프로세스의 스레드들
ls -l /proc/<PID>/fd          # 열어 둔 파일들
cat /proc/<PID>/status        # 메모리를 포함한 상태 전부

/procは本物のディスクではなく、カーネルが作ってくれる画面です。

バックグラウンドで起動するとき

Pythonの出力が見えないときは、flush=Trueを付け忘れたことが原因です。

print(os.getpid(), flush=True)

ステップ

  1. プロセスの系譜 → 01-tree.txt
  2. スレッド → 02-threads.txt
  3. ゾンビ → 03-zombie.txt
  4. 孤児とPID 1 → 04-orphan.txt
  5. VSZとRSS → 05-memory.txt
  6. 削除したファイル → 06-deleted.txt
  7. TERMとKILL → 07-signal.txt
  8. まとめ → 08-notes.md

参考

ステップ5とステップ6では、数値を両方とも残す必要があります。前後の比較が、そのステップのすべてだからです。

誰が誰を生んだのか

今のPodで動いているプロセスの親子関係を取り出し、01-tree.txtに残してください。自分のシェルのPIDとその親も、あわせて書きます。

ps -eo pid,ppid,stat,commを使います。自分のシェルのPIDはecho $$、その親はps -o ppid= -p $$で確認できます。

すべてのプロセスには親がいます。親が子をforkで作り、子が終わると親がwaitで結果を刈り取ります。この2つの文が、次の3つのステップのすべてです。

スレッドはどこにあるのか

スレッドを4つ作るプログラムを起動し、psではプロセスが1つに見えますが、実際には複数あることを02-threads.txtに示してください。

Pythonならthreading.Threadを4つ作れば十分です。print(os.getpid(), flush=True)でPIDを出力してください(flush=Trueがないと出力が見えません)。

確認はls /proc/<PID>/task | wc -lで行います。メインの1つとスレッド4つで、5と表示されます。Linuxでスレッドは、メモリを共有するタスクにすぎず、カーネルから見るとプロセスと大きくは変わりません。

ゾンビを作る

子が先に終了したのに、親が刈り取らない状態を作り、psでZと表示されることを03-zombie.txtに残してください。

os.fork()で子を作り、子はos._exit(0)、親はwait()を呼ばずに眠っていれば十分です。

ゾンビはメモリを使いません。終了したという事実と終了コードだけが残っている場所です。ただしPIDは占有するので、溜まると新しいプロセスを作れなくなります。

親が先に死ぬと

親が子より先に死ぬ状況を作り、その子のPPIDが何に変わるかを04-orphan.txtに残してください。このPodのPID 1が何であるかも、あわせて残します。

ps -o ppid= -p <자식PID>で確認します(プレースホルダーは子のPIDです)。親を失ったプロセスはPID 1に引き継がれます(リペアレンティング)。

続けてps -o comm= -p 1を見てください。コンテナのPID 1は、たいていinitではなくアプリケーション自身です。initでなければ、引き継いだゾンビを刈り取りません。これがコンテナでゾンビが溜まる理由であり、--initやtiniを使う理由です。

予約したメモリと実際に使ったメモリ

300MBを予約だけして、そのうち80MBだけを実際に触るプログラムを起動し、VSZとRSSがどう違うかを05-memory.txtに残してください。

mmap.mmap(-1, 300*1024*1024)で予約し、触る前と後のps -o vsz=,rss= -p <PID>を両方残してください。

bytearray(300*1024*1024)は使えません。Pythonが0で埋める際に、すでにすべて触れてしまうからです。

VSZはアドレスを確保した量、RSSは実際に物理メモリが割り当てられた量です。300MBを予約しても、RSSは10MBにもなりません。

削除したのに容量が戻らない

64MBのファイルを作り、開いたまま削除してから、dfで容量が戻らないことを示し、プロセスを終了すると戻るところまで06-deleted.txtに残してください。

dd if=/dev/zero of=big.bin bs=1M count=64で作成し、Pythonでopen()した後にos.remove()して眠らせれば十分です。

証拠はここにあります。ls -l /proc/<PID>/fd/にbig.bin (deleted)として残ります。

ファイル名を消すことと、データを消すことは別です。最後の参照(名前でも開いているfdでも)がなくなって初めて、ブロックが返却されます。ログを削除したのにディスクが空かない事故の原因は、まさにこれです。

TERMとKILLの違い

SIGTERMを受け取って処理はするものの、終了はしないプログラムを作り、TERMでは生き残りKILLでは死ぬことを07-signal.txtに残してください。

signal.signal(signal.SIGTERM, 핸들러)を使います(プレースホルダーはハンドラー関数です)。確認はkill -TERM <PID>のあとにkill -0 <PID>(生きていれば成功)、そのあとにkill -KILL <PID>です。

TERMはお願いで、KILLは通告です。TERMはプログラムが受け取って後始末をする機会を与え、KILLはカーネルがそのまま消し去ります。そのため後始末ができません。Kubernetesが終了するときにまずTERMを送り、terminationGracePeriodSecondsだけ待ってからKILLする理由がこれです。

3つのことをまとめる

08-notes.mdに3行以上書いてください。ゾンビとは何でなぜ生まれるのか、VSZとRSSの違い、削除したファイルの容量がいつ戻るのか、の3点です。

本文に좀비、RSS、참조が含まれている必要があります(韓国語の2つの語は、それぞれ「ゾンビ」と「参照」を意味します)。