システム・プログラム
電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-05-21
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
---------------------------------------------------------------------- adonis1% set prompt="base% "base% echo $x
x: Undefined variable. base% echo $x
x: Undefined variable. base% set x=100
base% echo $x
100 base% tcsh
adonis1% echo $x
x: Undefined variable. adonis1% set x=200
adonis1% echo $x
200 adonis1% exit
exit base% echo $x
100 base% unset x
base% echo $x
x: Undefined variable. base% ----------------------------------------------------------------------
---------------------------------------------------------------------- base% echo $xx: Undefined variable. base% setenv x 100
base% echo $x
100 base% tcsh
adonis1% echo $x
100 adonis1% setenv x 200
adonis1% echo $x
200 adonis1% exit
exit base% echo $x
100 base% base% unsetenv x
base% echo $x
x: Undefined variable. base% ----------------------------------------------------------------------
----------------------------------------------------------------------
19: argv[0] = "cal" ;
20: argv[1] = "5" ;
21: argv[2] = "2001" ;
22: argv[3] = 0 ;
sleep( 3 );
23: execve( "/usr/bin/cal", argv, environ );
----------------------------------------------------------------------
~yas/syspro/proc/proc-create.c には、上のような修正を加えた。
WWW ページは修正していない。
----------------------------------------------------------------------
1: /*
2: cont-0.c -- cont-1 を実行するプログラム
3: ~yas/syspro/proc/cont-0.c
4: $Header: /home/lab2/OS/yas/syspro-2001/proc/RCS/cont-0.c,v 1.3 2001/05/20 14:03:36 yas Exp $
5: Start: 2001/05/20 22:42:27
6: */
7:
8: #include <unistd.h>
9:
10: extern char *environ[] ;
11:
12: main()
13: {
14: char *argv[2] ;
15: argv[0] = "./cont-1" ;
16: argv[1] = 0 ;
17: printf("I am executing cont-0 (%d).\n",getpid() );
18: execve( argv[0],argv,environ );
19: perror( argv[0] );
20: }
----------------------------------------------------------------------
----------------------------------------------------------------------
1: /*
2: cont-1.c -- cont-2 を実行するプログラム
3: ~yas/syspro/proc/cont-1.c
4: $Header: /home/lab2/OS/yas/syspro-2001/proc/RCS/cont-1.c,v 1.3 2001/05/20 14:03:36 yas Exp $
5: Start: 2001/05/20 22:42:27
6: */
7:
8: #include <unistd.h>
9:
10: extern char *environ[] ;
11:
12: main()
13: {
14: char *argv[2] ;
15: argv[0] = "./cont-2" ;
16: argv[1] = 0 ;
17: printf("I am executing cont-1 (%d).\n",getpid() );
18: execve( argv[0],argv,environ );
19: perror( argv[0] );
20: }
----------------------------------------------------------------------
----------------------------------------------------------------------
1: /*
2: cont-2.c -- 自分の名前を表示するだけのプログラム
3: ~yas/syspro/proc/cont-2.c
4: $Header: /home/lab2/OS/yas/syspro-2001/proc/RCS/cont-2.c,v 1.3 2001/05/20 14:03:36 yas Exp $
5: Start: 2001/05/20 22:42:27
6: */
7: main()
8: {
9: char *argv[2] ;
10: printf("I am executing cont-2 (%d).\n",getpid() );
11: exit( 0 );
12: }
----------------------------------------------------------------------
実行例。
---------------------------------------------------------------------- % make cont-0cc cont-0.c -o cont-0 % make cont-1
cc cont-1.c -o cont-1 % make cont-2
cc cont-2.c -o cont-2 % ./cont-0
I am executing cont-0 (28403). I am executing cont-1 (28403). I am executing cont-2 (28403). % ./cont-1
I am executing cont-1 (28404). I am executing cont-2 (28404). %
----------------------------------------------------------------------
インターネット上のアプリケーションの多くは、 TCP/IPという仕組みを用いて通信を行っている。
TCP/IPは、信頼性のある(reliable)双方向のストリーム転送サービス (stream transport service)を提供する通信プロトコルである(図1)。ス トリームは、次のような性質がある転送サービスである。
なお、C言語のライブラリ関数である fopen(), fgets(), fputs() なども、 ストリームと呼ばれることがある。これは、もともとランダム・アクセス可能 で、メモリ中の配列と同じようにアクセスするすることもできるファイルを、 まるでプロセス間通信のストリームと同じように扱うことができることに由来 する。ストリームの元の意味は、プロセス間通信である。
TCP/IPによる通信では、図2に示すように、4つのプロトコル(規約、約束事) の層が使われる。TCP/IP自身は、TCP層と IP層という2つのプロトコルに分解 される。このようにさまざまなプロトコルが決められ、全体として層をなして いる。この様子を、プロトコル・スタックと呼ぶ。
TCP は、IP という通信プロトコルを利用して実現されている。IPは、(信頼 性がない)データグラム(datagram)転送サービスを提供する通信プロトコル である。データグラムとは、次のような性質を持つ。
IPのデータグラムが配達されるときに使われる番号が、 IPアドレス である。IPアドレスとしては、現在、32ビットの整数が 使われてる。IPアドレスを表現する時には、普通、8ビットずつ区切って、 次のように4つ部分に分けて書かれる。たとえば、
は、12.34.56.78
という数を表する。(((12 * 256)+34)*256+56)*256+78
TCPで通信をする時に、通信相手を識別するにはIPアドレスと ポート番号(port number) が必要になる。ポート番号は、同じホストの中で提供されている様々なサービスを 区別するために使われる。 ポート番号は、16ビットの整数であり、よく使われる アプリケーション では、あらかじめどの番号を使うかが決められている。これを well-knownポート番号(well-known port number) という。Unix では、1024 番より小さいポート番号を使うには、 スーパー・ユーザ(後述)の権限が必 要であり、このようなポート番号は、 特権ポート番号(privileged port number) と呼ばれる。
TCP層の上には、応用層が定義されている。この層では、ftp, rlogin, WWW, mnews,sendmail などの、TCP/IP を利用するプログラムの間の会話の方法が定 義される。TCP/IPを使った通信は、まるでプロセス同士が電話で会話するよう に進められる。普通の電話では、日本語を話す人と英語を話す人は、電話で情 報交換を行うことができない。同様に、同じTCP/IPを使っていても、会話の方 法が違うと、まったく情報交換を行うことができない。ゆえに、TCP/IPの上に さらに、情報交換のためにさまざまなプロトコルが取り決められている。
TCP/IPの上に構築されているプロトコルの例を、表1に示す。
IPのデータグラムを転送するためには、さまざまな物理的な媒体が使われる。 現在LANでは、イーサネットやFDDIがよく使われいる。イーサネットは、同軸 ケーブルやより対線(Twisted Pair Cable)を使ってデータを転送する。FDDIは、 光ケーブルを使っている。モデムなどを使ったシリアル回線では、PPP(Point to Point Protocol)というプロトコルの上に、IPデータグラムが流される。
データグラムは、ネットワーク通信では、最も基本的な転送サービスである。 IP上に構築された UDP(User Datagram Protocol)も、IPとほとんど同じ機能 を提供する。また、イーサネットやFDDIが提供する転送サービスも、データグ ラムである。
ネットワークに接続されている計算機の中で、ネットワークに1ヵ所の出入り 口(インタフェース)を持っているものは、ホストと呼ばれる。2ヵ所以上の 出入り口を持っている計算機は、ルータと呼ばれる。ルータは、ネットワーク とネットワークを接続するための計算機である。ルータは、入ってきたIPのパ ケットのIPアドレスを見て、どのネットワークに送ればよいかを判断する。
図1で、左端と右端にあり、4層全てそろっている部分がホストである。 TCP/IPの通信は、ホストとホストの間で行われる。中央の、2層しかない部分 は、ルータである。ルータの仕事は、IP層において行われる。
TCP/IP では、プロセスとプロセスが、電話で会話をするように通信が行われ る。普通の電話で人間同士が話をするには、まず電話番号を指定して、話相手 に電話をとってもらわなければならない。TCP/IP においても同様である。 TCP/IPでは、電話を掛ける方をクライアント・プロセス、電話を待つ方をサー バ・プロセスと言いう。
TCP/IPにおいて、プロセス間に形成されたストリーム通信路のことを、計算機 間に張られた物理的な回線に似ていることから、仮想的回線(virtual circuit)とも言う。TCP/IP では、回線を接続する段階では、クライアント・ プロセスとサーバ・プロセスは非対称である。一度仮想回線が接続された後は、 両方のプロセスは、TCP/IPのレベルでは、まったく対称的になる。
TCP/IPにおいてプロセス間に仮想回線を開設するには、IPアドレスとポート番 号が必要である。ポート番号は、同じIPアドレスを持つホスト上で動いている プロセスを区別するために使われる。
以下に、通信路が開設される手順を示す。
こうして一度通信路が開設されると、クライアントとサーバは、どちらからで もデータを送り始めることができる。
図3(a) TCP/IP通信路の開設(1)
図3(b) TCP/IP通信路の開設(2)
図3(c) TCP/IP通信路の開設(3)
TCP/IPにおける通信路開設において、クライアントは、サーバ側の接続要求受 付用ポートのポート番号を、事前に知っている必要がある。表1に、いくつか の応用層のプロトコルについて、公に利用目的が決められているポート番号を 示す。
クライアント側の通信用ポートのポート番号は、通常は、オペレーティング・ システムにより自動的に割り当てられる。サーバ側の通信用ポートのポート番 号も、同様である。
プロセス間通信は、本来自由に行うことができる。どのプロセスも自由にメッ セージを送信する権利がある。プロセス間通信におけるクライアント・サーバ・ モデルは、本来対称的なプロセスを最初にメッセージを送る方(クライアント・ プロセス)と受ける方(サーバ・プロセス)に分類することで、プロセス間通 信を構造化し、わかりやすくするものである。TCP/IPの通信路開設時における クライアントとサーバの役割は、このプロセス間通信におけるクライアント・ サーバ・モデルの1つの例になっている。
プロセス間通信におけるクライアント・サーバ・モデルにおける意味の他に、 クライアントとサーバという言葉は、サービスを受けるプロセスとサービスを 提供するプロセスの意味で使われることがある。インターネットにおけるプロ セス間通信では、多くの場合、サービスの授受の関係におけるクライアントと サーバと、プロセス間通信におけるクライアントとサーバが一致している(稀 に一致していないこともあるので、注意しなさい)。
UNIX オペレーティング・システム上で動作するプログラムがTCP/IPの機能を 使う場合、UNIXオペレーティング・システムが提供するソケットというインタ フェースを通じて利用することになる。ソケットは、TCP/IP をはじめとして、 XNS, OSI などさまざまな通信プロトコルを UNIX オペレーティング・システ ム上で使うために設計されたものである。TCP/IP だけを考えると、ソケット のインタフェースは、繁雑であり、使いにくくなっている。
UNIXでは、ソケットをドメイン(Address Family)と型で区別する。 下の表は、socket() システム・コールに与えるドメインと型である。
---------------------------------------------------------------------- ドメイン 型 option プロトコル ---------------------------------------------------------------------- AF_INET SOCK_STREAM 0 TCP AF_INET SOCK_DGRAM 0 UDP AF_INET SOCK_RAW ? IP AF_INET SOCK_RAW ? ICMP AF_UNIX SOCK_STREAM 0 (UNIXドメインのストリーム) AF_UNIX SOCK_DGRAM 0 (UNIXドメインのデータグラム) AF_NS SOCK_SEQPACKET ? XEROX NS protocol の順序付きパケット AF_NS SOCK_RDM ? XEROX NS protocol の信頼性のあるデータグラム ----------------------------------------------------------------------これ以外の組み合わせ、使えない。たとえば、AF_INETとSOCK_SEQPACKET を socket システム・コールで指定しても、うまくいかない。
TCP/IPによる通信は、通信相手のIPアドレス(32ビットの整数)とポート番号 (16ビットの整数)さえわかれば、可能である。IPアドレスやポート番号は、 計算機にとって扱いやすいものであるが、人間にとって扱いやすいものではな い。人間にとってわかりやすい記号の名前から、IPアドレスに変換するサービ スがあれば便利である。このサービスを、名前サービス、それを行うプログラ ムを名前サーバという。
インターネットにおける名前サービスは、名前空間をドメイン(領域)に分割 して、階層的に管理することで実現されている。これを、ドメイン・ネーム・ サービス(Domain Name Service, DNS)という。DNSという言葉は、名前サー ビスを提供するプログラム(名前サーバ, Domain Name Server)を意味するこ ともある。
DNSでは、主に名前をIPアドレスへ変換するサービスが使われている。その他 に、名前から電子メールの配送先、名前から名前サーバが動いているホストの 名前、名前から名前サーバ自身の管理情報、逆にIPアドレスから名前を引くた めにも使われる。
これは、次の省略形である。-------------------------------------------------------------------- % telnet host1--------------------------------------------------------------------
ここで、第2引数の "telnet" という文字列は、TCP/IP のポート番号を示す 記号である。この記号は、/etc/services というファイルに次のように格納さ れている。-------------------------------------------------------------------- % telnet host1 telnet--------------------------------------------------------------------
-------------------------------------------------------------------- telnet 23/tcp --------------------------------------------------------------------
telnet コマンドは、/etc/services ファイルを検索し、与えられた記号から ポート番号(この例では23)を得る。また、host1 の IP アドレスを、 /etc/hosts や DNS から得る。telnet コマンドは、この IP アドレスとポー ト番号の2つを使って、TCP/IP の通信路を開設する。(注意:NISが動いてい る場合には、/etc/services や /etc/hosts の代わりに、NISのデータベース (マップ)が検索される。)
telnet コマンドでは、次のように、ホストのIPアドレスとポート番号を数字 で打ち込むこともできる。
ここで、a.b.c.d とは、32ビットのIPアドレスを8ビットずつに区切り、 それぞれの8ビットの整数を10進数で表記したものである。よって、実際のIP アドレスは、次のようしてに計算できる。-------------------------------------------------------------------- % telnet a.b.c.d 23--------------------------------------------------------------------
((((a*256)+b)*256)+c)*256+d == XXXXXXXXXXよって、telnet コマンドに次のようIPアドレスを与えてもよい。
-------------------------------------------------------------------- % telnet XXXXXXXXXX 23--------------------------------------------------------------------
注意:セキュリティ上の理由から、echo などの、システムプログラムの講義 くらいでしか役に立たないようなサービスを停止することが、最近では一般的 である。-------------------------------------------------------------------- % telnet localhost echoTrying... Connected to localhost. Escape character is '^]'. hello
hello exit
exit quit
quit aaa
aaa ^] telnet> quit
Connection closed. %
--------------------------------------------------------------------
以下のプログラムは、echo サービスを利用するクライアントである。 "hello" という文字列を送り、それを受け取っている。---------------------------------------------------------------------- % egrep echo /etc/servicesecho 7/tcp echo 7/udp % hostname
adonis1 % telnet adonis1 7
Trying 130.158.86.1... Connected to adonis1. Escape character is '^]'. hello
hello aaa
aaa ^] telnet> quit
Connection closed. %
----------------------------------------------------------------------
1:
2: /*
3: echo-client.c -- 文字列を送受信するクライアント(TCP/IP版)
4: ~yas/syspro-2001/ipc/echo-client.c
5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-client.c,v 1.5 2001/05/20 08:55:17 yas Exp $
6: <-- ipc/RCS/rdaytime.c,v 1.4 1997/06/02 22:27:08 yas Exp $
7: Start: 1998/06/01 23:48:29
8: */
9: #include <stdio.h>
10: #include <sys/types.h> /* socket() */
11: #include <sys/socket.h> /* socket() */
12: #include <netinet/in.h> /* struct sockaddr_in */
13: #include <netdb.h> /* gethostbyname() */
14:
15: extern int tcp_connect( char *hostname, int portno );
16: extern ssize_t writen(int fd, const void *vptr, size_t n);
17: extern ssize_t readline(int fd, void *vptr, size_t maxlen);
18:
19: main( int argc, char *argv[] )
20: {
21: if( argc != 3 )
22: {
23: fprintf( stdout,"Usage: %s host port\n",argv[0] );
24: exit( -1 );
25: }
26: echo_client( argv[1],atoi(argv[2]) );
27: }
28: #define BUFFERSIZE 1024
29:
30: echo_client( char *hostname, int portno )
31: {
32: int sock ;
33: int rcount ;
34: int slen ;
35: char sbuf[BUFFERSIZE+1];
36: char rbuf[BUFFERSIZE+1];
37:
38: sock = tcp_connect( hostname, portno );
39: if( sock<0 )
40: exit( -1 );
41:
42: strncpy( sbuf,"hello\n",BUFFERSIZE ); /* '\n' is important */
43: sbuf[BUFFERSIZE] = 0 ;
44: slen = strlen( sbuf );
45: printf("sending: %d bytes [%s]...\n",slen,sbuf );
46: if( writen( sock, sbuf, slen ) != slen )
47: {
48: perror("write");
49: exit( 1 );
50: }
51: printf("waiting for reply: ...\n" );
52: if( (rcount = readline( sock, rbuf, BUFFERSIZE ))<0 )
53: {
54: perror("read");
55: exit( 1 );
56: }
57: printf("received: %d bytes [%s]\n",rcount,rbuf );
58: close( sock );
59: }
60:
61: /* 新城、システムプログラム講義用 */
62:
63: int tcp_connect( char *hostname, int portno )
64: {
65: struct hostent *hostent ;
66: struct sockaddr_in addr ;
67: int addr_len ;
68: int s ;
69:
70: if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
71: {
72: perror("socket");
73: return( -1 );
74: }
75:
76: bzero( &addr, sizeof(addr) );
77: addr.sin_family = AF_INET ;
78: addr.sin_port = htons( portno );
79: if( (hostent = gethostbyname( hostname )) == NULL )
80: {
81: fprintf(stderr,"unknown host %s\n",hostname );
82: return( -1 );
83: }
84: bcopy( hostent->h_addr, &addr.sin_addr, hostent->h_length );
85:
86: if( connect(s, &addr, sizeof(addr)) < 0 )
87: {
88: perror( hostname );
89: close( s );
90: return( -1 );
91: }
92: return( s );
93: }
94:
95: /*
96: W.リチャード・スティーブンス著、篠田陽一訳:
97: "UNIXネットワークプログラミング第2版 Vol.1 ネットワークAPI:ソケットとXTI",
98: ピアソン・エデュケーション, 1999年. ISBN 4-98471-205-9
99: 3.9節 readn, writen, および readline 関数 (p.76)
100:
101: Richard Stevens: "UNIX Network Programming, Volume 1, Second Edition:
102: Networking APIs: Sockets and XTI", Prentice Hall, 1998.
103: ISBN 0-13-490012-X.
104: Section 3.9 readn, writen, and readline Functions (p.77)
105:
106: http://www.kohala.com/start/ (http://www.kohala.com/~rstevens/)
107: http://www.kohala.com/start/unpv12e/unpv12e.tar.gz
108:
109: */
110:
111: /* include writen */
112: /*#include "unp.h"*/
113: #include <errno.h>
114:
115: ssize_t /* Write "n" bytes to a descriptor. */
116: writen(int fd, const void *vptr, size_t n)
117: {
118: size_t nleft;
119: ssize_t nwritten;
120: const char *ptr;
121:
122: ptr = vptr;
123: nleft = n;
124: while (nleft > 0) {
125: if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
126: if (errno == EINTR)
127: nwritten = 0; /* and call write() again */
128: else
129: return(-1); /* error */
130: }
131:
132: nleft -= nwritten;
133: ptr += nwritten;
134: }
135: return(n);
136: }
137:
138: /* include readline */
139: /*#include "unp.h"*/
140:
141: #define MAXLINE 4096
142:
143: static ssize_t
144: my_read(int fd, char *ptr)
145: {
146: static int read_cnt = 0;
147: static char *read_ptr;
148: static char read_buf[MAXLINE];
149:
150: if (read_cnt <= 0) {
151: again:
152: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
153: if (errno == EINTR)
154: goto again;
155: return(-1);
156: } else if (read_cnt == 0)
157: return(0);
158: read_ptr = read_buf;
159: }
160:
161: read_cnt--;
162: *ptr = *read_ptr++;
163: return(1);
164: }
165:
166: ssize_t
167: readline(int fd, void *vptr, size_t maxlen)
168: {
169: int n, rc;
170: char c, *ptr;
171:
172: ptr = vptr;
173: for (n = 1; n < maxlen; n++) {
174: if ( (rc = my_read(fd, &c)) == 1) {
175: *ptr++ = c;
176: if (c == '\n')
177: break; /* newline is stored, like fgets() */
178: } else if (rc == 0) {
179: if (n == 1)
180: return(0); /* EOF, no data read */
181: else
182: break; /* EOF, some data was read */
183: } else
184: return(-1); /* error, errno set by read() */
185: }
186:
187: *ptr = 0; /* null terminate like fgets() */
188: return(n);
189: }
190:
----------------------------------------------------------------------
実行例。
TCP/IPのクライアント側のプログラムで大事なシステムコールとライブラリ関 数は、次の通りである。---------------------------------------------------------------------- % mkdir ipc% cd ipc
% cp ~yas/syspro-2001/ipc/echo-client.c .
% make echo-client
cc echo-client.c -o echo-client % ./echo-client
Usage: ./echo-client host port % ./echo-client adonis1 7
sending: 6 bytes [hello ]... waiting for reply: ... received: 6 bytes [hello ] %
----------------------------------------------------------------------
----------------------------------------------------------------------
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, int len,
int type);
----------------------------------------------------------------------
/etc/services をアクセスするには、次のような関数を使う。
----------------------------------------------------------------------
struct servent *getservbyname(const char *name, const char
*proto);
struct servent *getservbyport(int port, const char *proto);
----------------------------------------------------------------------
上の readline() は、遅い。性能が重要なら、書き換えるべきである。
次の通信プロトコルのクライアントを1つ以上作りなさい。(以下の項目には、 各プロトコルを説明したページへのリンクが埋め込まれている。)
まず、telnet で、これらのサーバに接続しなさい。そして、それぞれのプロ トコルに従って、要求を打ち込み、どのような結果が返ってくるかを調べなさ い。次に、telnet で行った要求の送信と結果の受信を行うようなプログラムを 作りなさい。このとき、必要なパラメタは、main() の引数から取りなさい。
注意。それぞれのプロトコルでは、接続先として次のホストを使いなさい。
プログラムをつくる時には、行末の扱い(CR-LF)に注意し ないさい。
ファイル記述子 0, 1, 2 は、シェルが標準入力、標準出力、標準エラー入力 として開いているので、その次にopen() システムコールを実行すると、3 が 返るはずである。その性質を使って、read(3,,) や write(3,,) とするとよい。
丁寧にやるならば、どのファイル記述子を使っているかを、sprintf() の %d などを使って文字列に変換して、main() の引数として渡す。