|
HMDT - Back Number / August, 2000 |
Augsut, 2000■なつやすみちょっと遅めのなつやすみをいただきます。9 月の頭まで更新をお休みすると思います。 9 月の半ばには Mac OS X もでるでしょうから、そしたら本格的に行くぞっ。 休みは北海道に行く予定。えへへ、、、 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
今日は 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_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() の中でやっているのは、これらの構造体に適当な値を放り込んでやることだ。興味は、どうやって値を得るかっていうことよりも、このコールを呼んだ側でこれらの値をどうやって使うか、っていうことにあるよね。
中断されたら再開せねば。というわけで、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() して、task の user_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() の逆だね。
次いこう!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 ではなかったら、中断させる必要がないんで終わり。次に task の user_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 を増やすことと、スレッドをロックすることで実行されるわけ。
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 を呼ぶ、、、
別にさみしがらんでもいいがな。
SGI の Open Inventor が Open Source になったよ!
Open Inventor は簡単に説明すると、Open GL の上に構築される、ハイレベル API だ。昔、これが使えなくてくやしい思いをしたからなぁ。時代は変わるもんだ。
Mac OS X で動かしてみーようっと。やることがいっぱいできたなぁ、、、
Java FAQ 経由で知った、IBM の Java 関係技術情報、Java technology zone のページ。大変、有用な情報が多いです。Java プログラミングの中級〜上級テクニックだね。こういう情報は、書籍では手に入りにくいから、雑誌か Web が頼りだよ。
IBM は alphaWorks といい、Java に関する基礎的な技術力がしっかりとある、という印象があるよ。Mac OS X が来たら、本格的に alphaWorks を servey してみるか、、、
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_ports と num_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)。
セットされていない場合は、デフォルトが割当たる。
ここまでで変数の初期化が終わって、タスクをスタートさせる処理が続く (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);
}
|
あー、疲れた。これでタスクの完成でーす。
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 が指定されていないので、lock は mutex_t ということになる。ref_count、active、kernel_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 */
|
/* 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 */
|
続いてプロセス間通信 (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 */
|
/* 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 */
|
/* User space system call emulation support */
struct eml_dispatch *eml_dispatch;
/* Ledgers */
struct ipc_port *wired_ledger_port;
struct ipc_port *paged_ledger_port;
|
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 */
|
で、おしまい。
} Task; |
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 って何(誰)?だれか知っている人教えて!
結構、数が多いね。あと、スケジュールのポリシーに関するところはこれから整理されるんでしょう。一応、説明を書いておいたけど、これだけじゃ分からないよね。詳しいことを知るにはソースを読むのがいちばん!ということで、これらのうちのいくつかのソースを読んでいくよ。
さて、いよいよ始まるよ!まずはプロセスから。
プロセスとは何か?実はこの概念って、初めて触ったコンピュータが Mac な人には、馴染みにくいものかもしれない。なぜなら、Mac は設計理念として、計算機科学的な考え方をユーザから隠していたら。かつ、まともなプロセスを実装していなかったから(笑)。
ひとことでいうと、プログラムを実行する単位のひとつだ。分からないって?Mac の用語でいうと、アプリケーションがこれに相当すると思う *1。そして、プロセスの中では複数のスレッドが並行して走る。スレッドっていのは、プログラムを並列に同時に走らせる仕組みだ。例えば、ダイアログのプログレスバーのアニメーションがあるでしょ *2?あれは、後ろで何か処理をやっていて、それと同時にアニメーションの処理も行っているんだ。そういう感じのもの *3。
*1) Mac OS X では違うことになるだろう。一つのアプリケーションが複数のプロセスで構成されるはずだ。
*2) mkino は、ほんとにプログレスバーアニメーションがスレッドを使って実装されているか知らない。だれか教えて。
*3) 昔の Mac プログラムはスレッドが使えなかったから(Thread Manager 以前)、アニメーションをやるには、Idle Event 時に動かしてやるようにしなきゃいけなかった。今思うと、泣けるよなあ。
さて、Darwin のプロセスは?プロセスでは、アドレス空間(メモリの空間のこと)と、複数のスレッドを属性として持っている。さらに、カーネルと通信するためのポートがある。テキストとして使っている、タネンバウムの『OS の基礎と応用』から、図を引用しよう。
まぁ、こんな感じ。実際のプログラムは、スレッドの中で動くと考えて。
Classic な Mac OS との最大の違いは、プロセスごとにアドレス空間があること。Mac OS の特に有名な特徴の一つは、プロセスがアドレス空間を共有していることだ(OS も!)。したがって、一つのプロセスがメモリを破壊すると(つまり爆弾をだすと)、他のプロセスにも影響を与えることができるんだ。つまり、あるアプリケーションがクラッシュすると、OS をも道連れにしてお亡くなりになるという、荒技が可能なんだ。いくら、G4 Dual が速いといっても、こんなことする OS はかなわんというのが本音だ。
アドレス空間がプロセスごとに別々ならば、あるプロセスが死んでも、他のプロセスには影響がないんだ。昨今の OS なら当たり前のことなんだけどね。
ポートっていうのは、プロセス間通信を行うためのものだ。ポートを経由したメッセージの形で、他のプロセスと通信を行う。現在の Mac OS で、プロセス間通信として用いられているものひとつは、Apple Script だ。Mac OS X でも Apple Script はサポートされるらしいけど、きっとポートを用いて実装されるんでしょう。
Darwin ソースコード読んだれプロジェクトは、Darwin 解体新書と名前を変えました。ありがちだって?うーん、たしかに。
Darwin 解体新書プロジェクトのミッションは、Mach Kernel の構造を把握し、それぞれのコンポーネントに対応する箇所のソースを読むこと。解説文を読んだだけで分かったつもりになってちゃ、どこぞの「こんさるたんとおやじ」みたいだぜ!ナマのソースコードがあるんだから、杉田玄白のように、読むべし、読むべし!
で、このプロジェクトのアイテムリストは次のとおりだ:
もうちょっと増えるかも。
でも、こうやってみると、Mach Kernel は非常にシンプルだね。Mach のように、Kernel、つまり OS のコアの部分をできるだけシンプルにして、それ以外のものはユーザ空間で実行させる、という考え方をマイクロカーネルっていうんだ。Mach が提供するマイクロカーネルの機能は、プロセス管理、メモリ管理、プロセス間通信ぐらいなんだ。
それに対して、Classic な MacOS のように、OS を階層的に分離できないで一枚岩のような構造をモノリシックカーネルっていうらしい。まあ、モノリシックでも安定して動いていれば、ぜんぜん問題はないんだ、実は。問題になるのは、モノリシックカーネルでは、規模が大きくなればなるほど、管理するのが難しくなること。それは不安定と、移植しにくいという特性につながっていく。
そうそう、移植しやすいっていうのも Mach の特徴のひとつだよね。Darwin のソースコードを眺めていると、機種依存部分がきっちり分けられていることに気付くんだ。ppc 用と i386 用がきちんと用意されている。インテルチップ用の MacOS X が出るかどうか、っていうことはたいした問題じゃ無い。ポイントは、kernel のレベルできちんと他機種対応が考えられているっていうことだ。これは、MacOS X の将来性を考える上で、とても明るい材料なんだと思うな。
先日、ああ書いた手前、ちょっと調べてみたよ。そうしたら、IBM ってまだ OpenDoc のページ残しているのね。toolkit のソースコードが公開されている。AIX と OS/2 と Win 用。とうぜんのごとく、Mac 用はなかった。
もしや、と思って Apple のサイトも探しにいったけど、やっぱりなかったよん。Jobs は OpenDoc の字を見るのも嫌なのか?合掌。
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++ で記述されている、と書かれているだけで詳しく突っ込まれていない。取材の時に、軽くかわされたって感じかな。
まずは、ディレクトリ構成から始めよう。今回扱うのは、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 ディレクトリだな。
あっつーい!今日は暑いっす。まぁ、夜はそれなりに過ごしやすいんだけどね、ここは。
まずは、Darwin を手に入れよう。とうぜん Apple のサイトに行くわけだ。いまだ、行け!
Darwin のホームはここ。ソースコードはここからダウンロードできる。CVS を使って最新版をダウンロードすることもできるけど、今回はそこまでやらなくていいでしょう。
Darwin Project のファイルとして、たくさんのコードが用意されている。さすがにこれ全部読むのは大変なので、kernel に焦点を絞ろう。kernel のファイルは xnu。"Darwin kernel" って、すごく素っ気なく説明が書いてある。8/5/2000 の時点でのバージョンは 68.4-1.1。これをもとにすることにするよ。
さあ、ダウンロード。クリック、クリック!
いきなりだが、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
|