プロセスとパイプ

システム・プログラム

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-05-07
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html

■印刷配布資料

■復習

■今日の重要な話

プロセスの考え方と操作

■捕捉--数の入出力

Unix の read(), write() システムコールは、「バイト列」を読み書きするも のである。Unix で数を読み書きするには、次の2つの方法がある。

キーボードで打てるのは、ASCII 文字の並びである。キーボードから int を 読みたい時には、文字の並びとしてまず読み込み(fgets())、次に、 文字の並びから整数に変換する(atoi(), strtol(), sscanf())

scanf() は、人間が打つ、間違い(意図的な攻撃)を含む可能性がある場所で 使うべきではない。

機械語序論の spim (xspim) のシステムコールは、特殊で、キーボードからは、 ASCII 文字の並びを打ち込んでも、OSの中で整数に変換して、返す機能があ る。Unix のシステムコールでは、そのようなことはできない。

■プロセスとは

◆プログラムとプロセス

プロセッサ(CPU)が実行できる機械命令の列がプログラムである。 プロセスとは、プログラムがオペレーティング・システムによってメモリに読 み込まれ、やはりオペレーティング・システムの管理下にあるプロセッサによっ て実行の対象になったものである。

メモリにプロセスが3つ、UNIXカーネル、ハードディスク、CPU

図1 プログラムとプロセス

プロセスには、保護、資源割り当てのような機能がある。

◆保護

あるプロセスが 暴走 して他のプロセスを破壊したり、あるいは、意図的に他のプロセスからデータ を盗もうとしたとしても、それを禁止してプロセスを守ることをプロセスの保 護という。プロセスを保護するためには、プロセスが直接他のプロセスのメモ リなどにアクセスできないようにプロセスとプロセスを隔離する。Unix では、 基本的にはプロセスごとに独立した論理アドレス空間を割り当てることで実現 している。

◆資源割当て

資源とは、 メモリ、ディスク、プリンタ、ディスプレイ、キーボードなど コンピュータが処理を進める上で利用価値のあるものを総称である。 プロセスは、資源を公平に配分するための単位としての役割がある。

◆プロセスの操作

プロセスの操作には次のようなものがある。

◆プロセスを作ろう==プログラムを実行しよう

シェルにコマンドを与えると、それに対応したプロセスが作られ、 その結果コマンドが実行される。 (ただし、cdやsetなどのシェルの内部コマンドではプロセスは作られない。) 例えば、lsコマンドを与えると、 新しいプロセスが作られ、ls というプログラム ( /bin/ls/usr/bin/ls などのファイルに保存されている ) がメモリに読み込まれて実行される。 Xウインドウでメニューから ktermを起動すると、ktermのプロセスとその端末 の中で動くシェルのプロセスの2つが作られる。

◆プロセスの観察

◆プロセスの属性

ps(process) コマンドを実行すると、プロセスの一覧を表示する。 psコマンドの表示の例を示す。
----------------------------------------------------------------------
% ps [←]
   PID TTY     TIME CMD
  9086 ttyq1   0:00 cat 
  9084 ttyq1   0:00 emacs 
  9123 ttyq1   0:00 ps 
  9072 ttyq1   0:01 tcsh 
% []
----------------------------------------------------------------------
psコマンドの実行結果は1行が1プロセスである。 左から、以下のような意味がある。
PID (プロセス識別子)
プロセスを区別するための16ビットの整数。0〜65535、または、0〜32767の範囲。
TTY (端末名)
プロセスがどの端末と結びつけられているかを示している。 端末 /dev/ttyq1ttyq1の意味。
TIME
CPU時間 (CPUがそのプロセスを実行するために費やした時間)。
COMMAND
そのプロセスを起動した時のコマンド。

◆プロセスと資源

