第8回NP資料 forkで遊ぶ&ミニプロ説明

第8回ネットワークプ
ログラミング
中村 修
今日のお題

講義
 今後の授業予定

ミニプロなどの説明
 fork
サーバの挙動
 プロセス
 練習1: forkに慣れよう

---------休憩------------------------------- 実習: 並列echoサーバを作ろう
今後の授業予定
12/01(月)
12/08(月)
12/15(月)
12/22(月)
01/08(木)
01/19(月)
ミニプロ説明,fork
select
ミニプロプロポーザル提出&チェック
IPv6プログラミング
ミニプロレポート提出&チェック
ミニプロチェック(再提出)、優秀作品発表
*ミニプロについては次ページに詳細あり
ミニプロのルール


個人でプログラムを作る
OSは問わない
 一応、公式サポート環境はCNS
 他の環境でもTA/SAは(できるだけ)頑張ります


ネットワークを使うプログラム
レポートには以下の項目を含めること
 使用方法
 面白さ
 実行環境

提出ソースコードにはコメントを付加すること
 日本語
or 英語
今日のテーマ

forkします
よくあるサーバー
複数のクライアントから
同時にアクセスを受ける
サーバー
集中!
クライアント
クライアント
クライアント
クライアント
クライアント
クライアント
サンプルの問題点

一つのクライアントに対応している間は、他の
クライアントに対応できない!
 複数のクライアントが一度にアクセスした場合を
考慮しなければいけない

どこがネックか?
 accept()を多重化して実行できると良い?
 実際の処理を多重化して実行できると良い?
サーバの気持ちになってみよう

たくさんのソケットを扱ったり,めんどくさいこ
とはしたくない
 いちいちソケットを検査するのが面倒
 どれにどれだけ時間がかけられるんだ?

沢山のクライアントが接続してきたら,大変
自分の分身を作ればいいんだ!

仕事は全部,分身にまかせよう
 親の仕事は,コネクションがきたときに分身を作
ることだけ
親プロセス
子プロセス二号
子プロセス一号
t
分身のための関数

forkシステムコール
 int
fork();
 自分とまったく同じ複製プロセスを作るシステ
ムコール
 自分がコピーかどうかわからなくならない?
0が帰ってきたら自分は子プロセス
 正の整数が帰ってきたら自分はfork()を呼び出した
親プロセス

 -1は失敗
プロセスってなぁに
仕事=アプリケーション=コマンド=プログラム
 アプリケーションの実行

 プログラムをプロセスとして実行

食べ物をのせるお皿
お皿:プロセス
アプリケーション
プロセスを見る

Solaris
%

ps –efj
BSD系
%
ps auxj
USER PID PPID PGID …………… COMMAND
nobody 123 89
89 ……………
httpd
プログラムの実行とプロセス
プログラム
プログラム
親プロセス
main(int argc, char *argv)
{
struct sockaddr_in me, peer;
…….
if(pid = fork()) == 0) {
child process.
}
else if(pid > 0) {
parenet process.
}
else {
error process.
}
}
main(int argc, char *argv)
{
struct sockaddr_in me, peer;
…….
if(pid = fork()) == 0) {
child process.
}
else if(pid > 0) {
parenet process.
}
else {
error process.
}
}
プログラム
コピー
プロセス
実行
生成
子プロセス
main(int argc, char *argv)
{
struct sockaddr_in me, peer;
…….
if(pid = fork()) == 0) {
child process.
}
else if(pid > 0) {
parenet process.
}
else {
error process.
}
}
プロセス
fork()
OS
生成
親と子の区別
fork()の返り値
int parent;
parent = fork();
親側
if(parent > 0){
親の処理;
}
子側
else if(parent == 0){
子の処理
}
よくあるサーバでの処理の流れ
時間のかかるサービス(telnet,ssh,ftpなど)
を提供するサーバでよくある処理
 接続要求があったら,acceptして,とりあえず
