環境変数、プロセスの生成、パイプ

システム・プログラムI

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

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

■復習

■環境変数


----------------------------------------------------------------------
   1:	/*
   2:	        env-print.c -- 環境変数を表示するプログラム
   3:	        ~yas/syspro1-1998/proc/env-print.c
   4:	        $Header: /home/lab2/OS/yas/syspro1-1998/proc/RCS/env-print.c,v 1.3 1998/05/25 14:47:51 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:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./env-print [←]
envp == 0x7fff2f4c
environ == 0x7fff2f4c
envp[0]==0x7fff300c, "HOME=/home/lab2/OS/yas"
envp[1]==0x7fff3023, "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[2]==0x7fff3109, "LOGNAME=yas"
envp[3]==0x7fff3115, "HZ=100"
...
envp[24]==0x7fff32eb, "LESSCHARSET=japanese-euc"
envp[25]==0x7fff3304, "IRCSERVER=130.158.87.200"
envp[26]==0x7fff331d, "KCODE=euc"
% []
----------------------------------------------------------------------

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

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

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

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


----------------------------------------------------------------------
   1:	/*
   2:	        proc-create.c -- calプログラムよりプロセスを作る
   3:	        ~yas/syspro1/proc/proc-create.c
   4:	        $Header: /home/lab2/OS/yas/syspro1-1998/proc/RCS/proc-create.c,v 1.3 1998/05/25 14:58: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] = "1998" ;
  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 == 27405, ppid == 27404
 parent: pid == 27404, ppid == 27288, child_pid == 27405
 %    1998 年  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
 [←]
 % [←]
 % ./proc-create [←] 
 child: pid == 27429, ppid == 27428
    1998 年  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 == 27428, ppid == 27288, child_pid == 27429
 % []
----------------------------------------------------------------------

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);

■pipe() と dup()

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

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

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

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

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

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


----------------------------------------------------------------------
   1:	/*
   2:	        pipe-rw.c -- pipe() と dup() を使ったプログラム
   3:	        ~yas/syspro1/ipc/pipe-rw.c
   4:	        $Header: /home/lab2/OS/yas/syspro1/ipc/RCS/pipe-rw.c,v 1.5 1998/05/25 15:34:59 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 ;
  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:	            write( 1,p++,1 );
  54:	        }
  55:	}
  56:	
  57:	void child( int fildes[2] )
  58:	{
  59:	    char c ;
  60:	        close( fildes[1] );
  61:	        close( 0 );
  62:	        dup( fildes[0] );
  63:	        close( fildes[0] );
  64:	
  65:	        while( read(0,&c,1) >0 )
  66:	        {
  67:	            putchar( toupper(c) );
  68:	        }
  69:	}
----------------------------------------------------------------------

実行例。

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

■練習問題と課題

★練習問題34 printenvコマンド

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

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

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

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

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

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

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

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

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

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

proc-createを実行すると、上のように、親プロセスの出力と子 プロセスの出力が入り交じることがある。この理由を考えなさい。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

プロセスを実行してその結果を得るには、popen() ライブラリ関数が便利であ る。
----------------------------------------------------------------------
     FILE *popen(const char *command, const char *type);
     int pclose (FILE *stream);
----------------------------------------------------------------------
これと同じような機能を持つ関数を定義しなさい。ただし、入出力は、ファイ ル記述子のままでもよい。(ファイル記述子は、fdopen() 使えば、FILE * に 変えることができる。)

■課題提出方法


↑[もどる] [課題提出方法] ←[5月19日] ・[5月26日] →[6月2日]
Last updated: 1998/06/18 01:15:40
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>