Linuxのしくみ
武内覚, 2018
chap. 2 システムコールの基本
strace -o hello.log ./hello
システムコールの呼出履歴を記録
-Tオプションで所要時間をマイクロ秒精度で測定可能
sar -P ALL 1 (1はinterval)
CPU timeの使用割合を表示
-qオプションで見られるrunq-szが実行中・実行待ちプロセスの合計数
ldd ./hello
バイナリがリンクしてる動的ライブラリを参照
man 1 man
1: 汎用コマンド
2: システムコール
3: ライブラリ関数
static void function() {}
staticが付いた関数は同じ.cファイルからしか参照できない
chap. 3 プロセスの作成
fork() -> clone() syscall
parentにはchild pidが、childには0が返る
メモリはコピーされるが、各プロセスからの見た目のアドレスは変化しない
readelf -h/-T /bin/sleep
-hでelfファイルからエントリポイントアドレスなどが取得できる
-Tでコード領域、データ領域のファイル内オフセット、サイズ、開始アドレスが得られる
/proc/pid/maps
実行中のプロセスのメモリマップが得られる
r-xpとなっているのがコード領域、rw-pとなっているのがデータ領域
execve() -> execve() syscall
読み込んだELFに指示された内容に現在のプロセスメモリを書き換え、エントリポイントから実行
chap. 4 プロセススケジューラ
taskset -c 0 ./sched
指定プログラムの実行コアを固定できる
ビジーループを複数fork()させるとラウンドロビンの様子が確認できる
プロセスが動いてないのはsleep状態、CPUが動いてないのはアイドル状態
ps -eo pid,comm,etime,time
好きな項目を表示できる。etimeはelapsed (real) time, timeは使用CPU時間
CPUを専有できてるプロセスはetime~timeになる
nice(5)
nice値(大きいほど優先度が下がる)を設定できる。デフォルトでゼロ
nice -n 5 echo helloのようにniceコマンドを使うこともできる
chap. 5 メモリ管理
sar -r 1
メモリ情報の逐次レポート free コマンドでも同じような情報が得られる
available memoryがなくなるとOOM killerが発動
仮想記憶
ELFにはコード領域・データ領域のオフセット、サイズ、メモリマップアドレスが指定
メモリ上の任意の位置に実際に割り当てるにはどうするべきか?(実アドレスは不定)
プロセスごとそれぞれ見た目のアドレス(仮想アドレス)を与え、ページテーブルで実アドレスと対応付ける
仮想アドレスは/proc/PID/mapsやポインタに入ってる値として確認できる
カーネルメモリは全てのプロセスのアドレス空間にマップされているが、アクセスできるのはカーネルモードのみ
glibcのmalloc()は内部でmmap()を利用している
ファイルマップ
mmap()の第5引数にファイルディスクリプタ(open()で得られる)を指定
flush時にファイルに書き戻される
hello[LF]をhelloHEとmemcpyで書き換えると、書き込み時にはhelloHまでしか書かれない
元のファイルの長さを超えて書くことはできない
ftruncate(fd, length)でファイルサイズを変更できる(伸長分は\0が入る)
デマンドページング
仮想アドレスが割り振られても、実際にアクセスが発生するまでは物理アドレスに割り当てられない
初めてアクセスする際にpage faultが発生し、物理メモリが割り当てられる(sar -r 1のkbmemusedで確認できる)
sar -B でページフォールトの秒間発生回数を監視できる
ps -eo pid,comm,vsz,rss,maj_flt,min_flt
vszは仮想メモリ量、rssは物理メモリ量、maj_fltとmin_fltの和がページフォルト回数
コピーオンライト
fork()実行時にはページテーブルだけコピーし、親プロセスの全ページ書き込みを不可能にする
読み込みは共有されたメモリに対して行う
どちらかのプロセスが書き込みを試みた際にページフォールトが発生、当該ページをコピーして書き込み可能で割り当てる
他方のプロセスに対しては、元のページの書き込みを許可する
ps -o pid,comm,vsz,rssで表示される仮想・物理メモリ量は共有中はそれぞれで重複して表示される
スワップ
sar -W 1でスワップイン・スワップアウトの統計情報が見られる
sar -S でスワップ使用量の統計情報が見られる
階層型ページテーブル
ページテーブルはエントリあたり8バイト、64bitなら仮想アドレス空間は128TBで128TB/4096Bページが存在
ページエントリを全て愚直に書き出すと256GBもの容量が必要になる
実際には階層的に親ページテーブルから子ページテーブルが参照される構造になっている (x86_64なら4段)
sar -r ALL 1のkbpgtblを見るとページテーブルが占めるメモリ容量を確認できる
大量のページテーブルエントリはfork()の性能も劣化させるので、ヒュージページテーブルで対処することがある
chap. 6 記憶階層
キャッシュ性能の比較
L1, L2, L3それぞれに乗るサイズの書き込み速度実験
L1 (48kB → 4,8,16,32) : ~0.5ns
L2 (512kB → 64,128,256,512) : ~1ns
L3 (8192kB → 1024,2048,4096) : ~2ns
memory (8192, 16384, 32768) : 3.6ns, 5.1ns, 5.6ns
局所性が重要
TLB (translation lookaside buffer)
ページテーブルのキャッシュ相当物
ページキャッシュ
メインメモリとキャッシュの関係と同様の、ストレージのキャッシュ
物理メモリのカーネル領域上に作成される
ダーティページの内容が適当なタイミングでストレージに書き戻される
dd if=/dev/zero of=testfile oflag=direct bs=1M count=1K
oflag=directを指定するとページキャッシュが発生しない
cat testfile最初に行うと実行時間がほぼsystemに消費され、freeで見られるbuff/cacheが1GB増加する
2回目のcatはキャッシュが効くので10倍くらい高速化
sar -Bでページイン・アウト回数、sar -d -pでデバイスごとの読み書きを監視できる
ライトバック周期はsysctl vm.dirty_writeback_centisecsで確認・変更できる
ライトバックを強制するダーティページ割合はsysctl vm.dirty_background_ratio
chap.7 ファイルシステム
ジャーナリング
アトミックな操作をまずメタデータ(ジャーナル)に書き出してから実行、完了したらジャーナルを削除
途中で電源遮断等が起こったときも、ジャーナル内容を再実行することで整合性を保てる
デバイスファイル
ls -lで属性文字列の最初の一文字がcならキャラクタデバイス、bならブロックデバイス
キャラクタデバイス
読み書きができるがシークはできない(例:端末)
echo hello > /dev/pts/1などと書き込むと画面表示される
ブロックデバイス
ランダムアクセス可能
strings -t x /dev/sda3でブロックデバイス中の文字列と位置を抽出
dd if=... of=/dev/sda3 seek=$((0x01234567)) bs=1で当該領域に書き込み(マウント解除してやるべし)