home link download back number special issue

HMDT - Back Number / August, 2000


Augsut, 2000

August 23, 2000

■なつやすみ

ちょっと遅めのなつやすみをいただきます。9 月の頭まで更新をお休みすると思います。

9 月の半ばには Mac OS X もでるでしょうから、そしたら本格的に行くぞっ。

休みは北海道に行く予定。えへへ、、、


August 21, 2000

■Darwin 解体新書 - Task System Call - task_info

Associated Files:
/xnu-1-1/osfmk/kern/task.c
/xnu-1-1/osfmk/mach/mach_types.defs
/xnu-1-1/osfmk/mach/task_info.h
/xnu-1-1/osfmk/mach/policy.h

今日は task_info をいってみよう。タスクの各種情報を取得するためのコールだ (line: 1439):

kern_return_t
task_info(
    task_t			task,
    task_flavor_t		flavor,
    task_info_t		task_info_out,
    mach_msg_type_number_t	*task_info_count)

プロトタイプはこんな感じだ。task_t は task 構造体。次の task_flavor_t と task_info_t は?これは /xnu-1-1/osfmk/mach/mach_types.defs に定義がある(line: 196):

        /* task_info_t: this inline array can hold any of:
         * task_basic_info_t (8 ints)
         * task_events_info_t (8 ints)
         * task_thread_times_info_t (4 ints)
         * policy_timeshare_info_t (5 ints)
         * policy_fifo_info_t (4 ints)
         * policy_rr_info_t (5 ints)
         * If other task_info flavors are added, this
         * definition may need to be changed. (See
         * mach/task_info.h and mach/policy.h) */
type task_flavor_t		= int;
type task_info_t		= array[*:8] of integer_t;

.defs ファイルなので、C とはちょっと違うけど意味は分かるよね。task_flavor_t は int 型で、task_info_t は int 8 つ分の配列。上のコメントに書いてある通り、この配列には幾通りかのデータが入るんだ。

  • task_basic_info_t (8 ints)
  • task_events_info_t (8 ints)
  • task_thread_times_info_t (4 ints)
  • policy_timeshare_info_t (5 ints)
  • polict_fifo_info_t (4 ints)
  • policy_rr_info_t (5 ints)

task_info_t には、このデータ型のどれかが入り、task_flavor_t はどの型が入っているかのインデックスを表すわけだ。

じゃあ、これらの構造体の定義はどこにある?これもコメントに書いてある通り、/xnu-1-1/osfmk/mach/task_info.h だ(line: 177):

#define TASK_BASIC_INFO         4       /* basic information */

struct task_basic_info {
    integer_t       suspend_count;  /* suspend count for task */
    vm_size_t       virtual_size;   /* number of virtual pages */
    vm_size_t       resident_size;  /* number of resident pages */
    time_value_t    user_time;      /* total user run time for
                                       terminated threads */
    time_value_t    system_time;    /* total system run time for
                                       terminated threads */
    policy_t	policy;		/* default policy for new threads */
};

...

#define	TASK_EVENTS_INFO	2	/* various event counts */

struct task_events_info {
    integer_t	faults;		/* number of page faults */
    integer_t 	pageins;	/* number of actual pageins */
    integer_t 	cow_faults;	/* number of copy-on-write faults */
    integer_t 	messages_sent;	/* number of messages sent */
    integer_t 	messages_received; /* number of messages received */
    integer_t 	syscalls_mach;  /* number of mach system calls */
    integer_t 	syscalls_unix;  /* number of unix system calls */
    integer_t 	csw;            /* number of context switches */
};

...

#define	TASK_THREAD_TIMES_INFO	3	/* total times for live threads -
					   only accurate if suspended */

struct task_thread_times_info {
    time_value_t	user_time;	/* total user run time for
					   live threads */
    time_value_t	system_time;	/* total system run time for
					   live threads */
};

マクロが task_flavor_t によって示されるインデックス。task_basic_info が、中断カウントと、仮想メモリ、中断されたスレッド用の時間。task_events_info が、メモリアクセスの際のフォルトの回数と、メッセージングやシステムコールを読んだ回数。パフォーマンスのチューニングに使うのか?task_thread_times_info は、生きているスレッドの時間に関するところ。

そしてもうひとつ定義があるのが /xnu-1-1/osfmk/mach/policy.h (line: 109):

struct policy_timeshare_info {
    integer_t		max_priority;
    integer_t		base_priority;
    integer_t		cur_priority;
    boolean_t		depressed;
    integer_t		depress_priority;
};

...

struct policy_rr_info {
    integer_t		max_priority;
    integer_t		base_priority;
    integer_t		quantum;
    boolean_t		depressed;
    integer_t		depress_priority;
};

...

struct policy_fifo_info {
    integer_t		max_priority;
    integer_t		base_priority;
    boolean_t		depressed;
    integer_t		depress_priority;
};