プロセスにはメモリやCPUなどの資源が割り当てられる。 これはpsコマンドに-lオプションをつけると表示される。
----------------------------------------------------------------------
% ps -l [←]
  F S   UID   PID  PPID  C PRI NI  P    SZ:RSS      WCHAN TTY     TIME CMD
 b0 T  1231  9155  9153  0  60 20  *   462:106          - ttyq1   0:00 man 
 b0 T  1231  9086  9072  0  60 20  *    50:28           - ttyq1   0:00 cat 
 b0 T  1231  9084  9072  0  60 20  *  2114:849          - ttyq1   0:00 emacs 
 b0 T  1231  9153  9072  0  60 20  *   462:108          - ttyq1   0:01 man 
 b0 R  1231  9161  9072  4  62 20  0   405:160          - ttyq1   0:00 ps 
 b0 T  1231  9156  9155  0  60 20  *    78:44           - ttyq1   0:00 sh 
 b0 T  1231  9157  9156  0  60 20  *   519:210          - ttyq1   0:00 less 
 b0 S  1231  9072  9070  1  39 20  *   703:324   8027eb80 ttyq1   0:01 tcsh 
% []
----------------------------------------------------------------------
TIME は過去に利用した CPU 時間の割合、 SZ は、プロセスが確保しているメモリ、 RSSは、そのうちメインメモリに入っている部分である。

S は、 プロセスの 状態 (STATe) であり、次のようなものがある。

R (runnable)
実行可能な状態。CPUが空いていれば実行できる。
D (Disk)
ディスク入出力を行ない、その完了を待っている状態。
S (Sleep)、I (I)
キーボードや他のプロセスからの入力を待っている状態。
Z (Zombie)
既に終了していて、終了処理の完了を待っている。
T (Traced)
一時的に停止しているか、デバッグの対象になっている。
Irix には、そのほかに 0, X などがある。

◆プロセスの親子関係

Unix では、後で説明する fork() システムコールを 発行すると、新しくプロセスが作られる。 (Unix では、これ以外の方法ではプロセスは作られない。) この時、「もとのプロセス」の「親プロセス」という。 プロセスの親プロセスのプロセス識別子は、psコマンドに「-l」オプションを つけるとPPIDのところに表示されている。

■プロセスの資源と属性

プロセスには、 図?のような属性と資源がある。

図? プロセスの資源と構成要素

図2 プロセスの資源と属性

◆資源

プロセスは、資源を割当てる対象である。逆にいえば 割当てられた資源がまとまったプロセスを構成するといえる。
メモリ(memory)
プロセスの重要な資源はメモリ(主記憶、main memory, main storage) である。 メモリのうち主記憶に入っている部分を レジデント・セット(resident set) という。
スワップ領域
スワップ領域とは、主記憶(main memory)が不足した時に、プロセスのメモリ・ イメージを格納するための、二次記憶(secondary storage)上の領域である。 二次記憶としては、ハードディスクがよくつかわれる。
レジスタ・セット
プロセスが実行されている時はCPUのレジスタに格納されている データは、プロセスが待ち状態や待機状態の時は、 メモリに保存される。
ファイル記述子の表
プロセスごとにファイル記述子の表(開いたファイルの表)がある。通常、0 番目がキーボード、1番目と2番目がディスプレイを指している。
制御端末
プロセスごとにキーボードからのシグナルを受け取る 端末が一つ定められてる。この端末を 制御端末 とう。
カレント・ワーキング・ディレクトリ
相対パス名を解釈する時に使う出発点となるディレクトリである。 cd コマンド、chdirシステムコールで変更できる。
ルート・ディレクトリ
プロセス毎にルート・ディレクトリを設定することができる。 匿名ftp(anonymous ftp)など、 特に高いセキュリティが求められる時に プロセスがアクセスできる ディレクトリの範囲を制限するために使われる。 変更するには、chrootシステムコールを使う。

◆属性

