システム・プログラム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-printenvp == 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()シ ステム・コール)。元のプロセスを親プロセス、作られたプロセスを子プロセ スという。
現在のプログラムの実行はそのまま続けて、新しくプログラムを実行するには、 次のようにする。
---------------------------------------------------------------------- 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: } ----------------------------------------------------------------------実行例。
execve() は、システム・コールである。これを使いやすくするために、次の ようなライブラリ関数が用意されている。---------------------------------------------------------------------- % ./proc-createchild: 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 %
----------------------------------------------------------------------
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);
この例では、echo のプロセスと、tr のプロセスは、パイプで接続されている。 パイプは、open() したファイルと同じようにread() したり write() したり することがでる。しかし実際には、ファイルではなく、プロセスとプロセスが データを交換する仕組(プロセス間通信の仕組)の1つである。---------------------------------------------------------------------- % echo "hello,world" | tr a-z A-ZHELLO,WORLD %
----------------------------------------------------------------------
パイプを作るには、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-rwHELLO,WORLD % ----------------------------------------------------------------------
proc-create
を実行すると、上のように、親プロセスの出力と子
プロセスの出力が入り交じることがある。この理由を考えなさい。
この問題を解決しなさい。そのためには、wait() システム・コール (waitpid(),wait3(),wait4()など)を用いて、同期を行えばよい。すなわち、 子プロセスが表示する可能性がある間は、親プロセスを待ち状態にしなさい。
ヒント:使わないパイプのファイル記述子は、全部 close() すること。パイ プの書き込み側のファイル記述子が開いている間は、read() しても EOF (End Of File) にならない。
先にパイプを2つ作ってから2回 fork() してもよい。パイプを1つ作って fork() してから もう1つパイプを作って fork() するという方法でもよい。
ヒント:書き手がいないパイプは、全ての書込み側のファイル記述子を closeすると作れる。
ヒント:プロセスを fork() しなくても、パイプに書くことはできる。プロセ スが1個の状態で、バッファの大きさ以上のデータを書き込むと、プロセスが ブロックされる(先に進まなくなる)。
---------------------------------------------------------------------- FILE *popen(const char *command, const char *type); int pclose (FILE *stream); ----------------------------------------------------------------------これと同じような機能を持つ関数を定義しなさい。ただし、入出力は、ファイ ル記述子のままでもよい。(ファイル記述子は、fdopen() 使えば、FILE * に 変えることができる。)