These pages are written by only Japanese.

Welcom to My Diary.com
最新の日記タイトル一覧カテゴリ別タイトル一覧トップへ戻る〜

こんばんわ♪ 現在は3月29日(金)21時31分。 今日のニュースは何でしょう?


hns - 日記自動生成システム - Version 2.19.5 (色々 Fixed)

先月 2002年12月 来月
01 02 03 04 05 06 07
08 09 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Namazu for hns による簡易全文検索です。
詳細は 詳細指定/ヘルプをご参照下さい。
検索式:

2002年12月30日(月)(自宅)

(自宅)

監視システム

朝起きて確認をしたところ、ディスクが溢れていました。
あわててデータをファイルサーバに退避しました…
ただ…
bash-2.05$ uptime
 6:55AM  up 1 day, 11:21, 1 user, load averages: 6.42, 6.18, 6.02
という状況では、バックアップ作業さえキャプチャーに悪影響を及ぼします。
バックアップ中に家の前でいたずらされると、その瞬間を見逃す可能性が増えますので、気軽にバックアップも出来ません。
続く

ゾンビ・プロセスについて

昨日の問題をきっかけに、いままで曖昧なままにしていた SIGCHLD とゾンビ・プロセスについて整理してみました。
恐らくツッコミどころ満載な説明だと思いますが、 間違いをそのまま覚えているのはつらいですので、 あえて無知を晒け出そうと思います。
尚、ゾンビは BSD よりの表現らしく、SYSV では defunct と呼ぶようです。 自分はとりあえずゾンビで呼び方を統一しますので、 その辺りは適当に読み替えて下さい。

仕組み:

一般に UNIX では、親が子プロセス(の終了)と同期を取る為に、
  • exit や C-c interrupt で終了した時等 *1 に、親プロセスに送られる SIGCHLD
  • 子プロセスの処理終了まで待つ wait システムコール
といった仕組みが用意されているのですが、
  • プロセスが死ぬと親プロセスに CHLD シグナルを送って自分はゾンビになる。 (処理系に応じてゾンビを作らない設定をする事で避ける事も可能)
  • ゾンビの子がいる時に親プロセスが wait を発行すると子プロセスは成仏する。
  • 親プロセスが死んだ場合は、init が代わりに処理をしてくれる。 (実は、自分はここの理解が曖昧です… daemon ライブラリを使っているせいでしょうか…)
という挙動をします。つまり、
親に先立ち子プロセスが死んだ場合は、子プロセスは水子(ゾンビ)として
自縛霊のようにプロセステーブルを占有して、親に (wait という)
お経を唱えてもらうまで成仏できません。	
お経 (wait) を忘れて、短命な子プロセスを生成し続ける無責任な親がいると、 プロセステーブルがゾンビで溢れて新たなプロセスが生成できなくなり、 (一般ユーザでは)ログインさえ出来なくなります。
一つのプロセスがシステム全体に悪影響を及ぼしますので、 fork を利用する際には、子プロセスを作り過ぎないというだけでなく、 ゾンビの処理にも気をつけなければなりません。

対策:

自分は、親子で同期を取る必要のない 放任主義的なプログラムを作成する時には、 CHLD シグナルの割り込みハンドラで wait を実行するルーチンを 設定しています。
先の例では、
$SIG{'CHLD'} = sub { wait(); };
といったコードを追加していますが、 それだけでは問題が生じる事があります。なぜなら、
  • 複数の子プロセスが同時に終了すると、 一つの SIGCHLD しか伝えられない。
