システム・プログラム
電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-06-04
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
この授業でC言語でプログラムを書く時には、必ずメモリの中でどのような操 作が行われているかを意識すること。
----------------------------------------------------------------------
1:
2: /*
3: echo-client-udp.c -- 文字列を送受信するクライアント(UDP/IP版)
4: ~yas/syspro-2001/ipc/echo-client-udp.c
5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-client-udp.c,v 1.7 2001/06/03 10:48:06 yas Exp $
6: Start: 1997/06/16 21:22:26
7: */
8: #include <stdio.h>
9: #include <sys/types.h> /* socket() */
10: #include <sys/socket.h> /* socket() */
11: #include <netinet/in.h> /* struct sockaddr_in */
12: #include <netdb.h> /* gethostbyname() */
13:
14: extern void echo_client_udp( char *hostname, int portno );
15: extern int udp_port_nobind();
16: extern int sockaddr_in_init( struct sockaddr_in *addr, char *hostname, int portno );
17: extern void sockaddr_in_print( struct sockaddr_in *addr );
18: extern void sockname_print( int s );
19:
20: main( int argc, char *argv[] )
21: {
22: if( argc != 3 )
23: {
24: fprintf( stdout,"Usage: %s host port\n",argv[0] );
25: exit( -1 );
26: }
27: echo_client_udp( argv[1],atoi(argv[2]) );
28: }
29:
30: #define BUFFERSIZE 1024
31:
32: void echo_client_udp( char *hostname, int portno )
33: {
34: int s, rcount, scount, len, fromlen ;
35: struct sockaddr_in to, from ;
36: char buffer[BUFFERSIZE];
37:
38: s = udp_port_nobind();
39: if( s<0 )
40: exit( -1 );
41: printf("my port is "); sockname_print( s );
42:
43: strncpy( buffer,"hello",sizeof(buffer) );
44: len = strlen( buffer ) + 1 ;
45: sockaddr_in_init( &to, hostname, portno );
46: printf("sending [%s] (%d bytes) to ", buffer,len );
47: sockaddr_in_print( &to );
48: if( (scount = sendto( s, buffer, len, 0, &to, sizeof(to) ))!= len )
49: {
50: perror("sendto()");
51: exit( 1 );
52: }
53: printf("after sendto(), my port is "); sockname_print( s );
54:
55: fromlen = sizeof( from );
56: if( (rcount = recvfrom( s, buffer, BUFFERSIZE, 0, &from, &fromlen )) < 0 )
57: {
58: perror("recvfrom()");
59: exit( 1 );
60: }
61: printf("received from "); sockaddr_in_print( &from );
62: buffer[rcount] = 0 ;
63: printf("%d bytes received. [%s] \n", rcount, buffer );
64:
65: close( s );
66: }
67:
68: int udp_port_nobind()
69: {
70: int s ;
71: if( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
72: {
73: perror("socket");
74: return( -1 );
75: }
76: return( s );
77: }
78:
79: int sockaddr_in_init( struct sockaddr_in *addr, char *hostname, int portno )
80: {
81: struct hostent *hostent ;
82: addr->sin_family = AF_INET ;
83: if( (hostent = gethostbyname( hostname )) == NULL )
84: {
85: fprintf(stderr,"unknown host %s\n",hostname );
86: return( -1 );
87: }
88: bcopy( hostent->h_addr, &addr->sin_addr, hostent->h_length );
89: addr->sin_port = htons( portno );
90: return( 0 );
91: }
92:
93: void sockaddr_in_print( struct sockaddr_in *addr )
94: {
95: union {
96: int i ;
97: unsigned char byte[4] ;
98: } x ;
99: x.i = addr->sin_addr.s_addr ;
100: printf("sockaddr_in: %d.%d.%d.%d:%d\n",
101: x.byte[0],x.byte[1],x.byte[2],x.byte[3],
102: ntohs( addr->sin_port ));
103: }
104:
105: void sockname_print( int s )
106: {
107: struct sockaddr_in addr ;
108: int len ;
109: len = sizeof( addr );
110: if( getsockname( s, &addr, &len )< 0 )
111: {
112: perror("getsockname");
113: exit( -1 );
114: }
115: sockaddr_in_print( &addr );
116: }
----------------------------------------------------------------------
クライアント側では、bind() で名前を付けていない。sendto() の時に、自動 的にオペレーティング・システムにより、名前が付けられる。同じことは、 porno=0 で bind() しても可能である。オペレーティング・システムによって 付けられた名前は、後で getsockname() で調べることができる。
明示的に bind() する方法もある。
実行例。
---------------------------------------------------------------------- % ./echo-client-udp adonis1 7my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.1:7 after sendto(), my port is sockaddr_in: 0.0.0.0:8013 received from sockaddr_in: 130.158.86.1:7 6 bytes received. [hello] % ./echo-client-udp adonis11 7
my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.11:7 after sendto(), my port is sockaddr_in: 0.0.0.0:8014 received from sockaddr_in: 130.158.86.11:7 6 bytes received. [hello] % ./echo-client-udp localhost 7
my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 127.0.0.1:7 after sendto(), my port is sockaddr_in: 0.0.0.0:8015 received from sockaddr_in: 127.0.0.1:7 6 bytes received. [hello] %
----------------------------------------------------------------------
----------------------------------------------------------------------
1:
2: /*
3: echo-server-udp.c -- 文字列を送受信するサーバ(UDP/IP版)
4: ~yas/syspro-2001/ipc/echo-client-udp.c
5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-server-udp.c,v 1.5 2001/06/03 10:49:16 yas Exp $
6: Start: 1997/06/16 21:22:26
7: */
8: #include <stdio.h>
9: #include <sys/types.h> /* socket() */
10: #include <sys/socket.h> /* socket() */
11: #include <netinet/in.h> /* struct sockaddr_in */
12: #include <netdb.h> /* gethostbyname() */
13:
14: extern void echo_server_udp( int portno );
15: extern int udp_port_bind( int portno );
16: extern void sockaddr_in_print( struct sockaddr_in *addr );
17: extern void sockname_print( int s );
18:
19: main( int argc, char *argv[] )
20: {
21: int portno ;
22: if( argc >= 3 )
23: {
24: fprintf( stdout,"Usage: %s [portno]\n",argv[0] );
25: exit( -1 );
26: }
27: if( argc == 2 )
28: portno = atoi( argv[1] );
29: else
30: portno = getuid();
31: echo_server_udp( portno );
32: }
33:
34: #define BUFFERSIZE 1024
35:
36: void echo_server_udp( int portno )
37: {
38: int s, rcount, scount, addrlen ;
39: struct sockaddr_in addr ;
40: char buffer[BUFFERSIZE];
41:
42: s = udp_port_bind( portno );
43: if( s<0 )
44: exit( -1 );
45: printf("my port is "); sockname_print( s );
46:
47: while( 1 )
48: {
49: addrlen = sizeof( addr );
50: if( (rcount = recvfrom( s, buffer, BUFFERSIZE, 0, &addr, &addrlen )) < 0 )
51: {
52: perror("recvfrom()");
53: exit( 1 );
54: }
55: buffer[rcount] = 0 ;
56: printf("received %d bytes [%s] from ",rcount, buffer );
57: sockaddr_in_print( &addr );
58: printf("sending back [%s] (%d bytes) to ", buffer, rcount );
59: sockaddr_in_print( &addr );
60: if( (scount=sendto( s, buffer, rcount, 0, &addr, addrlen ))!= rcount )
61: {
62: perror("sendto()");
63: exit( 1 );
64: }
65: }
66: }
67:
68: int udp_port_bind( int portno )
69: {
70: struct hostent *hostent ;
71: struct sockaddr_in addr ;
72: int addr_len ;
73: int s ;
74:
75: if( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
76: {
77: perror("socket");
78: return( -1 );
79: }
80:
81: addr.sin_family = AF_INET ;
82: addr.sin_addr.s_addr = INADDR_ANY ;
83: addr.sin_port = htons( portno );
84:
85: if( bind(s,&addr,sizeof(addr)) < 0 )
86: {
87: perror( "bind: " );
88: fprintf(stderr,"port number %d is already used. kill another program.", portno );
89: return( -1 );
90: }
91: return( s );
92: }
93:
94: void sockaddr_in_print( struct sockaddr_in *addr )
95: {
96: union {
97: int i ;
98: unsigned char byte[4] ;
99: } x ;
100: x.i = addr->sin_addr.s_addr ;
101: printf("sockaddr_in: %d.%d.%d.%d:%d\n",
102: x.byte[0],x.byte[1],x.byte[2],x.byte[3],
103: ntohs( addr->sin_port ));
104: }
105:
106: void sockname_print( int s )
107: {
108: struct sockaddr_in addr ;
109: int len ;
110: len = sizeof( addr );
111: if( getsockname( s, &addr, &len )< 0 )
112: {
113: perror("getsockname");
114: exit( -1 );
115: }
116: sockaddr_in_print( &addr );
117: }
<以下省略>
----------------------------------------------------------------------
実行例。
サーバ側。サーバは、終了しないので、最後に、^C か Del を押して、割り込みを掛けて終了させる。
クライアント側(その1)。---------------------------------------------------------------------- % ./echo-server-udpmy port is sockaddr_in: 0.0.0.0:1231 received 6 bytes [hello] from sockaddr_in: 130.158.86.11:5890 sending back [hello] (6 bytes) to sockaddr_in: 130.158.86.11:5890 received 6 bytes [hello] from sockaddr_in: 130.158.86.1:8023 sending back [hello] (6 bytes) to sockaddr_in: 130.158.86.1:8023 received 6 bytes [hello] from sockaddr_in: 127.0.0.1:8024 sending back [hello] (6 bytes) to sockaddr_in: 127.0.0.1:8024 ^C %
----------------------------------------------------------------------
クライアント側(その2)。---------------------------------------------------------------------- % ./echo-client-udp adonis1 1231my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:5890 received from sockaddr_in: 130.158.86.1:1231 6 bytes received. [hello] %
----------------------------------------------------------------------
クライアント側(その3)。---------------------------------------------------------------------- % ./echo-client-udp adonis1 1231my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 130.158.86.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:8023 received from sockaddr_in: 130.158.86.1:1231 6 bytes received. [hello] % ----------------------------------------------------------------------
---------------------------------------------------------------------- % ./echo-client-udp localhost 1231my port is sockaddr_in: 0.0.0.0:0 sending [hello] (6 bytes) to sockaddr_in: 127.0.0.1:1231 after sendto(), my port is sockaddr_in: 0.0.0.0:8024 received from sockaddr_in: 127.0.0.1:1231 6 bytes received. [hello] %
----------------------------------------------------------------------

図1 バスにより接続されたCPU、メモリ、デバイス
バス:何本かの配線の束
例:キーボード用のコントローラの働き
データが、電気信号などの形で送られてくる。コントローラの中のレジスタ (小容量のメモリ)に保存され。
CPU から見える場所
CPU の速度に比べて、デバイスの速度は遅い。
普通の割込み(ハードウェアによる割込み)は、オペレーティング・システム のカーネルにおいて利用されている。
ソフトウェア割込みとは、本来はオペレーティング・システムのカーネルしか 使えない割込みの機能を、ソフトウェアにより実現して、一般の利用者プログ ラム(プロセス)でも使えるようにしたものである。
UNIXでは、ソフトウェア割込みの機能は、シグナル(signal)という名前で実現 されている。シグナルとは、本来はプロセス間通信の一種で、ある事象が起き たことを他のプロセスに知らせることである。ここで伝わるのは、ある事象が 起きたかどうかだけで、引数などを付けることはできない。UNIXでは、プロセ ス間でシグナルにより通信をする他に、キーボードからシグナルを送ることも できる。これは、「ソフトウェア割込み」として、プロセス1つひとつに割込 みボタンが付いているようなものである。また、プログラムの中で例外 (exception)が起きた時にも、ハードウェアの割込みと同様に、ソフトウェ ア割込みが生じる。これも、他のシグナルと同じように受け取ることができる。
UNIXのソフトウェア割込み(シグナル)を使うには、次のようなことが必要で ある。
ソフトウェア割り込みは、1つのプログラムの中に制御の流れが1つしかない ようなプログラムの時に有効な方法である。最近のマルチスレッドのプログラ ムでは、シグナルの意味が不明確である。
----------------------------------------------------------------------
1: /*
2: signal-int.c -- SIGINT を3回受け付けて終了するプログラム。
3: ~yas/syspro-2001/proc/signal-int.c
4: $Header: /home/lab2/OS/yas/syspro-2001/proc/RCS/signal-int.c,v 1.4 2001/06/03 10:57:12 yas Exp $
5: Start: 1997/05/26 18:38:38
6: */
7:
8: #include <stdio.h>
9: #include <signal.h>
10:
11: int sigint_count = 3 ;
12: void sigint_handler();
13:
14: main()
15: {
16: signal( SIGINT, &sigint_handler );
17: printf("main(): going into infinite loop, sigint_count == %d\n",
18: sigint_count);
19: while( 1 )
20: {
21: printf("main(): sigint_count == %d, pause() ....\n",
22: sigint_count );
23: pause();
24: printf("main(): return from pause(). sigint_count == %d\n",
25: sigint_count );
26: }
27: }
28:
29: void sigint_handler()
30: {
31: printf("sigint_handler():\n");
32: if( -- sigint_count <= 0 )
33: {
34: printf("sigint_handler(): exit() ... \n");
35: exit( 1 );
36: }
37: signal( SIGINT, &sigint_handler ); /* System V */
38: printf("sigint_handler(): sigint_count == %d\n",sigint_count);
39: }
----------------------------------------------------------------------
実行例。
stty で intr に相当するキーを3回押す。---------------------------------------------------------------------- % stty -aspeed 9600 baud; line = 1; 48 rows; 80 columns intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = ^@; old-swtch = ^Z; susp = ^Z lnext = ^V; werase = ^W; rprnt = ^R; flush = ^O; stop = ^S; start = ^Q; dsusp = ^Y parenb -parodd cs8 -cstopb hupcl cread -clocal -cnew_rtscts -loblk -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc ixon -ixany -ixoff -imaxbel isig icanon iexten -xcase echo echoe echok echoke echoctl -echoprt -echonl -noflsh -flusho -pendin -tostop opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel % ./signal-int
main(): going into infinite loop, sigint_count == 3 main(): sigint_count == 3, pause() .... ^C sigint_handler(): sigint_handler(): sigint_count == 2 main(): return from pause(). sigint_count == 2 main(): sigint_count == 2, pause() .... ^C sigint_handler(): sigint_handler(): sigint_count == 1 main(): return from pause(). sigint_count == 1 main(): sigint_count == 1, pause() .... ^C sigint_handler(): sigint_handler(): exit() ... %
----------------------------------------------------------------------
そのような場合でも、問題なく動く関数を、再入可能な関数(reentrant function)という。
引数やauto変数だけを使った関数は、再入可能になる。
extern や static を使った関数は、再入可能でないことがある。
次のような関数は、リエントラントではない。
シグナル・ハンドラの中では、リエントラントな関数を使うようにする。

図? プロセスのアドレス空間
機械語命令を置くためのセグメンを テキスト・セグメント ( text segment, ) という。このセグメントは、普通、読み出し専用になっていて, 同じロードモ ジュールのプロセスの間で共有される。例えばシェルは複数のユーザが同時に 使うことが多いが、この場合、テキストセグメントのためのメモリは1セット だけでよい。機械語を書き換えながら実行するプログラムは、最近は行儀が悪いとされてい る。キャッシュの関係で特別な操作をしないと動かない。
データ・セグメント ( data segment, ) は、データを置く領域である. C言語の 静的変数(static)、 大域変数(extern)malloc() で割り当てたヒープ上の変数は、
このセグメントに置かれます。
(
自動変数(auto)
は、次のスタック・セグメントに置かれる。
)
データセグメントは, 次の3つにわかれています.
malloc() で確保する変数が置かれるところ
と書くとstatic int x=100 ;
xはデータセグメントに割りつけられ,
実行形式ファイルに100という整数のビットパタンが含まれる。
BSSとは初期値を指定しない変数が置かれる場所である. C言語で
と書くとstatic int x ;
yはBSSに割りつけられる。実行形式のファイルに
は、BSSの領域はない。これは実行時にカーネルが領域を割り当て、内容
を0に初期化します。
ヒープとは実行時に大きさが決まる変数を置くための領域である. C言語で
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-2001/cc/vaddr-print.c
4: $Header: /home/lab2/OS/yas/syspro-2001/cc/RCS/vaddr-print.c,v 1.1 2001/06/03 12:16:47 yas Exp $
5: Start: 1997/05/19 22:58:49
6: */
7: #include <stdlib.h>
8:
9: void recursive( int n );
10:
11: int x1=1 ;
12: int x2 ;
13:
14: extern int etext, edata, end ;
15:
16: main( argc,argv,envp )
17: int argc ;
18: char *argv[] ;
19: char *envp[] ;
20: {
21: int x3 ;
22: char *x4p ;
23:
24: printf("&main == 0x%x \n",&main );
25: printf("&etext == 0x%x \n",&etext );
26: printf("&edata == 0x%x \n",&edata );
27: printf("&end == 0x%x \n",&end );
28:
29: printf("&x1 == 0x%x (data)\n",&x1 );
30: printf("&x2 == 0x%x (bss)\n",&x2 );
31: printf("&x3 == 0x%x (auto)\n",&x3 );
32: x4p = malloc( 10 );
33: printf("x4p == 0x%x (heap)\n",x4p );
34: x4p = malloc( 10 );
35: printf("x4p == 0x%x (heap)\n",x4p );
36: recursive( 3 );
37: }
38:
39: void recursive( int n )
40: {
41: int x5 ;
42: printf("&x5 == 0x%x (auto,%d)\n",&x5,n );
43: if( n<=0 )
44: return;
45: recursive( n-1 );
46: }
----------------------------------------------------------------------
実行例。
---------------------------------------------------------------------- % cp ~yas/syspro1/cc/vaddr-print.c .% make vaddr-print
cc vaddr-print.c -o vaddr-print % ./vaddr-print
&main == 0x400ad0 &etext == 0x400cb0 &edata == 0x10002000 &end == 0x10002000 &x1 == 0x100010d0 (data) &x2 == 0x10001130 (bss) &x3 == 0x7fff2ecc (auto) x4p == 0x10002010 (heap) x4p == 0x10002028 (heap) &x5 == 0x7fff2ea4 (auto,3) &x5 == 0x7fff2e7c (auto,2) &x5 == 0x7fff2e54 (auto,1) &x5 == 0x7fff2e2c (auto,0) % size vaddr-print
text data bss dec hex filename 2933 304 28 3265 cc1 vaddr-print %
----------------------------------------------------------------------
UDP/IP では、一度に送ることができるデータの大きさにには、実装上の制限 が付いている。これがいくつかを調べるプログラムを作りなさい。
UDP/IP のデータを中継するようなプログラムを作りなさい。 単方向だけでなく、双方向で中継するようにしなさい。
このようなプログラムの例として、udprelay と呼ばれるプログラムがある。
UDP/IP のサーバ、または、中継プログラムで、クライアントの IP アドレス によってアクセスを許したりエラーを発生させたりしなさい。
signal-int.c のプロ グラムを動かし、kill コマンドを使って SIGINT (kill -INT)でシグナルを送 りなさい。そして、キーボードから intr のキー^Cを打った時と動 作を比較しなさい。
ヒント:kterm を2つ開いて、片方でこのプログラムを動かし、片方で kill コマンドを動かす。kill 引数として必要な PID は、ps コマンドで調べる。
ヒント:そのシグナルを無視(SIG_IGN)するように設定する。
cc -D_BSD_SIGNALS -o prog prog.cBSD 系では、一度登録したシグナルは、ハンドラの中で再登録する必要はない。 このことを調べなさい。
また、シグナル・ハンドラの中でシグナルを受けた時の動きを調べなさい。
くわしくは、man 3b signal で表示される。