Perlで学ぶ「詳解 UNIXプログラミング」(その8) 第8章 プロセス制御
はじめに
「詳解 UNIXプログラミング」の第八章を示します。
8.1 fork関数の例
forkでは親プロセスと子プロセスのメモリ空間が異なるため
グローバル変数も共有しません。これはマルチスレッドと異なる点です。
#!/usr/bin/env perl use strict; use warnings; # fork関数の例 my $glob = 6; my $buf = "a write to stdout\n"; sub main { my $var = 88; print STDOUT $buf; my $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { #child $glob += 1; $var += 1; } else { # parent sleep 2; } printf "pid=%d, glob=%d, var=%d\n", $pid, $glob, $var; } main();
8.2 vfork関数の例
いまどきの forkは Copy on Writeなので vforkを使う需要はありません。
Copy on Writeの実現はページを全部読み出し専用にしておいて、
書き込みが発生したときにトラップを起こすようにして、書き込みを検知するようにしています。
8.3 プロセスの終了状態を出力する
#!/usr/bin/env perl use strict; use warnings; use POSIX; # プロセスの終了状態を出力する sub pr_exit { my $status = shift; if (POSIX::WIFEXITED($status)) { printf "normal termination, exit status = %d\n" , POSIX::WEXITSTATUS($status); } elsif (POSIX::WIFSIGNALED($status)) { printf "abnormal termination, signal number = %d\n" , POSIX::WTERMSIG($status); } elsif (POSIX::WIFSTOPPED($status)) { printf "child stopped, signal number = %d\n" , POSIX::WSTOPSIG($status) } }
8.4 様々な終了状態を出力する
Python同様、0除算をしても SIGFPEが発生しません。
#!/usr/bin/env perl use strict; use warnings; use POSIX; do "8_3.pl"; # import pr_exit # 様々な終了状態を出力する my $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { # child exit 7; } my $childpid = wait; my $status = $?; pr_exit($status); $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { POSIX::abort; } $childpid = wait; $status = $?; pr_exit($status); $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { my $dummy = 1 / 0; } $childpid = wait; $status = $?; pr_exit($status);
8.5 forkを2度呼んでゾンビプロセスを避ける
#!/usr/bin/env perl use strict; use warnings; use POSIX; # forkを2度呼んでゾンビプロセスを避ける my $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { my $pid2 = fork; # 一つ目の子プロセス die "Error: fork $!\n" unless defined $pid2; if ($pid2 > 0) { # 2回目のfork()の親プロセス (=一つ目の子プロセス) print "exit 0"; exit 0; } # 二つ目の子プロセス # このプロセスの本来の親プロセスは起動後すぐに sys.exit() を呼び出すので、 # 親プロセスが init に変更される。 # このプロセスが終了すると、終了状態は init で解放される sleep 2; printf "second child, parent pid = %d\n", POSIX::getppid; exit 0; } waitpid $pid, 0;
8.6 レースコンディションを有するプログラム
Pythonでは sleepを入れる必要があったそうですが、Perlでは
というか私の環境では sleepなしでも出力が混在しました。
#!/usr/bin/env perl use strict; use warnings; use IO::Handle; use Time::HiRes; # レースコンディションを有するプログラム sub charatatime { my $str = shift; for my $c (split //, $str) { print STDOUT $c; STDOUT->autoflush; sleep 0.1; } } my $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { charatatime("output from child\n"); } else { charatatime("output from parent\n"); }
8.7 プログラム8.6をレースコンディションを避けるように変更
あんまり自信がない。キューとRPCみたいなものを使った方が良さそう。
#!/usr/bin/env perl use strict; use warnings; use POSIX; use Fcntl; use IO::Handle; # プログラム8.6をレースコンディションを避けるように変更 sub charatatime { my $str = shift; for my $c (split //, $str) { print STDOUT $c; STDOUT->autoflush; sleep 0.1; } } my $sigflag = 0; sub TELL_WAIT { $SIG{USR1} = sub { $sigflag = 1; }; } sub WAIT_PARENT { # wait for parent to terminate while ($sigflag == 0) { POSIX::pause(); } } sub TELL_CHILD { my $pid = shift; kill POSIX::SIGUSR1, $pid; } TELL_WAIT(); my $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { WAIT_PARENT(); charatatime("output from child\n"); } else { charatatime("output from parent\n"); TELL_CHILD($pid); }
8.8 exec関数の例
カレントディレクトリに echoallを設置してください
#!/usr/bin/env perl use strict; use warnings; # exec関数の例 my $pid = fork; die "Error fork $!\n" unless defined $pid; if ($pid == 0) { exec { "./echoall" } "echoall", "myarg1", "MY ARG2", "USER" => "unknown"; exit 0; } waitpid $pid, 0; my $pid2 = fork; die "Error fork $!\n" unless defined $pid; if ($pid2 == 0) { exec { "./echoall" } "echoall", "only one arg"; exit 0; } waitpid $pid2, 0;
8.9 全てのコマンド行引数と全ての環境変数を出力する
#!/usr/bin/env perl use strict; use warnings; # 全てのコマンド行引数と全ての環境変数を出力する # echo all args my $length = scalar @ARGV; for my $i ( 0..($length-1) ) { printf "ARG[%d] = %s\n", $i, $ARGV[$i]; } # echo all env strings while (my ($key, $val) = each %ENV) { print "$key:$val\n"; }
8.10 解釈実行ファイルをexecするプログラム
#!/usr/bin/env perl use strict; use warnings; # 解釈実行ファイルをexecするプログラム my $pid = fork; die "Error: fork $!\n" unless defined $pid; if ($pid == 0) { exec { "./testinterp" } "testinterp", "arg1", "MY ARG2"; } waitpid($pid, 0);
8.11 解釈実行ファイルに入れたPerlプログラム
#!/usr/bin/env perl use strict; use warnings; # 解釈実行ファイルに入れたPerlプログラム my $length = scalar @ARGV; for my $i ( 0..($length-1) ) { printf "ARG[%d] = %s\n", $i, $ARGV[$i]; }
8.12 (シグナルを処理しない)システム関数
Python版では EINTRの部分は '=='ですが、本では '!='なので本に合わせました。
#!/usr/bin/env perl use strict; use warnings; use POSIX; # (シグナルを処理しない)システム関数 my $cmd = shift or die "Usage: $0 command\n"; my $pid = fork; die "Error: fork $!\n" unless defined $pid; my $status; if ($pid == 0) { exec { '/bin/sh' } "sh", "-c", $cmd; exit 127; } else { while (1) { my $pid = waitpid $pid, 0; $status = $?; last if defined $pid; if ($! != POSIX::EINTR) { # error other than EINTR from waitpid() $status = -1; } } } print "status = $status\n"; exit $status;
8.13 system関数を呼ぶ
system関数はリストで引数を渡すべきでしょう。
#!/usr/bin/env perl use strict; use warnings; do '8_3.pl'; # system関数を呼ぶ my $status; $status = system ('date'); pr_exit($status); $status = system ('nosuchcommand'); pr_exit($status); $status = system('who; exit 44'); pr_exit($status);
8.14 systemを用いてコマンド行引数を実行する
systemにユーザの入力を与えるのは大変危険です。
#!/usr/bin/env perl use strict; use warnings; do '8_3.pl'; # systemを用いてコマンド行引数を実行する my $status = system ($ARGV[0]); pr_exit($status);
8.15 実ユーザIDおよび実効ユーザIDを出力する
Perlだと特殊変数で取得できますが、POSIXモジュールの方が
わかりやすいと思います。
#!/usr/bin/env perl use strict; use warnings; use POSIX; # 実ユーザIDおよび実効ユーザIDを出力する printf "real uid = %d, effective uid = %d\n", POSIX::getuid(), POSIX::geteuid(); printf "real uid = %d, effective uid = %d\n", $<, $>;
8.16 実効記録データを生成するためのプログラム
#!/usr/bin/env perl use strict; use warnings; use autodie; use POSIX; # 実効記録データを生成するためのプログラム my $pid = fork; if ($pid != 0) { sleep 2; exit 2; } $pid = fork; if ($pid != 0) { sleep 4; POSIX::abort(); } $pid = fork; if ($pid != 0) { exec { "/bin/dd" } "dd", "if=/etc/shells", "of=/dev/null"; exit 7; } $pid = fork; if ($pid != 0) { sleep 8; exit 0; } sleep 8; kill POSIX::SIGKILL, $$; exit 6;
8.17 システムの実効記録ファイルの特定フィールドを出力する
CPANで使えそうなものが見当たらなかったので、Inline::Cで、そのまま Cで書きました。
Python版はなんかカッコイイですよね。Cバインディングがわりと簡単そうな印象を受けた.
#!/usr/bin/env perl use strict; use warnings; use Inline 'C'; # システムの実効記録ファイルの特定フィールドを出力する #sudo aptitude install acctで /var/log/account/pacctに書きだされる my $file = shift or die "Usage: $0 filename\n"; get_acct($file); __END__ __C__ #include <stdio.h> #include <sys/acct.h> #ifdef HAS_SA_STAT #define FMT "%-*.*s e = %6ld, chars = %7ld, stat = %3u: %c %c %c %c\n" #else #define FMT "%-*.*s e = %6ld, chars = %7ld, %c %c %c %c\n" #endif #ifndef HAS_ACORE #define ACORE 0 #endif #ifndef HAS_AXSIG #define AXSIG 0 #endif static unsigned long compt2ulong(comp_t comptime) { unsigned long val; int exp; val = comptime & 0x1fff; exp = (comptime >> 13) & 7; while (exp-- > 0) val *= 8; return(val); } void get_acct(char *file_name) { struct acct_v3 acdata; FILE *fp; fp = fopen(file_name, "r"); if (fp == NULL) { return; } while (fread(&acdata, sizeof(struct acct_v3), 1, fp) == 1) { printf(FMT, (int)sizeof(acdata.ac_comm), (int)sizeof(acdata.ac_comm), acdata.ac_comm, compt2ulong(acdata.ac_etime), compt2ulong(acdata.ac_io), #ifdef HAS_SA_STAT (unsigned char) acdata.ac_stat, #endif acdata.ac_flag & ACORE ? 'D' : ' ', acdata.ac_flag & AXSIG ? 'X' : ' ', acdata.ac_flag & AFORK ? 'F' : ' ', acdata.ac_flag & ASU ? 'S' : ' '); } }
8.18
Benchmarkモジュールのメソッド戻り値が各種時間の入った
配列リファレンスとは知りませんでした。
#!/usr/bin/env perl use strict; use warnings; use Benchmark qw(timeit); # 全てのコマンド行引数を実行し時間を計る for my $arg (@ARGV) { my $t = timeit( 1, sub { my @commands = ($arg); system @commands; }, ); # Please see perldoc Benchmark, NOTES setction. my ($real, $user, $system, $children_user, $children_system, undef) = @{$t}; printf STDERR " real:%g\n", $real; printf STDERR " user:%g\n", $user; printf STDERR " sys:%g\n", $system; printf STDERR " child user:%g\n", $children_user; printf STDERR " child sys:%g\n", $children_system; }
まとめ
「詳解 UNIXプログラミング」の第八章を示しました。