ソフトウェア割込み、プロセス間通信

システム・プログラムI

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro1-1997/1997-05-27
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html

■復習

■passwd構造体


----------------------------------------------------------------------
   1:	/*
   2:	        passwd-get-uid.c -- UID から /etc/passwd のエントリを引く
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/proc/passwd-get-uid.c
   4:	        $Header: passwd-get-uid.c,v 1.2 97/05/26 14:52:56 yas Exp $
   5:	        Start: 1997/05/26 14:38:47
   6:	*/
   7:	
   8:	#include <pwd.h>        /* getpwuid() */
   9:	#include <stdio.h>      /* NULL, stderr */
  10:	
  11:	main()
  12:	{
  13:	        passwd_print_uid( getuid() );
  14:	}
  15:	
  16:	passwd_print_uid( uid )
  17:	    uid_t uid ;
  18:	{
  19:	    struct passwd *pw ;
  20:	        printf("uid == %d \n",uid );
  21:	        pw = getpwuid( uid );
  22:	        if( pw == NULL )
  23:	        {
  24:	            fprintf(stderr,"no passwd entry\n");
  25:	            exit( 1 );
  26:	        }
  27:	        passwd_print( pw );
  28:	}
  29:	
  30:	passwd_print( pw )
  31:	    struct passwd *pw ;
  32:	{
  33:	        printf("pw_name: %s\n",pw->pw_name );
  34:	        printf("pw_passwd: %s\n",pw->pw_passwd );
  35:	        printf("pw_uid: %d\n",pw->pw_uid );
  36:	        printf("pw_gid: %d\n",pw->pw_gid );
  37:	        printf("pw_gecos: %s\n",pw->pw_gecos );
  38:	        printf("pw_dir: %s\n",pw->pw_dir );
  39:	        printf("pw_shell: %s\n",pw->pw_shell );
  40:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./passwd-get-uid [←]
uid == 1231 
pw_name: yas
pw_passwd: 8QGOq0kY9R21k
pw_uid: 1231
pw_gid: 40
pw_gecos: Yasushi SHINJO,[os],5163
pw_dir: /home/lab2/OS/yas
pw_shell: /usr/local/bin/tcsh
% []
----------------------------------------------------------------------
その他の passwd 構造体(/etc/passwd)をアクセスする関数。
----------------------------------------------------------------------
     struct passwd *getpwnam(const char *name);
     struct passwd *getpwuid(uid_t uid);
     struct passwd *getpwent(void);
     void setpwent(void);
     void endpwent(void);
     struct passwd *fgetpwent(FILE *f);
----------------------------------------------------------------------
UID ではなく、GID 操作するには、getcrent() ライブラリ関数などを使う。

★練習問題(19) idコマンド

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

■setuid()

UID が 0 のプロセス(rootのプロセス)は、さまざまな特権がある。

Set UID とは、そのプログラムを実行する時に、そのプログラムが保存されて いるファイルのユーザ(所有者)の権限で実行するための仕組みである。ファ イルの stat(2) の st_uid が、そのプロセスの (実効) uid となる。これは、 stat(2) のst_mode で S_ISUID ビットが 1 の時にの動きである。

プロセスの UID には、次の2種類がある。

アクセス制御に使われるのは、実効UIDである。

Set-UID と同様に、Set-GID という考え方もある。

★練習問題(20) SetUIDのプログラム

SetUID のプログラムを探しなさい。たとえば、su コマンドは、そうである。

----------------------------------------------------------------------
% ls -l /bin/su [←]
-r-sr-xr-x   1 root     bin       147456 Dec  2  1993 /bin/su
% []
----------------------------------------------------------------------
SetUID のコマンドは、ls -l で見ると、user の x が s になっている。

★練習問題(21) 電子メールの送信とSetUID

ローカルで、電子メールを送る時に、SetUID のプログラム (HPUX では、 /bin/rmail, 一般には /bin/mail が多い)が使われている。なぜ、SetUID が 必要なのか、説明しなさい。また、電子メールを送る局面では、実効UIDだけ でなく、実UIDも必要になる。それは、どういう時か。

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

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

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

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

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

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

ソフトウェア割り込みは、1つのプログラムの中に制御の流れが1つしかない ようなプログラムの時に有効な方法である。最近のマルチスレッドのプログラ ムでは、シグナルの意味が不明確である。


----------------------------------------------------------------------
   1:	/*
   2:	        signal-int.c -- SIGINT を3回受け付けて終了するプログラム。
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/proc/signal-int.c
   4:	        $Header: signal-int.c,v 1.1 97/05/26 19:51:22 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:	        while( 1 )
  18:	        {
  19:	            printf("main(): sigint_count == %d\n", sigint_count );
  20:	            pause();
  21:	        }
  22:	}
  23:	
  24:	void sigint_handler()
  25:	{
  26:	        printf("sigint_handler(): sigint_count == %d\n", sigint_count );
  27:	        if( -- sigint_count <= 0 )
  28:	        {
  29:	            exit( 1 );
  30:	        }
  31:	        signal( SIGINT, &sigint_handler ); /* System V */
  32:	}
