システム・プログラム
                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2002/2002-06-10
あるいは、次のページから手繰っていくこともできます。
	http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
	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 でサービスを提供するクライアントとしては使えない。
このプログラムは、コマンドラインから3つ以上の引数をとる。第1引数で指 定されたホスト上の、第2引数で指定されたポートで動作しているサーバに接 続する。そして、第3引数以降に指定された文字列を送る。echo サービスは、 同じものを送り返してくるので、それを受け取る。---------------------------------------------------------------------- % cp ~yas/syspro/ipc/echo-client-udp.c .% make echo-client-udp
cc echo-client-udp.c -o echo-client-udp % ./echo-client-udp
Usage: ./echo-client-udp host port str1 ... % ./echo-client-udp localhost 1231 hello world.
my port is sockaddr_in: 0.0.0.0:0 sockaddr_in: 127.0.0.1:1231 sending [hello ] (6 bytes) to sockaddr_in: 127.0.0.1:1231 received 6 bytes [hello ] from sockaddr_in: 127.0.0.1:1231 sending [world. ] (7 bytes) to sockaddr_in: 127.0.0.1:1231 received 7 bytes [world. ] from sockaddr_in: 127.0.0.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:32862 % ./echo-client-udp adonis9 1231 hello world.
my port is sockaddr_in: 0.0.0.0:0 sockaddr_in: 130.158.86.29:1231 sending [hello ] (6 bytes) to sockaddr_in: 130.158.86.29:1231 received 6 bytes [hello ] from sockaddr_in: 130.158.86.29:1231 sending [world. ] (7 bytes) to sockaddr_in: 130.158.86.29:1231 received 7 bytes [world. ] from sockaddr_in: 130.158.86.29:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:32862 %
----------------------------------------------------------------------
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>      /* gethostbyname() */
  12:	
  13:	extern void echo_client_udp( char *server, int portno, int nstr, char *strv[] );
  14:	
  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:	     char *server ;
  23:	     int portno ;
  24:	        if( argc < 3 )
  25:	        {
  26:	            fprintf( stdout,"Usage: %s host port str1 ...\n",argv[0] );
  27:	            exit( -1 );
  28:	        }
  29:	        server = argv[1] ;
  30:	        portno = atoi( argv[2] );
  31:	        echo_client_udp( server, portno, argc-3, &argv[3] );
  32:	}
----------------------------------------------------------------------
main の部分は、TCP/IP 版の echo-client.c と同じである。
main() 関数は、コマンドラインの引数を調べて、echo_client() を読んでい る。atoi() で、文字列で与えられた数を、int に変換している。
----------------------------------------------------------------------
  33:	
  34:	#define BUFFERSIZE      1024
  35:	
  36:	void
  37:	echo_client_udp( char *server, int portno, int nstr, char *strv[] )
  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, from ;
  46:	
  47:	        sock = udp_port_nobind();
  48:	        if( sock<0 )
  49:	            exit( -1 );
  50:	    printf("my port is "); sockname_print( sock );
  51:	
  52:	        if( sockaddr_in_init( &to, server, portno )<0 )
  53:	        {
  54:	            perror("sockaddr_in_init");
  55:	            exit( -1 );
  56:	        }
  57:	    sockaddr_in_print( &to );
  58:	
  59:	        for( i=0 ; i<nstr ; i++ )
  60:	        {
  61:	            snprintf( sbuf,BUFFERSIZE,"%s\n",strv[i] );
  62:	            slen = strlen( sbuf );
  63:	
  64:	    printf("sending [%s] (%d bytes) to ", sbuf, slen );
  65:	    sockaddr_in_print( &to );
  66:	
  67:	            scount = sendto( sock, sbuf, slen, 0,
  68:	                             (struct sockaddr *)&to, sizeof(to) );
  69:	            if( scount != slen )
  70:	            {
  71:	                perror("sendto()");
  72:	                exit( 1 );
  73:	            }
  74:	            fromlen = sizeof( from );
  75:	            rcount = recvfrom( sock, rbuf, BUFFERSIZE, 0,
  76:	                               (struct sockaddr *)&from, &fromlen);
  77:	            if( rcount < 0 )
  78:	            {
  79:	                perror("recvfrom()");
  80:	                exit( 1 );
  81:	            }
  82:	            rbuf[rcount] = 0 ;
  83:	    printf("received %d bytes [%s] from ",rcount, rbuf );
  84:	    sockaddr_in_print( &from );
  85:	        }
  86:	    printf("after sendto(), my port is "); sockname_print( sock );
  87:	        close( sock );
  88:	}