つまり、一つの SIGCHLD の割り込みハンドラで一度 wait を実行したとしても、 取りこぼしが生じて、ゾンビが生じる可能性が残るからです。
この辺を care しないデーモンは DoS で簡単にサービス不能になります… の「シグナルの取りこぼしとwaitpid()システムコール」に実例が載っていますので 参考になると思います。
これは解決しないといけない問題ですが、 wait(2) は、単純に子プロセスが死ぬまで待ちますので、 死んだ子プロセスがいる時に呼ばないとブロックします。 *2 無条件で呼ぶとブロックする可能性があります。
その為、非同期( ブロックしない)指定が可能な wait である waitpid(2)wait3(2) 、 wait4(2) 等を使うのが定石のようです。
$SIG{'CHLD'} = sub { while(wait3() > 0) { } };
use POSIX ":sys_wait_h";
$SIG{'CHLD'} = sub { shift; while(waitpid(-1,&WNOHANG)> 0) { } };
このようにすれば、 wait3 *3 waitpid は、
  • ゾンビの子プロセスが残っている間は 処理したプロセスの pid を返す
  • ゾンビを処理しきった後はブロックせずに -1 を返すので while を抜ける
という挙動をしますので、残っているゾンビプロセスを一掃できます。

まとめ:

そういう訳で、自分は fork をして、 かつ 親子で同期を取る必要のないプログラムを書く時には、 呪文のように、先の wait3waitpid のコードを SIGCHLD ハンドラに設定する事にしています。
尚、 にあるように、SIG_IGN を設定すると、 システムが良きに計らってくれる処理系もありますし、 もしかしたら、それが主流派かもしれません。
ですが、(いささか古い FAQ ではありますが) を読むと、
POSIX では SIG_IGN に SIGCLD をセットした時の振舞いは規定されていないので、
POSIX 系をサポー トするようなプログラムには使うことができません。
とありますので、今のところ、SIGCHLD でSIG_IGN を利用せず、 自前でハンドラを用意する事にしています。

備考:

