思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

OSの誕生

一昨日のコードのmth_sample.cは、suspend()でもresume()でも引数ctxを指定する必要があってダサい。いやダサいだけならまだしも、実際問題としてプログラミングしにくい。

例えば、整数0,1,2,3,...という数列を出力するスレッドを(1個でなく)2個走らせたいとすると、単純に思いつく書き方は次のコードだが、、

リスト1
TCtx ctx1 = { 4096 };
TCtx ctx2 = { 4096 };

void bar1() {   /* スレッド1 */
  unsigned x = 0;
  for (;;) {
    printf("%d ", x++);
    suspend(&ctx1);
  }
}

void bar2() {    /* スレッド2 */
  unsigned x = 0;
  for (;;) {
    printf("%d ", x++);
    suspend(&ctx2);
  }
}

main() {
  exec(&ctx1, bar1);
  exec(&ctx2, bar2);
  for (;;) {
    resume(&ctx1);
    resume(&ctx2);
  }
}

こんな調子でスレッドの数だけ似たような関数(bar1(), bar2(), ...)を書かされるのではかなわない。第一、スレッドの個数を動的に変えられない。(変えるには、関数のコピー&ペーストと再コンパイルが必要になる。)

問題の根源は、bar1()やbar2()といったスレッド関数(スレッドの処理ロジックを記述した関数)に、ctx1とかctx2とかいった、具体的な識別子(これらはマイクロスレッドコンテキストを指定する)を埋め込むことにあることが見て取れる。具体的に埋め込まなければおkなのではなかろうか、ってことで次のコード。

リスト2
TCtx ctx1 = { 4096 };
TCtx ctx2 = { 4096 };

TCtx *g_pCtx;     /* ! */

void bar() {      /* スレッド関数 */
  unsigned x = 0;
  for (;;) {
    printf("%d ", x++);
    suspend(g_pCtx);
  }
}

main() {
  g_ctx = &ctx1; exec(g_ctx, bar);
  g_ctx = &ctx2; exec(g_ctx, bar);
  exec(ctx2, bar);
  for (;;) {
    g_ctx = &ctx1; resume(g_ctx);
    g_ctx = &ctx2; resume(g_ctx);
  }
}

さらにもう一押し。

リスト3
extern void pure_suspend(void);

/*------------------------------------------------------------------------*
 *  スレッド関数
 *------------------------------------------------------------------------*/

void bar() {
  unsigned x = 0;
  for (;;) {
    printf("%d ", x++);
    pure_suspend();
  }
}


/*------------------------------------------------------------------------*
 *  ディスパッチャ
 *------------------------------------------------------------------------*/

TCtx ctx1 = { 4096 };
TCtx ctx2 = { 4096 };

TCtx *g_pCtx;           /* ! */

void pure_suspend() {   /* !! */
  suspend(g_pCtx);
}

main() {
  g_ctx = &ctx1; exec(g_ctx, bar);
  g_ctx = &ctx2; exec(g_ctx, bar);
  exec(ctx2, bar);
  for (;;) {
    g_ctx = &ctx1; resume(g_ctx);
    g_ctx = &ctx2; resume(g_ctx);
  }
}

リスト2〜3にかけて、二つのメカニズムを導入した。

  1. CPU時間割り当て対象のマイクロスレッドコンテキストを指す大域変数g_pCtx
  2. 「マイクロスレッドの中断」という機能を抽象化した関数pure_suspend()

そして今や、次のことが達成されている。

  1. bar()を分離して別のソースファイルに書ける
  2. bar()を同一処理の複数のスレッドについて使いまわせる
  3. main()がCPU時間割り当て対象のスレッドを常に把握している

我々はここに、ギリシャ神話に朧に姿を見せた自動機械の夢からチャールズ・バベッジの機械、ワイヤードロジック、そしてプログラム内臓方式を経て自意識をもつ機械知性に至る進化の里程において、種の量的拡大のきっかけとなる重要なイベント、OSの誕生を目の当たりにした!
…とか書いちゃだめですかOS論な教科書に怒られちゃいますかそうですか_| ̄|○|||*1

まだスレッドの個数を動的に変えるという目標にはとどいていないが、それはあと少しの工夫でできる予定…は未定
何しろ日記だし、
(つづく)

*1:正統とされるOSの進化史は、CPUとI/Oの処理の速度差を埋め合わせるための割り込みメカニズムの発明→TSSでCPUを活用し倒す技法としてのマルチタスキング技法の台頭→汎用機売り込みのための共通アーキテクチャ整備(System/360)→仮想メモリや階層化ファイルシステムやDLL等のアイデアを取り込んで重厚長大OSとして結実(Multics)→生息環境の変化(人間組織やハードウェアの離合集散)に起因する分化と淘汰(Unix等)→GUIとネットワーク能力の獲得(Unixの末裔とWindows他)、らしい(多分)。