fork();

 子プロセスだったら、サービスの提供開始
 親プロセスだったら、接続要求待ち
自分でたくさんのFDの面倒を見なくてよい
 「後はまかせた」方式

fork()を用いたマルチクライアント
サーバの仕組み
ファイルディスクリプタ
サーバ
クライアント1
クライアント2
クライアント3
listen()してクライアントからの
リクエストを待っている状態
fork()を用いたマルチクライアント
サーバの仕組み
ファイルディスクリプタ
リクエスト
サーバ
クライアント1
クライアント2
クライアント3
クライアントからリクエストが
飛んで来た(accept()する直前)
fork()を用いたマルチクライアント
サーバの仕組み
リクエスト
accept()
サーバ
ファイルディスクリプタ
fork()
クライアント1
サーバ(子)
クライアント2
クライアント3
サーバ側でaccept()し、
子プロセスを生成
fork()を用いたマルチクライアント
サーバの仕組み
ファイルディスクリプタ
サーバ(親)
クライアント1
サーバ(子)
クライアント2
クライアント3
親プロセスはaccept()が作成した
ファイルディスクリプタを閉じ、
子プロセスはlisten()しているファ
イルディスクリプタを閉じる
fork()を用いたマルチクライアント
サーバの仕組み
ファイルディスクリプタ
サーバ(親)
クライアント1
サーバ(子)
クライアント2
クライアント3
子プロセスがクライアント1からの
リクエストを処理
親プロセスは他のクライアントか
らの接続要求を待ちつづける
fork()を用いたマルチクライアント
サーバの仕組み
accept()
ファイルディスクリプタ
サーバ(親)
クライアント1
fork()
サーバ(子)
クライアント2
サーバ(子)
クライアント3
新たなクライアントからのリク
エストを受け付けると、再び
fork()し子プロセスを作成する
練習1: forkに触れてみよう

以下のソースを
 コピー
 コンパイル
 実行
してみよう。
 ソースコードをよーく読んでみよう。
/home/kaizaki/osamuNP/8/fork_simple.c
練習1:重要な点①

forkの基本構文
int pid;
if( (pid = fork()) == 0){
/* child process */
} else if(pid > 0){
/* parent process */
} else {
perror("fork()");
}
練習1:重要な点②

変数がコピーされる


Int numberの中身を考えてみよう
別のメモリ領域が確保される
original process id: 21146
original parent process id: 20587
this is parent process
number: 25
fork() return value: 21147
parent process id: 21146
parent parent process id: 20587
number: 25
press enter
this is child process
number: 15
fork() return value: 0
child process id: 21147
child parent process id: 21146
number: 15
press enter
練習1:覚えた方がお得かも

int getpid()
 プロセスidを取得

int getppid()
 親プロセスidを取得
実習:echoサーバをforkしよう
forkを用いて並列処理できるechoサーバを
作成しよう。
 ポイント

 親プロセスと子プロセスでの役割分担をちゃんと
考えよう
 役目が終わった子プロセスはちゃんと殺してある
かな?
fork()を利用した並行サーバの注
意点:親プロセスの義務




子プロセスが終了すると、「どのような死に様だったか」が親プ
ロセスに伝えられる
 システムが親プロセスに通知
 シグナルを利用
親プロセスは子プロセスの死に様を見届けなければならない
 親プロセスが子プロセスの終了状態を受け取らなくては,
子プロセスは成仏できずにゾンビプロセスに変わる
子プロセスが死んで,終了状態を受け取らずに親プロセスが
死ぬと,ゾンビプロセスがいつまでも残る
子プロセスが終了する前に親プロセスが死ぬと?
 プロセスID1のinitが養子として引き取ってくれる