UNIXのプロセスが持っている重要な属性には、 次の様なものがある。
プロセス識別子(PPID)
親プロセスのプロセス識別子(PPID)
PGID(Process Group ID)
プロセスにはプロセス・グループ識別子(PGID)と呼ばれる属性がある。プロセ ス・グループ識別子を指定してシグナルを送る機能(シグナルのマルチキャス ト機能)を使うと、同じプロセス・グループIDを持つプロセスにまとめてシグ ナルを送ることができる。これはプロセスの一時停止、再開のためのジョブ制 御で使われる。
UID(User ID)
GID(group ID)
マスク(mask)
状態
優先順位(priority)
優先順位は、CPU資源を割当てる時に使われる。 優先順位は0から127の数で表わされ、 数字が小さい方が優先順位が高いことを意味する。
制限資源量
計算機上のメモリ資源や同時に開けるファイルの数は有限である。 この有限の資源を有効に分配するために、 プロセスごとに使える資源量の上限が決められている。 これを制限資源量とう。 プロセスは制限資源量を越えて資源を使用することはできない。 メモリの制限資源量を越えた場合はメモリ割り当てに失敗し、 ファイルを開く場合は、ファイルの開くことに失敗する。 ただし、CPU時間が制限値に達した場合は、 プロセスは強制終了させられる。 制限できる資源には次のようなものがある ( 制限できる資源の種類はかなりシステムに依存する) ()中はlimitコマンドで使うキーワード。 多くのシェルでは limitコマンドで制限資源量を設定したり、 unlimitコマンドで制限を解除することができる。
その他
その他の属性としては、 シグナル制御関連の属性 や 統計情報 がある

■psコマンド(System V系)

ps

System V 系の ps(process) コマンドは、引数を付けないで実行すると、 プロセス識別子、端末名、 CPU時間、コマンド名を表示する。
----------------------------------------------------------------------
% ps [←]
   PID TTY      TIME CMD
 11614 pts/2    0:01 mule
 11481 pts/2    0:00 tcsh
 11616 pts/2    0:00 cat
 11615 pts/2    0:01 mule
% []
----------------------------------------------------------------------
ただし、次に説明する「-u」オプションを使わないと、ps コマンドを実行した端末と結びつけられているプロセスしか表示しない。

◆ps -u (user)

特定のユーザのプロセスだけを表示する。たとえば、 ユーザ名が root のユーザのプロセスだけを表示さたせい時には、次のよ うに打つ。
% ps -u root [←]
自分のプロセスだけを表示したいなら、次のようにする。
% ps -u $user [←]
$user は、csh()や tcsh()のシェル変数で、自分自身 のユーザ名が入っている。

◆ps -l (long)

プロセスの大きさ(SZ(size))、親プロセスの PID (PPIID(Parent PID))優先順位(PRI(priority)) プロセスの資源と属性を表示する。
----------------------------------------------------------------------
% ps -l [←]
 F S   UID   PID  PPID  C PRI NI     ADDR     SZ    WCHAN TTY      TIME CMD
 8 R  1231 18864 18862  1  51 20 50804680    164          pts/1    0:00 tcsh
% []
----------------------------------------------------------------------

◆ps -f (full)

コマンド名を引数まで含めて全部(the full cmmand name)を表示する。
----------------------------------------------------------------------
% ps -f [←]
     UID   PID  PPID  C    STIME TTY      TIME CMD
     yas 18864 18862  0 23:18:49 pts/1    0:00 -tcsh
% []
----------------------------------------------------------------------

◆ps -e (every)

全てのプロセスを表示する。

◆ps -ef (every,full)

-e, -f の組合わせ。全てのプロセスを 表示する時に、よく使われる。

◆ps -fu $user (full,user)

-f, -u の組合わせ。自分のプロセスを 表示する時に、よく使われる。

プロセスを殺そう

プロセスには、ls のように、勝手に終了するプログラムもあるが、エディタ のように、終了のための操作をして始めて終了するプログラムもある。 場合によっては、プロセスが自主的に終了する前に、 強制的に終了させなければならないことがある。 これを、プロセスを 殺す(kill) という。

プロセスを殺す方法には、次のような方法がある。

どちらの方法でも、裏ではシグナル(ソフトウェア割込み) という仕掛けが働く。

◆キーによるプロセスの強制終了