おいおい、全部いっしょだねぇ。

task_info() の中でやっているのは、これらの構造体に適当な値を放り込んでやることだ。興味は、どうやって値を得るかっていうことよりも、このコールを呼んだ側でこれらの値をどうやって使うか、っていうことにあるよね。


August 20, 2000

■Darwin 解体新書 - Task System Call - task_resume

Associated Files:
/xnu-1-1/osfmk/kern/task.c

中断されたら再開せねば。というわけで、task_resume() ね (line: 1334):

kern_return_t 
task_resume(register task_t task)
{
    register boolean_t	release;

    if (task == TASK_NULL)
        return(KERN_INVALID_ARGUMENT);

    release = FALSE;
    task_lock(task);
    if (!task->active) {
        task_unlock(task);
        return(KERN_FAILURE);
    }
    if (task->user_stop_count > 0) {
        if (--(task->user_stop_count) == 0)
            release = TRUE;
    }
    else {
        task_unlock(task);
        return(KERN_FAILURE);
    }
    task_unlock(task);

まず、引き数 task のチェック。task_lock() して、taskuser_stop_count を調べる。これが 0 より大きかったら、1 減らす。減らした結果、0 になったら、後でリリースするんだ。user_stop_count が 0 以上だったらエラーを返して終わり。

残りは (line: 1358):

    /*
     *	Release the task if necessary.
     */
    if (release)
        return(task_release(task));

    return(KERN_SUCCESS);
}

task_release() を呼び出す。ここで、すべてのスレッドをリリースしてやるんだ。

つまり、タスクの再開は user_stop_count を減らして、スレッドをリリースする。ちょうど、あたりまえだけど、task_suspend() の逆だね。


August 20, 2000

■Darwin 解体新書 - Task System Call - task_suspend

Associated Files:
/xnu-1-1/osfmk/kern/task.c

次いこう!task_suspend() だ。読んでのとおり、タスクを中断するためのコールだ。 (line: 1238):

kern_return_t
task_suspend(
    register task_t		task)
{
    if (task == TASK_NULL)
        return (KERN_INVALID_ARGUMENT);

    task_lock(task);
    if (!task->active) {
        task_unlock(task);
        return (KERN_FAILURE);
    }
    if ((task->user_stop_count)++ > 0) {
    /*
     *	If the stop count was positive, the task is
     *	already stopped and we can exit.
     */
        task_unlock(task);
        return (KERN_SUCCESS);
    }

引き数 task はとうぜん、中断させるタスクね。引き数チェックのあと、task_lock() をする。タスクが active ではなかったら、中断させる必要がないんで終わり。次に taskuser_stop_count を増やす。増やす前の user_stop_count が 0 より大きかったら、これはもうすでに止まっていたということだ。そこでそのまま返して終わり。そうでないときは (line: 1259):

    /*
     *	Hold all of the threads in the task, and wait for
     *	them to stop.  If the current thread is within
     *	this task, hold it separately so that all of the
     *	other threads can stop first.
     */
    if (task_hold_locked(task) != KERN_SUCCESS) {
        task_unlock(task);
        return (KERN_FAILURE);
    }

    task_wait_locked(task);
    task_unlock(task);
    return (KERN_SUCCESS);
}

task_hold_locked() を呼んで、スレッドをロックする。この関数の返り値が返ってきても、まだすべてのスレッドがロックされたわけではないらしい。そこで、すべてのスレッドがロックされるのを待つ関数 task_wait_locked() (line: 1275) を呼ぶ。その後、task_unlock() しておしまい。

要約すると、タスクのサスペンドは user_stop_count を増やすことと、スレッドをロックすることで実行されるわけ。


August 19, 2000

■Darwin 解体新書 - Task System Call - task_terminate

Associated Files:
/xnu-1-1/osfmk/kern/task.c

task を作った次は、とうぜん task の終了さー、というわけで task_terminate を調べよう。ファイルはいつもの /xnu-1-1/osfmk/kern/task.c だよ (line: 667):

kern_return_t
task_terminate(
    task_t		task)
{
    if (task == TASK_NULL)
        return(KERN_INVALID_ARGUMENT);
    if (task->bsd_info)
        return(KERN_FAILURE);
    return (task_terminate_internal(task));
}

引き数 task は終了させるタスクだ。引き数チェックをしているけど、ちょっと注目は task->bsd_info のチェックだ。bsd_info は BSD エミュレーションで使われるんだけど、これが NULL だと、この関数では終了できないようだ。bsd_info が NULL というのは何を意味しているのか?kernel の task ということかな?引き数をチェックしたら、task_terminate_internal() を呼び出すよ (line: 678):