----------------------------------------------------------------------
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版の同じく文字列である。この例では、行末に「\n」を 送っているが、UDP/IP の通信では、行の概念がなく、構造体をそのまま送る ものも多い。
sendto() システムコールで、データを送信している。送り先は、第5引数に指 定されている。TCP/IP とは異なり、データの区切りは保存される。また、一 度に全データが送られるので、TCP/IP とは異なり、writen() のように、全デー タを送り出すためのループは不要である。ただし、一度に送ることができるデー タの大きさには上限がある。
途中、階層の通信媒体の上限を越えた場合、「フラグメント化」が行われ、実 際のデータとしては分割されることがある。たとえば、イーサネットでは、約 1500バイトを越えた場合、フラグメント化される。フラグメント化されたデー タは、受信側のプロセスに送られる前に組み立てられる。
データを受け取るには、recvfrom() システムコールを使っている。この時、 データの送り手のアドレス(IPアドレスとポート番号)が&from番 地に保存される。普通は、sendto() と同じアドレスから返ってくるが、 違うアドレスから返ってくることもある。
recvfrom() では、最後に終端の 0 を付けてくれないので、自分で 0 を付け ている。
----------------------------------------------------------------------
  89:	
  90:	int
  91:	udp_port_nobind()
  92:	{
  93:	    int s ;
  94:	        if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 )
  95:	        {
  96:	            perror("socket");
  97:	            return( -1 );
  98:	        }
  99:	        return( s );
 100:	}
 101:	
----------------------------------------------------------------------
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() したデータのパケットが失われた時には、再転 送は行われない。
----------------------------------------------------------------------
 102:	int
 103:	sockaddr_in_init( struct sockaddr_in *addr, char *hostname, int portno )
 104:	{
 105:	    struct hostent *hostent ;
 106:	        memset( addr, 0, sizeof(struct sockaddr_in) );
 107:	        addr->sin_family = AF_INET ;
 108:	        if( (hostent = gethostbyname( hostname )) == NULL )
 109:	        {
 110:	            fprintf(stderr,"unknown host %s\n",hostname );
 111:	            return( -1 );
 112:	        }
 113:	        memcpy( &addr->sin_addr, hostent->h_addr, hostent->h_length );
 114:	        addr->sin_port = htons( portno );
 115:	        return( 0 );
 116:	}
 117:	
----------------------------------------------------------------------
UDP/IP の場合も、TCP/IP と同様に、
アドレスの指定には、sockaddr_in 構造体を使う。sendto() 
システムコールのマニュアルには、sockaddr 構造体を使うようにと書かれて
いるが、UDP/IP では、そのサブクラス(オブジェクト指向用語)である
sockaddr_in を使う。それには、
IP アドレスとポート番号が必要である。
その他に、先頭に、sockaddr_in であることを示す定数 AF_INET を置く。
gethostbyname() に関しては、 TCP/IP 版の echo-client.c を参照しなさい。
----------------------------------------------------------------------
 118:	void
 119:	sockaddr_in_print( struct sockaddr_in *addr )
 120:	{
 121:	    union {
 122:	        int i ;
 123:	        unsigned char byte[4] ;
 124:	    } x ;
 125:	        x.i = addr->sin_addr.s_addr ;
 126:	        printf("sockaddr_in: %d.%d.%d.%d:%d\n",
 127:	               x.byte[0],x.byte[1],x.byte[2],x.byte[3],
 128:	               ntohs( addr->sin_port ));
 129:	}
 130:	
 131:	void
 132:	sockname_print( int s )
 133:	{
 134:	    struct sockaddr_in addr ;
 135:	    int len ;
 136:	        len = sizeof( addr );
 137:	        if( getsockname( s, (struct sockaddr *)&addr, &len )< 0 )
 138:	        {
 139:	            perror("getsockname");
 140:	            exit( -1 );
 141:	        }
 142:	        sockaddr_in_print( &addr );
 143:	}
