printenv コマンドは,現在のプロセスの環境変数を表示してくれる. 同様の表示をしてくれるコマンドを,C 言語のプログラムとして記述せよ.
メモリマップを確かめるプログラムを Linux 上でコンパイルし,繰り返し実行したところ,以下のようにスタックのアドレスが毎回若干変化することがわかった. この現象が起きることを実際にプログラムを実行して確認しなさい.さらに,実行するごとにスタックの場所が変わる理由を予想した後,調査しなさい.
$ ./a.outenviron: 0x7ffc47977e08 argv: 0x7ffc47977df8 stack: 0x7ffc47977d0f bss: 0x6009d8 data: 0x6009a4 $ ./a.out
environ: 0x7ffd5246bc08 argv: 0x7ffd5246bbf8 stack: 0x7ffd5246bb0f bss: 0x6009d8 data: 0x6009a4 $ ./a.out
environ: 0x7ffd3db44b48 argv: 0x7ffd3db44b38 stack: 0x7ffd3db44a4f bss: 0x6009d8 data: 0x6009a4 $
以下のプログラムをコンパイル,実行すると Hello. が8回表示される. 8回表示される理由を説明しなさい.
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4
5 int main(void)
6 {
7 fork();
8 fork();
9 fork();
10 printf("Hello.\n");
11 wait(NULL);
12 return 0;
13 }
system は,与えられたコマンドを sh というシェルを起動して実行させるライブラリ関数である. 以下のように,コマンド行を文字列で渡すとそれを実行してくれる. リダイレクションやパイプやワイルドカードを意味する記号は system ではなく sh が解釈することに注意せよ.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main(void)
6 {
7 system("ls *.c | wc");
8 return 0;
9 }
fork, execve(または相当のライブラリ関数),wait などを使用して,system 相当の機能を持つ関数 mysystem を作りなさい.
system のマニュアルページに記述されているシグナルについての仕様は無視してよい.
リダイレクションによって標準入力をファイルに切り替えるプログラムを変更し,標準出力への書き込みもファイルに対して行われるようにせよ. 書き込み先のファイルはプログラムの第2引数として受け取るものとする.
第1引数で与えられるコマンドの実行結果(exit status)に応じて,第2引数または第3引数で与えられるコマンドを実行するプログラム if-then-else を作りなさい.例えば
$ ./if-then-else "test 1 -eq 1" "echo yes" "echo no"yes $ ./if-then-else "test 1 -eq 2" "echo yes" "echo no"
no $
となるようなプログラムである. fork, execve, wait などを使う練習のために,system 関数やシェルを使わずにプログラムを記述すること.
ダブルクオーテーションで囲った3つの文字列を与えるのではなく,コマンドの区切りとなる単語をあらかじめ決めておき,それによって区切りをプログラムに伝えるようにしても良い. 例えば以下は then, else を区切りとなる単語に決めたときのコマンド列である. execve を使用する場合には,if-then-else が受け取った文字列の配列の then, else に対応する要素を NULL で上書きすれば,その文字列の配列を execve にそのまま引数として渡せるので,以下の仕様のほうが,プログラムを作りやすいかもしれない.
$ ./if-then-else test 1 -eq 1 then echo yes else echo no
fork で子プロセスを作り,親プロセスと子プロセスが交互に実行を繰り返すプログラムを作りなさい.
% ./a.out 60子プロセスが1文字(最初は0)を出力した後,実行を親プロセスに戻し,親プロセスが1文字(最初はA)を出力した後,また実行を子プロセスに移し,子プロセスが1文字(1)を出力し…というように,親プロセスと子プロセスが交互に実行される.0A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6A7B8C9D %
練習問題(407)のプログラムを変更し,子プロセスの数をプログラムへの引数で与えられるようにしたプログラムを作りなさい.
3個のプロセスの標準入出力を2つのパイプで結び,それらのプロセスがデータをパイプでやりとりするプログラムを作りなさい.
先にパイプを2つ作ってから2回 fork する方法と,1つパイプを作り fork し,もう1つパイプを作りまた fork する方法があるが,どちらでも良い.
popen, pclose は,指定されたプログラムを実行するプロセスを生成し,そのプロセスと現在のプロセスをパイプにより接続し,そのプロセスからの入力またはそのプロセスへの出力に使えるファイルポインタを返すライブラリ関数である.
popen, pclose 相当の機能を持つ関数 mypopen, mypclose を作りなさい. 最初は,popenで実行するプログラムからの出力を受け取る機能だけを実装するところから始めるとよい.
パイプを使用するプログラムを,以下のように,親プロセスからの出力を子プロセスの入力にするように書き換えたところ,プログラムが終了しなくなってしまった. その理由を考え,プログラムが終了するようにプログラムを修正せよ.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/uio.h>
6 #include <sys/wait.h>
7
8 int pipe_fd[2];
9
10 void do_parent(void)
11 {
12 char *p = "Hello, my kid.";
13 int status;
14
15 printf("This is parent.\n");
16
17 close(pipe_fd[0]);
18
19 while (*p) {
20 if (write(pipe_fd[1], p, 1) < 0) {
21 perror("write");
22 exit(1);
23 }
24 p++;
25 }
26
27 if (wait(&status) < 0) {
28 perror("wait");
29 exit(1);
30 }
31 }
32
33 void do_child(void)
34 {
35 char c;
36 int count;
37
38 printf("This is child.\n");
39
40 close(pipe_fd[1]);
41
42 while ((count = read(pipe_fd[0], &c, 1)) > 0) {
43 putchar(c);
44 }
45 putchar('\n');
46
47 if (count < 0) {
48 perror("read");
49 exit(1);
50 }
51 }
52
53 int main(void)
54 {
55 int child;
56
57 if (pipe(pipe_fd) < 0) {
58 perror("pipe");
59 exit(1);
60 }
61
62 if ((child = fork()) < 0) {
63 perror("fork");
64 exit(1);
65 }
66
67 if (child)
68 do_parent();
69 else
70 do_child();
71
72 return 0;
73 }