システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-06-04
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
この授業でC言語でプログラムを書く時には、必ずメモリの中でどのような操 作が行われているかを意識すること。
---------------------------------------------------------------------- 1: 2: /* 3: echo-client-udp.c -- 文字列を送受信するクライアント(UDP/IP版) 4: ~yas/syspro-2001/ipc/echo-client-udp.c 5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-client-udp.c,v 1.7 2001/06/03 10:48:06 yas Exp $ 6: Start: 1997/06/16 21:22:26 7: */ 8: #include <stdio.h> 9: #include <sys/types.h> /* socket() */ 10: #include <sys/socket.h> /* socket() */ 11: #include <netinet/in.h> /* struct sockaddr_in */ 12: #include <netdb.h> /* gethostbyname() */ 13: 14: extern void echo_client_udp( char *hostname, int portno ); 15: extern int udp_port_nobind(); 16: extern int sockaddr_in_init( struct sockaddr_in *addr, char *hostname, int portno ); 17: extern void sockaddr_in_print( struct sockaddr_in *addr ); 18: extern void sockname_print( int s ); 19: 20: main( int argc, char *argv[] ) 21: { 22: if( argc != 3 ) 23: { 24: fprintf( stdout,"Usage: %s host port\n",argv[0] ); 25: exit( -1 ); 26: } 27: echo_client_udp( argv[1],atoi(argv[2]) ); 28: } 29: 30: #define BUFFERSIZE 1024 31: 32: void echo_client_udp( char *hostname, int portno ) 33: { 34: int s, rcount, scount, len, fromlen ; 35: struct sockaddr_in to, from ; 36: char buffer[BUFFERSIZE]; 37: 38: s = udp_port_nobind(); 39: if( s<0 ) 40: exit( -1 ); 41: printf("my port is "); sockname_print( s ); 42: 43: strncpy( buffer,"hello",sizeof(buffer) ); 44: len = strlen( buffer ) + 1 ; 45: sockaddr_in_init( &to, hostname, portno ); 46: printf("sending [%s] (%d bytes) to ", buffer,len ); 47: sockaddr_in_print( &to ); 48: if( (scount = sendto( s, buffer, len, 0, &to, sizeof(to) ))!= len ) 49: { 50: perror("sendto()"); 51: exit( 1 ); 52: } 53: printf("after sendto(), my port is "); sockname_print( s ); 54: 55: fromlen = sizeof( from ); 56: if( (rcount = recvfrom( s, buffer, BUFFERSIZE, 0, &from, &fromlen )) < 0 ) 57: { 58: perror("recvfrom()"); 59: exit( 1 ); 60: } 61: printf("received from "); sockaddr_in_print( &from ); 62: buffer[rcount] = 0 ; 63: printf("%d bytes received. [%s] \n", rcount, buffer ); 64: 65: close( s ); 66: } 67: 68: int udp_port_nobind() 69: { 70: int s ; 71: if( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) 72: { 73: perror("socket"); 74: return( -1 ); 75: } 76: return( s ); 77: } 78: 79: int sockaddr_in_init( struct sockaddr_in *addr, char *hostname, int portno ) 80: { 81: struct hostent *hostent ; 82: addr->sin_family = AF_INET ; 83: if( (hostent = gethostbyname( hostname )) == NULL ) 84: { 85: fprintf(stderr,"unknown host %s\n",hostname ); 86: return( -1 ); 87: } 88: bcopy( hostent->h_addr, &addr->sin_addr, hostent->h_length ); 89: addr->sin_port = htons( portno ); 90: return( 0 ); 91: } 92: 93: void sockaddr_in_print( struct sockaddr_in *addr ) 94: { 95: union { 96: int i ; 97: unsigned char byte[4] ; 98: } x ; 99: x.i = addr->sin_addr.s_addr ; 100: printf("sockaddr_in: %d.%d.%d.%d:%d\n", 101: x.byte[0],x.byte[1],x.byte[2],x.byte[3], 102: ntohs( addr->sin_port )); 103: } 104: 105: void sockname_print( int s ) 106: { 107: struct sockaddr_in addr ; 108: int len ; 109: len = sizeof( addr ); 110: if( getsockname( s, &addr, &len )< 0 ) 111: { 112: perror("getsockname"); 113: exit( -1 ); 114: } 115: sockaddr_in_print( &addr ); 116: } ----------------------------------------------------------------------
クライアント側では、bind() で名前を付けていない。sendto() の時に、自動 的にオペレーティング・システムにより、名前が付けられる。同じことは、 porno=0 で bind() しても可能である。オペレーティング・システムによって 付けられた名前は、後で getsockname() で調べることができる。
明示的に bind() する方法もある。
実行例。
---------------------------------------------------------------------- % ./echo-client-udp adonis1 7my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.1:7 after sendto(), my port is sockaddr_in: 0.0.0.0:8013 received from sockaddr_in: 130.158.86.1:7 6 bytes received. [hello] % ./echo-client-udp adonis11 7
my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.11:7 after sendto(), my port is sockaddr_in: 0.0.0.0:8014 received from sockaddr_in: 130.158.86.11:7 6 bytes received. [hello] % ./echo-client-udp localhost 7
my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 127.0.0.1:7 after sendto(), my port is sockaddr_in: 0.0.0.0:8015 received from sockaddr_in: 127.0.0.1:7 6 bytes received. [hello] %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: 2: /* 3: echo-server-udp.c -- 文字列を送受信するサーバ(UDP/IP版) 4: ~yas/syspro-2001/ipc/echo-client-udp.c 5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-server-udp.c,v 1.5 2001/06/03 10:49:16 yas Exp $ 6: Start: 1997/06/16 21:22:26 7: */ 8: #include <stdio.h> 9: #include <sys/types.h> /* socket() */ 10: #include <sys/socket.h> /* socket() */ 11: #include <netinet/in.h> /* struct sockaddr_in */ 12: #include <netdb.h> /* gethostbyname() */ 13: 14: extern void echo_server_udp( int portno ); 15: extern int udp_port_bind( int portno ); 16: extern void sockaddr_in_print( struct sockaddr_in *addr ); 17: extern void sockname_print( int s ); 18: 19: main( int argc, char *argv[] ) 20: { 21: int portno ; 22: if( argc >= 3 ) 23: { 24: fprintf( stdout,"Usage: %s [portno]\n",argv[0] ); 25: exit( -1 ); 26: } 27: if( argc == 2 ) 28: portno = atoi( argv[1] ); 29: else 30: portno = getuid(); 31: echo_server_udp( portno ); 32: } 33: 34: #define BUFFERSIZE 1024 35: 36: void echo_server_udp( int portno ) 37: { 38: int s, rcount, scount, addrlen ; 39: struct sockaddr_in addr ; 40: char buffer[BUFFERSIZE]; 41: 42: s = udp_port_bind( portno ); 43: if( s<0 ) 44: exit( -1 ); 45: printf("my port is "); sockname_print( s ); 46: 47: while( 1 ) 48: { 49: addrlen = sizeof( addr ); 50: if( (rcount = recvfrom( s, buffer, BUFFERSIZE, 0, &addr, &addrlen )) < 0 ) 51: { 52: perror("recvfrom()"); 53: exit( 1 ); 54: } 55: buffer[rcount] = 0 ; 56: printf("received %d bytes [%s] from ",rcount, buffer ); 57: sockaddr_in_print( &addr ); 58: printf("sending back [%s] (%d bytes) to ", buffer, rcount ); 59: sockaddr_in_print( &addr ); 60: if( (scount=sendto( s, buffer, rcount, 0, &addr, addrlen ))!= rcount ) 61: { 62: perror("sendto()"); 63: exit( 1 ); 64: } 65: } 66: } 67: 68: int udp_port_bind( int portno ) 69: { 70: struct hostent *hostent ; 71: struct sockaddr_in addr ; 72: int addr_len ; 73: int s ; 74: 75: if( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) 76: { 77: perror("socket"); 78: return( -1 ); 79: } 80: 81: addr.sin_family = AF_INET ; 82: addr.sin_addr.s_addr = INADDR_ANY ; 83: addr.sin_port = htons( portno ); 84: 85: if( bind(s,&addr,sizeof(addr)) < 0 ) 86: { 87: perror( "bind: " ); 88: fprintf(stderr,"port number %d is already used. kill another program.", portno ); 89: return( -1 ); 90: } 91: return( s ); 92: } 93: 94: void sockaddr_in_print( struct sockaddr_in *addr ) 95: { 96: union { 97: int i ; 98: unsigned char byte[4] ; 99: } x ; 100: x.i = addr->sin_addr.s_addr ; 101: printf("sockaddr_in: %d.%d.%d.%d:%d\n", 102: x.byte[0],x.byte[1],x.byte[2],x.byte[3], 103: ntohs( addr->sin_port )); 104: } 105: 106: void sockname_print( int s ) 107: { 108: struct sockaddr_in addr ; 109: int len ; 110: len = sizeof( addr ); 111: if( getsockname( s, &addr, &len )< 0 ) 112: { 113: perror("getsockname"); 114: exit( -1 ); 115: } 116: sockaddr_in_print( &addr ); 117: } <以下省略> ----------------------------------------------------------------------実行例。
サーバ側。サーバは、終了しないので、最後に、^C か Del を押して、割り込みを掛けて終了させる。
クライアント側(その1)。---------------------------------------------------------------------- % ./echo-server-udpmy port is sockaddr_in: 0.0.0.0:1231 received 6 bytes [hello] from sockaddr_in: 130.158.86.11:5890 sending back [hello] (6 bytes) to sockaddr_in: 130.158.86.11:5890 received 6 bytes [hello] from sockaddr_in: 130.158.86.1:8023 sending back [hello] (6 bytes) to sockaddr_in: 130.158.86.1:8023 received 6 bytes [hello] from sockaddr_in: 127.0.0.1:8024 sending back [hello] (6 bytes) to sockaddr_in: 127.0.0.1:8024 ^C %
----------------------------------------------------------------------
クライアント側(その2)。---------------------------------------------------------------------- % ./echo-client-udp adonis1 1231my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:5890 received from sockaddr_in: 130.158.86.1:1231 6 bytes received. [hello] %
----------------------------------------------------------------------
クライアント側(その3)。---------------------------------------------------------------------- % ./echo-client-udp adonis1 1231my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:8023 received from sockaddr_in: 130.158.86.1:1231 6 bytes received. [hello] % ----------------------------------------------------------------------
---------------------------------------------------------------------- % ./echo-client-udp localhost 1231my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 127.0.0.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:8024 received from sockaddr_in: 127.0.0.1:1231 6 bytes received. [hello] %
----------------------------------------------------------------------
図1 バスにより接続されたCPU、メモリ、デバイス
バス:何本かの配線の束
例:キーボード用のコントローラの働き
データが、電気信号などの形で送られてくる。コントローラの中のレジスタ (小容量のメモリ)に保存され。
CPU から見える場所
CPU の速度に比べて、デバイスの速度は遅い。
普通の割込み(ハードウェアによる割込み)は、オペレーティング・システム のカーネルにおいて利用されている。
ソフトウェア割込みとは、本来はオペレーティング・システムのカーネルしか 使えない割込みの機能を、ソフトウェアにより実現して、一般の利用者プログ ラム(プロセス)でも使えるようにしたものである。
UNIXでは、ソフトウェア割込みの機能は、シグナル(signal)という名前で実現 されている。シグナルとは、本来はプロセス間通信の一種で、ある事象が起き たことを他のプロセスに知らせることである。ここで伝わるのは、ある事象が 起きたかどうかだけで、引数などを付けることはできない。UNIXでは、プロセ ス間でシグナルにより通信をする他に、キーボードからシグナルを送ることも できる。これは、「ソフトウェア割込み」として、プロセス1つひとつに割込 みボタンが付いているようなものである。また、プログラムの中で例外 (exception)が起きた時にも、ハードウェアの割込みと同様に、ソフトウェ ア割込みが生じる。これも、他のシグナルと同じように受け取ることができる。
UNIXのソフトウェア割込み(シグナル)を使うには、次のようなことが必要で ある。
ソフトウェア割り込みは、1つのプログラムの中に制御の流れが1つしかない ようなプログラムの時に有効な方法である。最近のマルチスレッドのプログラ ムでは、シグナルの意味が不明確である。
---------------------------------------------------------------------- 1: /* 2: signal-int.c -- SIGINT を3回受け付けて終了するプログラム。 3: ~yas/syspro-2001/proc/signal-int.c 4: $Header: /home/lab2/OS/yas/syspro-2001/proc/RCS/signal-int.c,v 1.4 2001/06/03 10:57:12 yas Exp $ 5: Start: 1997/05/26 18:38:38 6: */ 7: 8: #include <stdio.h> 9: #include <signal.h> 10: 11: int sigint_count = 3 ; 12: void sigint_handler(); 13: 14: main() 15: { 16: signal( SIGINT, &sigint_handler ); 17: printf("main(): going into infinite loop, sigint_count == %d\n", 18: sigint_count); 19: while( 1 ) 20: { 21: printf("main(): sigint_count == %d, pause() ....\n", 22: sigint_count ); 23: pause(); 24: printf("main(): return from pause(). sigint_count == %d\n", 25: sigint_count ); 26: } 27: } 28: 29: void sigint_handler() 30: { 31: printf("sigint_handler():\n"); 32: if( -- sigint_count <= 0 ) 33: { 34: printf("sigint_handler(): exit() ... \n"); 35: exit( 1 ); 36: } 37: signal( SIGINT, &sigint_handler ); /* System V */ 38: printf("sigint_handler(): sigint_count == %d\n",sigint_count); 39: } ----------------------------------------------------------------------実行例。
stty で intr に相当するキーを3回押す。---------------------------------------------------------------------- % stty -aspeed 9600 baud; line = 1; 48 rows; 80 columns intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = ^@; old-swtch = ^Z; susp = ^Z lnext = ^V; werase = ^W; rprnt = ^R; flush = ^O; stop = ^S; start = ^Q; dsusp = ^Y parenb -parodd cs8 -cstopb hupcl cread -clocal -cnew_rtscts -loblk -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc ixon -ixany -ixoff -imaxbel isig icanon iexten -xcase echo echoe echok echoke echoctl -echoprt -echonl -noflsh -flusho -pendin -tostop opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel % ./signal-int
main(): going into infinite loop, sigint_count == 3 main(): sigint_count == 3, pause() .... ^C sigint_handler(): sigint_handler(): sigint_count == 2 main(): return from pause(). sigint_count == 2 main(): sigint_count == 2, pause() .... ^C sigint_handler(): sigint_handler(): sigint_count == 1 main(): return from pause(). sigint_count == 1 main(): sigint_count == 1, pause() .... ^C sigint_handler(): sigint_handler(): exit() ... %
----------------------------------------------------------------------
そのような場合でも、問題なく動く関数を、再入可能な関数(reentrant function)という。
引数やauto変数だけを使った関数は、再入可能になる。
extern や static を使った関数は、再入可能でないことがある。
次のような関数は、リエントラントではない。
シグナル・ハンドラの中では、リエントラントな関数を使うようにする。
図? プロセスのアドレス空間
機械語命令を置くためのセグメンを テキスト・セグメント ( text segment, ) という。このセグメントは、普通、読み出し専用になっていて, 同じロードモ ジュールのプロセスの間で共有される。例えばシェルは複数のユーザが同時に 使うことが多いが、この場合、テキストセグメントのためのメモリは1セット だけでよい。機械語を書き換えながら実行するプログラムは、最近は行儀が悪いとされてい る。キャッシュの関係で特別な操作をしないと動かない。
データ・セグメント ( data segment, ) は、データを置く領域である. C言語の 静的変数(static)、 大域変数(extern)malloc()
で割り当てたヒープ上の変数は、
このセグメントに置かれます。
(
自動変数(auto)
は、次のスタック・セグメントに置かれる。
)
データセグメントは, 次の3つにわかれています.
malloc()
で確保する変数が置かれるところ
と書くとstatic int x=100 ;
x
はデータセグメントに割りつけられ,
実行形式ファイルに100という整数のビットパタンが含まれる。
BSSとは初期値を指定しない変数が置かれる場所である. C言語で
と書くとstatic int x ;
y
はBSSに割りつけられる。実行形式のファイルに
は、BSSの領域はない。これは実行時にカーネルが領域を割り当て、内容
を0に初期化します。
ヒープとは実行時に大きさが決まる変数を置くための領域である. C言語で
とすると100バイトの領域がヒープ上に確保される。char *p ; ... p = malloc( 100 );
ヒープ領域の大きさは、brk()
システム・コールや
sbrk()
システム・コールで変えることができる。これらの
システム・コールは、malloc()
ライブラリ関数から呼び出さ
れる。
C言語で
とすると, 4バイトの領域がスタックセグメントに割りつけられる。 (main() { auto int z; }
auto
というキーワードは、普通省略される。スタックセグメ
ントは、普通、0xffffff (32ビットのアドレス空間の場合)からからアドレス
が小さくなるほうに向かって伸びていく。この部分は、関数呼び出しが続くと
伸びていき、割り当てられたメモリよりも多くなると、カーネルが自動的に拡
張する。
UNIXではプログラムを実行するときに,
引数(argument)
と
環境変数(environment variable)
が渡される。
C言語では、次のようにmain()
関数が3つの引数で呼び出される
ようになっている。
ここで、main( int argc, char *argv[], *envp[] ) { }
argc
が、引数の数(argv
の数) ,
argv
が、引数のベクタの先頭番地、
envp
が環境変数のベクタの先頭番地である。この引数と環境変数は、スタックセグ
メントのスタックの底、
図?
では上(アドレスが大きいところ)にある。
共有ライブラリは、ヒープとスタック・セグメントの間にある。 どの番地に割り当てられるかは、システムに依存する。
---------------------------------------------------------------------- 1: /* 2: vaddr-print.c -- 変数の番地をしらべるプログラム 3: ~yas/syspro-2001/cc/vaddr-print.c 4: $Header: /home/lab2/OS/yas/syspro-2001/cc/RCS/vaddr-print.c,v 1.1 2001/06/03 12:16:47 yas Exp $ 5: Start: 1997/05/19 22:58:49 6: */ 7: #include <stdlib.h> 8: 9: void recursive( int n ); 10: 11: int x1=1 ; 12: int x2 ; 13: 14: extern int etext, edata, end ; 15: 16: main( argc,argv,envp ) 17: int argc ; 18: char *argv[] ; 19: char *envp[] ; 20: { 21: int x3 ; 22: char *x4p ; 23: 24: printf("&main == 0x%x \n",&main ); 25: printf("&etext == 0x%x \n",&etext ); 26: printf("&edata == 0x%x \n",&edata ); 27: printf("&end == 0x%x \n",&end ); 28: 29: printf("&x1 == 0x%x (data)\n",&x1 ); 30: printf("&x2 == 0x%x (bss)\n",&x2 ); 31: printf("&x3 == 0x%x (auto)\n",&x3 ); 32: x4p = malloc( 10 ); 33: printf("x4p == 0x%x (heap)\n",x4p ); 34: x4p = malloc( 10 ); 35: printf("x4p == 0x%x (heap)\n",x4p ); 36: recursive( 3 ); 37: } 38: 39: void recursive( int n ) 40: { 41: int x5 ; 42: printf("&x5 == 0x%x (auto,%d)\n",&x5,n ); 43: if( n<=0 ) 44: return; 45: recursive( n-1 ); 46: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % cp ~yas/syspro1/cc/vaddr-print.c .% make vaddr-print
cc vaddr-print.c -o vaddr-print % ./vaddr-print
&main == 0x400ad0 &etext == 0x400cb0 &edata == 0x10002000 &end == 0x10002000 &x1 == 0x100010d0 (data) &x2 == 0x10001130 (bss) &x3 == 0x7fff2ecc (auto) x4p == 0x10002010 (heap) x4p == 0x10002028 (heap) &x5 == 0x7fff2ea4 (auto,3) &x5 == 0x7fff2e7c (auto,2) &x5 == 0x7fff2e54 (auto,1) &x5 == 0x7fff2e2c (auto,0) % size vaddr-print
text data bss dec hex filename 2933 304 28 3265 cc1 vaddr-print %
----------------------------------------------------------------------
UDP/IP では、一度に送ることができるデータの大きさにには、実装上の制限 が付いている。これがいくつかを調べるプログラムを作りなさい。
UDP/IP のデータを中継するようなプログラムを作りなさい。 単方向だけでなく、双方向で中継するようにしなさい。
このようなプログラムの例として、udprelay と呼ばれるプログラムがある。
UDP/IP のサーバ、または、中継プログラムで、クライアントの IP アドレス によってアクセスを許したりエラーを発生させたりしなさい。
signal-int.c のプロ グラムを動かし、kill コマンドを使って SIGINT (kill -INT)でシグナルを送 りなさい。そして、キーボードから intr のキー^Cを打った時と動 作を比較しなさい。
ヒント:kterm を2つ開いて、片方でこのプログラムを動かし、片方で kill コマンドを動かす。kill 引数として必要な PID は、ps コマンドで調べる。
ヒント:そのシグナルを無視(SIG_IGN)するように設定する。
cc -D_BSD_SIGNALS -o prog prog.cBSD 系では、一度登録したシグナルは、ハンドラの中で再登録する必要はない。 このことを調べなさい。
また、シグナル・ハンドラの中でシグナルを受けた時の動きを調べなさい。
くわしくは、man 3b signal で表示される。