5 プロセスの概念とその基本操作

軽量プロセスの生成

課題11 軽量プロセスと手続きの比較

次のようにして、mp-create と function-call プログラムをコンパイルし、実行してみなさい。

% make mp-create
% ./mp-create
% make function-call
% ./function-call
%

この二つのプログラムの動きの違いから、軽量プロセスと手続き(C言語 の関数)の違いについて考察しなさい。

mp-create.c のプログラムを以下に示す。

/*
	mp-create.c -- 軽量プロセスの生成
	$Header: /home/h1/yas/slab-info1-os/1-process/mp-create.c,v 1.1 1995/03/08 16:14:47 yas Exp $
	Start: 1995/02/27 16:42:19
*/
#include 
#include 
軽量プロセスを利用するプログラムは、このヘッダを読み込む。このpthread は、UnixのPOSIX, X/Openによる標準機能であ。pthread は Solaris および BSD/OSで利用することができる。しかし、Solaris はkernel threadであり、 BSD/OSは、user level thread である。

軽量プロセスとして用いる関数は、利用する前に宣言、あるいは本体の定義を しておく必要がある。

void *main_0(void*);
void *process_A(void*);
void *process_B(void*);
void *process_C(void*);
void ready_print();
このpthreadではready_printというのは、あまりたいしたことができない。 pthread の情報格納する領域をthreadごとに用意する。また、引数は 配列という形で提供しなければならないので、それも別に用意する。
pthread_t thread_main,thread_a,thread_b,thread_c;

int arg_a[]={0,1,2};
int arg_b[]={1,3,5};
int arg_c[]={2,3,5};
pmpthread_create() は、軽量プロセスを作る手続きである。第1引数は、 pthreadの情報である。次の引数には、pthreadの属性を指定することが できるが、ここではNULLを指定している。第3引数が、 もとになる 関数へのポインタである。第4引数は、その関数を呼び出す時に引き渡される 引数である。ここでも引数は指定していない。
int
main(int ac,char *av[]) 
{
	pthread_create(&thread_main, NULL, &main_0, NULL);
	pthread_join(thread_main, NULL);
}
ここでは、ただ一つのpthreadを生成している。そして、最後に、pthread_join を使って、このthread_main の終了を待つ必要がある。これは、普通のプロセ スのwaitに相当する。main では、次に3つの軽量プロセスを生成する。
void *
main_0(void *arg)
{
	fprintf(stderr,"creating process_A() ...\n");
	/* 軽量プロセスの生成 */
	pthread_create(&thread_a, NULL, &process_A, arg_a);
	fprintf(stderr,"done.\n");
	fprintf(stderr,"creating process_B() ...\n");
	/* 軽量プロセスの生成 */
	pthread_create(&thread_b, NULL, &process_B, arg_b);
	fprintf(stderr,"done.\n");
	fprintf(stderr,"creating process_C() ...\n");
	/* 軽量プロセスの生成 */
	pthread_create(&thread_c, NULL, &process_C, arg_c);
	fprintf(stderr,"done.\n");
次に ready_print() としているが残念ながら、あまり有用な情報は 得られない。良く観察して何が起きているかを考察すること。これらの thread は終了を待つ必要がある。
	pthread_join(thread_a, NULL);
	pthread_join(thread_b, NULL);
	pthread_join(thread_c, NULL);
この時点でCPU資源の待ち行列には、いくつのプロセスがどのような順番で 並んでいるかを観察しなさい。
void *
process_A( void *arg )
{
    int x ;
    int *a = arg;
	fprintf(stderr,"process_A( %d,%d,%d ) created.\n", a[0],a[1],a[2]);
	fprintf(stderr,"&x == 0x%x\n", &x );
	ready_print(); /* CPU資源の待ち行列の表示 */
	fprintf(stderr,"process_A() exit.\n");
}
process_A,process_B,process_C の終了をpthread_joinで確認すると、 CPU資源の待ち行列は、空になる。全ての軽量プロセスが終了す ると、mainのthreadの終了を確認してプロセス全体が終了する。 (本当は、この実験では特にmainのtreadは必要ない) ready_print()では、単に自分のthread情報のアドレスを表示する。 本来は、ここでthreadのqueueを表示するべきだろう。

このプログラムと、function-call.c の手続き呼び出しと比較
してみよ。

このプログラムは、SolarisとBSD/OS上で動作する。Makefile の中に、 Solaris と BSD/OS を切り替える行が記述されている。これを編集して、 Solaris上とBSD/OS上の両方での動作を確認してみよ。

課題12 プロセスのコンテキスト切り替えの観察

次のようにして、mp-yieldをコンパイルし、実行してコンテキスト切り替え の様子を観察しなさい。
% make mp-yield
% ./mp-yield
%
この結果より、2つの軽量プロセスが交互に動作していることがわかる。 動作が切り替わる切っ掛けとなっているものが、sched_yield()である。 (Solaris では名称が異なる)

sched_yield() は、現在実行中の軽量プロセスが、利用しているCPU 資源を自発的に他の軽量プロセスに譲る命令である。sched_yield()を 呼び出した結果、現在実行中の軽量プロセスは、実行可能状態となる。 制御はスケジューラに移る。スケジューラは、CPU資源の待ち行列から、 実行可能状態の軽量プロセスを選び、その軽量プロセスに制御を移す。

課題13 3つのプロセスの間のコンテキスト切り替え

課題12のプログラムを変更して、3つのプロセスの間で制御が入れ替わる ようなプログラムを書きなさい。

課題14 FIFOスケジューリングの観察

次のようにして、mp-wakeupをコンパイルし、実行してみなさい。
% make mp-wakeup
% ./make-wakeup
%
この軽量プロセス・システムでは、FIFO (First In First Out,FCFS (First come first serveともいう) スケジューリングを行っている。このことを、 この mp-wakeup-.c と課題11 の mp-create.c の動きの違いから説明しなさない。 以下に mp-wakeup.c を示す。
int
main(int ac,char *av[])
{
     	pthread_attr_init(&attr_main);
	attr_main.schedparam.sched_priority = 100;
        pthread_create(&thread_main, &attr_main, &main_0, NULL);
        pthread_join(thread_main, NULL);
}
ここではpthread_create()の時に属性としてpriorityを指定している。 このように直接構造体を指定する方法は若干ポータビリティが低い。

このpriority を変更することによってFIFOスケジューリングを 変更することができる。

void *
main_0(void *arg)
{
        fprintf(stderr,"creating process_A() ...\n");
        /* 軽量プロセスの生成 */
     	pthread_attr_init(&attr_a);
	attr_a.schedparam.sched_priority = 10;
        pthread_create(&thread_a, &attr_a, &process_A, arg_a);
        fprintf(stderr,"done.\n");

        fprintf(stderr,"creating process_B() ...\n");
        /* 軽量プロセスの生成 */
     	pthread_attr_init(&attr_b);
	attr_b.schedparam.sched_priority = 20;
        pthread_create(&thread_b, &attr_b, &process_B, arg_b);
        fprintf(stderr,"done.\n");

        fprintf(stderr,"creating process_C() ...\n");
        /* 軽量プロセスの生成 */
     	pthread_attr_init(&attr_c);
	attr_c.schedparam.sched_priority = 30;
        pthread_create(&thread_c, &attr_c, &process_C, arg_c);
        fprintf(stderr,"done.\n");
このpriorityを変更して実行がどのようになるかを観察せよ。