kern_return_t
task_terminate_internal(
    task_t		task)
{
    register thread_t	thread, cur_thread;
    register queue_head_t	*list;
    register task_t		cur_task;
    thread_act_t		thr_act, next_thr_act, cur_thr_act;
    spl_t			s;

    assert(task != kernel_task);

    list = &task->thr_acts;
    cur_task = current_task();
    cur_thr_act = current_thread()->top_act;

で、次に終了させるタスクを active ではなくする。このときに、終了させたいタスクが今のタスク(つまり自分で自分を終了させようとしている状態だ。ややこしい)かどうかで、処理が変わるんだ。まず、自分で自分を終了させるとき(line: 720):

    if (task == cur_task) {
        task_lock(task);

        ...

        task_hold_locked(task);

        task->active = FALSE;

        /*
         *	Make sure current thread is not being terminated.
         */
        mutex_lock(&task->act_list_lock);
        cur_thread = act_lock_thread(cur_thr_act);

        ...

        /*
         * make sure that this thread is the last one in the list
         */
        queue_remove(list, cur_thr_act, thread_act_t, thr_acts);
        queue_enter(list, cur_thr_act, thread_act_t, thr_acts);
        act_unlock_thread(cur_thr_act);
        mutex_unlock(&task->act_list_lock);

        /*
         *	Shut down this thread's ipc now because it must
         *	be left alone to terminate the task.
         */
        ipc_thr_act_disable(cur_thr_act);
        ipc_thr_act_terminate(cur_thr_act);
    }

まず task_lock() する。task_lock() は /xnu-1-1/osfmk/kern/task.h に定義があるマクロだ。task 構造体の lock を lock する (line: 212):

#define task_lock(task)		mutex_lock(&(task)->lock)

次に task_hold_locked() する。task_hold_locked() の方は関数なんだ (line: 1066)。この関数では task の中断カウントを増やして、スレッドをロックする。実行中のタスクをロックさせるわけだね。この関数は task_suspend() からも呼び出されてるよ。そして、task->active を FALSE にセットする。これでタスクが deactive になりました、と。

スレッドもいじくる。queue_remove() をしてから queue_enter() をする。これよくわかんねーよ。なんで必要なんだ?最後に、プロセス間通信のスレッドを終了させる。

つぎは current task じゃない場合だ(line: 764):

    else {
        /*
         *	Lock both current and victim task to check for
         *	potential deadlock.
         */
        if (task < cur_task) {
            task_lock(task);
            task_lock(cur_task);
        }
        else {
            task_lock(cur_task);
            task_lock(task);
        }

        ...

        task_hold_locked(task);
        task->active = FALSE;
    }

まずは、現在のタスク犠牲者(笑)のタスクを lock する。このとき、アドレスを比較してどっちを先に lock するか決定するんだね。はー、kernel だ。その後、スレッドのチェックをするんだけど省略。task_hold_locked() して、deactive にする。

そして、後片付け処理 (line: 805):

    ipc_task_disable(task);
    task_wait_locked(task);

    ...

    thr_act = (thread_act_t)queue_first(&task->thr_acts);
    while ( thr_act!= (thread_act_t)&task->thr_acts) {
        act_reference(thr_act);
        next_thr_act =  (thread_act_t)queue_next(&thr_act->thr_acts);
        task_unlock(task);
        thread_terminate(thr_act);
        act_deallocate(thr_act);
        task_lock(task);
        thr_act = next_thr_act;
    }

    task_unlock(task);

    ...

    task_synchronizer_destroy_all(task);

    ...

    ipc_task_terminate(task);

    ...

    task_subsystem_destroy_all(task);

    ...

    (void) vm_map_remove(task->map,
        task->map->min_offset,
        task->map->max_offset, VM_MAP_NO_FLAGS);

    ....

    task_deallocate(task);

    return(KERN_SUCCESS);
}

task_wait_locked() (line: 1275) はスレッドをすべてストップさせる。そして thread_terminate() を呼び出して、スレッドをすべて終了させる。task_synchronizer_destroy_all() (line: 2191) は semaphore と lock set をすべて破壊する。ipc_task_terminate() でプロセス間通信を終わらせて、task_subsystem_destroy_all() (line: 2216) で subsystem を破壊。vm_map_remove() で仮想メモリも削除し、最後に task_deallocate() (line: 633) で task 構造体のメモリをも解放する。

そして誰もいなくなったところで、寂しく return を呼ぶ、、、

別にさみしがらんでもいいがな。


August 17, 2000

■Open Inventor が Open Source に

SGI の Open Inventor が Open Source になったよ!

Open Inventor は簡単に説明すると、Open GL の上に構築される、ハイレベル API だ。昔、これが使えなくてくやしい思いをしたからなぁ。時代は変わるもんだ。

Mac OS X で動かしてみーようっと。やることがいっぱいできたなぁ、、、


August 12, 2000

■Java technology zone

Java FAQ 経由で知った、IBM の Java 関係技術情報、Java technology zone のページ。大変、有用な情報が多いです。Java プログラミングの中級〜上級テクニックだね。こういう情報は、書籍では手に入りにくいから、雑誌か Web が頼りだよ。

