システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/syspro-2004/2004-04-12
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
このような基本的な考え方は、時間が経過してもそれほど変わらない。 この講義で得られた知識は、10年後も十分通用する。
オペレーティング・システムの「内部の動き」は、2学期の「オペレーティン グ・システム」という講義で扱う。内部の動きの前に、外から眺めてみて、オ ペレーティング・システムの考え方を理解することを目的とする。 3学期には、さらに「オペレーティング・システム II 」、「分散システム」 で最近の話題に繋がる。
この講義では、Unix の API (Application Program Interface) を利用し てプログラムを作成する。API は、次の3つに分類される。
この講義で扱う Unix の API は、他のものと比較すると簡単になっている。 複雑なものには、Xウインドウ・システム、MS-Windows (Win32 API) などが ある。
「雰囲気」から「仕組みの理解」へ。
図? オペレーティング・システムの構成要素
図? ソース・プログラムから実行形式まで
cc -c
で実行されるプログラム
cpp(the C language preprocessor)
cc1(C Compiler)
as(Assembler)
図? cc -c が実行するプログラム
コンパイルの観察。% nl -ba today.c 1 #define TODAY "Monday" 2 main() 3 { 4 printf("Today is %s.\n",TODAY ); 5 } % cc today.c % ./a.out Today is Monday. %
% cc -v today.c Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) /usr/lib/gcc-lib/i386-redhat-linux/2.96/cpp0 -lang-c -v -D__GNUC__=2 -D__GNUC_MINOR__=96 -D__GNUC_PATCHLEVEL__=0 -D__ELF__ -Dunix -Dlinux -D__ELF__ -D__unix__ -D__linux__ -D__unix -D__linux -Asystem(posix) -D__NO_INLINE__ -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ today.c /tmp/ccBBSvXh.i GNU CPP version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) (cpplib) (i386 Linux/ELF) ignoring nonexistent directory "/usr/i386-redhat-linux/include" #include "..." search starts here: #include <...> search starts here: /usr/local/include /usr/lib/gcc-lib/i386-redhat-linux/2.96/include /usr/include End of search list. /usr/lib/gcc-lib/i386-redhat-linux/2.96/cc1 /tmp/ccBBSvXh.i -quiet -dumpbase today.c -version -o /tmp/cc8l7Fmp.s GNU C version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) (i386-redhat-linux) compiled by GNU C version 2.96 20000731 (Red Hat Linux 7.1 2.96-85). as -V -Qy -o /tmp/ccB4lWuy.o /tmp/cc8l7Fmp.s GNU assembler version 2.10.91 (i386-redhat-linux) using BFD version 2.10.91.0.2 /usr/lib/gcc-lib/i386-redhat-linux/2.96/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/2.96 -L/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../.. /tmp/ccB4lWuy.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o %
% cc -E today.c > today.i % nl -ba today.i 1 # 2 "today.c" 2 main() 3 { 4 printf("Today is %s.\n","Monday" ); 5 } %
% cc -c today.c % ls -l today.o -rw-r--r-- 1 yas lab 932 4月 14 17:11 today.o % nm today.o 00000000 t gcc2_compiled. 00000000 T main U printf % cc -v -o a.out today.o Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-85) /usr/lib/gcc-lib/i386-redhat-linux/2.96/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o a.out /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/2.96 -L/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../.. today.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o %様々なライブラリ(-lgcc -lc -lgcc) が参照されている。
open(),close(),read(),write()
ライブラリ関数の例
fopen(),fclose(),fread(),fwrite(),printf(),scanf(),getchar(),putchar()
マクロ定義の例
getc(),putc(),isalpha(),isdigit(),
(stdin,stdout,stderr,getchar(),putchar() がマクロにもなっているシステムも多い。)
ヘッダ・ファイル stdio.h の観察
% egrep getc /usr/include/stdio.h extern int fgetc (FILE *__stream) __THROW; extern int getc (FILE *__stream) __THROW; extern int getchar (void) __THROW; #define getc(_fp) _IO_getc (_fp) extern int getc_unlocked (FILE *__stream) __THROW; extern int getchar_unlocked (void) __THROW; extern int fgetc_unlocked (FILE *__stream) __THROW; extern int ungetc (int __c, FILE *__stream) __THROW; % cat getchar.c #includemain() { printf("%c\n",getc(stdin) ); printf("%c\n",getchar() ); } % cc -E getchar.c > getchar.i % tail -5 getchar.i main() { printf("%c\n",_IO_getc (stdin) ); printf("%c\n",getchar() ); } %
1: 2: /* 3: file-copy.c -- ファイルをコピーする簡単なプログラム 4: ~yas/syspro/file/file-copy.c 6: Start: 1995/03/04 16:40:24 7: */ 8: 9: #include <stdio.h> /* stderr */ 10: #include <fcntl.h> /* open(2) */ 11: 12: extern void file_copy( char *from_name, char *to_name ); 13: 14: main( int argc, char *argv[] ) 15: { 16: if( argc != 3 ) 17: { 18: fprintf( stderr,"Usage: %s from to\n", argv[0] ); 19: exit( 1 ); 20: } 21: file_copy( argv[1],argv[2] ); 22: } 23: 24: #define BUFFERSIZE 1024 25: 26: void file_copy( char *from_name, char *to_name ) 27: { 28: int from_fd,to_fd ; 29: char buffer[BUFFERSIZE] ; 30: int rcount ; 31: int wcount ; 32: 33: from_fd = open( from_name,O_RDONLY ); 34: if( from_fd == -1 ) 35: { 36: perror( from_name ); 37: exit( 1 ); 38: } 39: to_fd = open( to_name,O_WRONLY|O_CREAT|O_TRUNC,0666 ); 40: if( to_fd == -1 ) 41: { 42: perror( to_name ); 43: exit( 1 ); 44: } 45: while( (rcount=read(from_fd,buffer,BUFFERSIZE)) > 0 ) 46: { 47: if( (wcount=write(to_fd,buffer,rcount))!= rcount ) 48: { 49: perror( to_name ); 50: exit( 1 ); 51: } 52: } 53: close( from_fd ); 54: close( to_fd ); 55: }
プログラムに引数を渡すことができる。argv[0] には、プログラムの名前を示 す文字列が入っている。自分のプログラムの名前で動作を変える時にだけ参照 する。argv[1] 以降が本当の意味での引数である。argc には、argv[0]を含め た引数の数が入っている。
open()は、ファイルを開くシステム・コールである。O_RDONLYとは、 読み込み専用でファイルを開くことを意味している。open() システム・コー ルは、結果としてファイル記述子を返す。ファイル記述子は、負でない小さな 整数(だいたい0〜1024の範囲、最大はシステムによって違う)である。 ファイル記述子は、read()システム・コールやwrite()システム・コールで実 際にファイルを読み書きする時に使われる。
エラーが起きた時には、open() システム・コールは、-1 を返す。この時、エ ラーの番号が errno という変数に格納される。perror() は、errno変数を解 析して、より詳しいエラー・メッセージを表示する関数である。perror() の 引数は、エラー・メッセージとともに表示される文字列である。
ここでは、出力用のファイルを開いている。O_WRONLYは、書き込み専用でファ イルを開くことを意味している。O_CREATは、ファイルが存在しなければ作る ように指示するものである。ファイルが存在する場合、上書きされる。 O_TRUNCは、ファイルが存在している時には、その大きさを0にすることを指 示するものである。0666 (C言語で0から始まる数は、8進数)は、ファイルを 作る時のモードである。この数値に従って、ファイルのモード(ls -l で rwxrwxrwx と表示される部分)が決定される。作成されるファイルのモードは、 ここで指定されたモードから現在の umask が落とされた(引かれた)値とな る。
read() システム・コールは、第1引数で指定されたファイル記述子のファイ ルを読み込み、それを第2引数の番地へ保存する。読み込むバイト数は、第3 引数で与えられる。結果として読み込んだバイト数を返す。通常は、BUFFERSIZE が返される。read() システム・コールの結果、ファイル上の読み書きする位 置が、実際に読み込んだバイト数だけずれる。ファイルの末尾近くや、ファイ ルが端末の時、BUFFERSIZE 以下の値が返される。ファイルの末尾に行き着くと 0 が返される。エラーが起きると、-1 が返される。
write() システム・コールは、第1引数で指定されたファイル記述子のファイ ルへデータを書き込む。書き込まれるデータは、第2引数で与えられた番地か ら、第3引数で与えらた大きさである。write() システム・コールは、結果と して書き込んだバイト数を返す。ファイル上の読み書きする位置が、実際に読 み込んだバイト数だけずれる。通常は、BUFFERSIZE が返される。空き容量不足 などで書き込みが失敗した時には、-1 を返す。エラーが起きると、-1 が返さ れる。
close() は、ファイルを閉じるシステム・コールである。このシステム・コールを実行してしまうと、そのファイル記述子は無効になる。すなわち、read() や write() を行うと、エラーになる。
% mkdir file % cd file % cp ~yas/syspro/file/file-copy.c . % make file-copy cc file-copy.c -o file-copy % ls file-copy file-copy.c % echo "This is file1" > file1 % cat file1 This is file1 % ls file-copy file-copy.c file1 % ./file-copy file1 file2 % ls file-copy file-copy.c file1 file2 % ls -l 総ブロック数 30 -rwxr-xr-x 1 yas lab 12616 4月 22日 17時19分 file-copy -rw-r--r-- 1 yas lab 1096 4月 22日 17時19分 file-copy.c -rw-r--r-- 1 yas lab 14 4月 22日 17時20分 file1 -rw-r--r-- 1 yas lab 14 4月 22日 17時20分 file2 % diff file1 file2 % cmp file1 file2 % cat file2 This is file1 %strace コマンドを使うと、どのようなシステムコールが使われているかがわ かる。strace コマンドは、Linux, FreeBSD 等で使える。System V 系では、 truss, BSD 系では、ktrace が使える。
% strace ./file-copy file1 file2 execve("./file-copy", ["./file-copy", "file1", "file2"], [/* 52 vars */]) = 0 uname({sys="Linux", node="adonis9.coins.tsukuba.ac.jp", ...}) = 0 brk(0) = 0x80498c8 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40016000 open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=87112, ...}) = 0 old_mmap(NULL, 87112, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000 close(3) = 0 open("/lib/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0 \304\1"..., 1024) = 1024 fstat64(3, {st_mode=S_IFREG|0755, st_size=5737218, ...}) = 0 old_mmap(NULL, 1267240, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x4002d000 mprotect(0x40159000, 38440, PROT_NONE) = 0 old_mmap(0x40159000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x12b000) = 0x40159000 old_mmap(0x4015f000, 13864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x4015f000 close(3) = 0 munmap(0x40017000, 87112) = 0 open("file1", O_RDONLY) = 3 open("file2", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4 read(3, "This is file1\n", 1024) = 14 write(4, "This is file1\n", 14) = 14 read(3, "", 1024) = 0 close(3) = 0 close(4) = 0 _exit(0) = ? %
1: /* 2: fd-print.c -- ファイル記述子を表示するプログラム 3: ~yas/syspro/file/fd-print.c 5: Start: 1997/04/21 18:23:13 6: */ 7: 8: #include <fcntl.h> 9: 10: void main( int argc, char *argv[] ) 11: { 12: int i ; 13: int fd ; 14: for( i=1 ; i<argc ; i++ ) 15: { 16: fd = open( argv[i],O_RDONLY ); 17: printf("%s: %d\n",argv[i],fd ); 18: } 19: }実行例:
% ./fd-print fd-print.c fd-print.c: 3 % ./fd-print fd-print.c /etc/passwd fd-print.c fd-print.c: 3 /etc/passwd: 4 fd-print.c: 5 %
プログラムが実行される時、open() システム・コールを使わなくても、次の 3つのファイル記述子は使える状態になっている。
stdio-thru.cは、標準入力で指定されたファイルの内容を標準出力で指定され たファイルへコピーするプログラムである。
1: 2: /* 3: stdio-thru.c -- 標準入力から標準出力へのコピー 4: ~yas/syspro/file/stdio-thru.c 6: Start: 1995/03/04 16:40:24 7: */ 8: 9: #include <stdio.h> 10: 11: extern void stdio_thru(void); 12: 13: main( int argc, char *argv[] ) 14: { 15: if( argc != 1 ) 16: { 17: fprintf( stderr,"Usage: %s\n", argv[0] ); 18: exit( 1 ); 19: } 20: stdio_thru(); 21: } 22: 23: #define BUFFERSIZE 1024 24: 25: void stdio_thru() 26: { 27: char buffer[BUFFERSIZE] ; 28: int rcount ; 29: int wcount ; 30: 31: while( (rcount=read(0,buffer,BUFFERSIZE)) > 0 ) 32: { 33: if( (wcount=write(1,buffer,rcount))!= rcount ) 34: { 35: perror("stdout"); 36: exit( 1 ); 37: } 38: } 39: close( 0 ); 40: close( 1 ); 41: }このプログラムは、引数をとらない。"<" や ">" は、シェルにより解釈され、 このプログラムには渡されない。
file_copy() とは異なり、stdio_thru() では、ファイルを開く操作(open()) を行うことなく、入出力(read(),write)を行っている。このような事が可能 な理由は、UNIXでは、ファイル記述子 0, 1, 2 は、シェルにより既に開 かれているからである。
ファイル記述子 0 は、なにもしないと端末のキーボードに割り当てられてい る。ファイル記述子 1, 2 は、端末の画面に割り当てられている。しかし、 "<"、">"、"|"を使うと別のファイル やパイプに切替えられる。
% ./stdio-thru ljd ljd sk sk ^D % ls bbb bbb がアクセスできません: そのようなファイルまたはディレクトリはありません % ./stdio-thru < stdio-thru.c > bbb % ls -l stdio-thru.c bbb -rw-r--r-- 1 yas lab 690 4月 22日 17時37分 bbb -rw-r--r-- 1 yas lab 690 4月 22日 17時35分 stdio-thru.c %
プログラムは、~yas/syspro/file/file-copy-lib.c にある。
1: 2: /* 3: file-copy.c -- ファイルをコピーする簡単なプログラム(ライブラリ) 4: ~yas/syspro/file/file-copy-lib.c 5: Created on 2004/04/11 17:31:29 6: */ 7: 8: #include <stdio.h> /* stderr, fopen(), fread(), frwite(), fclose() */ 9: #include <fcntl.h> /* open(2) */ 10: 11: extern void file_copy_lib( char *from_name, char *to_name ); 12: 13: main( int argc, char *argv[] ) 14: { 15: if( argc != 3 ) 16: { 17: fprintf( stderr,"Usage: %s from to\n", argv[0] ); 18: exit( 1 ); 19: } 20: file_copy_lib( argv[1],argv[2] ); 21: } 22: 23: #define BUFFERSIZE 1024 24: 25: void file_copy_lib( char *from_name, char *to_name ) 26: { 27: FILE *from_file, *to_file ; 28: char buffer[BUFFERSIZE] ; 29: int rcount ; 30: int wcount ; 31: 32: from_file = fopen( from_name,"r" ); 33: if( from_file == NULL ) 34: { 35: perror( from_name ); 36: exit( 1 ); 37: } 38: to_file = fopen( to_name,"w" ); 39: if( to_file == 0 ) 40: { 41: perror( to_name ); 42: exit( 1 ); 43: } 44: while( (rcount=fread(buffer,1,BUFFERSIZE,from_file)) > 0 ) 45: { 46: if( (wcount=fwrite(buffer,1,rcount,to_file))!= rcount ) 47: { 48: perror( to_name ); 49: exit( 1 ); 50: } 51: } 52: fclose( from_file ); 53: fclose( to_file ); 54: }実行例
% cp ~yas/syspro/file/file-copy-lib.c . % make file-copy-lib cc file-copy-lib.c -o file-copy-lib % ./file-copy-lib file1 file3 % cat file3 This is file1 %strace コマンドで調べると、同じシステムコールを使っていることがわかる。
% strace ./file-copy-lib file1 file3 execve("./file-copy-lib", ["./file-copy-lib", "file1", "file3"], [/* 52 vars */]) = 0 ... brk(0x804a000) = 0x804a000 open("file1", O_RDONLY) = 3 open("file3", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4 fstat64(3, {st_mode=S_IFREG|0644, st_size=14, ...}) = 0 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40017000 read(3, "This is file1\n", 4096) = 14 read(3, "", 4096) = 0 fstat64(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40018000 read(3, "", 4096) = 0 close(3) = 0 munmap(0x40017000, 4096) = 0 write(4, "This is file1\n", 14) = 14 close(4) = 0 munmap(0x40018000, 4096) = 0 _exit(0) = ? %Linux では、ltrace コマンドを使うと、どのようなライブラリ関数が使われ ているかがわかる。
% ltrace ./file-copy-lib file1 file3 __libc_start_main(0x080485e0, 3, 0xbfffe604, 0x080483fc, 0x08048790__register_frame_info(0x080497e0, 0x080498d4, 0xbfffe5a8, 0x0804850e, 0x080483fc) = 0x4015e460 fopen("file1", "r") = 0x080498f8 fopen("file3", "w") = 0x08049a68 fread(0xbfffe160, 1, 1024, 0x080498f8) = 14 fwrite("This is file1\n", 1, 14, 0x08049a68) = 14 fread(0xbfffe160, 1, 1024, 0x080498f8) = 0 fclose(0x080498f8) = 0 fclose(0x08049a68) = 0 __deregister_frame_info(0x080497e0, 0x4000c981, 0x400164c4, 0x40016618, 1) = 0x080498d4 +++ exited (status 0) +++ %
1: /* 2: filter-char.c -- 文字単位の簡単なフィルタ 3: ~yas/syspro/file/filter-char.c 5: Start: 1997/04/21 18:23:13 6: */ 7: 8: #include <stdio.h> /* stdin, stdout, fopen(), fclose(), getc(), */ 9: #include <ctype.h> /* toupper() */ 10: 11: extern void filter_char_upper( FILE *in, FILE *out ); 12: 13: main( int argc, char *argv[] ) 14: { 15: int i ; 16: FILE *fp ; 17: if( argc == 1 ) 18: filter_char_upper( stdin, stdout ); 19: else 20: { 21: for( i=1 ; i< argc ; i++ ) 22: { 23: if( (fp = fopen( argv[i],"r" )) == NULL ) 24: { 25: perror( argv[i] ); 26: exit( -1 ); 27: } 28: filter_char_upper( fp, stdout ); 29: fclose( fp ); 30: } 31: } 32: } 33: 34: void filter_char_upper( FILE *in, FILE *out ) 35: { 36: int c ; /* int型。char は、不可 */ 37: while( (c=getc(in)) != EOF ) 38: { 39: putc( toupper(c),out ); 40: } 41: }
fopen()は、ファイルを開くライブラリ関数である。"r" は、 読み込み専用で ファイルを開くことを意味している。fopen()は、結果として FILE 構造体へ のポインタを返す。これは、次のようなライブラリ関数で使われる。
fgetc() fputs() getc() getw() fgets() fread() ungetc() fprintf() fscanf() fputc() fwrite() putc() putc() fflush() fseek() ftell() frewind() fclose() fdopen() freopen() ferrror() feof() clearerr() fileno()
このような、FILE * を使うライブラリ関数群は、次のような名前で呼ば れる。
fread(), fwrite() は、ある固定長の構造体を入出力するための関数である。
fclose() は、ファイルを閉じるライブラリ関数である。
実行例
% ./filter-char abc ABC asdfjkl; ASDFJKL; ^D %
putc() は、第1引数で指定された文字を第2引数で与えられたストリームへ 出力する。
1: /* 2: file-copy.c -- ファイルをコピーする簡単なプログラム(ライブラリ) 3: ~yas/syspro/file/file-copy-lib.c 4: Created on 2004/04/11 18:14:52 5: */ 6: 7: import java.io.*; 8: 9: public class FileCopy 10: { 11: static java.io.BufferedReader stdin = 12: new java.io.BufferedReader( new java.io.InputStreamReader(System.in) ); 13: static java.io.PrintStream stdout = System.out; 14: static java.io.PrintStream stderr = System.err; 15: 16: public static void main(String argv[]) 17: { 18: if( argv.length != 2 ) 19: { 20: stderr.println("Usage: % java FileCopy from to"); 21: System.exit( 1 ); 22: } 23: file_copy( argv[0], argv[1] ); 24: } 25: 26: static int BUFFERSIZE = 1024 ; 27: 28: public static void file_copy( String from, String to ) 29: { 30: try 31: { 32: InputStream from_is = new FileInputStream( from ); 33: OutputStream to_os = new FileOutputStream( to ); 34: byte buffer[] = new byte[BUFFERSIZE]; 35: int rcount; 36: while( (rcount=from_is.read(buffer))>=0 ) 37: { 38: to_os.write( buffer,0,rcount ); 39: } 40: from_is.close(); 41: to_os.close(); 42: } 43: catch( Exception e ) 44: { 45: stderr.println( e ); 46: } 47: } 48: }実行例
% cp ~yas/syspro/file/FileCopy.java . % javac FileCopy.java % ls FileCopy.class file-copy file-copy-lib.c file1 file3 FileCopy.java file-copy-lib file-copy.c file2 % java FileCopy Usage: % java FileCopy from to % java FileCopy file1 file4 % ls FileCopy.class file-copy file-copy-lib.c file1 file3 FileCopy.java file-copy-lib file-copy.c file2 file4 % cat file1 This is file1 % cat file4 This is file1 %strace コマンドで調べると、同じシステムコールを使っていることがわかる。
% strace java FileCopy file1 file4 execve("/usr/java/j2sdk1.4.1_02/bin/java", ["java", "FileCopy", "file1", "file4"], [/* 52 vars */]) = 0 ... open("file1", O_RDONLY|O_LARGEFILE) = 5 fstat64(5, {st_mode=S_IFREG|0644, st_size=14, ...}) = 0 open("file4", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 6 fstat64(6, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 read(5, "This is file1\n", 1024) = 14 write(6, "This is file1\n", 14) = 14 read(5, "", 1024) = 0 close(5) = 0 close(6) = 0 ...
特殊な状況では、混ぜる必要がでてくる。 その時は、次のような関数を使う。
この機能を抑止するには、stty cbreak とするシステムが多い。 (Irix など、cbreak が使えないシステムもある。)
% cat hello hello ^D % stty cbreak;cat hheelllloo ^C %stty raw でも、バッファリングがなくなるが、^C による強制終了もできなく なるので、注意する。
% cat abc abc ^D % stty raw; cat aabbcc (^Dが利かない) (別の端末から ps して kill) Terminated %
プロセッサ(CPU)が実行できる機械命令の列がプログラムである。 プロセスとは、プログラムがオペレーティング・システムによってメモリに読 み込まれ、やはりオペレーティング・システムの管理下にあるプロセッサによっ て実行の対象になったものである。
図? プログラムとプロセス
プロセスの機能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: }実行例。
% 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" %argv[0] には、プログラムの名前が含まれている。argv[1] 以降に、普通の意 味での引数が含まれている。argc には、プログラムの名前まで含めての引数 の数が含まれている。argv[0] からargv[argc-1] まで参照できる。 argv[argc] は、参照してはいけない(0が入っているはずではあるが)。
% printenv LANG ja_JP.eucJP % date 日 4月 27 23:45:23 JST 2003 % setenv LANG C % date Sun Apr 27 23:45:29 JST 2003 %環境変数は、プログラムからは、main() の3番目の引数、外部変数 environ 、 ライブラリ関数 getenv() でアクセスできる。メモリ中の構造は、argv と同 じである。各文字列は、「変数名=値」の形式になっている。
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: }実行例。
% 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 %main の第3引数 envp と大域変数 environ は、同じ値である。環境変数には、 ホーム・ディレクトリの名前を保持している HOME、コマンドを検索するディ レクトリの名前のリストを表す PATH、標準的に使われるエディタを表す EDITOR などがある。
char *homedir ; homedir = getenv("HOME");
csh の特殊な機能として、シェル変数 path (小文字)を変更すると、環境変数 PATH (大文字)が変化する。シェル変数と環境変数が連動しているものには、 他に、term, user, home などがある。
sh, bash では、シェル変数を export すると環境変数になる。
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: }
実行結果
% 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 %fork() がなければ、1回しか表示されないはずである。1回の fork() でプ ロセスが2つに分かれる(新しいプロセスが1つ増える。)fork() した先の プロセスもさらに分かれるので、全部で2の3乗個のプロセスに分かれる。
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: }
実行結果
% 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 %execve() は、最初の1回しか有効ではない。execve() に成功すると、現在の プログラムがメモリから消され、新しいプログラム(/bin/date)がメモリに読 込まれる。/bin/date は、仕事が終ると exit() する。元のプログラムにもど ることはない。
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 の命令が記述可能である。
1: /* 2: RunProgram.java -- プログラムを実行するプログラム 3: ~yas/syspro/proc/RunProgram.java 4: Created on 2004/04/11 19:44:22 5: */ 6: 7: import java.lang.Runtime; 8: 9: public class RunProgram { 10: public static void main( String not_used_argv[] ) 11: { 12: try { 13: Runtime rt = Runtime.getRuntime(); 14: String argv[] = new String[5]; 15: argv[0] = "kterm" ; 16: argv[1] = "-e" ; 17: argv[2] = "tr" ; 18: argv[3] = "a-z" ; 19: argv[4] = "A-Z" ; 20: java.lang.Process child = rt.exec(argv); 21: int status = child.waitFor(); 22: stdout.println("status == "+status); 23: } 24: catch( Exception e ) 25: { 26: stderr.println( e ); 27: } 28: } 29: static java.io.BufferedReader stdin = 30: new java.io.BufferedReader( new java.io.InputStreamReader(System.in) ); 31: static java.io.PrintStream stdout = System.out; 32: static java.io.PrintStream stderr = System.err; 33: }
exec() の結果は、Process クラスのオブジェクトである。 実行終了を待ちたい時には、waitFor() を行う。 実行するプログラムの標準入力や標準出力を読み書きする機能もある。
% man システムコール名 % man コマンド名 % man -k キーワード
% xman &
% man 2 write
Unix System V (Solaris など) では、2章の write を読むには、次のように -s オプションを使う。
% man -s2 write
各章の説明は、intro を読むとよい。たとえば、システムコールの場合は、次 のように打つ。
% man 2 intro
% xxgdb progname & %xgdb ではなく、xxgdb。x が2回。 Emacs (Mule) の中から gdb を呼ぶ。ソース・プログラム上でどこを実行して いるかを追うことができる。 使い方。
gdb progname リターン
」と打つ。
int myputchar(int c) { ... write(1,...,...); ... } int myputs(char *s) { ... write(1,...,...); ... }main() 関数を付けて、正しく動いていることがわかるようにしなさい。たと えば、次のようになる。
main() { myputchar('h'); myputchar('e'); myputchar('l'); myputchar('l'); myputchar('o'); myputchar('\n'); } main() { myputs("hello\n"); }注意:myputchar() では、符合拡張にも気をつける。
myputchar() では、番地を調べて、その番地から1 バイトだけ write する。 myputs() では、文字列の長さを調べて、その長さ分のバイトだけ write する。
int mygetchar() { ... read(0,...,...); ... }main() 関数を付けて、正しく動いていることがわかるようにしなさい。
注意:プログラムを実行する時に、stty cbreak; をした後に実行すると オペレーティング・システムによるバッファリングを抑止できる。
% cat hello hello ^D % stty cbreak;cat hheelllloo ^C %通常の実行では、^D (Control D, Control キーを押しながら d キーを押す) で、プログラムに EOF (end of file) が伝わる。stty cbreak では、^D は効 かないので、^C でプログラムを強制終了させる。
できれば、myputchar() と共に解きなさい。
注意:ライブラリ関数 gets() には、バッファの長さ以上のデータが入力され た時に、問題がある。今後、決して使ってはいけない。
高水準入出力ライブラリ関数を持ちいて、次のようなフィルタを作りなさい。
入力: ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 出力: NOPQRSTUVWXYZABCDEFGHIJKLM nopqrstuvwxyzabcdefghijklmそれ以外のものは、そのまま出力する。この暗号法を rot13 (rotation)とい う。これは、まったく同じルーチンで暗号化と復号化ができる。 ネットワーク・ニュースなでど、ネタばらしの投稿に使われる。
fgrep は、grep, egrep などと違って単純な文字列の比較しかできない。 regcomp(3) などを使えば、正規表現の検索部分は自分で作らなくてもよい。
以下に rot-N 暗号プログラムの main() 関数の例を示す。
1: /* 2: rotn.c -- 文字単位の簡単なフィルタ(rotN) 3: ~yas/syspro/file/rotn.c 4: Start: 2003/04/21 13:31:46 5: */ 6: 7: #include <stdio.h> /* stdin, stdout, fopen(), fclose(), getc(), */ 8: #include <ctype.h> /* isalpha() */ 9: #include <stdlib.h> /* strtol() */ 10: 11: main( int argc, char *argv[] ) 12: { 13: int n ; 14: char *filename ; 15: if( argc != 3 ) 16: { 17: fprintf(stderr,"Usage: %% %s n file\n",argv[0]); 18: exit( 1 ); 19: } 20: n = strtol( argv[1],0,10 ); 21: filename = argv[2] ; 22: printf("n==%d, file == %s \n", n, filename ); 23: }以下に fgrep プログラムの main() 関数の例を示す。
1: /* 2: myfgrep.c -- 簡単な行単位のフィルタ(fgrep) 3: ~yas/syspro/file/myfgrep.c 4: Start: 2003/04/23 14:51:27 5: */ 6: 7: #include <stdio.h> /* stdin, stdout, fopen(), fclose(), getc(), */ 8: #include <string.h> /* strstr() */ 9: 10: main( int argc, char *argv[] ) 11: { 12: char *pattern ; 13: char *filename ; 14: if( argc != 3 ) 15: { 16: fprintf(stderr,"Usage: %% %s pattern file\n",argv[0]); 17: exit( 1 ); 18: } 19: pattern = argv[1] ; 20: filename = argv[2] ; 21: printf("pattern==%s, file == %s \n", pattern, filename ); 22: }
rot13 や rot-N などの文字単位のフィルタは、例題の
filter-char.c
の大部分をそのまま使い
toupper() だけを置き換えても実現できる。
file-copy.c, stdio-thru.c の2つのプログラムを参考にして、UNIX の tee プログラムと同じような機能をもつプログラムを作りなさい。(tee コマンド について詳しくは、man tee を見なさい。)プログラムの名前は、tee ではな く、別の名前(mytee) としなさい。tee の場合、標準の tee が動いているの か、自分で作った tee が動いているのか区別がつかない。ただし、出力する ファイルは、一つでもよい。-i オプションや -a オプションには対応しなく てもよい。
mytee は、次のようにして、標準入力を標準出力にコピーしながら、同時にファ イルにも保存するものである。
% grep pattern file1 | ./mytee resultこの結果として、画面には、次のように grep コマンドを実行した時と同じ結果が表示される。
% grep pattern file1
同時に、ファイル reult には、画面に出力された結果とまったく同じものが 保存される。
tee コマンドの名前は、アルファベットの「T」に由来する。図形的に考えれ ば、動きを理解することができる。tee コマンドは、左(パイプラインで標準 入力)から入ったデータを下(引数で与えられたファイル)に保存しながら、 右(標準出力)にも出力する。tee コマンドは、時間のかかる処理の結果や、 後で参照したい中間結果をファイルに保存するために利用される。次の例は、 makeコマンドの実行結果を make.out へ保存すると同時に、それを user にメー ルで送るものである。
% make |& tee make.out | mail -s "make result" user
これにより、user は、メールが届いたことで、make コマンドの実行終了を知 ることができる。同時に、作業していたディレクトリでその結果を参照するこ とができる。
この課題では、入出力には、システムコール (open(),read(),write(),close()))を使いなさい。
UNIXの cat コマンドと同様の動きをするプログラムを作りなさい。このプロ グラムの名前を mycat とする。引数として、なにも与えられなかった時には、 標準入力を標準出力にコピーしなさい。また、引数として "-" が指定された 時には、その位置で標準入力を標準出力にコピーしなさい。たとえば、次のよ うな場合、file1 とfile2 の内容の間に、標準入力の内容をコピーしなさい。
% mycat file1 - file2
この課題では、入出力には、システムコール (open(),read(),write(),close()))を使いなさい。
新しい環境変数を追加するには、environ の先の領域もコピーして増やす。
シグナルについては何もしなくてもよい。
% ./run-n 3 date -u 月 4月 28 03:01:11 UTC 2003 月 4月 28 03:01:11 UTC 2003 月 4月 28 03:01:12 UTC 2003 %argv[1] の文字列を、strtol() などで処理して数を得る。 その回数だけ、fork(),execve(),wait() をする。 &argv[2] 以降を、execve() にそのまま与えるとよい。
ライブラリ関数 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[] ); 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] ); 21: } 22: 23: int run_n( int n, char *argv[] ) 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:
% ./countdown 3 3 2 1 0 %このプログラムでは、単に表示がこのようになるのではなくて、カウント・ダ ウンするたびに必ず execve() などでプログラムを切替えなさい。次のような プログラムは、「不可」である。
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() も、不要である。
% ./if-then-else "cc file1.c" "play happy" "play sad"argv[1] で与えられたプログラムを実行し、成功すれば、argv[2] を、実行す る。失敗すれば、argv[3] を実行する。実行する時には、上のように 引用符でコマンドを括ると、間に空白を含まれていても1つの引数になる。 このような引数は、ライブラリ関数 system() に渡す時に都合がよい。
コマンドの区切りは、別のもでもよい。
% ./if-then-else cc file1.c then play happy else play sadこの場合、argv[3] の then や argv[6] の else を 0 で潰して execvp() な どで実行するとよい。この場合は、system() よりも、execvp() を使った方が 簡単であろう。まず最初の条件判定をする部分を fork-exec で実行し、wait する。wait の結果、成功していたら、then 以下の部分をexec し、そうでな ければ、else 以下の部分を exec する。then 以下や else 以下を実行する時 には、もどってくる必要がないので、fork しなくてもよい。
% 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] %複数の wget コマンドを同時に実行して、複数の WWW 資源を同時にコピーす るプログラムをつくりなさい。たとえば、次のように実行すると、最大3個の wget コマンドを同時に動かし、同時にコピーを行う。
% ./run-wget-n 3 url1 url2 url3 url4 url5 url6このプログラムでは、ライブラリ関数 system() を使ってもよい(使わなくて もよい)。
必ずしも同数に分割する必要はない。高度なプログラムを記述すれば、終了し 次第、次の URL に進むようにすることができる。wget コマンドにはもともと も複数の URL を取る機能もあるので、それを使ってもよい。
man で表示された #includeの部分や定数を、Xウインドウ、または、mule の コピー&ペースト機能を使ってソース・プログラムに取り込みなさい。