プロセス間通信(3)/UDP/IPとシグナル

システム・プログラム

                                       電子・情報工学系
                                       新城 靖
                                       <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言語でプログラムを書く時には、必ずメモリの中でどのような操 作が行われているかを意識すること。

■TCP/IPの基本的な考え方

通信絽の開設

■echo-client

■echo-server-fork

■echo-client-udp

UDP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。以下は、このサービスを利用するクライアントである。

----------------------------------------------------------------------
   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 7 [←]
my 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] 
% []
----------------------------------------------------------------------

■echo-server-udp

UDP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。以下は、これと同じような機能を提供するサーバである。

----------------------------------------------------------------------
   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:	}
<以下省略>
----------------------------------------------------------------------

実行例。

サーバ側。サーバは、終了しないので、最後に、^CDel を押して、割り込みを掛けて終了させる。


----------------------------------------------------------------------
% ./echo-server-udp  [←]
my 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
% []
----------------------------------------------------------------------
クライアント側(その1)。

----------------------------------------------------------------------
% ./echo-client-udp adonis1 1231 [←]
my 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] 
% []
----------------------------------------------------------------------
クライアント側(その2)。

----------------------------------------------------------------------
% ./echo-client-udp adonis1 1231 [←]
my 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] 
%
----------------------------------------------------------------------
クライアント側(その3)。

----------------------------------------------------------------------
% ./echo-client-udp localhost 1231 [←]
my 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] 
% []
----------------------------------------------------------------------

■入出力装置(デバイス)

デバイス(device) とは、コンピュータの箱の中に内蔵されているハードウェアの部品やケーブル で外で接続する部品。 普通は、CPU とメモリ以外のことを指す。 周辺装置(peripheral device)、 入出力装置(IO device)とも呼ばれる。

◆ハードウェアの構成

メモリ、CPU、デバイスは、バス(bus)(システム・バス(system bus))を通 じて接続されている。

図1 バスにより接続されたCPU、メモリ、デバイス

図1 バスにより接続されたCPU、メモリ、デバイス

バス:何本かの配線の束

アドレスバス
メモリのアドレスを示すための線
データバス
データを送るための線
コントロールバス
その他、制御用の線

◆デバイス・コントローラ

各デバイスとCPU で実行されるプログラムとデバイスとり橋渡しをする機器。

例:キーボード用のコントローラの働き

データが、電気信号などの形で送られてくる。コントローラの中のレジスタ (小容量のメモリ)に保存され。

◆CPUとデバイスの間のデータの受け渡しの方法

CPU から見える場所

  1. デバイス・コントローラが I/O 空間にあり、ポート番号と呼ばれる番地 が振られている。CPU が、ポート番号を指定して入出力命令を実行すると、 I/O 空間にあるレジスタの内容が読み書きできる(I/O 空間を示す信号線が1に なる)。
  2. デバイス・コントローラが、普通のメモリと同じ空間にあり、番地が番 地が振られている。CPU が、その番地へ普通のロード命令やストア命令でアク セスすると、コントローラのレジスタの内容が読み書きできる。
入出力の時に CPU が働くかどうか
  1. CPU が入出力命令、あるいは、ロード命令/ストア命令を実行する。
  2. DMA (Direct Memory Access)やバスマスタと呼ばれる機器があり、その 機器がCPU が普通に命令を実行している合間をぬって一時的にバスを乗っ取り、 データをメモリにコピーする。

◆ポーリングと割込み

CPU の速度に比べて、デバイスの速度は遅い。

ポーリング(polling): 周期的にデバイス・コントローラの状態をチェックする。 割込み(interrupt):入出力が可能になった時にデバイスがCPUに知らせる。
入力デバイス
コントローラは、入力データが到着すると、制御バスの割込み要求(IRQ, Interrupt Request)信号線を1にする。 (DMAやバスマスタを使っている時には、メモリへのコピーが完了した時)
出力デバイス
コントローラは、出力用バッファが空になると、割込み要求信号線を1に する。
CPU は、割込み要求を受け付けると、現在実行中の処理を中断して、 割込み処理ルーチン あるいは 割込みハンドラ と呼ばれるプログラムを実行する。 割込み処理ルーチンでは、実際に入力命令を実行したり、 次のデータを出力を開始する。 最後に、割込み処理から復帰する命令を実行する。すると、先ほど中断してい た処理が再開される。

■ソフトウェア割込みとシグナル

普通の割込み(ハードウェアによる割込み)は、オペレーティング・システム のカーネルにおいて利用されている。

ソフトウェア割込みとは、本来はオペレーティング・システムのカーネルしか 使えない割込みの機能を、ソフトウェアにより実現して、一般の利用者プログ ラム(プロセス)でも使えるようにしたものである。

UNIXでは、ソフトウェア割込みの機能は、シグナル(signal)という名前で実現 されている。シグナルとは、本来はプロセス間通信の一種で、ある事象が起き たことを他のプロセスに知らせることである。ここで伝わるのは、ある事象が 起きたかどうかだけで、引数などを付けることはできない。UNIXでは、プロセ ス間でシグナルにより通信をする他に、キーボードからシグナルを送ることも できる。これは、「ソフトウェア割込み」として、プロセス1つひとつに割込 みボタンが付いているようなものである。また、プログラムの中で例外 (exception)が起きた時にも、ハードウェアの割込みと同様に、ソフトウェ ア割込みが生じる。これも、他のシグナルと同じように受け取ることができる。

UNIXのソフトウェア割込み(シグナル)を使うには、次のようなことが必要で ある。

