システム・プログラム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 * に
変えることができる。)