終了状態の受け渡し
親プロセス
子プロセス
子プロセス
終了状態の受け渡し
親プロセス
子プロセス
終了(正常/異常)
子プロセス
終了状態の受け渡し
親プロセス
ゾンビ
プロセス
終了(正常/異常)
子プロセス
終了状態の受け渡し
親プロセス 終了した子プロセスから
状態を取得する
終了(正常/異常)
ゾンビ
プロセス
子プロセス
終了状態の受け渡し
親プロセス
シグナル
SIGCHLD
ゾンビ
プロセス
正確にはプロセスを管理
しているOSから通知される
子プロセス
終了状態の受け渡し
親プロセス
ゾンビ
プロセス
消滅
子プロセス
シグナルとは?

非同期な事象をプログラムで扱うための方法

非同期(いつおきるか分からない事)な事を知らせてくれる

種類がある

割り込み・子プロセスの状態変化など


kill(), raise(), alarm()で意図的に発生させることもできる
事前にOSに「これが起こったら教えてよ」と登録する


逆に「これが起きても無視して」とも言える
それらが起きた時に何らかの処理を実行させられる

例えば、割り込み(Ctrl-C)を受け取ったら「ばかやろー」と画面に表
示できる

あるシグナルを受け取った時に実行する処理を一つの関数にしてそれを登録
する

シグナルハンドラ

シグナルを受け取った時に呼び出される関数
プログラマが定義する
注意:



シグナルはキューイングできない
時間的な処理の流れ
何かの処理
シグナル
発生
中断した所から
実行を再開
シグナル
ハンドラ
自動的に
呼ばれる
処理終了
知っておきたいシグナル

SIGINT


SIGTERM


Ctrl-Zにバインドされていて、プロセスをサスペンドするためによく使われる。
SIGCHLD


これもプロセスを終了するためによく使われる。killコマンドがdefaultで送るシ
グナル
SIGTSTP


Ctrl-Cにバインドされていて、プロセスを終了するためによく使われる
fork()で作成した子プロセスがexit()したことを通知するシグナル。これを受け
て wait() などの後処理を走らせる
SIGALRM

alarm()によって指定された秒数後に送られるシグナル。タイマーの実装でよ
く使う
シグナルハンドラの指定:
signal()

void (*signal(int signo, void (*func)(int)))(int);
 指定したシグナル発生時に呼び出される関数(シ
グナルハンドラ)へのポインタを指定する
 以前に設定されていたシグナルハンドラを指すポ
インタが戻り値
 Signoは捕捉するシグナル
 funcはシグナルハンドラへのポインタ

第2引数のfuncは引数として整数をひとつとり、戻り
値を返さない関数へのポインタ
親プロセスの義務(再び)

つまり、子プロセスが終了したというシグナルを受け
取ったら「南無阿弥陀仏」と唱えなければいけない
 子プロセスの終了状態(ステータス)を受け取る

呪文: wait() システムコール
 子プロセスの状態を受け取り、変数へ代入
 子プロセスの終了状態から、適切な処理を進められる

注意: シグナルのキューイング
 複数のシグナルを同時に受信しても「1つ」しか通知され
ない
 複数の子プロセスが同時に死んだ場合の処理を工夫
waitシステムコール群
子プロセスの状態(ステータス)を取得するた
めのシステムコール
 pid_t wait(int *status);
 pid_t waitpid(pid_t wpid, int *status, int
option);

 子プロセスのpidが戻り値
 失敗:
-1
wait()とwaitpid()の違い

waitは子プロセスが終了する(ほんとは状態が変わ
る)までブロックする
 親プロセスは別の処理を行えない。

waitpid
 ブロックしないようにするオプションがある。


WNOHANG
戻り値0は、もう状態の変化したプロセスがないこと
 pidを指定できる


-1 を指定すると最初に状態の変化した子プロセス
0を指定すると、同一プロセスグループidを持つ子プロセス
サンプルコード
void sig_child(int signo)
{
int pid, status;
while((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("PID: %d, terminated\n", pid);
}
}
int main(int argc, char *argv[])
{
….
signal(SIGCHLD, sig_child);
….
}