システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/classes/syspro-2003/2003-05-12
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
システムコールでのプロセスの扱い
C言語では、(C++とは異なり)構造体の名前だけでは、型にならない。頭 にstruct を付けると型になる。sizeof() に与える時には、struct も含める。
sizeof() に変数を指定した場合、ポインタは常に4バイトになる。ポインタ の先のバイト数ではない。配列は、配列全体の大きさにる。
---------------------------------------------------------------------- 1: /* 2: sizeof.c -- sizeof() の意味 3: ~yas/syspro/cc/sizeof.c 4: Start: 2003/05/11 16:45:52 5: */ 6: 7: #include <stdio.h> /* stderr */ 8: #include <stdlib.h> /* malloc() */ 9: 10: 11: char a1[] = { 'h','e','l','l','o',0 }; 12: char *s1 = "hello"; 13: 14: main() 15: { 16: char *p ; 17: printf("sizeof(char) == %d\n",sizeof(char)); 18: printf("sizeof(int) == %d\n",sizeof(int)); 19: printf("sizeof(char *) == %d\n",sizeof(char *)); 20: 21: printf("sizeof(p) == %d\n",sizeof(p)); 22: p = malloc( 100 ); 23: if( p == 0 ) 24: { 25: fprintf(stderr,"no memory\n"); 26: exit( 1 ); 27: } 28: printf("after malloc, sizeof(p) == %d\n",sizeof(p)); 29: free( p ); 30: printf("after free, sizeof(p) == %d\n",sizeof(p)); 31: 32: printf("%%s a1: %s, strlen(a1)==%d\n",a1,strlen(a1)); 33: printf("%%s s1: %s, strlen(s1)==%d\n",s1,strlen(s1)); 34: printf("sizeof(a1) == %d\n",sizeof(a1)); 35: printf("sizeof(s1) == %d\n",sizeof(s1)); 36: printf("sizeof(\"hello\") == %d\n",sizeof("hello")); 37: } ----------------------------------------------------------------------実行例。
配列とポインタは、sizeof() で見ると違う。ポインタは、どんなポインタで も固定長になる。現在の coins の環境では、4バイトである。---------------------------------------------------------------------- % cp ~yas/syspro/cc/sizeof.c .% make sizeof
cc sizeof.c -o sizeof % ./sizeof
sizeof(char) == 1 sizeof(int) == 4 sizeof(char *) == 4 sizeof(p) == 4 after malloc, sizeof(p) == 4 after free, sizeof(p) == 4 %s a1: hello, strlen(a1)==5 %s s1: hello, strlen(s1)==5 sizeof(a1) == 6 sizeof(s1) == 4 sizeof("hello") == 6 %
----------------------------------------------------------------------
この例では、echo のプロセスと、tr のプロセスは、パイプで接続されている。 パイプは、open() したファイルと同じようにread() したり write() したり することがでる。しかし実際には、ファイルではなく、プロセスとプロセスが データを交換する仕組(プロセス間通信の仕組)の1つである。片方のプロセ スが write() したデータを、もう片方のプロセスがread() できる。---------------------------------------------------------------------- % echo "hello,world" | tr '[a-z]' '[A-Z]'HELLO,WORLD %
----------------------------------------------------------------------
パイプにより提供されるプロセス間通信のサービスは、 (単方向の)ストリームとも呼ばれる。FIFO (First In, First Out)や Queue (待ち行列) と呼ばれることもある。
図4−0 バッファによるパイプの実現
パイプを作るには、pipe() システム・コールを使う。これで、パイプ が1本作られ、2つのファイル記述子(読込み用と書込み用)が返される。
---------------------------------------------------------------------- 1: /* 2: pipe-rw-nodup.c -- pipe() を使ったプログラム(dupなし) 3: ~yas/syspro/proc/pipe-rw-nodup.c 4: Start: 1997/05/26 20:43:29 5: */ 6: 7: #include <stdio.h> 8: #include <unistd.h> /* pipe() */ 9: #include <sys/types.h> /* pid_t */ 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: exit( 0 ); 31: } 32: else if( pid > 0 ) 33: { 34: parent( fildes ); 35: wait( 0 ); 36: } 37: else 38: { 39: perror("fork"); 40: exit( 1 ); 41: } 42: } 43: 44: void parent( int fildes[2] ) 45: { 46: char *p, c ; 47: close( fildes[0] ); 48: p = "hello,world\n" ; 49: while( *p ) 50: { 51: c = *p ++ ; 52: write( fildes[1],&c,1 ); 53: } 54: close( fildes[1] ); 55: } 56: 57: void child( int fildes[2] ) 58: { 59: char c, c2 ; 60: close( fildes[1] ); 61: while( read(fildes[0],&c,1) >0 ) 62: { 63: c2 = toupper(c); 64: write( 1, &c2, 1 ); 65: } 66: close( fildes[0] ); 67: } ----------------------------------------------------------------------実行例。
親プロセス(関数parent()を実行)は、パイプの読込み側(fildes[0])を閉じる。 そして、文字列から1バイトずつ取り出し、0 が現れるまで、パイプの書込み 側(fildes[1])に書込む。---------------------------------------------------------------------- % cp ~yas/syspro/proc/pipe-rw-nodup.c .% make pipe-rw-nodup
cc pipe-rw-nodup.c -o pipe-rw-nodup % ./pipe-rw-nodup
HELLO,WORLD %
----------------------------------------------------------------------
子プロセス(関数child()=を実行)は、パイプの書込み側(fildes[1])を閉じる。 そしてパイプの読込み側(fildes[0])から1バイト取り出す。これをtoupper() で大文字に変換して、標準出力(1)に書き出す。
図4−1 pipe() システムコール実行前 図4−2 pipe() システムコール実行後 図4−3 fork() システムコール実行後 図4−4 親子で close() システムコール実行後
dup() よりも、dup2() の方が便利である。
---------------------------------------------------------------------- 1: /* 2: pipe-rw.c -- pipe() と dup() を使ったプログラム 3: ~yas/syspro/proc/pipe-rw-dup.c 4: Start: 1997/05/26 20:43:29 5: */ 6: 7: #include <stdio.h> 8: #include <unistd.h> /* pipe() */ 9: #include <sys/types.h> /* pid_t */ 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: exit( 0 ); 31: } 32: else if( pid > 0 ) 33: { 34: parent( fildes ); 35: wait( 0 ); 36: } 37: else 38: { 39: perror("fork"); 40: exit( 1 ); 41: } 42: } 43: 44: void parent( int fildes[2] ) 45: { 46: char *p, c ; 47: close( fildes[0] ); 48: close( 1 ); /* 標準出力をパイプに切替える */ 49: dup( fildes[1] ); 50: close( fildes[1] ); 51: 52: p = "hello,world\n" ; 53: while( *p ) 54: { 55: c = *p ++ ; 56: write( 1,&c,1 ); 57: } 58: close( 1 ); 59: } 60: 61: void child( int fildes[2] ) 62: { 63: char c, c2 ; 64: close( fildes[1] ); 65: close( 0 ); /* 標準入力をパイプに切替える */ 66: dup( fildes[0] ); 67: close( fildes[0] ); 68: 69: while( read(0,&c,1) >0 ) 70: { 71: c2 = toupper(c); 72: write( 1, &c2, 1 ); 73: } 74: close( 0 ); 75: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % cp ~yas/syspro/proc/pipe-rw-dup.c .% make pipe-rw-dup
cc pipe-rw-dup.c -o pipe-rw-dup % ./pipe-rw-dup
HELLO,WORLD %
----------------------------------------------------------------------
図4−5 親子で close();dup();close() 後
親プロセス(関数parent()を実行)は、まず、パイプの読込み側(fildes[0])を 閉じている。標準出力(ファイル記述子 1) を閉じ、dup() で、fildes[1]をコ ピーしている。この時点で、write(fildes[1],...) としてもwrite(1,...,) としても同じ意味になっている。ここで不要な fildes[1] を閉じている。そ して、文字列から1バイトずつ取り出し、0 が現れるまで、標準出力(ファイ ル記述子 1)に書込む。
子プロセス(関数child()=を実行)は、まず、パイプの書込み側(fildes[1])を 閉じている。 標準入力(ファイル記述子 0) を閉じ、dup() で、fildes[0]をコ ピーしている。この時点で、read(fildes[0],...) としてもread(0,...,) としても同じ意味になっている。ここで不要な fildes[0] を閉じている。 そして標準入力(ファイル記述子 0)から1バイト取り出す。これをtoupper() で大文字に変換して、標準出力(ファイル記述子 1)に書き出す。
---------------------------------------------------------------------- % egrep time_t /usr/include/bits/types.htypedef long int __time_t; %
----------------------------------------------------------------------
現在の時刻を取得するには、time() システム・コール(System V系)や gettimeofday() システム・コール (BSD系)を使う。
#include <time.h> time_t time(time_t *t); #include <sys/time.h> #include <unistd.h> int gettimeofday(struct timeval *tv, struct timezone *tz);
その秒数からカレンダ的な日付と時刻の形式(struct tm)に変換するライブラ リ関数が、localtime() ライブラリ関数である。struct tm は、次のようなフィー ルドがある。
CTIME(3) Linux Programmer's Manual CTIME(3) #include <time.h> char *asctime(const struct tm *timeptr); char *ctime(const time_t *timep); struct tm *gmtime(const time_t *timep); struct tm *localtime(const time_t *timep); time_t mktime(struct tm *timeptr); ... struct tm { int tm_sec; /* 秒 */ int tm_min; /* 分 */ int tm_hour; /* 時間 */ int tm_mday; /* 日 */ int tm_mon; /* 月 */ int tm_year; /* 年 */ int tm_wday; /* 曜日 */ int tm_yday; /* 年内通算日 */ int tm_isdst; /* 夏時間 */ };
ctime(), strftime() などを使うと、文字列に変換してくれる。逆に、struct tm から time_t を作る関数が mktime() ライブラリ関数である。
localtime() や mktime() は、TZ環境変数(Time Zone)を見ている。閏秒など が混じると単純には計算できない。TZ がセットされていない時、Linux の場 合、/etc/localtime というファイルにあるものが使われる。
プログラムの中で Time Zone を変えることはあまりないが、変えるなら setenv() や putenv() で TZ 環境変数を変えて、tzset() ライブラリ関数を 呼ぶ。---------------------------------------------------------------------- % setenv LANG C% ls -l /etc/localtime
-rw-r--r-- 1 root root 73 Feb 7 2002 /etc/localtime % date
Mon May 12 00:05:41 JST 2003 % setenv TZ EST
% date
Sun May 11 10:05:50 EST 2003 % ls -l /usr/share/zoneinfo/EST
-rw-r--r-- 6 root root 286 Mar 6 09:07 /usr/share/zoneinfo/EST %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: get-time.c -- 現在の時刻を表示するプログラム 3: ~yas/syspro/time/get-time.c 4: Start: 1998/05/18 22:29:17 5: */ 6: 7: #include <time.h> /* time(2) */ 8: 9: /* 10: Linux: 11: /usr/include/bits/types.h 12: typedef long int __time_t; 13: 14: /usr/include/time.h: 15: typedef __time_t time_t; 16: extern time_t time (time_t *__timer) ; 17: */ 18: 19: main() 20: { 21: time_t t ; 22: t = 0 ; 23: printf("%d: %s",t,ctime(&t) ); 24: t ++ ; 25: printf("%d: %s",t,ctime(&t) ); 26: t = time( 0 ); 27: printf("%d: %s",t,ctime(&t) ); 28: t ++ ; 29: printf("%d: %s",t,ctime(&t) ); 30: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % cp ~yas/syspro/time/get-time.c .% make get-time
cc get-time.c -o get-time % ./get-time
0: Wed Dec 31 19:00:00 1969 1: Wed Dec 31 19:00:01 1969 1052670767: Sun May 11 11:32:47 2003 1052670768: Sun May 11 11:32:48 2003 %
----------------------------------------------------------------------
構造体を読み書きするには、システムコール read(), write() または、高水 準(ライブラリ)の fread(), fwrite() を使う。
struct struct1 var1 ; FILE *fp ; ... fread(&var1, sizeof(struct struct1),1,fp ); または struct struct1 var1 ; int fd ; read(fd, &var1, sizeof(struct struct1) );
構造体をファイルに保存する時に注意することは、ポインタが(直接的には) 使えないことである。
さらにポータビリティ(別のOSやCPUのコンピュータでも使う)を考える 時には、 文字コード、 バイトオーダ、 浮動小数点の形式、構造体の穴に注意する。
Cコンパイラは、構造体に見えない要素(穴)を追加して、int や double を、 4バイト境界に合うようにしてしまうことがある。 (XDR ライブラリを使えば、自動的にこのあたりのことを変換してくれる。 XDR については、3学期の「分散システム」で扱う。)
---------------------------------------------------------------------- 1: 2: /* 3: utmp-print.c -- /var/run/utmp の内容を表示するプログラム 4: ~yas/syspro/file/utmp-print.c 5: Start: 1995/03/06 09:01:09 6: */ 7: 8: #include <stdio.h> /* FILE, NULL, stderr */ 9: #include <utmp.h> /* struct utmp, UTMP_FILE */ 10: #include <time.h> /* ctime() */ 11: 12: #if 0 13: /* Linux の場合 /usr/include/bits/utmp.h にある。 */ 14: #define UT_LINESIZE 32 15: #define UT_NAMESIZE 32 16: #define UT_HOSTSIZE 256 17: 18: /* The structure describing an entry in the user accounting database. */ 19: struct utmp 20: { 21: short int ut_type; /* Type of login. */ 22: pid_t ut_pid; /* Process ID of login process. */ 23: char ut_line[UT_LINESIZE]; /* Devicename. */ 24: char ut_id[4]; /* Inittab ID. */ 25: char ut_user[UT_NAMESIZE]; /* Username. */ 26: char ut_host[UT_HOSTSIZE]; /* Hostname for remote login. */ 27: struct exit_status ut_exit; /* Exit status of a process marked 28: as DEAD_PROCESS. */ 29: long int ut_session; /* Session ID, used for windowing. */ 30: struct timeval ut_tv; /* Time entry was made. */ 31: int32_t ut_addr_v6[4]; /* Internet address of remote host. */ 32: char __unused[20]; /* Reserved for future use. */ 33: }; 34: #endif 35: 36: void utmp_print_file(); 37: void utmp_print( struct utmp *u ); 38: void print_char_array( char a[], int len ); 39: 40: main() 41: { 42: utmp_print_file(); 43: } 44: 45: void utmp_print_file() 46: { 47: struct utmp entry; 48: FILE *fp ; 49: fp = fopen( UTMP_FILE, "r" ); 50: if( fp == NULL ) 51: { 52: perror( UTMP_FILE ); 53: exit( -1 ); 54: } 55: while( fread(&entry, sizeof(entry),1,fp ) == 1 ) 56: { 57: utmp_print( &entry ); 58: } 59: fclose( fp ); 60: } 61: 62: void utmp_print( struct utmp *u ) 63: { 64: print_char_array( u->ut_user, sizeof(u->ut_user) ); putchar('|'); 65: print_char_array( u->ut_id, sizeof(u->ut_id) ); putchar('|'); 66: print_char_array( u->ut_line, sizeof(u->ut_line) ); putchar('|'); 67: printf("%010d", u->ut_pid ); putchar('|'); 68: printf("%06d", u->ut_type ); putchar('|'); 69: printf("%s",ctime(&u->ut_time) ); /* '\n' will be added by ctime()*/ 70: } 71: 72: void print_char_array( char a[], int len ) 73: { 74: int i ; 75: for( i=0 ; i<len && a[i] ; i++ ) 76: putchar( a[i] ); 77: for( ; i<len ; i++ ) 78: putchar('.'); 79: } ----------------------------------------------------------------------
実行結果
struct utmp というデータ構造に会わせて、表示する関数が分かれている点に 注意する。データ構造が、アルゴリズムを決める。構造体なら、関数、配列な ら、ループになる。---------------------------------------------------------------------- % cp ~yas/syspro/file/utmp-print.c .% make utmp-print
cc utmp-print.c -o utmp-print % ./utmp-print
................................|si..|................................|000000001 0|000008|Tue May 7 17:45:29 2002 reboot..........................|~~..|~...............................|000000000 0|000002|Tue May 7 17:45:29 2002 <略> yas.............................|/1..|pts/1...........................|000001156 6|000007|Sun May 19 23:21:56 2002 yas.............................|ts/0|pts/0...........................|000001153 4|000007|Sun May 19 23:21:49 2002 yas.............................|/3..|pts/3...........................|000001163 2|000008|Sun May 19 23:26:39 2002 <略> % who
yas pts/1 May 19 23:21 yas pts/0 May 19 23:21 % who -l
yas pts/1 May 19 23:21 yas pts/0 May 19 23:21 (bridge.hlla.is.tsukuba.ac.jp) %
----------------------------------------------------------------------
print_char_array() では、char の配列を表示している。この構造体 utmp 場 合、C言語の文字列と違って、0 で終端されていないことがある。従って、最 大のバイト数を渡している。
ut_time には、1970年1月1日0:00(UTC)からの秒数で時刻 が含まれている。 構造体 struct utmp には、ut_timeが定義されていない。これは、他のシステ ム(BSD や Solaris)では、そういう名前のフィールドが存在する。Linux の場 合、ut_time は、struct timeval ut_tv の一部になっている。ut_time と書 いても動作するのは、次のようなマクロが含まれているからである。
/usr/include/bits/utmp.h: #define ut_time ut_tv.tv_sec
ut_time と書くと、BSD でも Linux でも両方で動作する。ut_tv を使うと、 Linux でしか動作しない。
ctime() は、文字列のの最後に改行 \n を含めてしまう。改行を含まないよう な文字列が必要な時には、localtime() を使うか、strftime() を使う。
UNIXでは、ファイルを read() したり write() したりするたびに、ファイル を読み書きする位置(先頭からのバイト数)が移動する。この位置は、オフセッ ト、シーク・ポインタ、または、ファイル・ポインタと呼ばれる。(ファイル・ ポインタと言っても、FILE *とは違うので注意。)
ファイルを先頭から順にアクセスするのではなく、ランダムにアクセスしたい 時には、まず読み書きしたい場所にシーク・ポインタを移動させる。これを行 うのが、lseek() システム・コールである。
引数は、ファイル記述子、オフセット(バイト数)、移動方法である。移動方法 (whence)には、次の3種類のいずれかを指定する。off_t lseek(int fildes, off_t offset, int whence);
---------------------------------------------------------------------- 1: 2: /* 3: wtmp-last10.c -- /var/log/wtmp の最後の10個を表示するプログラム 4: ~yas/syspro/file/wtmp-last10.c 5: Start: 1995/03/06 09:01:09 6: */ 7: 8: #include <stdio.h> /* FILE, NULL, stderr */ 9: #include <utmp.h> /* struct utmp, WTMP_FILE */ 10: #include <sys/types.h> /* open(),lseek() */ 11: #include <sys/stat.h> /* open() */ 12: #include <fcntl.h> /* open() */ 13: #include <unistd.h> /* lseek() */ 14: 15: void wtmp_last10_print_file(); 16: void utmp_print( struct utmp *u ); 17: void print_char_array( char a[], int len ); 18: 19: main() 20: { 21: wtmp_last10_print_file(); 22: } 23: 24: void wtmp_last10_print_file() 25: { 26: struct utmp entry; 27: int fd, i ; 28: off_t offset ; 29: 30: if( (fd = open( WTMP_FILE, O_RDONLY )) == -1 ) 31: { 32: perror( WTMP_FILE ); 33: exit( -1 ); 34: } 35: for( i=-1 ; i>-10; i-- ) 36: { 37: offset = sizeof(struct utmp)*i ; 38: 39: if( lseek( fd, offset, SEEK_END ) == -1 ) 40: { 41: perror("lseek"); 42: exit( -1 ); 43: } 44: if( read(fd, &entry, sizeof(entry)) != sizeof(entry) ) 45: { 46: perror("read"); 47: exit( -1 ); 48: } 49: utmp_print( &entry ); 50: } 51: close( fd ); 52: } 54: void utmp_print( struct utmp *u ) <以下略> ----------------------------------------------------------------------実行例。
WTMP_FILE というファイルを、今回は、oepn() システムコールで開いている。 for 文で、i が -1 から -10 まで回している。offset = sizeof(struct utmp)*i で、ファイルの終わりからのバイト数を計算している。iはマイナス なので、ファイルの終わりからファイルのデータが先頭方向を意味する。 lseek(,offset,SEEK_END) で、ファイルの最後ら、マイナスの方向にシーク・ ポインタを移動している。そこの場所から、read() で、struct utmp の sizeof(entry)分だけデータを読み出し、utmp_print() で画面に表示している。 utmp_print() は、utmp-print.cと同じである。---------------------------------------------------------------------- % cp ~yas/syspro/file/wtmp-last10.c .% make wtmp-last10
cc wtmp-last10.c -o wtmp-last10 % ./wtmp-last10
................................|....|pts/2...........................|000001161 2|000008|Sun May 19 23:26:39 2002 yas.............................|/3..|pts/3...........................|000001163 2|000008|Sun May 19 23:26:39 2002 yas.............................|/3..|pts/3...........................|000001163 2|000007|Sun May 19 23:26:37 2002 yas.............................|ts/2|pts/2...........................|000001161 5|000007|Sun May 19 23:26:33 2002 yas.............................|/1..|pts/1...........................|000001156 6|000007|Sun May 19 23:21:56 2002 yas.............................|ts/0|pts/0...........................|000001153 4|000007|Sun May 19 23:21:49 2002 ................................|....|pts/0...........................|000000286 3|000008|Sat May 18 19:20:57 2002 i000000.........................|ts/0|pts/0...........................|000000286 4|000007|Sat May 18 16:37:30 2002 ................................|....|pts/0...........................|000000277 0|000008|Sat May 18 12:31:18 2002 % last | head -5
yas pts/3 Sun May 19 23:26 - 23:26 (00:00) yas pts/2 bridge.hlla.is.t Sun May 19 23:26 - 23:26 (00:00) yas pts/1 Sun May 19 23:21 still logged in yas pts/0 bridge.hlla.is.t Sun May 19 23:21 still logged in i000000 pts/0 dhcp06.xxlab.is. Sat May 18 16:37 - 19:20 (02:43) %
----------------------------------------------------------------------
ファイルの内容を操作する方法として、read(), write() とは全く違った方法 がある。それは、ファイルの内容をメモリにマップ(map, 張り付ける)し、 ファイルの内容を配列のようにして扱う方法である。
---------------------------------------------------------------------- 1: 2: /* 3: mmap-head.c -- mmap(2) を使った head プログラム 4: ~yas/syspro/file/mmap-head.c 5: Start: 1998/05/12 01:06:15 6: */ 7: 8: #include <stdio.h> /* stderr */ 9: #include <sys/types.h> /* open() */ 10: #include <sys/stat.h> /* open() */ 11: #include <fcntl.h> /* open() */ 12: #include <unistd.h> /* mmap() */ 13: #include <sys/mman.h> /* mmap() */ 14: 15: extern void mmap_head( char *name ); 16: extern char *mmap_file( char *name /* in */, size_t *filesizep /* out */); 17: 18: main( int argc, char *argv[] ) 19: { 20: if( argc != 2 ) 21: { 22: fprintf( stderr,"Usage: %% %s filename\n",argv[0] ); 23: exit( 1 ); 24: } 25: mmap_head( argv[1] ); 26: } 27: 28: void mmap_head( char *name ) 29: { 30: size_t size,i ; /* unsigned int */ 31: char *file_address ; 32: int nlcount ; 33: 34: file_address = mmap_file( name,&size ); 35: if( ((int)file_address) == -1 ) 36: { 37: perror( name ); 38: exit( 1 ); 39: } 40: for( i = 0, nlcount=0 ; i<size ; i ++ ) 41: { 42: if( file_address[i] == '\n' ) 43: { 44: nlcount ++ ; 45: if( nlcount >= 10 ) 46: { 47: i++ ; 48: break; 49: } 50: } 51: } 52: write( 1, file_address, i ); 53: } 54: 55: char *mmap_file( char *name /* in */, size_t *filesizep /* out */) 56: { 57: int fd ; 58: struct stat buf ; 59: size_t size,p ; /* unsigned int */ 60: off_t off ; /* long */ 61: caddr_t addr ; /* char * */ 62: if( (fd = open( name,O_RDONLY )) == -1 ) 63: return( (char *)-1 ); 64: fstat( fd, &buf ); 65: size = buf.st_size ; 66: addr = 0 ; off = 0 ; 67: addr = mmap( addr, size, PROT_READ, MAP_PRIVATE, fd, off ); 68: if( (int)addr == -1 ) 69: { 70: close( fd ); 71: return( (char *)-1 ); 72: } 73: close( fd ); 74: if( filesizep ) 75: *filesizep = size ; 76: return( addr ); 77: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % cp ~yas/syspro/file/mmap-head.c .% make mmap-head
cc mmap-head.c -o mmap-head % ./mmap-head /etc/passwd
root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin: daemon:x:2:2:daemon:/sbin: adm:x:3:4:adm:/var/adm: lp:x:4:7:lp:/var/spool/lpd: sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail: news:x:9:13:news:/var/spool/news: %
----------------------------------------------------------------------
mmap-head.c は、head プログラムと同様に、最初の 10 行を画面に表示する ものである。関数 mmap_file() は、第1引数で指定された名前のファイルを メモリにマップし、その番地を返す。ファイルのバイト数を第2引数で指定さ れた番地に格納する。(ファイルがテキスト型でも、0で終端したりはしない。)
mmap_head() は、メモリ中の file_address 番地からsize バイトだけ、文字 の配列があるとみなしている。file_address[i] で、i バイト目にアクセスできる。 '\n' の数を数えて、10 に達したら抜ける。 そのバイト数まで、file_address 番地から一気に write() で標準出力に 書き出している。
先にパイプを2つ作ってから2回 fork() してもよい。パイプを1つ作って fork() してから もう1つパイプを作って fork() するという方法でもよい。
ヒント: 使わないパイプのファイル記述子は、全部 close() すること。パイプの書き 込み側のファイル記述子が開いている間は、read() しても EOF (End Of File) にならない。自分自身で write() する可能性もあることに注意する。
ヒント:書き手がいないパイプは、全ての書込み側のファイル記述子を closeすると作れる。
ヒント:プロセスを fork() しなくても、パイプに書くことはできる。プロセ スが1個の状態で、バッファの大きさ以上のデータを書き込むと、プロセスが ブロックされる(先に進まなくなる)。引数で与えて与えられたバイト数だけ パイプに書込むプログラムを作り、何バイトからブロックされて止まるかを調べる。 止まったプログラムは、^C で強制終了させる。
1バイトずつ増やしていくのではなくて、2分探索をつかいなさい。大きいバ イト数から始める。止まれば、バイト数を半分にする。動けば、今のバイト数 と前にとまったバイト数の中間のバイト数にする。
---------------------------------------------------------------------- FILE *popen(const char *command, const char *type); int pclose (FILE *stream); ----------------------------------------------------------------------これを利用して、プロセスを作成し、プロセスにデータを与えたり、あるいは、 プロセスからデータを受け取るプログラムをつくりなさい。
たとえば、expr コマンドを実行して、その結果を受け取るプログラムをつく りなさい。expr は、次のように引数で与えられた式を評価し、結果を標準出 力に返すものである。
この課題では、expr コマンドに似たようなプログラム作るのではなく、それ をそのまま利用して、結果を受け取るプログラムを作る。実行するプログラム としては、expr 以外に次のようなものが考えられる。---------------------------------------------------------------------- % expr 1 + 23 %
----------------------------------------------------------------------
その main() 関数の例を以下に示す。% ./wakeupUsage: % hh mm % ./wakeup 13 00
13:00 <13:00になったらbeep音を鳴らして終了する。> %
![]()
---------------------------------------------------------------------- 1: /* 2: wakeup.c -- 目覚まし時計 3: ~yas/syspro/time/wakeup.c 4: Start: 2003/05/12 00:59:43 5: */ 6: 7: #include <time.h> /* mktime */ 8: #include <stdio.h> /* stderr() */ 9: 10: void wakeup( int hh, int mm ); 11: 12: main( int argc, char *argv[]) 13: { 14: int hh, mm ; 15: 16: if( argc != 3 ) 17: { 18: fprintf(stderr,"Usage: %% hh mm \n",argv[0] ); 19: exit( 1 ); 20: } 21: hh = strtol( argv[1],0,10 ); 22: mm = strtol( argv[2],0,10 ); 23: wakeup( hh, mm ); 24: exit( 0 ); 25: } 26: 27: void wakeup( int hh, int mm ) 28: { 29: int n ; 30: printf("%02d:%02d\n", hh, mm ); 31: n = 3 ; 32: sleep( n ); 33: write(0,"\a",1); 34: } ----------------------------------------------------------------------この wakeup() では、単に定数 3 を指定しているので、3秒間だけsleep し た後、画面に \a を出力することで音を鳴らしている。この課題では、定数で はなく、現在からの目的の時刻までの差を調べ、その秒数だけ sleep させる ようにする。
目的の時刻を得るには、次のようにする方法がある。
プログラムは、3行程度になる。日本語表示でなくてもよい。Linux のdate コマンドの日本語表示は、おかしいので、それまで真似する必要はない。独自 に改良するとよい。
この課題では、ctime(3) を用いてはならない。 strftime(3)、または、localtime(3) を使ってもよい。
utmp-print.cから出発する。ut_type が、 USER_PROCESS のものだけを、表示する。ただし、表示は、単に utmp_print() を呼ぶのではなく、本物の who コマンドにできるだけ似せなさい。
ut_time の表示には、ctime() を使ってもよいが、できれば、localtime() や strftime() を使って分単位で短く表示する。
wtmp-last10.c のプログラムから出発する。ロ グアウトの情報を、どこかに記録する。それとマッチするログインの情報が現 れた時に、表示する。
この課題では、wtmp にある情報だけが表示できればよい。
---------------------------------------------------------------------- host: 130.158.86.51 , time: 2003-04-21-12-23-56, 2 ----------------------------------------------------------------------この時刻に、このホストで、ローカルにログインしていた場合、そのホストで プログラムを実行すると、0 を返す( exit(0) で終了する)。そうでない場合、 1 を返す( exit(1) で終了する)。
引数として、wtmp ファイル(複数)を取れるようにすることが望ましい。(wtmp ファイルは、1ヶ月ごとに消されるが、消される前にホストごとに収集してあ る。)% ./llogin user1 2003-04-21-12-23-56% echo $status
0 % ./llogin user1 2003-04-21-12-23-56 && echo ok
ok %
![]()
たとえば、引数として日付、時刻、ユーザ名 user1, user2 ... のユーザが、 その時間にログインしていたら、yes を返す。
ヒント:まず struct tm に日付と時間をセットして、 mktime() を使って time_t を得る。ログイン時間とログアウトの時間を wtmp ファイルで調べる。 指定されていた範囲にかかれば、yes と判定する。% present 2003/05/11 12:15 13:30 user1 user2 ...user1 yes user2 no ... %
![]()
tail コマンドは、head コマンドとは反対に、ファイルの末尾を表示 するものである。