IBM は alphaWorks といい、Java に関する基礎的な技術力がしっかりとある、という印象があるよ。Mac OS X が来たら、本格的に alphaWorks を servey してみるか、、、


August 12, 2000

■Darwin 解体新書 - Task System Call - task_create

Associated Files:
/xnu-1-1/osfmk/kern/task.c
/xnu-1-1/osfmk/kern/syscall_emulation.c
/xnu-1-1/osfmk/kern/ipc_tt.c
/xnu-1-1/osfmk/kern/processor.c

task を作るシステムコール task_create を調べてみよう。対応する関数 task_create() は /xnu-1-1/osfmk/kern/task.h にあるんだ (line: 279):

kern_return_t
task_create(
    task_t			parent_task,
        ledger_port_array_t	ledger_ports,
        mach_msg_type_number_t	num_ledger_ports,
    boolean_t		inherit_memory,
    task_t			*child_task)		/* OUT */
{
    if (parent_task == TASK_NULL)
        return(KERN_INVALID_ARGUMENT);

    return task_create_local(
	    		 parent_task, inherit_memory, FALSE, child_task);
}

最初の引き数は parent_task。これは親になるタスクだ。新しく作られるタスクは、親の属性を引き継ぐかもしれない。2 番目の引き数 ledger_ports と、3 番目 num_ledger_ports は保留。どうも、あとから付け足されたような気がする。inherit_memory は親のメモリを受け継ぐかどうか。メモリの項で詳しく見るでしょう。最後の引き数 chid_task は、関数の返り値で、新しく作られたタスクが入る、と。

関数の中では、parent_task の値をチェックしている。NULL だったらエラーを返す。じゃあ、最初のタスクはどうやって作るの?後で調べてみよう。その後、task_create_local() を呼び出すだけ。このとき、2、3 番目の引き数 ledger_portsnum_ledger_ports は使われないんだよね。無視されるのか、こいつら?

続いて task_create_local() (line: 323):

kern_return_t
task_create_local(
    task_t		parent_task,
    boolean_t	inherit_memory,
    boolean_t	kernel_loaded,
    task_t		*child_task)		/* OUT */
{
    task_t		new_task;
    processor_set_t	pset;

    new_task = (task_t) zalloc(task_zone);

    if (new_task == TASK_NULL)
        return(KERN_RESOURCE_SHORTAGE);

1、2、4 番目の引き数はいっしょだよね。3 番目はカーネルにロードするか、どうかを示している。ユーザ空間に作るタスクだったら、ここが FALSE なわけだ。task_create() 経由で呼び出されたときは、常に FALSE だ。カーネルがタスクを作るときのコールは別にあるわけだ。

最初に、task_t 構造体を確保して、初期化している。

あとは、変数の初期化が続く (line: 338):

    /* one ref for just being alive; one for our caller */
    new_task->ref_count = 2;

    if (inherit_memory)
        new_task->map = vm_map_fork(parent_task->map);
    else
        new_task->map = vm_map_create(pmap_create(0),
                round_page(VM_MIN_ADDRESS),
                trunc_page(VM_MAX_ADDRESS), TRUE);

まず、参照回数の初期化。2 が意味しているのは、自分の参照分と、親の参照分だ。次にメモリ。inherit_memory が TRUE のときは、fork する。ね。そうでないときは、新しいマップを作る。

この後はセマフォ、キュー関係の初期化が続く。

ちょっと面白いのはここ (line: 390):

    eml_task_reference(new_task, parent_task);

    ipc_task_init(new_task, parent_task);

eml_task_reference() は、/xnu-1-1/osfmk/kern/syscall_emulation.c に定義がある。この関数では、エミュレーションベクトルにタスクを参照させている。あとで調べよう。もう一つは ipc_task_init() (/xnu-1-1/osfmk/kern/ipc_tt.c)。タスクにポートを設定する。

親タスクがセットされている場合は、親タスクから次の属性を受け継ぐ (line: 401)。

  • processor_set
  • policy
  • sp_attributes
  • sec_token

セットされていない場合は、デフォルトが割当たる。

ここまでで変数の初期化が終わって、タスクをスタートさせる処理が続く (line: 478)。

    pset_lock(pset);
    pset_add_task(pset, new_task);
    pset_unlock(pset);
...
    ipc_task_enable(new_task);

pset_add_task() (/xnu-1-1/osfmk/kern/processor.c) でプロセッサにタスクを割り当てて、ipc_task_enable() (/xnu-1-1/osfmk/kern/ipc_tt.c) でプロセス間通信を開始にする。

最後に返り値をセットして終わりだ。

    *child_task = new_task;
    return(KERN_SUCCESS);
}

あー、疲れた。これでタスクの完成でーす。


August 11, 2000