----------------------------------------------------------------------

実行例。
----------------------------------------------------------------------
% stty -a [←]
speed 9600 baud; line = 0; susp = ^Z; dsusp = ^Y
rows = 48; columns = 80
intr = ^C; quit = ^\; erase = ^H; kill = ^U; swtch = ^@
eof = ^D; eol ; min = 4; time = 255; stop = ^S; start = ^Q
parenb -parodd cs8 -cstopb hupcl cread -clocal -loblk -crts 
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc 
ixon -ixany -ixoff -rtsxoff -ctsxon -ienqak 
isig icanon iexten -xcase echo echoe echok -echonl -noflsh 
opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel -tostop 
% ./signal-int [←]
main(): sigint_count == 3
sigint_handler(): sigint_count == 3
main(): sigint_count == 2
sigint_handler(): sigint_count == 2
main(): sigint_count == 1
sigint_handler(): sigint_count == 1
% []
----------------------------------------------------------------------
stty で intr に相当するキーを3回押す。

★練習問題(22) killコマンドによるシグナルの生成

上のプログラムに、kill コマンドを使って SIGINT (kill -INT)でシグナルを 送りなさい。そして、キーボードから intr のキーを打った時と動作を比較し なさい。(kterm を2つ開いて、片方でこのプログラムを動かし、片方で kill コマンドを動かす。kill 引数として必要な PID は、ps コマンドで調べる。)

★練習問題(23) killコマンド

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

■setjmp() と longjmp()

関数間でも有効な goto がある。

割り込み処理やエラー回復に便利である。しかし、マルチスレッドの時代には、 少し合わない。