次は、cat のプロセスを^Cで強制終了している。
----------------------------------------------------------------------
% cat [←]
Line 1[←]
Line 1
Line 2[←]
Line 2
^C
% []
----------------------------------------------------------------------
^Cと同様に ^\ もあるが、 デバッグ用にcore という名前のファイルができることが異なる。 coreは、実行中のプロセスのメモリの内容をファイルに保存したものである。
----------------------------------------------------------------------
% cat [←]
Line 1[←]
Line 1
Line 2[←]
Line 2
^\
Quit (core dumped)
% ls -l core [←]
-rw-r--r--  1 yas       8430000 Sep  7 03:28 core
% []
----------------------------------------------------------------------
coreファイルはプログラムのデバッグに使える。

◆kill コマンドによるプロセスの強制終了

^C^\ で死なないプロセス を殺すには、kill コマンドを使う。
% kill pid [←]
プロセス識別子pidのプロセスを殺す。 オプションなしでkill コマンドを使っても死なないプロセスがある。 その場合はオプション -KILLをつけてkillコマンドを 実行する。
% kill -KILL pid [←]
これでほとんどのプロセスは死ぬ。(OSの都合上(ディスクのI/O完了の 報告など)で、死なないプロセスもある。)

◆kill システムコールによるプロセスの強制終了

kill コマンドは、内部では、kill システムコールを使っている。kill シス テムコールは、「ソフトウェア割込み」に分類される仕組みを提供する。ソフ トウェア割込みは、ハードウェアの割込みをソフトウェアによりエミュレート したようなものである。詳しくは、後半の講義で述べる。

■mainの引数


