以前 Working with Unix Processesという本を読んだのですが、この本がUnixにおけるプロセスについて非常にわかりやすく解説されていました。それで自分で内容をメモしてみたり、さらにわからないところを調べたり、参考のプログラムをPerlで書いたり(この本ではRubyで書かれています)してみたのですが、ブログにまとめてなかったので、ちょっと書いてみます。
(注意)書いていたらすごく長くなりました。興味のある方は、適当に時間のあるときにでもどうぞ。
Chapter 2 : Introduction
- プロセスのことを知るとコードを読むだけでは分からないややこしい問題が分かるようになるよ
Chapter 3 : Primer
- manual
- man manで利用方法がわかる
- manual section
- manualにはsectionがある
- find(1)とかgetpid(2)とかの括弧の中
- commandはいくつかのsectionに渡ることもある
- man 2 statみたいにすれば見れる
- sectionの種類
- 1 : だれもが実行できるユーザコマンド
- 2 : システムコール、つまり、カーネルが提供する関数
- 3 : サブルーチン、つまり、ライブラリ関数
- 4 : デバイス、つまり、/dev ディレクトリのスペシャルファイル
- 5 : ファイルフォーマットの説明、例 /etc/passwd
- 6 : ゲーム
- 7 : その他 例: マクロパッケージや取り決め的な文書
- 8 : システム管理者だけが実行できるシステム管理用のツール
- 9 : Linux 独自のカーネルルーチン用のドキュメンテーション
- n : 新しいドキュメンテーション:
- o : 古いドキュメンテーション
- l : 独自のシステムについてのローカルなドキュメンテーション
Chapter 4 : Processes Have IDs
- pid : unique process id
- psを使えばprocess情報が見れる
- macでpsとかするとこんな感じに表示される
PID TTY TIME CMD 391 ttys000 0:00.81 -zsh
perlだと以下のようにすればpidが取れる
use Perl6::Say; say $$; # or use POSIX; say POSIX::getpid;
Chapter 5 : Processes Have Parents
- 全てのprocessは親をもつ
- lsコマンドを実行したら terminal -> bash -> lsみたいな関係ができる
- ppid = parent id
perlでppidを取得するには以下のようにする
use POSIX; say POSIX::getppid;
Chapter 6 : Processes Have File Descriptors
- Unixではすべてはfileであるという思想を持っている
- files, sockets, pipesがfileっぽく扱える
- processがresourseを開いたらfile descriptorがつけられる
- perlだとIO::Fileとか使ったらできる。STDIN, STDOUTとかも同じ。
- processが閉じたらfile descriptorは全部なくなる
- forkしたらfile descriptorは共有される
- 上限はデフォルトで1024まで
Perlでfile descriptorの値を取ってくるには以下のようにする。
warn fileno(STDIN); warn fileno(STDOUT); warn fileno(STDERR); use IO::File; my $fh = IO::File->new; if ($fh->open("hoge.pl")) { warn $fh->fileno; # filenoでfile descriptorの値がわかる $fh->close; }
Chapter 7 : Processes Have an Environment
Chapter 8 : Processes Have Names
- processは自身の状態を伝える手段をほとんど持っていない
- なので二つの仕組みを使う
- process name
- exit code
- process name
- $0で見れる
- $0に値を入れると名称が変わる
- psで見れる
perlでprocess nameを変える方法は以下のとおり。これをうまく利用すると、プロセスの状態をプログラム側から知らせることができる。
for (1..20) { $0 = "perl process : $_"; sleep 1; }
PID TTY TIME CMD 36720 ttys003 0:00.01 perl process : 5
Chapter 9 : Processes Have Exit Codes
- exit codeはプロセスが状態を伝える最後のチャンス
- 0-255
- 0は成功
perlでexit codeを取得するには以下のようにする。256で割るのは、返ってきた値の下位8bitに受け取ったシグナルの情報と、coredumpを吐いたかどうかの情報が入っているから。
my $code = system("perl exit-with-code.pl") / 256; warn $code;
waitpidとかしている場合は$?とかでexit codeが取れる。
Chapter 10 : Processes Can Fork
- forkすることでprocessはcopyを作る
- child processはparentのメモリ内容のコピーを継承
- file descriptorsとかも継承される
- これによりfileとかsocketとかは共有できる
- childは自身の持っているメモリに対して、parentに影響なく書き込みを行える
- メモリ内容をコピーするので、forkしまくるとメモリ消費が多くなる
- Copy on Write的なものも関係するので一概には言えない
perlだとforkという関数でfork可能。forkコマンドは
- 親だと子プロセスのpidを返す
- 子供だと0を返す
- errorが起こったらundefを返す
my $pid = fork; die "Cannot fork: $!" unless defined $pid; if ($pid) { warn "pid:$$ is a parent process(child pid is $pid)"; wait; } else { warn "pid:$$ is a child process(parent pid is $0)"; }
Chapter 11 : Orphaned Processes
- ちゃんとハンドリングしなかった場合、parentが死んでもchildは生き続ける
- 管理方法は2つ
- daemon process
- processとsignal communicate
以下を実行すると親は死んでいるがI'm an orphan!と5回表示される
my $pid = fork; die "Cannot fork: $!" unless defined $pid; if ($pid) { warn "pid:$$ is a parent process(child pid is $pid)"; die; } else { for (1..5) { sleep 1; warn "I'm an orphan!" } }
Chapter 12 : Processes Are Friendly
- Copy on Write
- processをforkで作った後、書き込みが起こった時にcopyを行うこと
Chapter 13 : Processes Can Wait
- waitで子プロセスを待てる
- waitpidとかを使えばあるプロセスを待つことができる
- $? / 256で子プロセスの終了ステータスが取れる
- 256で割るのはwaitシステムコールがexit codeを256倍するから
- 下位8bitにはsignal情報とcore dumpがわかる(http://d.hatena.ne.jp/perlcodesample/20090415/1240762619)
- 子プロセスが終わった時に親プロセスが寝てても、その結果はキューイングされている
子プロセスが終わった時に親プロセスはsleepしているがちゃんと$?とかが取れる。
my $pid = fork; if ($pid) { warn 'parent'; sleep 5; my $pid2 = waitpid($pid, 0); warn $pid2; warn $? / 256; } else { warn 'child'; sleep 3; warn 'finish child'; exit 10; }
Chapter 14 : Zombie Processes
Chapter 15 : Processes Can Get Signals
- processはsignalを受け取れる
- SIGCHLDとかは子プロセスが終了した時に通知される
- waitをnonblockingにするにはwaitpidを使う
すべての子供が死ぬまでずっと何かし続けている例。perlだとuse POSIX ":sys_wait_h";をして、WNOHANGをwaitpidの第二引数に渡してあげると非同期にプロセスを待てる。
use POSIX ":sys_wait_h"; # 5回fork for (0..4) { my $pid = fork; if (!defined $pid) { die "fork failed"; # 生成に失敗した } elsif (!$pid) { sleep int rand(10); print "$$ exit"; exit; } } my $count = 0; while (1) { my $pid = waitpid(-1, WNOHANG); warn '================'; if ($pid) { warn "exit $pid"; $count++; exit if $count == 5; } warn 'heavy task : ', rand(); sleep 1; }
- signalを受け取った時kernelはいずれかを実行
- 無視
- 指定したaction
- default action
- signalはkernelを仲介人として、プロセスからプロセスに配信される
perlだとkillを使ってsignal送信できる。
my $pid = ...; kill(2, $pid);
- signal handlerの設定も出来る
perlだと%SIGに設定をしてあげる事でsignal handlerの設定を行える。
use strict; use warnings; warn $$; local $SIG{INT} = 'IGNORE'; sleep 100;
Chapter 16 : Daemon Processes
- daemon processは基本的にbackgroundで動くようなprocess
- 全ての親はinit process
- pidは1、ppidは0、つまり親がない
- forkした親が死んだら子はinit processの子になる
以下を実行するとppidは1を返す。つまり、init processの子になった。
use POSIX qw(setsid); my $pid = fork; if ($pid) { exit; } else { sleep 1; warn POSIX::getppid; }
- process管理にはprocess groupとsessionという概念がある
- sessionがprocess groupをまとめ、process groupがprocessをまとめる
- http://www.freebsd.org/doc/ja_JP.eucJP/books/design-44bsd/overview-process-management.html あたりがわかりやすい
- setsidを使うと、そのプロセスがsession leader, process group leaderになる
getpgrpを使うとprocess group idがわかる。forkすると親のprocess idを元にしてprocess groupが作られる事がわかる。
use POSIX qw(); warn POSIX::getpid; warn getpgrp; if (fork) { wait; } else { warn POSIX::getpid; warn getpgrp; }
pipeをするとそのコマンド群がプロセスグループとなる。
git log | grep shipped | less
session idを見るにはpsにオプションを付けるといい
ps ax -o pid,state,sess,command
- daemon processとは、インタラクティブに処理を行わず,時間やある一定のトリガーによって動作する常駐プロセスのこと
- セッションから切り離す
- 標準入力、標準出力、標準エラー出力から切り離す
- デーモンプロセス詳細 : http://pinka99.ddo.jp/nanao/work/daemon.html
以下のようにすることでperlでdaemon processを作ることができる。
use POSIX qw(setsid); my $pid = fork(); if ($pid != 0) { print "create daemon process...\n"; exit; } else { umask 0; chdir '/'; open STDIN, '<', '/dev/null'; open STDOUT, '>>', '/dev/null'; open STDERR, '>>', '/dev/null'; setsid; while(1){ warn "hello\n"; sleep 2; } }
またデーモン化するなら、孫プロセスを作ったほうが良いらしい。
- デーモンプロセス詳細 : http://pinka99.ddo.jp/nanao/work/daemon.html
- 孫プロセスを作る理由 : http://d.hatena.ne.jp/sleepy_yoshi/20100228/p1
Chapter 17 : Spawning Terminal Processes
- execによってprocessを違うprocessに変換できる
- execを実行すると元のprocessには返ってこない
- fork + execの場合も、waitで待てる
途中から子プロセスがsleep 10というprocessに変換される。
my $pid = fork; if ($pid) { my $res = wait; warn $res / 256; } else { sleep 5; exec "sleep 10"; }
- perlではprocessを作成すると...
- systemはexit codeが返ってくる
- ``はSTDOUTが返ってくる
- openとかもいろいろコマンド実行できる
その他
並列処理パターン
Perlにおける並列処理パターン。適当に書くとこんな感じ。
use Proc::Wait3; my %worker_pids; my $num_workers = 4; # ワーカープロセスの数 my $exit_loop; # サーバの初期化処理 # SIGTERM ハンドラをセット $SIG{TERM} = sub { $exit_loop = 1 }; # メインループ ($num_workers 個のワーカープロセスを駆動) while (! $exit_loop) { my $pid; if (keys %worker_pids < $num_workers) { $pid = fork; die 'fork error' unless defined $pid; if ($pid) { $worker_pids{$pid} = 1; } else { exit(child()); } sleep 1; } if (my ($exit_pid) = wait3(! $pid)) { delete $worker_pids{$exit_pid}; } } # ワーカーを停止して終了 foreach my $c (keys %worker_pids) { kill 'TERM', $c; } while (%worker_pids) { if (my $exit_pid = wait) { delete $worker_pids{$exit_pid}; } } exit 0; # ワーカープロセス sub child { # SIGTERM ハンドラをセット $SIG{TERM} = sub { exit 0 }; # 実際の処理 warn '======= start ==========='; sleep 10; # 終了コードを返す 0; }
実際作るなら、Parallel::Preforkとか、Parallel::ForkManagerとか使うといい。
Server::Starter
hot deployを可能にしてくれる。
以前まとめたので、Server::Starterから学ぶhot deployの仕組み - $shibayu36->blog;を参照してください。