----------------------------------------------------------------------
   1:	/*
   2:	        setjmp-longjmp.c -- setjmp() と longjmp() のテスト
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/proc/setjmp-longjmp.c
   4:	        $Header: setjmp-longjmp.c,v 1.1 97/05/26 20:33:22 yas Exp $
   5:	        Start: 1997/05/26 20:26:27
   6:	*/
   7:	
   8:	#include <setjmp.h>
   9:	
  10:	jmp_buf main_env ;
  11:	
  12:	main()
  13:	{
  14:	    int x ;
  15:	        if( (x=setjmp(main_env)) == 0 )
  16:	        {
  17:	            printf("setjmp(), first time\n");
  18:	            f();
  19:	        }
  20:	        else
  21:	        {
  22:	            printf("return from longjmp(), %d \n",x );
  23:	        }
  24:	}
  25:	
  26:	f()
  27:	{
  28:	        printf("f() called.\n");
  29:	        g();
  30:	        printf("f() return.\n");
  31:	}
  32:	
  33:	g()
  34:	{
  35:	        printf("g() called.\n");
  36:	        h();
  37:	        printf("h() return.\n");
  38:	}
  39:	
  40:	h()
  41:	{
  42:	        printf("h() called.\n");
  43:	        longjmp( main_env,10 );
  44:	        printf("h() return.\n");
  45:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./setjmp-longjmp [←]
setjmp(), first time
f() called.
g() called.
h() called.
return from longjmp(), 10 
% []
----------------------------------------------------------------------

■pipe() と dup()

シェルで、次のようなプログラムを動かすこと考えます。

----------------------------------------------------------------------
% echo "hello,world" | tr a-z A-Z [←]
HELLO,WORLD
% []
----------------------------------------------------------------------

この例では、echo のプロセスと、tr のプロセスは、パイプで接続されていま す。パイプは、open() したファイルと同じようにread() したり write() し たりすることができます。しかし実際には、ファイルではなく、プロセスとプ ロセスが通信(プロセス間通信)の1つです。

パイプを作るには、pipe() システム・コールを使います。これで、パイプの 読み込み用のファイル記述子と書き込み用のファイル記述子が返される。

pipe() システム・コールで作られたファイル記述子は、しばしば dup() シス テム・コールで、0, 1 に変えられる。dup() システム・コールは、記述子を コピーするものである。小さい数字から探していくので、たとえば close(0) の直後に dup(fd) すると、fd が 0 にコピーされる。dup() よりも、dup2() の方が便利である。


----------------------------------------------------------------------
   1:	/*
   2:	        pipe-rw.c -- pipe() と dup() を使ったプログラム
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/proc/pipe-rw.c
   4:	        $Header: pipe-rw.c,v 1.1 97/05/26 21:00:50 yas Exp $
   5:	        Start: 1997/05/26 20:43:29
   6:	*/
   7:	
   8:	#include <stdio.h>
   9:	#include <unistd.h>
  10:	
  11:	main()
  12:	{
  13:	    int fildes[2] ;
  14:	    pid_t pid ;
  15:	
  16:	        if( pipe(fildes) == -1)
  17:	        {
  18:	            perror("pipe");
  19:	            exit( 1 );
  20:	        }
  21:	        /* fildes[0] -- 読み込み用
  22:	         * fildes[1] -- 書き込み用
  23:	         */
  24:	        if( (pid=fork()) == 0 )
  25:	        {
  26:	            child( fildes );
  27:	        }
  28:	        else if( pid > 0 )
  29:	        {
  30:	            parent( fildes );
  31:	        }
  32:	        else
  33:	        {
  34:	            perror("fork");
  35:	            exit( 1 );
  36:	        }
  37:	}
  38:	
  39:	parent( fildes )
  40:	    int fildes[2] ;
  41:	{
  42:	    char *p ;
  43:	        close( fildes[0] );
  44:	        close( 1 );
  45:	        dup( fildes[1] );
  46:	        close( fildes[1] );
  47:	
  48:	        p = "hello,world\n" ;
  49:	        while( *p )
  50:	        {
  51:	            putchar( *p++ );
  52:	        }
  53:	}
  54:	
  55:	child( fildes )
  56:	    int fildes[2] ;
  57:	{
  58:	    int c ;
  59:	        close( fildes[1] );
  60:	        close( 0 );
  61:	        dup( fildes[0] );
  62:	        close( fildes[0] );
  63:	
  64:	        while( (c=getchar()) != EOF )
  65:	        {
  66:	            putchar( toupper(c) );
  67:	        }
  68:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./pipe-rw [←]
HELLO,WORLD
% []
----------------------------------------------------------------------

★練習問題(25) 3個のプロセスをパイプで結ぶ

3個のプロセスの標準入出力を、2つのパイプで結びなさい。あるいは、与え られた個数のプロセスの標準入出力をパイプで接続しなさい。

★練習問題(26) 書き手がいないパイプ

書き手がいないパイプから読み出そうとすると、どうなるか確かめなさい。

★練習問題(27) 読み手がいないパイプ

読み手がいないパイプに書き込むとどうなるか確かめなさい。

★練習問題(28) パイプ用のバッファの大きさ

各パイプには、オペレーティング・システムのカーネルの中にバッファが割り 当てられてている。この大きさを調べるプログラムを作りなさい。

★練習問題(29) 読み手が複数いるパイプ

1つのパイプに読み手(read() するプロセス)が複数いた場合、どうなるか。 逆に、書き手が複数いた場合、どうなるか。

★練習問題(30) popen() ライブラリ関数

プロセスを実行してその結果を得るには、popen() ライブラリ関数が便利であ る。
----------------------------------------------------------------------
     FILE *popen(const char *command, const char *type);
     int pclose (FILE *stream);
----------------------------------------------------------------------
これと同じような機能を持つ関数を定義しなさい。ただし、入出力は、ファイ ル記述子のままでもよい。(ファイル記述子は、fdopen() 使えば、FILE * に 変えることができる。)

■socketpair() による双方向通信

socketpair() を使えば、双方向の通進路を作ることができる。 一方の口から入ったデータは、反対側から出ていく。

----------------------------------------------------------------------
   1:	/*
   2:	        socketpair-rw.c -- socketpair() による双方向通信
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/ipc/socketpair-rw.c
   4:	        $Header: socketpair-rw.c,v 1.1 97/05/26 21:51:59 yas Exp $
   5:	        Start: 1997/05/26 21:29:34
   6:	*/
   7:	
   8:	#include <stdio.h>
   9:	#include <unistd.h>     /* fork() */
  10:	#include <sys/types.h>  /* socketpair() */
  11:	#include <sys/socket.h> /* socketpair() */
  12:	
  13:	main()
  14:	{
  15:	    int fildes[2] ;
  16:	    pid_t pid ;
  17:	
  18:	        if( socketpair(AF_UNIX,SOCK_STREAM,0,fildes) == -1)
  19:	        {
  20:	            perror("socketpair");
  21:	            exit( 1 );
  22:	        }
  23:	        /* fildes[0] -- 読み込み用
  24:	         * fildes[1] -- 書き込み用
  25:	         */
  26:	        if( (pid=fork()) == 0 )
  27:	        {
  28:	            close( fildes[1] );
  29:	            child( fildes[0] );
  30:	        }
  31:	        else if( pid > 0 )
  32:	        {
  33:	            close( fildes[0] );
  34:	            parent( fildes[1] );
  35:	        }
  36:	        else
  37:	        {
  38:	            perror("fork");
  39:	            exit( 1 );
  40:	        }
  41:	}
  42:	
  43:	parent( fd )
  44:	    int fd ;
  45:	{
  46:	    char *p,c,ret ;
  47:	        p = "hello,world\n" ;
  48:	        while( c = *p++ )
  49:	        {
  50:	            if( write( fd, &c, 1 ) != 1 )
  51:	                perror("write");
  52:	            if( read( fd, &ret, 1 ) != 1 )
  53:	                perror("read");
  54:	            putchar( ret );
  55:	        }
  56:	        close( fd );
  57:	}
  58:	
  59:	child( fd )
  60:	    int fd ;
  61:	{
  62:	    char c ;
  63:	        while( read(fd,&c,1) == 1 )
  64:	        {
  65:	            c = toupper( c );
  66:	            if( write( fd,&c,1 ) != 1 )
  67:	                perror("read");
  68:	        }
  69:	        close( fd );
  70:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./socketpair-rw  [←]
HELLO,WORLD
% []
----------------------------------------------------------------------

★練習問題(31) socketpair()を高水準入出力で利用する

socketpair-rw.c を書き直し、getchar(), putchar()、あるいは、fgetc(), fputc() を使ってプロセス間通信を行いなさい。注意することは、 socketpair() の結果が read() でも write() でも使えるのに対して、高水準 入出力では、1つの FILE * では、入力と出力のうち一方しかできないことで ある。よって、dup() により、入力と出力に別々のファイル記述子を割り当て ておく必要がある。

★[report] report-7,SetUID/pipe/socketpair

練習問題(21),(25),(28),(29),(30),(31) の中から1つ以上選んでやりなさい。 問題を難しい方に変更してもよい。
↑[もどる] ←[5月20日] ・[5月27日] →[6月3日]
Last updated: 1997/05/26 22:12:23
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>