----------------------------------------------------------------------
   1:	/*
   2:	        arg-print.c -- mainの引数を表示するプログラム
   3:	        ~yas/syspro/cc/arg-print.c
   4:	        $Header: /home/lab2/OS/yas/syspro/proc/RCS/arg-print.c,v 1.3 2001/05/06 12:10:02 yas Exp $
   5:	        Start: 1997/04/21 18:23:13
   6:	*/
   7:	
   8:	main( int argc, char *argv[], char *envp[] )
   9:	{
  10:	    int i ;
  11:	        printf("&argc == 0x%x, argc == %d\n", &argc, argc );
  12:	        printf("&argv == 0x%x, argv == 0x%x\n",&argv, argv );
  13:	        for( i=0 ; argv[i] ; i++ )
  14:	            printf("argv[%d]==0x%x, \"%s\"\n",i,argv[i],argv[i] );
  15:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% cp ~yas/syspro1/cc/arg-print.c . [←]
% make arg-print [←]
cc     arg-print.c   -o arg-print
% ./arg-print  [←]
argc == 1
argv == 0x7fff2ef4
argv[0]==0x7fff3000, "./arg-print"
% ./arg-print who am i [←]
argc == 4
argv == 0x7fff2ee4
argv[0]==0x7fff3000, "./arg-print"
argv[1]==0x7fff300c, "who"
argv[2]==0x7fff3010, "am"
argv[3]==0x7fff3013, "i"
% []
----------------------------------------------------------------------

◆argvの構造

argvは、2次元配列ではない。 char へのポインタの配列の先頭番地を入れた変数。

argvの構造、3つのメモリ

図3 argvの構造

◆printf()の%xと%s

printf() の %x, %s の意味に注意する。
%x
4バイトの値を数だと思って、16進表記の文字コードの並び変換して write() する。
%s
4バイトの値を番地だと思って、その番地から始まる 内容を、0が来るまで write() する。

■環境変数

csh からは、setenv コマンドで設定できる。

----------------------------------------------------------------------
% printenv LANG [←]
ja_JP.EUC
% echo $LANG [←]
ja_JP.EUC
% date [←]
2001年 5月 6日(日曜日) 21時55分02秒 JST 
% setenv LANG C [←]
% date [←]
Sun May  6 21:55:09 JST 2001
% []
----------------------------------------------------------------------
プログラムからは、main() の3番目の引数、外部変数 environ 、ライブラリ 関数 getenv() でアクセスできる。メモリ中の構造は、argv と同じである。 各文字列は、「変数名=値」の形式になっている。

----------------------------------------------------------------------
   1:	/*
   2:	        env-print.c -- 環境変数を表示するプログラム
   3:	        ~yas/syspro/proc/env-print.c
   4:	        $Header: /home/lab2/OS/yas/syspro/proc/RCS/env-print.c,v 1.4 2001/05/06 11:24:46 yas Exp $
   5:	        Start: 1997/05/05 16:42:22
   6:	*/
   7:	extern char **environ ;
   8:	
   9:	main( int argc, char *argv[], char *envp[] )
  10:	{
  11:	    int i ;
  12:	        printf("envp == 0x%x\n",envp );
  13:	        printf("environ == 0x%x\n",environ );
  14:	        for( i=0 ; envp[i] ; i++ )
  15:	            printf("envp[%d]==0x%x, \"%s\"\n",i,envp[i],envp[i] );
  16:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% make env-print [←]
cc     env-print.c   -o env-print
% ./env-print | head [←]
envp == 0x7fff2efc
environ == 0x7fff2efc
envp[0]==0x7fff300c, "HOME=/home/lab2/OS/yas"
envp[1]==0x7fff3023, "USER=yas"
envp[2]==0x7fff302c, "LOGNAME=yas"
envp[3]==0x7fff3038, "PATH=/home/lab2/OS/yas/bin:/usr/local/bin:/usr/local2/bin:/usr/local2/X11/bin:/usr/local/gnu/bin:/usr/local/X11/bin:/usr/local/tex/bin:/usr/java/bin:/usr/sbin:/usr/bsd:/sbin:/usr/bin:/usr/bin/X11:/usr/etc:/etc:/usr/freeware/bin"
envp[4]==0x7fff311c, "MAIL=/var/mail/yas"
envp[5]==0x7fff312f, "SHELL=/usr/bin/tcsh"
envp[6]==0x7fff3143, "TZ=JST-9"
envp[7]==0x7fff314c, "HZ=100"
% printenv | head [←]
HOME=/home/lab2/OS/yas
USER=yas
LOGNAME=yas
PATH=/home/lab2/OS/yas/bin:/usr/local/bin:/usr/local2/bin:/usr/local2/X11/bin:/usr/local/gnu/bin:/usr/local/X11/bin:/usr/local/tex/bin:/usr/java/bin:/usr/sbin:/usr/bsd:/sbin:/usr/bin:/usr/bin/X11:/usr/etc:/etc:/usr/freeware/bin
MAIL=/var/mail/yas
SHELL=/usr/bin/tcsh
TZ=JST-9
HZ=100
SSH_CLIENT=130.158.85.130 51726 22
SSH_TTY=/dev/ttyq0
% []
----------------------------------------------------------------------

◆環境変数とシェル変数

シェル変数と環境変数とは違う。 csh では、シェル変数は、set コマンドで設定し、環境変数はsetenv コマン ドで設定する。シェル変数は、そのシェルの中だけで参照できるが、環境変数 は、そのシェルから実行されるプロセスで参照できる。

■プロセスの生成とプログラムの実行

UNIX では、新たにプロセスを作る時に、自分のコピーしか作れない(fork()シ ステム・コール)。元のプロセスを親プロセス、作られたプロセスを子プロセ スという。

現在のプログラムの実行はそのまま続けて、新しくプログラムを実行するには、 次のようにする。

execve() システム・コールの引数は、 main() の引数と関連している。


----------------------------------------------------------------------
   1:	/*
   2:	        proc-create.c -- calプログラムよりプロセスを作る
   3:	        ~yas/syspro/proc/proc-create.c
   4:	        $Header: /home/lab2/OS/yas/syspro/proc/RCS/proc-create.c,v 1.4 2001/05/06 13:00:24 yas Exp $
   5:	        Start: 1995/02/27 15:27:54
   6:	*/
   7:	
   8:	#include <unistd.h>     /* pid_t */
   9:	
  10:	extern char **environ;
  11:	
  12:	main()
  13:	{
  14:	   pid_t child_pid ;
  15:	        if( (child_pid=fork()) == 0 )
  16:	        {
  17:	            char *argv[4] ;
  18:	            printf("child: pid == %d, ppid == %d\n", getpid(), getppid() );
  19:	            argv[0] = "cal" ;
  20:	            argv[1] = "5" ;
  21:	            argv[2] = "2001" ;
  22:	            argv[3] = 0 ;
  23:	            execve( "/usr/bin/cal", argv, environ );
  24:	            perror( "execve" );
  25:	            /* exec に失敗したら exit() を忘れないこと */
  26:	            exit( 1 );
  27:	        }
  28:	        else if( child_pid > 0 )
  29:	        {
  30:	            printf("parent: pid == %d, ppid == %d, child_pid == %d\n",
  31:	                    getpid(), getppid(),child_pid );
  32:	        }
  33:	        else
  34:	        {
  35:	            perror("fork");
  36:	        }
  37:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./proc-create [←]
child: pid == 13625, ppid == 13624
   2001 年  5 月
日 月 火 水 木 金 土   
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

parent: pid == 13624, ppid == 13070, child_pid == 13625
% []
----------------------------------------------------------------------

execve() は、システム・コールである。これを使いやすくするために、次の ようなライブラリ関数が用意されている。
     int execl (const char *path, const char *arg0, ..., const char *argn,
          (char *)0);

     int execv (const char *path, char *const *argv);

     int execle (const char *path, const char *arg0, ..., const char *argn,
          (char *0), const char *envp[]);

     int execve (const char *path, char *const *argv, char *const *envp);

     int execlp (const char *file, const char *arg0, ..., const char *argn,
          (char *)0);

     int execvp (const char *file, char *const *argv);

     int system(const char *string);

     FILE *popen(const char *command, const char *type);

     int pclose (FILE *stream);

親プロセスと子プロセスは、独立に動作する。 親プロセスが子プロセスの終了を待ちたい場合には、wait() システムコール を使う必要がある。

     pid_t wait (int *statptr);
     pid_t waitpid (pid_t pid, int *statptr, int options);
     pid_t wait3 (int *statptr, int options, struct rusage *rusage);

     pid_t wait4(pid_t pid, int  *statusp,  int  options,  struct
     rusage *rusage); /* SGI では使えない */

■pipe() と dup()

シェルで、次のようなプログラムを動かすこと考える。

----------------------------------------------------------------------
% echo "hello,world" | tr '[a-z]' '[A-Z]' [←]
HELLO,WORLD
% []
----------------------------------------------------------------------

この例では、echo のプロセスと、tr のプロセスは、パイプで接続されている。 パイプは、open() したファイルと同じようにread() したり write() したり することがでる。しかし実際には、ファイルではなく、プロセスとプロセスが データを交換する仕組(プロセス間通信の仕組)の1つである。

パイプを作るには、pipe() システム・コールを使う。これで、パイプ が1本作られ、2つのファイル記述子(読込み用と書込み用)が返される。


----------------------------------------------------------------------
   1:	/*
   2:	        pipe-rw.c -- pipe() を使ったプログラム
   3:	        ~yas/syspro/proc/pipe-rw-nodup.c
   4:	        $Header: /home/lab2/OS/yas/syspro/proc/RCS/pipe-rw-nodup.c,v 1.1 2001/05/06 13:13:22 yas Exp $
   5:	        Start: 1997/05/26 20:43:29
   6:	*/
   7:	
   8:	#include <stdio.h>
   9:	#include <unistd.h>
  10:	
  11:	extern  void parent( int fildes[2] );
  12:	extern  void child( int fildes[2] );
  13:	
  14:	main()
  15:	{
  16:	    int fildes[2] ;
  17:	    pid_t pid ;
  18:	
  19:	        if( pipe(fildes) == -1)
  20:	        {
  21:	            perror("pipe");
  22:	            exit( 1 );
  23:	        }
  24:	        /* fildes[0] -- 読み込み用
  25:	         * fildes[1] -- 書き込み用
  26:	         */
  27:	        if( (pid=fork()) == 0 )
  28:	        {
  29:	            child( fildes );
  30:	        }
  31:	        else if( pid > 0 )
  32:	        {
  33:	            parent( fildes );
  34:	        }
  35:	        else
  36:	        {
  37:	            perror("fork");
  38:	            exit( 1 );
  39:	        }
  40:	}
  41:	
  42:	void parent( int fildes[2] )
  43:	{
  44:	    char *p, c ;
  45:	        close( fildes[0] );
  46:	        p = "hello,world\n" ;
  47:	        while( *p )
  48:	        {
  49:	            c = *p ++ ;
  50:	            write( fildes[1],&c,1 );
  51:	        }
  52:	        close( fildes[1] );
  53:	}
  54:	
  55:	void child( int fildes[2] )
  56:	{
  57:	    char c, c2 ;
  58:	        close( fildes[1] );
  59:	        while( read(fildes[0],&c,1) >0 )
  60:	        {
  61:	            c2 = toupper(c);
  62:	            write( 1, &c2, 1 );
  63:	        }
  64:	        close( fildes[0] );
  65:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./pipe-rw-nodup  [←]
HELLO,WORLD
% []
----------------------------------------------------------------------

pipe() システムコール実行前

図4−1 pipe() システムコール実行前

pipe() システムコール実行後

図4−2 pipe() システムコール実行後

fork() システムコール実行後

図4−3 fork() システムコール実行後

親子で close() システムコール実行後

図4−4 親子で close() システムコール実行後

◆dup()

pipe() システム・コールで作られたファイル記述子は、しばしば dup() シス テム・コールで、0, 1 に変えられる。dup() システム・コールは、記述子を コピーするものである。小さい数字から探していくので、たとえば close(0) の直後に dup(fd) すると、fd が 0 にコピーされる。

dup() よりも、dup2() の方が便利である。


----------------------------------------------------------------------
   1:	/*
   2:	        pipe-rw.c -- pipe() と dup() を使ったプログラム
   3:	        ~yas/syspro/proc/pipe-rw.c
   4:	        $Header: /home/lab2/OS/yas/syspro/proc/RCS/pipe-rw-dup.c,v 1.7 2001/05/06 13:13:22 yas Exp $
   5:	        Start: 1997/05/26 20:43:29
   6:	*/
   7:	
   8:	#include <stdio.h>
   9:	#include <unistd.h>
  10:	
  11:	extern  void parent( int fildes[2] );
  12:	extern  void child( int fildes[2] );
  13:	
  14:	main()
  15:	{
  16:	    int fildes[2] ;
  17:	    pid_t pid ;
  18:	
  19:	        if( pipe(fildes) == -1)
  20:	        {
  21:	            perror("pipe");
  22:	            exit( 1 );
  23:	        }
  24:	        /* fildes[0] -- 読み込み用
  25:	         * fildes[1] -- 書き込み用
  26:	         */
  27:	        if( (pid=fork()) == 0 )
  28:	        {
  29:	            child( fildes );
  30:	        }
  31:	        else if( pid > 0 )
  32:	        {
  33:	            parent( fildes );
  34:	        }
  35:	        else
  36:	        {
  37:	            perror("fork");
  38:	            exit( 1 );
  39:	        }
  40:	}
  41:	
  42:	void parent( int fildes[2] )
  43:	{
  44:	    char *p, c ;
  45:	        close( fildes[0] );
  46:	        close( 1 );         /* 標準出力をパイプに切替える */
  47:	        dup( fildes[1] );
  48:	        close( fildes[1] );
  49:	
  50:	        p = "hello,world\n" ;
  51:	        while( *p )
  52:	        {
  53:	            c = *p ++ ;
  54:	            write( 1,&c,1 );
  55:	        }
  56:	        close( 1 );
  57:	}
  58:	
  59:	void child( int fildes[2] )
  60:	{
  61:	    char c, c2 ;
  62:	        close( fildes[1] );
  63:	        close( 0 );         /* 標準入力をパイプに切替える */
  64:	        dup( fildes[0] );
  65:	        close( fildes[0] );
  66:	
  67:	        while( read(0,&c,1) >0 )
  68:	        {
  69:	            c2 = toupper(c);
  70:	            write( 1, &c2, 1 );
  71:	        }
  72:	        close( 0 );
  73:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./pipe-rw-dup [←]
HELLO,WORLD
% []
----------------------------------------------------------------------

`親子で close(),dup(),close() システムコール実行後'

図4−5 親子で close(),dup(),close() システムコール実行後

◆不要なパイプを閉じる

使わないパイプのファイル記述子は、全部 close() すること。パイプの書き 込み側のファイル記述子が開いている間は、read() しても EOF (End Of File) にならない。自分自身で write() する可能性もある。

■練習問題

★練習問題 10 echoコマンド

echo コマンドと似た動きをするプログラムをつくりなさい。

★練習問題 11 printenvコマンド

printenv コマンドと似た動きをするプログラムを作りなさい。

★練習問題 12 getenv()の利用

getenv() ライブラリ関数を利用して環境変数を得なさい。

★練習問題 13 putenv()の利用

setenv() ライブラリ関数を利用して環境変数を変え、プログラムの動き(ラ イブラリ関数の動き)が変ることを確かめなさい。

★練習問題 14 getenv()の実現

getenv() ライブラリ関数と似た動きをする関数を作りなさい。

★練習問題 15 putenv()の利用

putenv() ライブラリ関数と似た動きをする関数を作りなさい。

★練習問題 16 waitシステム・コール

proc-createを実行すると、cal の出力より先に「%」が表示されたり、親プロセスの出力と子プロセスの出力 が入り交じることがある。この理由を考えなさい。

この問題を解決しなさい。そのためには、wait() システム・コール (waitpid(),wait3(),wait4()など)を用いて、同期を行えばよい。すなわち、 子プロセスが表示する可能性がある間は、親プロセスを待ち状態にしなさい。

★練習問題 17 3個のプロセスをパイプで結ぶ

3個のプロセスの標準入出力を、2つのパイプで結びなさい。

先にパイプを2つ作ってから2回 fork() してもよい。パイプを1つ作って fork() してから もう1つパイプを作って fork() するという方法でもよい。

ヒント: 使わないパイプのファイル記述子は、全部 close() すること。パイプの書き 込み側のファイル記述子が開いている間は、read() しても EOF (End Of File) にならない。自分自身で write() する可能性もある。

★練習問題 18 書き手がいないパイプ

書き手(write()するプロセス)がいないパイプから読み出そうとすると、どう なるか確かめなさい。

ヒント:書き手がいないパイプは、全ての書込み側のファイル記述子を closeすると作れる。

★練習問題 19 読み手がいないパイプ

読み手がいないパイプに書き込むとどうなるか確かめなさい。

★練習問題 20 パイプ用のバッファの大きさ

各パイプには、オペレーティング・システムのカーネルの中にバッファが割り 当てられてている。この大きさを調べるプログラムを作りなさい。

ヒント:プロセスを fork() しなくても、パイプに書くことはできる。プロセ スが1個の状態で、バッファの大きさ以上のデータを書き込むと、プロセスが ブロックされる(先に進まなくなる)。

★練習問題 21 読み手が複数いるパイプ

1つのパイプに読み手(read() するプロセス)が複数いた場合、どうなるか。 逆に、書き手が複数いた場合、どうなるか。

★練習問題 22 popen() ライブラリ関数

プロセスを実行してその結果を得るには、popen() ライブラリ関数が便利であ る。
----------------------------------------------------------------------
     FILE *popen(const char *command, const char *type);
     int pclose (FILE *stream);
----------------------------------------------------------------------
これを利用して、プロセスを作成し、プロセスにデータを与えたり、あるいは、 プロセスからデータを受け取るプログラムをつくりなさい。

たとえば、expr コマンドを実行して、その結果を受け取るプログラムをつく りなさい。expr は、次のように引数で与えられた式を評価し、結果を標準出 力に返すものである。


----------------------------------------------------------------------
% expr 1 + 2 [←]
3
% []
----------------------------------------------------------------------

この課題では、expr コマンドに似たようなプログラム作るのではなく、それ をそのまま利用して、結果を受け取るプログラムを作る。実行するプログラム としては、expr 以外に次のようなものが考えられる。