システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/classes/syspro-2003/2003-04-28
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
プロセッサ(CPU)が実行できる機械命令の列がプログラムである。 プロセスとは、プログラムがオペレーティング・システムによってメモリに読 み込まれ、やはりオペレーティング・システムの管理下にあるプロセッサによっ て実行の対象になったものである。
図1 プログラムとプロセス
プロセスには、保護、資源割り当てのような機能がある。 あるプロセスが 暴走 して他のプロセスを破壊したり、あるいは、意図的に他のプロセスからデータ を盗もうとしたとしても、それを禁止してプロセスを守ることをプロセスの保 護という。プロセスを保護するためには、プロセスが直接他のプロセスのメモ リなどにアクセスできないようにプロセスとプロセスを隔離する。Unix では、 基本的にはプロセスごとに独立した論理アドレス空間を割り当てることで実現 している。 資源とは、 メモリ、ディスク、プリンタ、ディスプレイ、キーボードなど コンピュータが処理を進める上で利用価値のあるものを総称である。 プロセスは、資源を公平に配分するための単位としての役割がある。/bin/ls
や/usr/bin/ls
などのファイルに保存されている
)
がメモリに読み込まれて実行される。
Xウインドウでメニューから ktermを起動すると、ktermのプロセスとその端末
の中で動くシェルのプロセスの2つが作られる。
ps(process)
コマンドを実行すると、プロセスの一覧を表示する。
psコマンドの表示の例を示す。
psコマンドの実行結果は1行が1プロセスである。 左から、以下のような意味がある。---------------------------------------------------------------------- % psPID TTY TIME CMD 21631 pts/5 00:00:00 tcsh 21714 pts/5 00:00:00 emacs 21718 pts/5 00:00:00 cat 21719 pts/5 00:00:00 ps %
----------------------------------------------------------------------
pts/5
は
/dev/pts/5
の意味。
TIME
COMMAND
プロセスにはメモリやCPUなどの資源が割り当てられる。 これはpsコマンドに-lオプションや -uをつけると表示される。---------------------------------------------------------------------- % ls -l /dev/pts/5crw--w---- 1 yas tty 136, 5 Apr 27 23:34 /dev/pts/5 % tty
/dev/pts/5 %
----------------------------------------------------------------------
---------------------------------------------------------------------- % ps -lF S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 000 S 1013 21631 21629 0 75 0 - 980 rt_sig pts/5 00:00:00 tcsh 000 T 1013 21718 21631 0 75 0 - 347 do_sig pts/5 00:00:00 cat 000 T 1013 21726 21631 1 75 0 - 2221 do_sig pts/5 00:00:00 emacs 000 R 1013 21733 21631 0 76 0 - 833 - pts/5 00:00:00 ps % ps -u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND yas 21631 0.0 0.7 3920 1872 pts/5 S 23:19 0:00 -csh yas 21718 0.0 0.1 1388 356 pts/5 T 23:33 0:00 cat yas 21726 1.2 1.7 8884 4532 pts/5 T 23:35 0:00 emacs -nw yas 21735 0.0 0.3 2844 892 pts/5 R 23:36 0:00 ps -u %
----------------------------------------------------------------------
TIME
は過去に利用した CPU 時間の割合、
SZ
は、プロセスが確保しているメモリ、
RSS
は、そのうちメインメモリに入っている部分である。
S
は、
プロセスの
状態
(STATe)
であり、次のようなものがある。
R
(runnable)
D
(Disk)
S
(Sleep)
Z
(Zombie)
T
(Traced)
W
がある。BSD系には、
S
の他に、I
(Idle) (20秒以上sleep している)
がある。
Unix では、後で説明する fork() システムコールを 発行すると、新しくプロセスが作られる。 (Unix では、これ以外の方法ではプロセスは作られない。) この時、「もとのプロセス」の「親プロセス」という。 プロセスの親プロセスのプロセス識別子は、psコマンドに「-l」オプションを つけるとPPIDのところに表示されている。
図2 プロセスの資源と属性
ps(process)
コマンドは、引数を付けないで実行すると、
プロセス識別子、端末名、
CPU時間、コマンド名を表示する。
ただし、そのままでは、psコマンドを実行した端末と結びつけられているプロ セスしか表示しない。 「-x」オプションを使うと、他の端末に結び付いているプロセスも表示する。---------------------------------------------------------------------- % psPID TTY TIME CMD 23788 pts/0 00:00:00 tcsh 23819 pts/0 00:00:00 emacs 23822 pts/0 00:00:00 cat 23958 pts/0 00:00:00 ps %
----------------------------------------------------------------------
プロセスが使っているメモリの大きさ (---------------------------------------------------------------------- % ps xPID TTY STAT TIME COMMAND 23788 pts/0 S 0:00 -tcsh 23819 pts/0 T 0:00 emacs -nw 23822 pts/0 T 0:00 cat 23967 pts/1 S 0:00 -tcsh 23996 pts/1 S 0:00 /bin/sh /usr/local/netscape/run-mozilla.sh /usr/local 24000 pts/1 S 0:03 /usr/local/netscape/mozilla-bin 24002 pts/1 S 0:00 /usr/local/netscape/mozilla-bin 24003 pts/1 S 0:00 /usr/local/netscape/mozilla-bin 24004 pts/1 S 0:00 /usr/local/netscape/mozilla-bin 24013 pts/0 R 0:00 ps x %
----------------------------------------------------------------------
SZ
、VSZ
、RSS
)、親プロセスの PID
(PPIID
(Parent PID))優先順位(PRI
(priority))
などの
プロセスの資源と属性を表示する。
Linux の ps は、ps -l と ps l で動きが若干異なる。-l は、System V 風、 l は、BSD 風である。 ps l, ps -l と同様に詳しい表示をする。---------------------------------------------------------------------- % ps -lF S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 100 S 1013 23788 23786 0 71 0 - 1025 rt_sig pts/0 00:00:00 tcsh 000 T 1013 23819 23788 0 69 0 - 2210 do_sig pts/0 00:00:00 emacs 000 T 1013 23822 23788 0 69 0 - 585 do_sig pts/0 00:00:00 cat 000 R 1013 24029 23788 0 74 0 - 817 - pts/0 00:00:00 ps % ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 100 1013 23788 23786 13 0 4100 2152 rt_sig S pts/0 0:00 -tcsh 000 1013 23819 23788 9 0 8840 4540 do_sig T pts/0 0:00 emacs -nw 000 1013 23822 23788 9 0 2340 552 do_sig T pts/0 0:00 cat 000 1013 24030 23788 12 0 3268 1304 - R pts/0 0:00 ps l %
----------------------------------------------------------------------
全てのプロセスを表示する。System V 風。 端末と結び付いている全てのプロセスを表示する。 x も付けた方がよい。 全てのプロセスを表示する。BSD風。---------------------------------------------------------------------- % ps uUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND yas 23788 0.0 0.8 4100 2152 pts/0 S 21:21 0:00 -tcsh yas 23819 0.0 1.7 8840 4540 pts/0 T 21:21 0:00 emacs -nw yas 23822 0.0 0.2 2340 552 pts/0 T 21:21 0:00 cat yas 24047 0.0 0.3 2840 904 pts/0 R 22:04 0:00 ps u %
----------------------------------------------------------------------
a
,u
, x
の組合わせ。全てのプ
ロセスを表示する時に、よく使われる。
プロセスを殺す方法には、次のような方法がある。
^Dを打つと、キーボードから EOF (End Of File) が送られ、cat プ ログラム自ら自発的に終了する(キーボードには、普通のファイルと違い、終 わりとう考え方はないが、OS がそういう仕組みを提供している)。 ^Cを打つと、cat は、自発的に終了するのではなくて、強制終了さ せれている。---------------------------------------------------------------------- % catLine 1
Line 1 Line 2
Line 2 ^C %
----------------------------------------------------------------------
^C
と同様に
^\
もあるが、
デバッグ用にcore
という名前のファイルができることが異なる。
coreは、実行中のプロセスのメモリの内容をファイルに保存したものである。
注意:以下の実験を行う場合、core を作る実験をする時には、
gnome-terminal ではなく、kterm を実行するとよい。標準の端末
(gnome-terminal) では、^\
では、漢字変換機能の on/off に割り当
てられている。
kterm の中で次の実験を行う。% kterm &%
![]()
core を作る実験をする時には、limit コマンドでcoredumpsize を unlimited にする必要がある。標準では、0 kbytes になっている。---------------------------------------------------------------------- % limit coredumpsize unlimited% ls -l core
ls: core: そのようなファイルやディレクトリはありません % cat
Line 1
Line 1 Line 2
Line 2 ^\ Quit (core dumped) % ls -l core
-rw------- 1 yas lab 61440 Apr 27 23:30 core %
----------------------------------------------------------------------
% limit coredumpsizecoredumpsize 0 kbytes % limit coredumpsize unlimited
% limit coredumpsize
coredumpsize unlimited %
![]()
core
ファイルはプログラムのデバッグに使える。
どのような関数(この例では番地だけ)が呼び出された結果、プログラムが終了 したかがわかる。---------------------------------------------------------------------- % gdb /bin/cat coreGNU gdb Red Hat Linux (5.1-0.71) Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... (no debugging symbols found)... Core was generated by `cat'. Program terminated with signal 3, Quit. Reading symbols from /lib/i686/libc.so.6...done. Loaded symbols for /lib/i686/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x4010cf74 in __libc_read () from /lib/i686/libc.so.6 (gdb) where
#0 0x4010cf74 in __libc_read () from /lib/i686/libc.so.6 #1 0x40162e34 in __DTOR_END__ () from /lib/i686/libc.so.6 #2 0x08048e9c in __fxstat64 () #3 0x08049902 in __fxstat64 () #4 0x40048657 in __libc_start_main (main=0x8049330 <__fxstat64+1756>, argc=1, ubp_av=0xbffff494, init=0x80489ac, fini=0x804a93c <__fxstat64+7400>, rtld_fini=0x4000dcd4 <_dl_fini>, stack_end=0xbffff48c) at ../sysdeps/generic/libc-start.c:129 (gdb) ----------------------------------------------------------------------
普通のデバッグ(自分が作ったプログラム)では、いちいち core を作る必要は ない。単にデバッガの中からプログラムを実行すればよい。強制的に core を 作るのは、普通、既に実行している他人が作ったプログラムを解析する時など に限られる。
^C
や ^\
で死なないプロセス
を殺すには、kill コマンドを使う。
プロセス識別子pidのプロセスを殺す。 オプションなしでkill コマンドを使っても死なないプロセスがある。 その場合はオプション% kill pid![]()
-KILL
をつけてkillコマンドを
実行する。
これでほとんどのプロセスは死ぬ。(OSの都合上(ディスクのI/O完了の 報告など)で、死なないプロセスもある。)% kill -KILL pid![]()
---------------------------------------------------------------------- 1: /* 2: arg-print.c -- mainの引数を表示するプログラム 3: ~yas/syspro/proc/arg-print.c 4: Start: 1997/04/21 18:23:13 5: */ 6: 7: main( int argc, char *argv[], char *envp[] ) 8: { 9: int i ; 10: printf("&argc == 0x%x, argc == %d\n", &argc, argc ); 11: printf("&argv == 0x%x, argv == 0x%x\n",&argv, argv ); 12: for( i=0 ; argv[i] ; i++ ) 13: printf("argv[%d]==0x%x, \"%s\"\n",i,argv[i],argv[i] ); 14: } ----------------------------------------------------------------------実行例。
argv[0] には、プログラムの名前が含まれている。argv[1] 以降に、普通の意 味での引数が含まれている。argc には、プログラムの名前まで含めての引数 の数が含まれている。argv[0] からargv[argc-1] まで参照できる。 argv[argc] は、参照してはいけない(0が入っているはずではあるが)。---------------------------------------------------------------------- % cp ~yas/syspro/proc/arg-print.c .% make arg-print
cc arg-print.c -o arg-print % ./arg-print
&argc == 0xbfffe7c0, argc == 1 &argv == 0xbfffe7c4, argv == 0xbfffe824 argv[0]==0xbffffa81, "./arg-print" % ./arg-print who am i
&argc == 0xbfffe740, argc == 4 &argv == 0xbfffe744, argv == 0xbfffe7a4 argv[0]==0xbffffa78, "./arg-print" argv[1]==0xbffffa84, "who" argv[2]==0xbffffa88, "am" argv[3]==0xbffffa8b, "i" %
----------------------------------------------------------------------
図3 argvの構造
char *argv[]
は、*argv[0] と書いたら char 型(8ビットの
整数)という意味である。
C言語で char *p と宣言した時、*p と p[0] は、同じである。 (p+1) と p[1] も同じである。
2次元配列は、C言語では配列の配列として表される。
char array[10][20];これで、全部で 10*20*1 == 200 バイトのメモリが確保される。array[i][j] の番地を計算するには、次のようになる。
(1) arrayの先頭番地を求める。 (2) (1)の値に + i*20 を加える。 (3) (2)の値に j を加える。array[10][20] のうち、最初の [10] は、番地の計算には使われない。
main() の引数で char *argv[] と char **argv は、同じ意味になる。 argv[i] は、 *(argv+i)、 argv[i][j] は、*(*(argv+i)+j) という意味になる。
char *argv[] で、argv[i][j] と書いた時の番地の計算の仕方は、次のように なる。
(1) argv の番地の内容を load する。 (2) (1)の値に + i*4 を加え、その値の番地の内容を load する。 (3) (2)の値に j を加える。2次元配列と違って、番地を求めるだけで、間に load が2回入る所に注意す る。2資源配列だと番地を計算する時には、load は出てこない。
(main の)引数で、char *argv[] と char **argv は、よいが、char argv[][] は、伝統的にはエラーになる。argv[i][j] を計算しようとすると、i を何倍 してよいのか計算できないので。しかし、受け付けるコンパイラもある。
C 言語で、char a[10] とした時には、10 バイトの領域が確保される。しかし、 char *p 宣言しただけでは、ポインタ自身(4バイト)の領域は確保されるが、 その先の領域は確保されない。
ポインタは、必ず番地をセットしてから使う。次のプログラムは、誤りである。
main() { char *p ; *p = 'A' ; }
環境変数は、プログラムからは、main() の3番目の引数、外部変数 environ 、 ライブラリ関数 getenv() でアクセスできる。メモリ中の構造は、argv と同 じである。各文字列は、「変数名=値」の形式になっている。---------------------------------------------------------------------- % printenv LANGja_JP.eucJP % date
日 4月 27 23:45:23 JST 2003 % setenv LANG C
% date
Sun Apr 27 23:45:29 JST 2003 %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: env-print.c -- 環境変数を表示するプログラム 3: ~yas/syspro/proc/env-print.c 4: Start: 1997/05/05 16:42:22 5: */ 6: extern char **environ ; 7: 8: main( int argc, char *argv[], char *envp[] ) 9: { 10: int i ; 11: printf("envp == 0x%x\n",envp ); 12: printf("environ == 0x%x\n",environ ); 13: for( i=0 ; envp[i] ; i++ ) 14: printf("envp[%d]==0x%x, \"%s\"\n",i,envp[i],envp[i] ); 15: } ----------------------------------------------------------------------実行例。
main の第3引数 envp と大域変数 environ は、同じ値である。環境変数には、 ホーム・ディレクトリの名前を保持している HOME、コマンドを検索するディ レクトリの名前のリストを表す PATH、標準的に使われるエディタを表す EDITOR などがある。---------------------------------------------------------------------- % cp ~yas/syspro/proc/env-print.c .% make env-print
cc env-print.c -o env-print % ./env-print
envp == 0xbfffe5bc environ == 0xbfffe5bc envp[0]==0xbffffa93, "USER=yas" envp[1]==0xbffffa9c, "LOGNAME=yas" envp[2]==0xbffffaa8, "HOME=/home/lab/Denjo/yas" envp[3]==0xbffffac1, "PATH=/home/lab/Denjo/yas/bin:/bin:/usr/local/bin:/usr/local3/bin:/usr/java/jdk1.3.1_01/bin:/usr/bin:/usr/bin/X11:/usr/X11R6/bin:/usr/local/Acrobat4/bin:/usr/lib/ICAClient:/usr/pgi/linux86/bin" <中略> envp[44]==0xbfffff9c, "NAME=Yasushi Shinjo" envp[45]==0xbfffffb0, "RHOST=bridge.hlla.is.tsukuba.ac.jp Wed" envp[46]==0xbfffffd7, "SIGNATURE=Yasushi Shinjo" % printenv | head -4
USER=yas LOGNAME=yas HOME=/home/lab/Denjo/yas PATH=/home/lab/Denjo/yas/bin:/bin:/usr/local/bin:/usr/local3/bin:/usr/java/jdk1.3.1_01/bin:/usr/bin:/usr/bin/X11:/usr/X11R6/bin:/usr/local/Acrobat4/bin:/usr/lib/ICAClient:/usr/pgi/linux86/bin %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: home-print.c -- 環境変数 HOME を表示するプログラム 3: ~yas/syspro/proc/home-print.c 4: Start: 1997/05/05 16:42:22 5: */ 6: 7: #include <string.h> /* strncmp() */ 8: #include <stdlib.h> /* getenv() */ 9: 10: extern char **environ ; 11: 12: main() 13: { 14: int i ; 15: char *homedir ; 16: for( i=0 ; environ[i] ; i++ ) 17: { 18: if( strncmp(environ[i],"HOME=",5)==0 ) 19: { 20: homedir = environ[i] ; 21: printf("0x%x, [%s]\n", homedir, homedir ); 22: break; 23: } 24: } 25: homedir = getenv("HOME"); 26: printf("getenv(\"HOME\")== 0x%x, [%s]\n", homedir, homedir); 27: } ----------------------------------------------------------------------実行例。
環境変数 HOME の値を調べるは、外部変数 environ の中から "HOME=" という 5 文字から始まるものを探す。これには、 str"n"cmp() を使うと便利である。---------------------------------------------------------------------- % cp ~yas/syspro/proc/home-print.c .% make home-print
cc home-print.c -o home-print % ./home-print
0xbffffa9d, [HOME=/home/lab/Denjo/yas] getenv("HOME")== 0xbffffaa2, [/home/lab/Denjo/yas] %
----------------------------------------------------------------------
自分でループで探さなくても、getenv() ライブラリ関数を使う方法がある。 この場合、"HOME=" の 5 文字がスキップされた値(0xbffffa9d+5== 0xbffffaa2)が得られる。
ホーム・ディレクトリにあるファイル、たとえば、.cshrc をアクセスするに は、この環境変数の値の後ろに "/.cshrc" を追加すればよい。ただし、単に strcat() してはいけない。別の長さが十分なバッファを用意して、それにコ ピーする。
homedir = getenv("HOME"); p = malloc( strlen(homedir)+strlen("/.cshrc")+1 ); strcpy( p, homedir ); strcat( p, "/.cshrc" );
csh の特殊な機能として、シェル変数 path (小文字)を変更すると、環境変数 PATH (大文字)が変化する。シェル変数と環境変数が連動しているものには、 他に、term, user, home などがある。
UNIX では、新たにプロセスを作る時に、自分のコピーしか作れない(fork()シ ステム・コール)。元のプロセスを親プロセス、作られたプロセスを子プロセ スという。
現在のプログラムの実行はそのまま続けて、新しくプログラムを実行するには、 次のようにする。
---------------------------------------------------------------------- 1: /* 2: fork-hello.c -- 画面に文字列を表示するプログラム 3: ~yas/syspro/proc/fork-hello.c 4: Start: 2001/05/13 23:19:01 5: */ 6: 7: main() 8: { 9: fork(); 10: fork(); 11: fork(); 12: printf("hello\n"); 13: } ----------------------------------------------------------------------
実行結果
fork() がなければ、1回しか表示されないはずである。1回の fork() でプ ロセスが2つに分かれる(新しいプロセスが1つ増える。)fork() した先の プロセスもさらに分かれるので、全部で2の3乗個のプロセスに分かれる。---------------------------------------------------------------------- % cp ~yas/syspro/proc/fork-hello.c .% make fork-hello
cc fork-hello.c -o fork-hello % ./fork-hello
hello hello hello hello hello hello hello hello %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: exec-date.c -- 実行中のプロセスのプログラムをdateプログラムに切替える 3: ~yas/syspro/proc/exec-date.c 4: Start: 2001/05/13 23:25:49 5: */ 6: 7: #include <unistd.h> 8: 9: extern char **environ; 10: 11: main() 12: { 13: char *argv[2] ; 14: argv[0] = "/bin/date" ; 15: argv[1] = 0 ; 16: execve( argv[0],argv,environ ); 17: execve( argv[0],argv,environ ); 18: execve( argv[0],argv,environ ); 19: execve( argv[0],argv,environ ); 20: } ----------------------------------------------------------------------
実行結果
execve() は、最初の1回しか有効ではない。execve() に成功すると、現在の プログラムがメモリから消され、新しいプログラム(/bin/date)がメモリに読 込まれる。/bin/date は、仕事が終ると exit() する。元のプログラムにもど ることはない。---------------------------------------------------------------------- % cp ~yas/syspro/proc/exec-date.c .% make exec-date
cc exec-date.c -o exec-date % ./exec-date
日 4月 27 23:49:30 JST 2003 %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: proc-create.c -- calプログラムよりプロセスを作る 3: ~yas/syspro/proc/proc-create.c 4: Start: 1995/02/27 15:27:54 5: */ 6: 7: #include <unistd.h> /* fork(), execve(), pid_t */ 8: #include <sys/types.h> /* wait() */ 9: #include <sys/wait.h> /* wait() */ 10: 11: extern char **environ; 12: 13: main() 14: { 15: pid_t child_pid ; 16: int status ; 17: if( (child_pid=fork()) == 0 ) 18: { 19: char *argv[4] ; 20: printf("child: pid == %d, ppid == %d\n", getpid(), getppid() ); 21: argv[0] = "cal" ; 22: argv[1] = "5" ; 23: argv[2] = "2003" ; 24: argv[3] = 0 ; 25: printf("child: 3\n");sleep( 1 ); 26: printf("child: 2\n");sleep( 1 ); 27: printf("child: 1\n");sleep( 1 ); 28: printf("child: 0\n"); 29: execve( "/usr/bin/cal", argv, environ ); 30: perror( "execve" ); 31: /* exec に失敗したら exit() を忘れないこと */ 32: exit( 1 ); 33: } 34: else if( child_pid > 0 ) 35: { 36: printf("parent: pid == %d, ppid == %d, child_pid == %d\n", 37: getpid(), getppid(),child_pid ); 38: printf("parent: waiting ...\n"); 39: wait( &status ); 40: printf("status == %d\n",status ); 41: } 42: else 43: { 44: perror("fork"); 45: } 46: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % cp ~yas/syspro/proc/proc-create.c .% make proc-create
cc proc-create.c -o proc-create % ./proc-create
parent: pid == 21776, ppid == 21750, child_pid == 21777 parent: waiting ... child: pid == 21777, ppid == 21776 child: 3 child: 2 child: 1 child: 0 5月 2003 日 月 火 水 木 金 土 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 status == 0 %
----------------------------------------------------------------------
fork() でプロセスが2つに分かれる。つまり、元のプロセスのコピーにより、 新しいプロセスが1つできる。コピーにより、fork() の結果は、違う。親プ ロセス(もとともあったプロセス)では、fork() の結果は、正の値が返され る。この正の値は、子プロセスの PID である。子プロセス(コピーの方)は、 0 が返される。
まれに、fork() が失敗することがある。その時には、子プロセスは作られず、 親プロセスに -1 が返される。
親プロセスと子プロセスは、独立に動作する。どちらが先に実行されるかとい う保証はない。本来は、独立に仕事をさせたい時に、プロセスを作るものであ り、1つのひとつ順序よくやりたい時にはプロセスを作らない。実行の順序を 制御するには、プロセス間で「同期」と呼ばれる操作を行う必要がある。
Unix の場合、別のプログラムをサブルーチンのように使いたいことがある。 この場合、子プロセスを fork() して、同期として、子供の終了(exit()する) を親が待つ(wait()する)。
それ以外の同期の方法は、少々複雑で、ファイルに対するロックやパイプなど のプロセス間通信を通じて行う。
sleep() は、引数で指定された秒数だけプロセスの実行を休止するライブラリ 関数である。
一度できたプロセスは、exit() しない限り、終了しない。main() がから抜け ると、プロセスが終了するのは、main() を呼び出した関数が exit() システ ムコールを実行しているからである。
start() { .... ret = main(argc, argv, envp); exit( ret ); }正常に終了した時には、exit(0)する。そうでない時には、0 以外の値で exit する。exit で返した値は、wait() で受け取ることができる。ただし、下位 8 ビットだけしか渡されない。WEXITSTATUS(status)で受け取る。
wait() で得られる status を調べると、exit() の値の他に、kill で殺され たかどうか、kill や ptrace() で停止したかどうかも調べられる。詳しくは、 man 2 wait を参照しなさい。
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 *status) pid_t waitpid(pid_t pid, int *status, int options); pid_t wait3(int *status, int options,struct rusage *rusage) pid_t wait4(pid_t pid, int *status, int options,struct rusage *rusage)Linux の wait() には、必ず引数を与える。status が不要の時には、0 を与 える。
main() { system("ls -l"); system("wc <file.c"); }引数の文字列では、/bin/sh の命令が記述可能である。
新しい環境変数を追加するには、environ の先の領域もコピーして増やす。
シグナルについては何もしなくてもよい。
argv[1] の文字列を、strtol() などで処理して数を得る。 その回数だけ、fork(),execve(),wait() をする。 &argv[2] 以降を、execve() にそのまま与えるとよい。% ./run-n 3 date -u日 4月 27 23:55:18 JST 2003 日 4月 27 23:55:18 JST 2003 日 4月 27 23:55:19 JST 2003 %
![]()
ライブラリ関数 system() も使ってもよいが、使わない方が簡単であろう。
main() プログラムの例を、~yas/syspro/proc/run-n.c
に置く。
---------------------------------------------------------------------- 1: /* 2: run-n.c -- 与えられたプログラム n 回実行するプログラム(mainのみ) 3: ~yas/syspro/proc/run-n.c 4: Start: 2003/04/27 23:57:38 5: */ 6: 7: #include <stdio.h> 8: 9: int run_n( int n, char *argv[], char *envp[] ); 10: 11: main( int argc, char *argv[], char *envp[] ) 12: { 13: int n ; 14: if( argc < 2 ) 15: { 16: fprintf(stderr,"Usage: %% %s num cmd arg1 arg2 ...\n",argv[0] ); 17: exit( -1 ); 18: } 19: n = strtol( argv[1],0,10 ); 20: run_n( n, &argv[2], envp ); 21: } 22: 23: int run_n( int n, char *argv[], char *envp[] ) 24: { 25: int i ; 26: printf("n == %d \n",n ); 27: for( i=0 ; argv[i] ; i++ ) 28: { 29: printf("argv[%d]: %s\n",i,argv[i] ); 30: } 31: } 32: ----------------------------------------------------------------------
このプログラムでは、単に表示がこのようになるのではなくて、カウント・ダ ウンするたびに必ず execve() などでプログラムを切替えなさい。次のような プログラムは、「不可」である。% ./countdown 33 2 1 0 %
![]()
main(int argc, char *argv[]) { int n, i ; n = strtol( argv[1],0,10 ); for( i=n ; i>=0 ; i-- ) { printf("%d\n",i); } }カウント・ダウンするたびに、execve() などでプログラムを切替えること。
main(int argc, char *argv[]) { ... n = strtol( argv[1],0,10 ); if( n <= 0 ) exit( 0 ); else { n -- ; ... execve(....); perror("execve"); exit( 1 ); } }注意:このプログラムでは、fork() も wait() も、不要である。
argv[1] で与えられたプログラムを実行し、成功すれば、argv[2] を、実行す る。失敗すれば、argv[3] を実行する。実行する時には、上のように 引用符でコマンドを括ると、間に空白を含まれていても1つの引数になる。 このような引数は、ライブラリ関数 system() に渡す時に都合がよい。---------------------------------------------------------------------- % ./if-then-else "cc file1.c" "play happy" "play sad"----------------------------------------------------------------------
コマンドの区切りは、別のもでもよい。
この場合、argv[3] の then や argv[6] の else を 0 で潰して execvp() な どで実行するとよい。この場合は、system() よりも、execvp() を使った方が 簡単であろう。まず最初の条件判定をする部分を fork-exec で実行し、wait する。wait の結果、成功していたら、then 以下の部分をexec し、そうでな ければ、else 以下の部分を exec する。then 以下や else 以下を実行する時 には、もどってくる必要がないので、fork しなくてもよい。---------------------------------------------------------------------- % ./if-then-else cc file1.c then play happy else play sad----------------------------------------------------------------------
複数の wget コマンドを同時に実行して、複数の WWW 資源を同時にコピーす るプログラムをつくりなさい。たとえば、次のように実行すると、最大3個の wget コマンドを同時に動かし、同時にコピーを行う。---------------------------------------------------------------------- % wget http://www/--02:00:20-- http://www/ => index.html www:80 に接続しています... 接続しました! HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 2,443 [text/html] 0K -> .. [100%] 02:00:20 (2.33 MB/s) - index.html を保存しました [2443/2443] %
----------------------------------------------------------------------
このプログラムでは、ライブラリ関数 system() を使ってもよい(使わなくて もよい)。---------------------------------------------------------------------- % ./run-wget-n 3 url1 url2 url3 url4 url5 url6----------------------------------------------------------------------
必ずしも同数に分割する必要はない。高度なプログラムを記述すれば、終了し 次第、次の URL に進むようにすることができる。wget コマンドにはもともと も複数の URL を取る機能もあるので、それを使ってもよい。