----------------------------------------------------------------------
sockaddr_in_print() は、sockaddr_in 構造体を表示する関数である。
sockname_print() は、自分自身のポートのアドレスを表示する関数である。 アドレスは、getsockname() で得られる。なお、getpeername() で通信相手を 調べることは、この場合できない。この場合は、通信相手は、recvfrom() の 引数で知ることができる。
----------------------------------------------------------------------
   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>      /* gethostbyname() */
  12:	
  13:	extern void echo_server_udp( int portno );
  14:	extern int udp_port_bind( int portno );
  15:	extern void sockaddr_in_print( struct sockaddr_in *addr );
  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 = atoi( argv[1] );
  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_in 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 );
  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_in_print( &addr );
  59:	
  60:	    printf("sending back [%s] (%d bytes) to ", buffer, rcount );
  61:	    sockaddr_in_print( &addr );
  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:	}
----------------------------------------------------------------------
udp_port_bind() は、引数で与えられたポート番号を使ってUDP/IP のポート
を作成し、そのファイル記述子(ソケット)を返す。sockname_print() は、そ
のアドレス(IPアドレスとポート番号)を表示する。
TCP/IP とは異なり、このポートは、そのまま通信に使うことができる。
逆に、accept() は使うことはできない。
サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。
このサーバは、fork() しない。サービスの内容が簡単であり、sendto() シス テムコールでブロックすることもないからである。サービスが重たい時には、 他のクライアントからの処理を並列に進めるために、子プロセスを作ること可 能である。
----------------------------------------------------------------------
  70:	
  71:	int udp_port_bind( int portno )
  72:	{
  73:	    struct hostent *hostent ;
  74:	    struct sockaddr_in addr ;
  75:	    int addr_len ;
  76:	    int s ;
  77:	
  78:	        if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 )
  79:	        {
  80:	            perror("socket");
  81:	            return( -1 );
  82:	        }
  83:	
  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. kill another program.\n",
  92:	                    portno );
  93:	            return( -1 );
  94:	        }
  95:	        return( s );
  96:	}
  97:	
<以下省略>
  98:	void sockaddr_in_print( struct sockaddr_in *addr )
...
 110:	void 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() をつかっているので、最初からポート番号が固定され ていることがわかる。---------------------------------------------------------------------- 2% ./echo-server-udp 1231 my port is sockaddr_in: 0.0.0.0:1231 received 6 bytes [hello ] from sockaddr_in: 127.0.0.1:32862 sending back [hello ] (6 bytes) to sockaddr_in: 127.0.0.1:32862 received 7 bytes [world. ] from sockaddr_in: 127.0.0.1:32862 sending back [world. ] (7 bytes) to sockaddr_in: 127.0.0.1:32862 received 6 bytes [hello ] from sockaddr_in: 127.0.0.1:32862 sending back [hello ] (6 bytes) to sockaddr_in: 127.0.0.1:32862 received 7 bytes [world. ] from sockaddr_in: 127.0.0.1:32862 sending back [world. ] (7 bytes) to sockaddr_in: 127.0.0.1:32862 received 6 bytes [hello ] from sockaddr_in: 130.158.86.29:32862 sending back [hello ] (6 bytes) to sockaddr_in: 130.158.86.29:32862 received 7 bytes [world. ] from sockaddr_in: 130.158.86.29:32862 sending back [world. ] (7 bytes) to sockaddr_in: 130.158.86.29:32862 ^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言語で
char *p ;
    ...
    p = malloc( 100 );
とすると100バイトの領域がヒープ上に確保される。
ヒープ領域の大きさは、brk() システム・コールや
sbrk() システム・コールで変えることができる。これらの
システム・コールは、malloc() ライブラリ関数から呼び出さ
れる。
C言語で
main()
{
    auto int z;
}
とすると, 4バイトの領域がスタックセグメントに割りつけられる。
(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] == 0xbfffe564 (stack) argv[0] == 0xbffffa44 (stack) &envp[0] == 0xbfffe56c (stack) envp[0] == 0xbffffa52 (stack) &x1 == 0x08049830 (data) &x2 == 0x08049928 (bss) &x3 == 0xbfffe4f4 (stack) x4p == 0x08049938 (heap) x4p == 0x08049948 (heap) &x5 == 0xbfffe4d4 (stack,3) &x5 == 0xbfffe4b4 (stack,2) &x5 == 0xbfffe494 (stack,1) &x5 == 0xbfffe474 (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/libc.so.6 (0x4002b000) /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)するように設定する。