2012年5月29日火曜日

いまどきのUNIXプログラミング

久々にblog更新しようとしたら、UIが一新されててビックリ。

さて、しばらく前の話になりますが、やや若い世代の人と集中的に開発を行う機会がありまして。「epoll使っていいですか、selectってあまり使った事ないので」と言われて愕然。当たり前と言えば当たり前なんだけど、90年代に身に着けたUNIXの知識もいまや年代物。少しはアップデートしないとなぁ・・・という事で本を読んで勉強したので、そのメモ。もしろん昔からあるけど知らなかったって事も沢山ありました。

読んだのは「LINUXシステムプログラミング」というO'REILLY本。400ページ弱という(この手の本にしては)薄い本なのだけど、興味深い話題が多く楽しんで読めました。以下、この本によってアップデートされた私の知識の項目一覧と概説。これを見て「おぉ」と思った人は仲間なので買って損はないと思う。

ファイル・I/O


ノンブロッキングI/O

O_NONBLOCKによるノンブロッキングI/O。知らなかったわけじゃないけど、自分のアプリでは使った事なかった。

同期I/O、ダイレクトI/O

fsync()とfdatasync()。あと、O_SYNC、O_DSYNC、O_RSYNCとか。O_DIRECTも。


ファイルサイズを超えたシーク

あぁ、これやるだけでsparse file作れたんだ・・・知らなかった。


ファイルポジション指定I/O

pread()、pwrite()とか。これはqemuの移植やってた時に見かけたのでちょっと前から知ってた。PepperのFileIOってpread()/pwrite()系列のAPIになってますね。


I/Oの多重化

select()ループがクールだった時代はとっくに終わってた。シグナルハンドラでできるのはpipeに書いてselectを起こす事くらい・・・と信じてた人はpselect()を調べるべし。あと知らなかったけどSystem V系ではpoll()とかppoll()というのがある。でも、今時Linuxでサーバ書くなら大量のデスクリプタに対してもスケールするepoll_*系関数群を使うべき。期待した通りのインタフェースを持ってる。Win32のWaitForMultipleObjectsより賢い多重化が可能になったと思う。


scatter-gather I/O

readv()とwritev()。リンクアレイチェーン転送的なI/Oインタフェースと言えば一部の人には分かりやすいかも。


ページサイズ取得

普段みかけるのはgetpagesize()ばかりなんだけど、こっちはLinux固有で、POSIX的にはsysconf(_SC_PAGESIZE)が正しいらしい。確かにNative Clientもsysconf()は持ってる。

I/Oスケジューラ

Deadline I/Oスケジューラ、Anticipatory I/Oスケジューラ、CFQ I/Oスケジューラ、Noop I/Oスケジューラ。4.6節でいきなりOSの教科書かと思うような細かいスケジューラの解説があります。なんでかと思ったら、Linuxは手軽にI/Oスケジューラが差し替えられるんですね。この辺は純粋なOS好きにとっても楽しめる内容かと。


拡張属性

xattr。時代はレジストリ、もといBFS。


ファイルイベント

inotify_*系のインタフェース。さらば、SIGHUP。


プロセス管理


プロセス階層

あんまり真面目に見て来なかったプロセスグループの話。


ゾンビプロセス

そういえば、なんでプロセス終了時に子プロセスを回収する処理をOSレベルで走らせないんだろ。ゾンビとして生かしておく根拠を知らないなぁ・・・。wait系は直接の子供しか待てないという理解なのだけど。


ユーザとグループ

プロセスのユーザIDは、実ユーザID、実効ユーザID、savedユーザIDの3種類ある・・・知らなかったし、一度読んだだけでは覚えられない・・・。


セッションとプロセスグループ

シェル書いてジョブコントロールとかしようとすると必要になる知識なんだろうけど。そう言えば、ちょっと前にLinuxに入ったスケジューラの改善って、この辺をうまく考慮してスケジューリングするとかなかったっけ?
※記憶と照合するとStaircase Schedulerの事だったのかなぁ・・・。でも、それももうobsoleteっぽい。ちなみに「Linux カーネル 2.6 Completely Fair Scheduler の内側」はスケジューラに関する良い記事な気がする。


デーモン

日頃お世話になっているdaemonさん。実は然るべき正規の手順が存在していたのですね・・・。新規プロセスグループのリーダープロセスにした上でchdir('/')して標準入出力は全て/dev/nullにする。わりと面倒な処理が必要みたいだけど、実はdaemon()ってライブラリ関数がある・・・というのも知らなかった。


I/Oの優先度

プロセス優先度だけでなく、ioprio_get()/ioprio_set()というインタフェースでプロセスのI/O優先度を指定できるらしい。ただしCFQ I/Oスケジューラ選択時に限る、か。


プロセッサアフィニティ

sched_getaffinity()とsched_setaffinity()。なるほど、SunOS+Oracle以外でも今や当たり前のインタフェース。


名前空間

Linuxでは(ファイルシステムの)名前空間はシステム固有ではなくプロセス固有。古典的なUNIXではシステム固有。Plan 9由来ですね。逆に、これがないとchrootってどうしてるんだろ。単にアクセス範囲をサブツリーに限定してるだけ?


メモリ管理


アラインメント

posix_memalign()。


/dev/zeroのmmap

そう言えば、そんなテクニックもあった。


高度なメモリ割り当て

mallopt()、malloc_usable_size()、malloc_trim()、mallinfo()あたりは知っていると使う機会もありそう。


スタック上への文字列コピー

strdupa() = alloca() + strdup()。C言語だけで開発する機会ってのも無くなってきてるので、知ってて役に立つかは微妙だけど。


ピンダウン

mlock()、mlockall()、munlock()、munlockall()。mincore()でスワップ判定。


オーバコミットとOOM

OOM Killerとの付き合い方。そういえば青山Killer物語の舞台って青山キラー通り


シグナル

「古い実装ではシグナルを喪失することがありました。」いえ、それは私が知っているシグナルの実装、そのものです・・・。古い実装ではシグナル送信はプロセスにただ1つ存在するバッファにシグナル情報を保存して戻るだけの単純な実装で、プロセスは起きる前にバッファを確認し、存在していれば最新のただ1つのシグナルを処理してました。ところが、今時のシグナルを考えると、他にも考えなければならない問題が。
例えば、この本にも載ってなかったけど、マルチスレッド環境でシグナルを処理するのは誰(どのスレッド)か。答えは任意のスレッド。具体的な実装を見たわけではないけど、シグナルが積まれた後、最初に起こすスレッドが処理するのが一番ナイーブな実装か。特定のスレッドで受けたければ、他のスレッドはシグナルマスクを設定するのが作法らしい。


時間


時刻表現

micro秒のtimevalとnano秒のtimespec。最近はnano系が標準化されてるので、timespec系に一本化しとくと間違いなさそう。


POSIXクロック

clock_gettime()。Native Client内でも動いてるので重宝してます。


スリープ

秒単位のsleep()、micro秒単位のselect()ってのが古典の世界。今はmicro秒単位のusleep()もあるけど、nano秒単位のnanosleep()、clock_nanosleep()が標準化の観点からもオススメらしい。


タイマー

POSIXクロックの一部。time_*系関数群。


と、まぁ、こんな感じです。この本には出て来なかったけど、ttyとかも深い闇なんだよなぁ。未だに理解しきれてないプロセスグループやネットワークスタックなんかの話も含めて、いずれBSD本あたりを熟読したい次第。