■Darwin 解体新書 - Task Structure

Associated Files:
/xnu-1-1/osfmk/kern/task.h
/xnu-1-1/osfmk/kern/lock.h
/xnu-1-1/osfmk/ppc/task.h
/xnu-1-1/osfmk/i386/task.h

task_t 構造体を調べよう。定義は /xnu-1-1/osfmk/kern/task.h にあるよ。これには、タスクが保持している情報が記されている。早い話、タスクでできることは、task_t 構造体が示している以上のものでも以下でもない(言い過ぎ?)。

まずは、同期関係から (line: 104):

typedef struct task {
    /* Synchronization/destruction information */
    decl_mutex_data(,lock) /* Task's lock */
    int ref_count; /* Number of references to me */
    boolean_t active; /* Task has not been terminated */
    boolean_t kernel_loaded; /* Created with kernel_task_create() */

最初の変数、lock についているマクロ decl_mutex_data は、/xnu-1-1/osfmk/kern/lock.h に定義がある (line: 150):

#define decl_mutex_data(class,name) class mutex_t name;

ここでは class が指定されていないので、lockmutex_t ということになる。ref_countactivekernel_loaded は説明に書いてある通りだね。

次はその他いろいろ (Miscellaneous)。おいおい、もうその他かよ (line: 111):

    /* Miscellaneous */
    vm_map_t map; /* Address space description */
    queue_chain_t pset_tasks; /* list of tasks assigned to pset */
    void *user_data; /* Arbitrary data settable via IPC */
    int suspend_count; /* Internal scheduling only */

map はアドレス空間。重要だね。これについてはメモリの項で詳しく説明するでしょう。task 構造体がアドレス空間を持っていることで、プロセスごとにアドレス空間が割り当たっていることが分かるね。pset_tasks はタスクのキュー(何かは保留。ごめん)。suspend_count は中断カウント。そのまんまじゃ。これは task_suspend() が呼び出される度に増えていくはず。

つづいて、アクティブかどうかの管理 (line: 129):

    /* Active activations in this task */
    int		thr_act_count;
    queue_head_t	thr_acts;	/* list of thread_activations */
    int		res_act_count;
    mutex_t		act_list_lock;	/* XXX act_list lock */

    processor_set_t	processor_set;	/* processor set for new threads */

thr_act_count はアクティブになっているスレッドの数。thr_acts は、そのアクティブになっているスレッドのキューの先頭。次の二つはなに?processor_set は、新しいスレッドに割り当てる予定のプロセッサ。ここで、スレッドごとのマルチプロセッサに対応していることが分かるね。G4 MP のリリースは、絶対 Mac OS X を睨んでるね。MP マシンが真価を発揮するのはもうすぐだ。

そして、スケジューリングの情報 (line: 141):

    /* User-visible scheduling information */
    int		user_stop_count;	/* outstanding stops */
        /*** ??? should `user_stop_count' be moved, too? ***/
    int		policy;			/* scheduling policy */
    sp_attributes_t	sp_attributes;		/* ptr to sched parameters */
user_stop_count はここにおいておくかどうか迷っているようだね。こういうことが分かるのもソースコードならではだ。policy はスケジューリングのポリシー。詳しくは後で。sp_attributes も同様。
        /* Task security token */
        security_token_t sec_token;
        
    /* Statistics */
    time_value_t	total_user_time;
				    /* total user time for dead threads */
    time_value_t	total_system_time;
				    /* total system time for dead threads */
これは統計用の変数 (line: 147)。でも、これ何に使うんだ?それはコードを読めばおいおい分かるでしょう。

続いてプロセス間通信 (IPC: Inter Process Communication) 用のポートの変数 (line: 161):

    /* IPC structures */
    decl_mutex_data(,itk_lock_data)
    struct ipc_port *itk_self;	/* not a right, doesn't hold ref */
    struct ipc_port *itk_sself;	/* a send right */
    struct exception_action exc_actions[EXC_TYPES_COUNT];
        /* a send right each valid element  */
    struct ipc_port *itk_bootstrap;	/* a send right */
    struct ipc_port *itk_registered[TASK_PORT_REGISTER_MAX];
        /* all send rights */

    struct ipc_space *itk_space;
ここでは、すべてポインタになっていることに注意。実体はカーネルにあるはず。詳しいことはポートの項で説明しよう。

つぎはリモート手続き呼び出し (RPC: Remote Procedure Call) の変数 (line: 173):

    /* RPC subsystem information */
    queue_head_t	subsystem_list;	/* list of subsystems */
    int		subsystem_count;/* number of subsystems */
そして、セマフォ管理用 (line: 177):
    /* Synchronizer ownership information */
    queue_head_t	semaphore_list;		/* list of owned semaphores   */
    queue_head_t	lock_set_list;		/* list of owned lock sets    */
    int		semaphores_owned;	/* number of semaphores owned */
    int 		lock_sets_owned;	/* number of lock sets owned  */
エミュレーション管理用と ledger ポート(?)(line: 183):
    /* User space system call emulation support */
    struct 	eml_dispatch	*eml_dispatch;

        /* Ledgers */
    struct ipc_port	*wired_ledger_port;
    struct ipc_port *paged_ledger_port;
最後に各種カウンタ(line: 197):
    MACHINE_TASK
    integer_t faults;              /* faults counter */
        integer_t pageins;             /* pageins counter */
        integer_t cow_faults;          /* copy on write fault counter */
        integer_t messages_sent;       /* messages sent counter */
        integer_t messages_received;   /* messages received counter */
        integer_t syscalls_mach;       /* mach system call counter */
        integer_t syscalls_unix;       /* unix system call counter */
        integer_t csw;                 /* context switch counter */
最初の行のマクロ MACHINE_TASK は /xnu-1-1/osfmk/ppc/taks.h および /xnu-1-1/osfmk/i386/task.h にあって、機種依存のタスク変数の存在を示しているんだけど、そこにはそれぞれ、“機種依存のタスク変数はなし”と書いてあるだけ。かっこいいー!

で、おしまい。

} Task;
テキストやニュースだけからは分からない情報がたくさんあっておもしろいねー。だけど、そろそろ情報工学の知識が無い人には辛くなってきたかな?

