システム・プログラム
電子・情報工学系
新城 靖
<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言語で
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] == 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)するように設定する。