以上の話はゾンビ対策に限った場合の話でして、 BSD のマニュアルにある通り SIGCHLD はプロセスのステータス変化を親に通知するシグナルですので、suspend した時にも SIGCHLD が発生するようです。
# まだ確かめていません。
また、プロセスが生きていたとしても、上記のような状態変化がある場合は、 wait でブロックしないそうです。
とはいえ…
自分としては子プロセスに resume させたりといったプログラムと しばらく縁がなさそう …でもなかったりします… (汗……ですので、子プロセスの終了に限定した話をするのも意味があると思います。

追記 (2009/4/22):

google 経由で来られる方が多いので、少しまとめました。

*1: signal(7) のマニュアルを読むと、 Linux2.4 は「Child stopped or terminated」、 NetBSD1.6 ではより抽象的に「child status has changed」と記載されていて、 処理系によって説明が異なるようですので、 「〜等」と表現しました…
*2: ブロックしない条件が曖昧ですので、断言は避けます。
*3: Solaris2.x や Linux2.x、FreeBSD4.x で wait3 を使って来たのですが、 今回、NetBSD1.6 では wait3 が使えなかったので、より一般的な waitpid に 変更しました…

本当は怖い家庭の医学 - テレビ朝日

食事中、たまたま家の人がテレビをつけまして、久々に鑑賞しました。
テレビ朝日で「本当は怖い家庭の医学」という番組が放映されていまして、 メディカル・ホラーという題で、 虫歯や肥満等が悪化して死亡したり足を切るような例が次々と紹介されました。
それを見た自分は、 人は恐怖でしか動けない事を理解しました…
ですが…

その30分後には黒糖3度塗りのふがしを食べている自分がここにいます…
甘いものを食べ過ぎて無性に牛乳が飲みたくなるほどに…
# 喉元過ぎればなんとやら…

無線LAN カード

これ の続き
ADTEC のユーティリティー&ドライバーセットを uninstall したところ、 VAIO の内蔵無線 LAN の機能が復活しました。
ほっとしてます。

2002年12月31日(火)(自宅)

(自宅)

年越し

年越し蕎麦も作って食べてみたのですが、 全く実感が湧きません。
色んなモノに追いたてられていて、心に余裕がないからでしょうか…

監視カメラ

この件はもう終わりのつもりでしたが、 また、 この為、緊急の対応を迫られました。

キャプチャと CGI の処理を分ける:

キャプチャマシンは単にキャプチャするだけで、 データはすぐ別の体力のあるマシンに流し、 データの分別や CGI のアクセスはそちらで処理をする。
といった方針でシステムを組む事にします。

キャプチャマシン:

bktr2jpeg.c を改造して bktrd.c を作成しました。
何をするものかといいますと、 8480 + video 入力番号の tcp port を listen していて、 アクセスされると問答無用で、対応するビデオ入力からキャプチャした 画像の jpeg データを確立されたコネクションに流し込むプログラムです。
簡易サーバといったところですので、
実装イメージを以下に示します。(例によって例外処理を省いて書いてます…)

SIGPIPE:

コネクションが切れたソケットを通じてデータを出力すると、SIGPIPE が発生して、 これを care しないと、 プロセス自体が落ちます。 面倒ですので今回は無視する事にします。
  signal(SIGPIPE, SIG_IGN);
デバッグをしていて気付いたのですが、 gdb でプロセスを動かすと、ハンドラに飛ばずにプロセスがいきなり 終了してしまいます。これは gdb の仕様なのでしょうか…
今さらながら、gdb の仕組みを知らずにプログラミングをしている つもりでいる自分に愕然としてしまいます。

port listen:

キャプチャカードが対応している入力ビデオ番号毎に、 tcp ポートを開きます。
extern int server_open(int port);

#define BASE_PORT 8480

int max_sockfd = -1;
for (i=0; i<n_input; i++ ) {
    sockfds[i] = server_open(BASE_PORT + inputs[i]);
    if (max_sockfd < sockfds[i])
        max_sockfd = sockfds[i];
}

select:

複数の tcp port を開いて監視しますので、 select で待ち構えて、 read enable だと判断できる socket に対して accept する事にします。
extern int server_accept(int sockfd);
extern int server_file2net(int sockfd, char *filename);

while(keep_running) {
	fd_set readfds;
	int n;
	# read enable 監視対象の fd を決める
	FD_ZERO(&readfds);
	for(i=0; i<n_input; i++) {
		 FD_SET(sockfds[i], &readfds);
	}
	# 上記で設定した fd のいずれかが read enable になるまで待つ
	n = select(max_sockfd + 1, &readfds, NULL, NULL, &timeout);
	if  (n < 0) {
		perror("seleect failed");
		return EXIT_FAILURE;
	}
	if (n == 0) {
		printf("may be timeout\n");
	}
	# 実際に read enable の fd を調べる
	for(i=0; i<n_input; i++) {
		if (FD_ISSET(sockfds[i], &readfds)) {
			int cfd;
			# accept して接続を確立する
			cfd = server_accept(sockfds[i]);
			<キャプチャ処理& jpeg 圧縮>
			# jpeg データをクライアントに送信
			server_file2net(cfd, jpeg_filename);
			close(cfd);
		}
	}
}
このようなコードを追加しました。
尚、このコードには、 クライアントが connect した後 read を拒否すると、その間サーバの動作が止まる問題があります。
これへの対処には、accept で受け取った socket を write_fds に設定して、 select で待てば良いのですが、少し面倒なのと、 今回、クライアントは自分が作ったものしか繋げない キャプチャマシンはプライベートネットワークの中に置く。という前提の元、この対処はあとまわしにしました。
少し言い訳をします。select に追加する改造自体は楽なのですが、 jpg ファイルを読み込んでネットワークに write する際、 ブロック単位で転送しますので、次のブロックでまたselect で待つ事になります。 この場合、一つの関数で一気に転送できない事になりますので、 どのブロックまで転送したかを大元で管理しないといけなくなります。
すみません、 このような処理を簡単に追加できない自らの非力を暴露しているようなものですね… (鬱
あと、複数のクライアントが繋ぎに来た時に、 並列に処理できない問題が残っているのは故意でして、 キャプチャデバイスは一度に一つのプロセスしか開けないので、 どのみち待ち合わせ処理が必要になります。 そこで、いっその事 backlog を多めに指定して TCP スタックに任せて 楽をしようという訳です。
と… ここまで書いて気付いたのですが、 非同期の writeを使って期待通りの量を転送できない時は、そのクライアントは見捨てて さっさと close する方針が取れる事に気付きました。
ネットワークの輻輳等で一時的に転送できない場合を考慮しろと指摘されそうですが、 そのようなネットワーク上で一秒置きに キャプチャデータを転送する訳にはいきませんし、 むしろ、失敗したらその回のデータをあきらめる方が、 ネットワークにも PC にも優しいといえます。
# なにより作るのが楽ですので。f(^^;

gethostbyname:

server_open は、とある本を参考にして作成した、 socket, bind, listen を行うサブルーチンで、 昔から使い続けていたのですが、
この中の gethostbyname をコールすると、 なぜか jpeg ライブラリの中で、
failure while saving jpeg
rename: No such file or directory
のエラーが発生する現象に見舞われました。
解析する時間は今ありませんので、 gethostbyname を使わないようにして問題を避けています。
こういう臭いモノには蓋をする姿勢を取っていると、後で痛い目にあうかもしれません。

画像収集:

別の PC から、先の bktrd のデーモンに繋いで、jpeg データを拾い、 時間に対応した名前を持つフォルダを作成して分別します。

bktrclient:

まずは、bktrd サーバに繋いで jpeg データを取得するツールです。 使いかたは以下のように3つの引数で起動します。
bktrclient <サーバ名> <ビデオ入力番号> <出力ファイル名>
コードのイメージを以下に示します。
# しつこいようですが、例外処理を省略して書いてます。実際には 全ての関数について戻り値を調べて、|状態に応じた処理を行っています。
int input, port;
int sockfd;
char *server_name;

server_name = argv[1];
input = atoi(argv[2]);
filename = argv[3];
sockfd = client_open(server_name, port);
client_net2file(sockfd, filename);
close(sockfd);

caploop - bktrclient 対応版:

先の caploop.pl とほぼ同じですが、少しだけ処理が増えていますので、 再度、コードを示します。
my $base_dir = "〜/htdocs";

my %old_dir;

while(1) {
	my ($dir_time, $file_time) = make_dir_time(time());
	foreach my $input (0 .. 1)  {
		 my $dir = $base_dir . $input .'/' .$dir_time;
		`mkdir -p $dir`;
 		`bktrclient -f $dir/$file_time -s $input`;
		# 次のディレクトリに処理が移った時、
		# 前のディレクトリで mkthum の処理を行う。
		if (exists($old_dir{$input})) {
		    if ($old_dir{$input} ne $dir) {
			my $pid = fork(); ## be careful !!
			if ($pid == 0) {
			    `cd $old_dir{$input} ; mkthum.pl -l 6 -r 10`;
			     exit 0;
			}
			$old_dir{$input} = $dir;
		    }
		} else {
		    $old_dir{$input} = $dir;
		}
	}
	sleep(1);
}

「一秒置き」をもう少し正確に…:

一秒より細かい時間情報を取得する gettimeofday 相当の機能が必要でしたので、 を導入しました。
use Time::HiRes;

my $start_time = Time::HiRes::time;
my $time_index = 0;

while(1) {
	<処理>
	$time_index++;
	my $diff_time = Time::HiRes::time - $start_time;
	my $wait_time = $time_index - $diff_time;
	if ($wait_time > 0) {
		select(undef, undef, undef, $wait_time);
	}
}
これで、一秒置きに処理してくれる…はずでしたが、 一秒+αの間隔で処理しています。
今後の課題にします…

完成:

それっぽく動いていますが、 これでも画質が悪いのは避けられません。
普段は、それなりの画質で写っているのですが、

まれにスキャンのタイミングがずれて、 奇数偶数で上下にずれたり、
ビデオ入力0 と1 が混ざった画像が取得できたりします。
また、動く物体はまず奇麗に撮れません。
ドライバ開発者のサンプルプログラムでデータを拾っても同様ですので、 おそらく、ドライバ自体に問題があるのではと思いますが、 残念ながら追う時間が自分には残されていません。 (ρ_;
cvs で kernel source が取得出来ていれば、修正は無理だとしても 原因だけでも推測して、今後のプログラマ生活の糧にしようと思うのですけど…

これで、2 日分だよ〜。

タイトル一覧
カテゴリ分類
Database
JXTA
Java
XML
awm
bookmark
keyword
memo
news
research
Powered by hns-2.19.5, HyperNikkiSystem Project