August 10, 2000

■Darwin 解体新書 - Task System Call

Associated Files:
/xnu-1-1/osfmk/mach/task.def

Today's BGM: "ensemble" by Taeko Onuki

やっぱり、大貫妙子はアコースティックがいいよね。

タスクのシステムコールを調べてみよう。ここで、用語の整理をしておくと、このページでは、プロセスは一般的な名称として、タスクは Darwin の実装として使っているよ。

システムコールは、/xnu-1-1/osfmk/mach/ にある、.def ファイルに書いてあるんだ。これを MIG (Mach Interface Generator) を使ってコンパイルして使うらしい。

タスクに関係するのは task.def だ。Darwin っつーか Mach では、システムコールにはプレフィックス(前置詞ね)がついている。タスク関連には task_ というのがついている。分かりやすくてきれいだね。じゃ、書き出してみるかい。

task_create 新しいタスクを作る
task_terminate タスクを削除する
task_threads タスクが持つスレッドを返す
task_info タスクの情報を返す
task_set_info タスクに情報を設定する
task_suspend タスクの中断カウンタを増やす
task_resume タスクの中断カウンタを減らす
task_set_special_port タスクに関する特別なポートを設定する
task_get_special_port タスクに関する特別なポートを返す
task_set_exception_ports 例外ハンドラを設定する
task_get_exception_ports 例外ハンドラを返す
task_swap_exception_ports 例外ハンドラを取り替える
task_set_sched スケジュールポリシーを設定する
task_get_sched スケジュールポリシーを返す
task_get_sample タスクを解析する
ここから下はなくなる可能性がある
task_policy スケジュールポリシーを設定する
task_set_emulation ユーザレベルのハンドラを設定する
task_get_emulation_vector ユーザレベルのハンドラを返す
task_set_ras_pc リスタート PC を確立する(?)
task_assign タスクをプロセッサセットに割り当てる
task_assign_default タスクをデフォルトプロセッサに割り当てる
task_get_assignment 現在の割り当てを返す
task_set_policy スケジュールポリシーを設定する

ところで。ソースの中に、JMM っていうコメントが埋め込まれているんだけど、JMM って何(誰)?だれか知っている人教えて!

結構、数が多いね。あと、スケジュールのポリシーに関するところはこれから整理されるんでしょう。一応、説明を書いておいたけど、これだけじゃ分からないよね。詳しいことを知るにはソースを読むのがいちばん!ということで、これらのうちのいくつかのソースを読んでいくよ。


August 9, 2000

■Darwin 解体新書 - Process Overview

◆まずはプロセスから

さて、いよいよ始まるよ!まずはプロセスから。

プロセスとは何か?実はこの概念って、初めて触ったコンピュータが Mac な人には、馴染みにくいものかもしれない。なぜなら、Mac は設計理念として、計算機科学的な考え方をユーザから隠していたら。かつ、まともなプロセスを実装していなかったから(笑)。

ひとことでいうと、プログラムを実行する単位のひとつだ。分からないって?Mac の用語でいうと、アプリケーションがこれに相当すると思う *1。そして、プロセスの中では複数のスレッドが並行して走る。スレッドっていのは、プログラムを並列に同時に走らせる仕組みだ。例えば、ダイアログのプログレスバーのアニメーションがあるでしょ *2?あれは、後ろで何か処理をやっていて、それと同時にアニメーションの処理も行っているんだ。そういう感じのもの *3

*1) Mac OS X では違うことになるだろう。一つのアプリケーションが複数のプロセスで構成されるはずだ。