割り込みハンドラが設定されていない時には、プロセスは、デフォルトの動き をする。デフォルトには、次の3つがある。 signal の種類は、man 5 signal (SGI IRIX) で表示される。

ソフトウェア割り込みは、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 -a [←]
speed 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() ... 
% []
----------------------------------------------------------------------
stty で intr に相当するキーを3回押す。

◆EINTR

read(), write(), accept(), select(), poll() などのシステムコールを実行 中にソフトウェア割込みが生じると、システムコールがエラーになる。errno は、EINTR になっている。 errno が EINTR の時、システムコールを再実行する必要がある場合が多い。

◆再入可能(reentrant)

一つの関数を実行の途中で、割込みが行われて、もう一度同じ関数が呼ばれた 時に何が起るか。

そのような場合でも、問題なく動く関数を、再入可能な関数(reentrant function)という。

引数やauto変数だけを使った関数は、再入可能になる。

extern や static を使った関数は、再入可能でないことがある。

次のような関数は、リエントラントではない。

シグナル・ハンドラの中では、リエントラントな関数を使うようにする。

■メモリ・マップ

図? のように, Unixのプロセスのメモリは、はテキストセグメント, データセグメ ント, スタックセグメントに分類される。ここでセグメントとは、連続した メモリの領域のことである。

図? プロセスのアドレス空間

図? プロセスのアドレス空間

◆テキストセグメント

 機械語命令を置くためのセグメンを テキスト・セグメント ( text segment, ) という。このセグメントは、普通、読み出し専用になっていて, 同じロードモ ジュールのプロセスの間で共有される。例えばシェルは複数のユーザが同時に 使うことが多いが、この場合、テキストセグメントのためのメモリは1セット だけでよい。

機械語を書き換えながら実行するプログラムは、最近は行儀が悪いとされてい る。キャッシュの関係で特別な操作をしないと動かない。

◆データセグメント

データ・セグメント ( data segment, ) は、データを置く領域である. C言語の 静的変数(static)大域変数(extern) malloc() で割り当てたヒープ上の変数は、 このセグメントに置かれます。 ( 自動変数(auto) は、次のスタック・セグメントに置かれる。 )  データセグメントは, 次の3つにわかれています.
データ
静的変数や大域変数のうち、初期値つき変数が置かれるところ
BSS
静的変数や大域変数のうち、初期値を指定しない(初期値が0)が置かれるところ
ヒープ(heap) malloc() で確保する変数が置かれるところ
 データの初期値は、実行形式ファイルに含まれている. 例えばC言語で

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() ライブラリ関数から呼び出さ れる。

◆スタックセグメント

スタック・セグメント ( stack segment, ) とはスタックを置くためのメモリ領域である。C言語の 自動変数(auto variable)、 変数は, ここに置かれる.

 C言語で


main()
{
    auto int z;
}
とすると, 4バイトの領域がスタックセグメントに割りつけられる。 (autoというキーワードは、普通省略される。スタックセグメ ントは、普通、0xffffff (32ビットのアドレス空間の場合)からからアドレス が小さくなるほうに向かって伸びていく。この部分は、関数呼び出しが続くと 伸びていき、割り当てられたメモリよりも多くなると、カーネルが自動的に拡 張する。

◆mainの引数と環境変数

 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
% []
----------------------------------------------------------------------

■練習問題と課題

★練習問題 45 UDP/IPで送れるデータの大きさの上限

UDP/IP では、一度に送ることができるデータの大きさにには、実装上の制限 が付いている。これがいくつかを調べるプログラムを作りなさい。

★練習問題 46 udprelay

UDP/IP のデータを中継するようなプログラムを作りなさい。 単方向だけでなく、双方向で中継するようにしなさい。

このようなプログラムの例として、udprelay と呼ばれるプログラムがある。

★練習問題 47 UDP/IPでのアクセス制御

UDP/IP のサーバ、または、中継プログラムで、クライアントの IP アドレス によってアクセスを許したりエラーを発生させたりしなさい。

★練習問題 48 kill コマンドによるシグナルの発生

signal-int.c のプロ グラムを動かし、kill コマンドを使って SIGINT (kill -INT)でシグナルを送 りなさい。そして、キーボードから intr のキー^Cを打った時と動 作を比較しなさい。

ヒント:kterm を2つ開いて、片方でこのプログラムを動かし、片方で kill コマンドを動かす。kill 引数として必要な PID は、ps コマンドで調べる。

★練習問題 49 killコマンド

kill コマンドと似た動きをするプログラムを作りなさい。

★練習問題 50 ^Cで死なないプロセス

SIGINT シグナルを発生させても終了しないプロセスを 作りなさい。

ヒント:そのシグナルを無視(SIG_IGN)するように設定する。

★練習問題 51 割込み処理中の割込み

割込み処理の最中に、同じ割込みが発生した時にはどうなるかを調べなさい。

★練習問題 52 BSD系のUNIXのシグナルのあつかい

SGI IRIX では、次のようにコンパイル時に _BSD_SIGNALS というマクロを定 義すると BSD 系の UNIX のシグナル機能が使える。
	cc -D_BSD_SIGNALS -o prog prog.c
BSD 系では、一度登録したシグナルは、ハンドラの中で再登録する必要はない。 このことを調べなさい。

また、シグナル・ハンドラの中でシグナルを受けた時の動きを調べなさい。

くわしくは、man 3b signal で表示される。

★練習問題 53 printfの番地

printf 関数の番地を調べなさい。

★練習問題 54 引数の番地

関数の引数の番地を調べなさい。auto変数と比較しなさい。