システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/classes/syspro-2003/2003-06-02
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
IP 層では、アドレスとしては、IP アドレスだけを使う。UDP/IP は、IP 層の 機能をほとんどそのままの形で利用可能にしたものである。違いは、ポート番 号を指定できることと、チェックサムでデータの内容を検査できることなどで ある。
UDP/IP でも、クライアント側のアドレスは、OSに任せ、自らは指
定しないことが多い(指定することもできる)。
図? UDP/IPのデータグラム
telnet は、TCP/IP の汎用のクライアント(文字列の送受信用)なので、UDP/IP でサービスを提供するクライアントとしては使えない。
UDP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。ただし、セキュリティ上の理由から、echo サービスを 停止することが、一般的である。以下は、このサービスを利用するクライアン トである。
---------------------------------------------------------------------- 1: 2: /* 3: echo-client-udp.c -- 文字列を送受信するクライアント(UDP/IP版) 4: ~yas/syspro/ipc/echo-client-udp.c 5: Start: 1997/06/16 21:22:26 6: */ 7: #include <stdio.h> 8: #include <sys/types.h> /* socket() */ 9: #include <sys/socket.h> /* socket() */ 10: #include <netinet/in.h> /* struct sockaddr_in */ 11: #include <netdb.h> /* getaddrinfo() */ 12: 13: extern void echo_client_udp( char *server, int portno ); 14: extern int udp_port_nobind(); 15: extern int sockaddr_in_init( struct sockaddr_in *addr, int addrlen, 16: char *hostname, int portno ); 17: extern void sockaddr_print( struct sockaddr *addrp, int addr_len ); 18: extern void sockname_print( int s ); 19: 20: main( int argc, char *argv[] ) 21: { 22: char *server ; 23: int portno ; 24: if( argc != 3 ) 25: { 26: fprintf( stdout,"Usage: %% %s host port\n",argv[0] ); 27: exit( -1 ); 28: } 29: server = argv[1] ; 30: portno = strtol( argv[2],0,10 ); 31: echo_client_udp( server, portno ); 32: } ----------------------------------------------------------------------
main の部分は、TCP/IP 版の echo-client.c とほとんど同じである。
main() 関数は、コマンドラインの引数を調べて、echo_client() を読んでい る。strtol() で、文字列で与えられたポート番号を、int に変換している。
---------------------------------------------------------------------- 33: 34: #define BUFFERSIZE 1024 35: 36: void 37: echo_client_udp( char *server, int portno ) 38: { 39: int sock ; 40: int slen,scount,rcount ; 41: char sbuf[BUFFERSIZE]; 42: char rbuf[BUFFERSIZE]; 43: int i ; 44: int fromlen ; 45: struct sockaddr_in to; struct sockaddr_storage from ; 46: 47: sock = udp_port_nobind(); 48: if( sock<0 ) 49: exit( -1 ); 50: printf("my port is "); sockname_print( sock ); printf("\n"); 51: 52: if( sockaddr_in_init( &to, sizeof(to), server, portno )<0 ) 53: { 54: perror("sockaddr_in_init"); 55: exit( -1 ); 56: } 57: printf("server is "); 58: sockaddr_print( (struct sockaddr *)&to, sizeof(to) ); printf("\n"); 59: 60: printf("==> "); fflush(stdout); 61: while( fgets(sbuf,BUFFERSIZE,stdin) ) 62: { 63: slen = strlen( sbuf ); 64: 65: printf("sending [%s] (%d bytes) to ", sbuf, slen ); 66: sockaddr_print( (struct sockaddr *)&to, sizeof(to) ); printf("\n"); 67: 68: scount = sendto( sock, sbuf, slen, 0, 69: (struct sockaddr *)&to, sizeof(to) ); 70: if( scount != slen ) 71: { 72: perror("sendto()"); 73: exit( 1 ); 74: } 75: printf("after sendo(), my port is "); sockname_print( sock ); printf("\n"); 76: 77: fromlen = sizeof( from ); 78: rcount = recvfrom( sock, rbuf, BUFFERSIZE, 0, 79: (struct sockaddr *)&from, &fromlen); 80: if( rcount < 0 ) 81: { 82: perror("recvfrom()"); 83: exit( 1 ); 84: } 85: rbuf[rcount] = 0 ; 86: printf("received %d bytes [%s] from ",rcount, rbuf ); 87: sockaddr_print( (struct sockaddr *)&from, fromlen ); printf("\n"); 88: 89: printf("==> "); fflush(stdout); 90: } 91: printf("\n"); 92: 93: close( sock ); 94: } ----------------------------------------------------------------------echo_client_udp() では、udp_port_nobind() という関数を呼び出している。 この結果、UDP/IP のポートが作られ、通信可能なファイル記述子が返される。 ただし、サーバとの間には、通信路は確保されない。このファイル記述子は、 標準入出力(0,1,2)や open() システム・コールの結果と同じもので、不要に なったら close() で解放する。しかし、この状態では、write() や read() システムコールは使えない。その代りに、sendto() や recvfrom() システム コールを使う。
クライアント側では、bind() でアドレス(IPアドレスとポート番号)を設定し ないことが多い。通信に使う前には、アドレスは固定されていない。通信に使 うと、その瞬間に固定され、close() するまで有効である。(クライアント側 でも、bind() でアドレスを固定することもできるが、一般的ではない。)
sockaddr_in_init() は、構造体 to に送り先のアドレス(IPアドレスとポート 番号)をセットしている。
送るデータは、TCP/IP版の同じく文字列である。UDP/IP の通信では、データ の区切りは保存されるので、行末の区切りをつけずに構造体をそのまま送るも のも多い。
sendto() システムコールで、データを送信している。送り先は、第5引数に指 定されている。TCP/IP とは異なり、データの区切りは保存される。また、一 度に全データが送られるので、TCP/IP とは異なり、writen() のように、全デー タを送り出すためのループは不要である。ただし、一度に送ることができるデー タの大きさには上限がある。
途中、階層の通信媒体の上限を越えた場合、「フラグメント化」が行われ、実 際のデータとしては分割されることがある。たとえば、イーサネットでは、約 1500バイトを越えた場合、フラグメント化される。フラグメント化されたデー タは、受信側のプロセスに送られる前に組み立てられる。
データを受け取るには、recvfrom() システムコールを使っている。この時、 データの送り手のアドレス(IPアドレスとポート番号)が&from番 地に保存される。普通は、sendto() と同じアドレスから返ってくるが、 違うアドレスから返ってくることもある。
recvfrom() では、最後に終端の 0 を付けてくれないので、自分で 0 を付け ている。
---------------------------------------------------------------------- 95: 96: int 97: udp_port_nobind() 98: { 99: int s ; 100: if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ) 101: { 102: perror("socket"); 103: return( -1 ); 104: } 105: return( s ); 106: } ----------------------------------------------------------------------
udp_port_nobind() は、クライアント側の UDP/IP のポートを作る関数である。 内部では、ソケットを socket() システムコールで作成しているだけである。 PF_INET と SOCK_DGRAMの組み合わせなので、 UDP を使うことを意味する。 socket() の引数で、PF_INET の変りに、AF_INET と書いてもよい。ここでは、 Protocol を選んでいるので、PF_ が正しいが、実際には、PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われいる。
UDP/IP でも、connect() システムコールで接続先を固定する方法もあるが、 一般的ではない。接続先を固定した場合は、sendto() や recvfrom() ではな く、write() やread() を使って通信することもできる。その場合も、UDP の 性質は保たれるので、write() したデータのパケットが失われた時には、再転 送は行われない。
---------------------------------------------------------------------- 107: 108: int 109: sockaddr_in_init( struct sockaddr_in *addr, int addrlen, 110: char *hostname, int portno ) 111: { 112: struct addrinfo hints, *ai; 113: int err ; 114: 115: if( addrlen < sizeof(struct sockaddr_in) ) 116: { 117: fprintf(stderr,"sockaddr_in, not enough space (%d) > (%d)\n", 118: addrlen, sizeof(struct sockaddr_in) ); 119: return( -1 ); 120: } 121: memset( &hints, 0, sizeof(hints) ); 122: hints.ai_family = AF_INET ; 123: hints.ai_socktype = SOCK_DGRAM ; 124: if( (err = getaddrinfo( hostname, NULL, &hints, &ai )) ) 125: { 126: fprintf(stderr,"unknown host %s (%s)\n",hostname, 127: gai_strerror(err) ); 128: return( -1 ); 129: } 130: if( ai->ai_addrlen > addrlen ) 131: { 132: fprintf(stderr,"sockaddr too large (%d) > (%d)\n", 133: ai->ai_addrlen,sizeof(addr) ); 134: freeaddrinfo( ai ); 135: return( -1 ); 136: } 137: memcpy( addr, ai->ai_addr, ai->ai_addrlen ); 138: addr->sin_port = htons( portno ); 139: freeaddrinfo( ai ); 140: 141: return( 0 ); 142: } ----------------------------------------------------------------------UDP/IP の場合も、TCP/IP と同様に、アドレスの指定には、sockaddr_in 構造 体を使う。sendto() システムコールのマニュアルには、sockaddr 構造体を使 うようにと書かれているが、UDP/IP (IPv4) では、そのサブクラス(オブジェ クト指向用語)であるsockaddr_in を使う。この場合、IP アドレスとポート番 号が埋められている必要がある。その他に先頭に、sockaddr_in であることを 示す定数 AF_INET を置く。 getaddrinfo() は、ホスト名を IP アドレスし、さらに、先頭に定数 AF_INET を設定している。ポート番号は、第2引数が NULL なので、0 を指定している。 この例では、引数で与えられたポート番号を htons() を介してバイトオーダ を調整してから設定している。
getaddrinfo() に関しては、 TCP/IP 版の echo-client.c も、参照しなさい。
---------------------------------------------------------------------- 143: 144: void 145: sockaddr_print( struct sockaddr *addrp, int addr_len ) 146: { 147: char host[BUFFERSIZE] ; 148: char port[BUFFERSIZE] ; 149: if( getnameinfo(addrp, addr_len, host, sizeof(host), 150: port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 ) 151: return; 152: printf("%s:%s", host, port ); 153: } 154: 155: void 156: sockname_print( int s ) 157: { 158: struct sockaddr_storage addr ; 159: int len ; 160: len = sizeof( addr ); 161: if( getsockname( s, (struct sockaddr *)&addr, &len )< 0 ) 162: { 163: perror("getsockname"); 164: exit( -1 ); 165: } 166: sockaddr_print( (struct sockaddr *)&addr,len ); 167: } ----------------------------------------------------------------------
sockaddr_print() は、sockaddr 構造体を表示する関数である。 getaddrinfo() ライブラリ関数により、ホスト名とポート番号の文字列表現を 得ている。文字列といっても、NI_NUMERICHOST|NI_NUMERICSERV というフラグ を指定しているので、結果は "123.4.5.6" や "80" のような数字の並びによ る表記になる。
sockname_print() は、自分自身のポートのアドレスを表示する関数である。 アドレスは、getsockname() で得られる。なお、UDP/IP では、TCP/IP とは異 なり、getpeername() で通信相手を調べることは、この場合できない。UDP/IP では、通信相手は、recvfrom() の引数で知ることができる。
実行例:
このプログラムは、コマンドラインから2の引数をとる。第1引数は、ホスト 名、第2引数は、ポート番号である。このプログラムは、標準入力から得られ た行を、その指定されたホスト上のポート番号で動作しているサーバに対して 送る。サーバは、 同じものを送り返してくるので、それを受け取る。---------------------------------------------------------------------- % cp ~yas/syspro/ipc/echo-client-udp.c .% make echo-client-udp
cc echo-client-udp.c -o echo-client-udp % ./echo-client-udp localhost 7
my port is 0.0.0.0:0 server is 127.0.0.1:7 ==> hello
sending [hello ] (6 bytes) to 127.0.0.1:7 after sendo(), my port is 0.0.0.0:33041 received 6 bytes [hello ] from 127.0.0.1:7 ==> world!
sending [world! ] (7 bytes) to 127.0.0.1:7 after sendo(), my port is 0.0.0.0:33041 received 7 bytes [world! ] from 127.0.0.1:7 ==> ^D % ./echo-client-udp adonis8 7
my port is 0.0.0.0:0 server is 130.158.86.28:7 ==> aaa
sending [aaa ] (4 bytes) to 130.158.86.28:7 after sendo(), my port is 0.0.0.0:33041 received 4 bytes [aaa ] from 130.158.86.28:7 ==> bbb
sending [bbb ] (4 bytes) to 130.158.86.28:7 after sendo(), my port is 0.0.0.0:33041 received 4 bytes [bbb ] from 130.158.86.28:7 ==> ^D %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: 2: /* 3: echo-server-udp.c -- 文字列を送受信するサーバ(UDP/IP版) 4: ~yas/syspro/ipc/echo-server-udp.c 5: Start: 1997/06/16 21:22:26 6: */ 7: #include <stdio.h> 8: #include <sys/types.h> /* socket() */ 9: #include <sys/socket.h> /* socket() */ 10: #include <netinet/in.h> /* struct sockaddr_in */ 11: #include <netdb.h> /* getaddrinfo() */ 12: 13: extern void echo_server_udp( int portno ); 14: extern int udp_port_bind( int portno ); 15: extern void sockaddr_print( struct sockaddr *addrp, int addr_len ); 16: extern void sockname_print( int s ); 17: 18: main( int argc, char *argv[] ) 19: { 20: int portno ; 21: if( argc >= 3 ) 22: { 23: fprintf( stdout,"Usage: %s [portno]\n",argv[0] ); 24: exit( -1 ); 25: } 26: if( argc == 2 ) 27: portno = strtol( argv[1],0,10 ); 28: else 29: portno = getuid(); 30: echo_server_udp( portno ); 31: } 32: ----------------------------------------------------------------------TCP/IP版echo-server-fork と同様に、 引数として、ポート番号を取る。ポート番号が与えられなければ、そのプロセ スの UID (User ID) から生成する。UID は、個人個人を識別するための番号 (16ビット程度、システムによっては 32 ビット)である。この課題では、(1台 のコンピュータでは)個人ごとに別々の UID を使う必要がある。上のように UID から生成する方法は、一般的ではない。(筑波大学情報学類のシステムで はうまく働く。)
---------------------------------------------------------------------- 33: #define BUFFERSIZE 1024 34: 35: void echo_server_udp( int portno ) 36: { 37: int s, rcount, scount, addrlen ; 38: struct sockaddr_storage addr ; 39: char buffer[BUFFERSIZE]; 40: 41: s = udp_port_bind( portno ); 42: if( s<0 ) 43: exit( -1 ); 44: printf("my port is "); sockname_print( s ); printf("\n"); 45: 46: while( 1 ) 47: { 48: addrlen = sizeof( addr ); 49: rcount = recvfrom( s, buffer, BUFFERSIZE, 0, 50: (struct sockaddr *)&addr, &addrlen ); 51: if( rcount < 0 ) 52: { 53: perror("recvfrom()"); 54: exit( 1 ); 55: } 56: buffer[rcount] = 0 ; 57: printf("received %d bytes [%s] from ",rcount, buffer ); 58: sockaddr_print( (struct sockaddr *)&addr,addrlen ); printf("\n"); 59: 60: printf("sending back [%s] (%d bytes) to ", buffer, rcount ); 61: sockaddr_print( (struct sockaddr *)&addr,addrlen ); printf("\n"); 62: scount=sendto( s, buffer, rcount, 0, (struct sockaddr *)&addr, addrlen ); 63: if( scount!= rcount ) 64: { 65: perror("sendto()"); 66: exit( 1 ); 67: } 68: } 69: } 70: ----------------------------------------------------------------------udp_port_bind() は、引数で与えられたポート番号を使ってUDP/IP のポート を作成し、そのファイル記述子(ソケット)を返す。sockname_print() は、そ のアドレス(IPアドレスとポート番号)を表示する。 TCP/IP とは異なり、このポートは、そのまま通信に使うことができる。 逆に、accept() は使うことはできない。
サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。
このサーバは、fork() しない。サービスの内容が簡単であり、sendto() シス テムコールでブロックすることもないからである。サービスが重たい時には、 他のクライアントからの処理を並列に進めるために、子プロセスを作ること可 能である。
---------------------------------------------------------------------- 71: int udp_port_bind( int portno ) 72: { 73: struct sockaddr_in addr ; 74: int addr_len ; 75: int s ; 76: 77: if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ) 78: { 79: perror("socket"); 80: return( -1 ); 81: } 82: 83: memset( &addr, 0, sizeof(addr) ); 84: addr.sin_family = AF_INET ; 85: addr.sin_addr.s_addr = INADDR_ANY ; 86: addr.sin_port = htons( portno ); 87: 88: if( bind(s,(struct sockaddr *)&addr,sizeof(addr)) < 0 ) 89: { 90: perror("bind"); 91: fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno ); 92: return( -1 ); 93: } 94: return( s ); 95: } 96: 97: <以下省略> 99: sockaddr_print( struct sockaddr *addrp, int addr_len ) ... 110: sockname_print( int s ) .. ----------------------------------------------------------------------udp_port_bind() は、サーバ用に、ポート番号を固定した UDP/IP のポートを作成する。 クライアント側との違いは、 bind() システムコールを使っている点にある。
bind() システムコールの引数は、 TCP版のecho-server-fork と同じである。 ソケットが作成できたら、bind() システムコールで、サーバ側で利用するア ドレス(IPアドレスとポート番号)を設定する。IP アドレスは、普通は、 INADDR_ANY を指定する。複数の IP アドレスがある時には、どれに要求が来 ても受け付ける。特定の IP アドレスを指定すると、そのアドレスに来た要求 だけを受け付けるようになる。
ポート番号は、引数で与えられたものを、htons() でネットワーク・バイトオー ダに変換して与える。
listen() システムコールは、UDP/IP では使われない。 要求、すなわち、データが大量に送られ、サーバが処理できない時には、(普 通は古いものから順に)捨てられる。
実行例。
サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。
サーバ側では、bind() をつかっているので、最初からポート番号が固定され ていることがわかる。---------------------------------------------------------------------- % ./echo-server-udp 1231my port is 0.0.0.0:1231 received 6 bytes [hello ] from 127.0.0.1:33042 sending back [hello ] (6 bytes) to 127.0.0.1:33042 received 7 bytes [world! ] from 127.0.0.1:33042 sending back [world! ] (7 bytes) to 127.0.0.1:33042 received 4 bytes [aaa ] from 130.158.86.28:33015 sending back [aaa ] (4 bytes) to 130.158.86.28:33015 received 4 bytes [133 ] from 130.158.86.28:33015 sending back [133 ] (4 bytes) to 130.158.86.28:33015 ^C %
----------------------------------------------------------------------
図1 バスにより接続されたCPU、メモリ、デバイス
バス:何本かの配線の束
例:キーボード用のコントローラの働き
データが、電気信号などの形で送られてくる。コントローラの中のレジスタ (小容量のメモリ)に保存され。
CPU から見える場所
CPU の速度に比べて、デバイスの速度は遅い。
普通の割込み(ハードウェアによる割込み)は、オペレーティング・システム のカーネルにおいて利用されている。
ソフトウェア割込みとは、本来はオペレーティング・システムのカーネルしか 使えない割込みの機能を、ソフトウェアにより実現して、一般の利用者プログ ラム(プロセス)でも使えるようにしたものである。
UNIXでは、ソフトウェア割込みの機能は、シグナル(signal)という名前で実現 されている。シグナルとは、本来はプロセス間通信の一種で、あるイベントが起き たことを他のプロセスに知らせることである。ここで伝わるのは、あるイベントが 起きたかどうかだけで、イベントの種類の区別ができることがあるが、 データを送ることはできない。
UNIXでは、プロセ ス間でシグナルにより通信をする他に、キーボードからシグナルを送ることも できる。これは、「ソフトウェア割込み」として、プロセス1つひとつに割込 みボタンが付いているようなものである。また、プログラムの中で例外 (exception)が起きた時にも、ハードウェアの割込みと同様に、ソフトウェ ア割込みが生じる。これも、他のシグナルと同じように受け取ることができる。
UNIXのソフトウェア割込み(シグナル)を使うには、次のようなことが必要で ある。
ソフトウェア割り込みは、1つのプログラムの中に制御の流れが1つしかない ようなプログラムの時に有効な方法である。最近のマルチスレッドのプログラ ムでは、シグナルの意味が不明確である。
スレッドの話は、3学期の授業「オペレーティングシステムII」などで出てく る。
---------------------------------------------------------------------- 1: /* 2: signal-int.c -- SIGINT を3回受け付けて終了するプログラム。 3: ~yas/syspro/proc/signal-int.c 4: Start: 1997/05/26 18:38:38 5: */ 6: 7: #include <stdio.h> 8: #include <signal.h> 9: 10: int sigint_count = 3 ; 11: void sigint_handler(); 12: 13: main() 14: { 15: signal( SIGINT, &sigint_handler ); 16: printf("main(): going into infinite loop, sigint_count == %d\n", 17: sigint_count); 18: while( 1 ) 19: { 20: printf("main(): sigint_count == %d, pause() ....\n", 21: sigint_count ); 22: pause(); 23: printf("main(): return from pause(). sigint_count == %d\n", 24: sigint_count ); 25: } 26: } 27: 28: void sigint_handler() 29: { 30: printf("sigint_handler():\n"); 31: if( -- sigint_count <= 0 ) 32: { 33: printf("sigint_handler(): exit() ... \n"); 34: exit( 1 ); 35: } 36: /* signal( SIGINT, &sigint_handler ); /* System V */ 37: printf("sigint_handler(): sigint_count == %d\n",sigint_count); 38: } ----------------------------------------------------------------------
signal() で、割込み処理ハンドラを登録している。この段階で、関数 sigint_handler() は呼び出されない。また、プログラムのどこからも、関数 sigint_handler() は明示的には呼び出されていない。
main() は、無限ループになっている。
pause() は、割込みを待つシステムコールである。システムコールの実行中に 割込みが発生するまで待つ。発生すると、割込み処理ハンドラの実行後にリター ンする。
sigint_handler() は、main() から呼び出されないが、割込みが発生すると実 行される。内部では、大域変数 sigint_count を減らして、0 以下になれば終 了している。
System V 系の Unix では、一度割込みハンドラが呼び出されると、それが解 除される。もう一度同じ割込みハンドラを使いたい場合には、割込みハンドラ の中で signal() で登録し直す必要がある。BSD 系の Unix と、 新しい目の Linux (glibc2 以降)では、再登録の必要はない。
古い Linux (libc4,libc5) では、標準では、System V と同じ動きをしていた。 Linux (RedHat 7.1)の日本語のマニュアルは、その時から更新されていない。 英語のマニュアルは、更新されている。
実行例。
stty -a で、 intr がどのキーに割り当てられているかを調べる。この場合、 ^C に割り当てられている。./signal-int を実行し、^C を3回押している。---------------------------------------------------------------------- % stty -aspeed 9600 baud; rows 40; columns 80; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol =
; eol2 = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke % ./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() ... %
----------------------------------------------------------------------
^C の代りに、別の端末から kill コマンドを使って割込みを発生さ せる方法もある。
ここで、---------------------------------------------------------------------- % kill -INT PID----------------------------------------------------------------------
PID
は、./signal-int のプロセスの PID (Process
ID)である。PID は、ps コマンドで調べる。
errno が EINTR の時、システムコールを再実行する必要がある場合が多い。
そのような場合でも、問題なく動く関数を、再入可能な関数(reentrant function)という。
引数やauto変数だけを使った関数は、再入可能になる。
extern や static を使った関数は、再入可能でないことがある。
次のような関数は、リエントラントではない。
シグナル・ハンドラの中では、リエントラントな関数を使うようにする。
タイマ割込みをソフトウェアで実現したものが、setitimer()システムコール である。
ゲームで、人間がボタンを押さなくても進んで行くところは、タイマ割込みに よる。
図? プロセスのアドレス空間
機械語命令を置くためのセグメンを テキスト・セグメント ( 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/proc/vaddr-print.c 4: Start: 1997/05/19 22:58:49 5: */ 6: #include <stdlib.h> 7: 8: void recursive( int n ); 9: 10: int x1=1 ; 11: int x2 ; 12: 13: extern int etext, edata, end ; 14: 15: main( argc,argv,envp ) 16: int argc ; 17: char *argv[] ; 18: char *envp[] ; 19: { 20: int x3 ; 21: char *x4p ; 22: 23: printf("&main == 0x%08x (text)\n",&main ); 24: printf("&etext == 0x%08x (text)\n",&etext ); 25: printf("&edata == 0x%08x (data)\n",&edata ); 26: printf("&end == 0x%08x (data)\n",&end ); 27: 28: printf("&argv[0] == 0x%08x (stack)\n",&argv[0] ); 29: printf(" argv[0] == 0x%08x (stack)\n", argv[0] ); 30: printf("&envp[0] == 0x%08x (stack)\n",&envp[0] ); 31: printf(" envp[0] == 0x%08x (stack)\n", envp[0] ); 32: 33: printf("&x1 == 0x%08x (data)\n",&x1 ); 34: printf("&x2 == 0x%08x (bss)\n",&x2 ); 35: printf("&x3 == 0x%08x (stack)\n",&x3 ); 36: x4p = malloc( 10 ); 37: printf("x4p == 0x%08x (heap)\n",x4p ); 38: x4p = malloc( 10 ); 39: printf("x4p == 0x%08x (heap)\n",x4p ); 40: recursive( 3 ); 41: } 42: 43: void recursive( int n ) 44: { 45: int x5 ; 46: printf("&x5 == 0x%08x (stack,%d)\n",&x5,n ); 47: if( n<=0 ) 48: return; 49: recursive( n-1 ); 50: } ----------------------------------------------------------------------実行例。
main() のような関数の番地は、0 に近い場所(0から0xffffffffの 間の 1/32 辺り)にある。スタックは、0 から遠い場所にある。---------------------------------------------------------------------- % cp ~yas/syspro/proc/vaddr-print.c .% make vaddr-print
cc vaddr-print.c -o vaddr-print % ./vaddr-print
&main == 0x08048490 (text) &etext == 0x0804866e (text) &edata == 0x08049910 (data) &end == 0x0804992c (data) &argv[0] == 0xbfffd984 (stack) argv[0] == 0xbffff966 (stack) &envp[0] == 0xbfffd98c (stack) envp[0] == 0xbffff974 (stack) &x1 == 0x08049830 (data) &x2 == 0x08049928 (bss) &x3 == 0xbfffd914 (stack) x4p == 0x08049938 (heap) x4p == 0x08049948 (heap) &x5 == 0xbfffd8f4 (stack,3) &x5 == 0xbfffd8d4 (stack,2) &x5 == 0xbfffd8b4 (stack,1) &x5 == 0xbfffd894 (stack,0) % size vaddr-print
text data bss dec hex filename 1815 240 28 2083 823 vaddr-print %
% ldd vaddr-print
libc.so.6 => /lib/i686/libc.so.6 (0x4002d000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) %
----------------------------------------------------------------------
引数や環境変数は、スタックの底(0から遠い方)にある。
初期値付きの変数は、&edata 以下にある。
初期値なしの変数は、&edata と&end の間にある。
malloc() で確保したデータの番地は、&end より大きい。free() がなければ、普通は、malloc() する度に大きくなるが、この性質は保証され ていない。
スタック上の変数は、再帰呼出しの度に、番地が小さくなる。
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)するように設定する。