*2) mkino は、ほんとにプログレスバーアニメーションがスレッドを使って実装されているか知らない。だれか教えて。

*3) 昔の Mac プログラムはスレッドが使えなかったから(Thread Manager 以前)、アニメーションをやるには、Idle Event 時に動かしてやるようにしなきゃいけなかった。今思うと、泣けるよなあ。

◆Darwin のプロセス

さて、Darwin のプロセスは?プロセスでは、アドレス空間(メモリの空間のこと)と、複数のスレッドを属性として持っている。さらに、カーネルと通信するためのポートがある。テキストとして使っている、タネンバウムの『OS の基礎と応用』から、図を引用しよう。

まぁ、こんな感じ。実際のプログラムは、スレッドの中で動くと考えて。

Classic な Mac OS との最大の違いは、プロセスごとにアドレス空間があること。Mac OS の特に有名な特徴の一つは、プロセスがアドレス空間を共有していることだ(OS も!)。したがって、一つのプロセスがメモリを破壊すると(つまり爆弾をだすと)、他のプロセスにも影響を与えることができるんだ。つまり、あるアプリケーションがクラッシュすると、OS をも道連れにしてお亡くなりになるという、荒技が可能なんだ。いくら、G4 Dual が速いといっても、こんなことする OS はかなわんというのが本音だ。

アドレス空間がプロセスごとに別々ならば、あるプロセスが死んでも、他のプロセスには影響がないんだ。昨今の OS なら当たり前のことなんだけどね。

ポートっていうのは、プロセス間通信を行うためのものだ。ポートを経由したメッセージの形で、他のプロセスと通信を行う。現在の Mac OS で、プロセス間通信として用いられているものひとつは、Apple Script だ。Mac OS X でも Apple Script はサポートされるらしいけど、きっとポートを用いて実装されるんでしょう。


August 6, 2000

■Darwin 解体新書 - Mission Items

◆Darwin 解体新書プロジェクト勃発!

Darwin ソースコード読んだれプロジェクトは、Darwin 解体新書と名前を変えました。ありがちだって?うーん、たしかに。

Darwin 解体新書プロジェクトのミッションは、Mach Kernel の構造を把握し、それぞれのコンポーネントに対応する箇所のソースを読むこと。解説文を読んだだけで分かったつもりになってちゃ、どこぞの「こんさるたんとおやじ」みたいだぜ!ナマのソースコードがあるんだから、杉田玄白のように、読むべし、読むべし!

で、このプロジェクトのアイテムリストは次のとおりだ:

  • プロセス
  • スレッド
  • スケジューリング
  • メモリ
  • ポート
  • メッセージ
  • BSD エミュレーション

もうちょっと増えるかも。

◆Mach Kernel のいいところ

でも、こうやってみると、Mach Kernel は非常にシンプルだね。Mach のように、Kernel、つまり OS のコアの部分をできるだけシンプルにして、それ以外のものはユーザ空間で実行させる、という考え方をマイクロカーネルっていうんだ。Mach が提供するマイクロカーネルの機能は、プロセス管理、メモリ管理、プロセス間通信ぐらいなんだ。

それに対して、Classic な MacOS のように、OS を階層的に分離できないで一枚岩のような構造をモノリシックカーネルっていうらしい。まあ、モノリシックでも安定して動いていれば、ぜんぜん問題はないんだ、実は。問題になるのは、モノリシックカーネルでは、規模が大きくなればなるほど、管理するのが難しくなること。それは不安定と、移植しにくいという特性につながっていく。

そうそう、移植しやすいっていうのも Mach の特徴のひとつだよね。Darwin のソースコードを眺めていると、機種依存部分がきっちり分けられていることに気付くんだ。ppc 用と i386 用がきちんと用意されている。インテルチップ用の MacOS X が出るかどうか、っていうことはたいした問題じゃ無い。ポイントは、kernel のレベルできちんと他機種対応が考えられているっていうことだ。これは、MacOS X の将来性を考える上で、とても明るい材料なんだと思うな。


August 6, 2000

■まだあったのかよ OpenDoc、IBM えらい

先日、ああ書いた手前、ちょっと調べてみたよ。そうしたら、IBM ってまだ OpenDoc のページ残しているのね。toolkit のソースコードが公開されている。AIX と OS/2 と Win 用。とうぜんのごとく、Mac 用はなかった。

もしや、と思って Apple のサイトも探しにいったけど、やっぱりなかったよん。Jobs は OpenDoc の字を見るのも嫌なのか?合掌。


August 5, 2000

■Real Basic on MacOS X

MacWire に OS Xing の最新版、Mac OS X 用の Real Basic が出ていた。

この記事は非常に面白いよ!とてもいい記事だ。MacOS X の開発関係では、ひさびさのヒットだね。

記事によると、MacOS 用の RAD (Rapid Application Development) として名高い、REALbasic が MacOS X に移植され、そのα版がダウンロード可能になっているらしい。REALbasic は位置付けとしては、入門的な開発ツールっていうことができる。Code Warrior までの機能はいらないけど、簡単にアプリを作りたいなー、っていう人にお勧めだ。それだけではなく、プロの使用にも十分たえる(らしい)。

この記事で特筆すべきは、REALbasic が非常に高いポータブル性を持っていることに言及している点だ。ようは移植しやすいってことね。記事によると REALbasic の IDE (Integrated Development Environment) は 10% のコードしか Mac に依存していないそうだ。おぉ、素晴らしい!多くの部分は REALbasicのランタイムに依存していると。こういうプログラムは、設計がしっかりしていて将来性が高いねぇ。ちなみにランタイムのマシン依存はどのくらいか?残念ながら、C++ で記述されている、と書かれているだけで詳しく突っ込まれていない。取材の時に、軽くかわされたって感じかな。


August 5, 2000

■Source Code Layout

まずは、ディレクトリ構成から始めよう。今回扱うのは、xnu-1-1 だ。その中でも特に、osfmk のディレクトリを見てみるぞ。osfmk は、Open Software Foundation Mach Kernel のことだ(と思う)。

osfmk の下のディレクトリは、次のような感じだ。

conf config の情報
ddb Dave's debuger (Dave ってだれ?)
default_pager ?
device generic なデバイスルーチン
i386 Intel 386 に依存するコード
ipc プロセス間通信 (interprocess communication)
kdp ?
kern クロック、システムコール、タスク、スレッドなど
libsa ?
mach mach の include ファイル
mach-o ローダ
machine ?
mach_debug ?
ppc Power PC に依存するコード
profiling 解析用
sys いろんな include ファイル
vm 仮想メモリ (virtual memory)

? のとこはおいおいうめていくからね。

さしあたってのターゲットは、タスクやスレッドがある kern ディレクトリ、仮想メモリの vm ディレクトリ、システムコールの定義がある mach ディレクトリだな。


August 5, 2000

■Darwin を手に入れる

あっつーい!今日は暑いっす。まぁ、夜はそれなりに過ごしやすいんだけどね、ここは。

まずは、Darwin を手に入れよう。とうぜん Apple のサイトに行くわけだ。いまだ、行け!

Darwin のホームはここ。ソースコードはここからダウンロードできる。CVS を使って最新版をダウンロードすることもできるけど、今回はそこまでやらなくていいでしょう。

Darwin Project のファイルとして、たくさんのコードが用意されている。さすがにこれ全部読むのは大変なので、kernel に焦点を絞ろう。kernel のファイルは xnu。"Darwin kernel" って、すごく素っ気なく説明が書いてある。8/5/2000 の時点でのバージョンは 68.4-1.1。これをもとにすることにするよ。

さあ、ダウンロード。クリック、クリック!


August 1, 2000

■Darwin 強化月間

いきなりだが、8 月は Darwin 強化月間だ!いきなり過ぎるって?Expo で MacOS X の Public beta が出なかったうさを、Darwin で晴らそう!っていうわけだ。いや、ちょっと違うか。

みんなご承知のとおり、Darwin は MacOS X の core 部分、つまり Mach kernel の、オープンソースプロジェクトだ。人類を、神の座から猿の座に引きずりおろしたおっちゃんとは、あんまり関係がないよ。この発表があったときは、「あの、NeXT の Mach がオープンになる?うぉぉぉぉっ!」と、ごく一部の人たちを狂喜させたんだ。分かりやすくいいかえると、各ニュースメディアはさも大きなニュースのように伝えたけど、大部分のユーザには興味のないことだったんだ。さらにいえば、ニュースメディアも、ほとんどのところは、重要性を理解していなかっただろう。

その後、Darwin は地味に進んでいたらしい。バイナリがリリースされたときは、いくつかの Web でインストール報告を見たけれど、ほとんどは「とりあえず動いた、けど特に何もしなかった」だけで終わっていたもんだ。ま、当たり前か。

HMDT は違う方向から攻めるぞ!ソースコードは読んでなんぼだ!まるで新聞を一字一句読むように、まるで野菜ジュースの成分を読み当てるように、読み尽くすのがソフトウェアデベロッパーだ(偏見あり)。ソースコードは DNA といっしょで、それ以上のものもそれ以下のものもない。HMDT の方針は、読み解くためのソースコードガイドを提供することだ。

とはいっても、全部読みとおすのは、当然だが膨大すぎるので、kernel に焦点を当てる。kernel のソースコード構成を理解して、プロセス、スレッド、メモリ、ポート、メッセージの管理を行っている箇所を指摘するのがゴールだ。8 月はこれで行くぞ。


Home | Link | Download | Back Number | Speciall Issue

mailto: mkino@xd5.so-net.ne.jp

HMDT