OS起動⑰ userinit() (Xv6を読む~OSコードリーディング~)

前回
jupiteroak.hatenablog.com
トップページ
jupiteroak.hatenablog.com




main.c(一部抜粋)
https://github.com/mit-pdos/xv6-public/blob/master/main.c#L36

int
main(void)
{
  ...
  userinit();      // first user process
  ...
}

proc.c
https://github.com/mit-pdos/xv6-public/blob/master/proc.c#L120

void
userinit(void)
{
  struct proc *p;
  extern char _binary_initcode_start[], _binary_initcode_size[];

  p = allocproc();
  
  initproc = p;
  if((p->pgdir = setupkvm()) == 0)
    panic("userinit: out of memory?");
  inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);
  p->sz = PGSIZE;
  memset(p->tf, 0, sizeof(*p->tf));
  p->tf->cs = (SEG_UCODE << 3) | DPL_USER;
  p->tf->ds = (SEG_UDATA << 3) | DPL_USER;
  p->tf->es = p->tf->ds;
  p->tf->ss = p->tf->ds;
  p->tf->eflags = FL_IF;
  p->tf->esp = PGSIZE;
  p->tf->eip = 0;  // beginning of initcode.S

  safestrcpy(p->name, "initcode", sizeof(p->name));
  p->cwd = namei("/");

  // this assignment to p->state lets other cores
  // run this process. the acquire forces the above
  // writes to be visible, and the lock is also needed
  // because the assignment might not be atomic.
  acquire(&ptable.lock);

  p->state = RUNNABLE;

  release(&ptable.lock);
}

userinit関数は、OS上で最初期に実行されるプロセス(プロセス1 initcode.Sのプログラム)を作成します。


処理の内容

プロセス1のプロセスディスクリプタの割り当てとカーネルスタックを用意する

p = allocproc();
initproc = p;

allocproc関数を呼び出して、プロセスで必要となるプロセスディスクリプタの割り当てとカーネルスタックの準備を行います。
割り当てられたプロセスディスクリプタを戻り値として取得し、プロセス1に対応するプロセスディスクリプタとして扱います(initproc = p;)。

プロセス1のページディレクトリを作成する

プロセス1の仮想アドレスのマッピングを行う(カーネル空間)

if( (p->pgdir = setupkvm()) == 0)
  panic("userinit: out of memory?");

setupkvm関数を呼び出して、カーネル空間のマッピングのみが設定されたページディレクトリを作成します。
p->pgdirには、作成されたページディレクトリの先頭アドレスが格納されます。
(p->pgdir = setupkvm()) == 0 が真となる場合→ページディレクトリの作成やマッピングに失敗した場合は、panic関数を呼び出してメッセージが出力されます。

プロセス1の仮想アドレスのマッピングを行う(ユーザ空間)

inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);
p->sz = PGSIZE;

inituvm関数を呼び出し、p->pgdirで指定されページディレクトリを使って、プロセス1のユーザ空間について仮想アドレスのマッピングを行います。
仮想アドレス0x0000 0000〜0x0000 9FFFと物理アドレス4KB分のメモリ領域をマッピングし、マッピングした物理アドレスのメモリ領域にinitcode.Sのバイナリファイルの内容をロードします。
また、プロセス1のユーザ空間の大きさを記録しておきます。ロードされるメモリサイズは_binary_initcode_sizeで、4KB(PGSIZE)以下である必要があります。

ユーザモードで使用されるハードウェアコンテキストの設定を行う

カーネルスタックを初期化する

memset(p->tf, 0, sizeof(*p->tf));

memset関数を呼び出して、カーネルスタックとして使用するメモリ領域(引数p->tfで指定されたアドレスからsizeof(*p->tf)で指定されたサイズ分のメモリ領域)を0で初期化します。

CSレジスタの値を設定する

p->tf->cs = (SEG_UCODE << 3) | DPL_USER;

カーネルスタックにおいてCSレジスタの値を退避させるメモリ領域に、ユーザコードセグメントのセレクタ値を設定します。
セレクタ値のbit15-3にはユーザコードセグメントディスクリプタを指定するインデックスの値SEG_UCODEを設定し、セレクタ値のbit1-0にはユーザモードとなるCPUの現行特権レベルを示す値0x3(DPL_USER)を設定します。

その他のセグメントレジスタの値を設定する

p->tf->ds = (SEG_UDATA << 3) | DPL_USER;
p->tf->es = p->tf->ds;
p->tf->ss = p->tf->ds;

カーネルスタックにおいてDSレジスタの値を退避させるメモリ領域に、ユーザデータセグメントのセレクタ値を設定します。
セレクタ値のbit15-3にはユーザデータセグメントディスクリプタを指定するインデックス値SEG_UDATAを設定し、セレクタ値のbit1-0にはユーザモードとなるCPUの現行特権レベル0x3(DPL_USER)を設定します。
ESレジスタの値を退避させるメモリ領域、SSレジスタの値を退避させるメモリ領域にも同様の値を設定します。

EFLFGSレジスタの値を設定する

p->tf->eflags = FL_IF;

カーネルスタックにおいてEFLFGSレジスタ(フラグレジスタ)の値を退避させるメモリ領域に、FL_IF(0x0000 0200)を設定します。
EFLFGSレジスタ(フラグレジスタ)のbit9(IF:インターラプトエネーブルフラグ)が1の場合は、外部割り込みを許可していることを示しています。

espレジスタの値を設定する

p->tf->esp = PGSIZE;

カーネルスタックにおいてespレジスタの値(スタックポインタの値)を退避させるメモリ領域に、PGSIZE(4096→0x1000)を設定します。
これにより、プロセス1がユーザモードで動作する際のスタック領域のボトムアドレスが0x0000 1000となります。

eipレジスタの値を設定する

p->tf->eip = 0;

カーネルスタックにおいてeipレジスタの値(プログラムカウンタの値)を退避させるメモリ領域に、0を設定します。
これにより、プロセス1がユーザモードで処理を開始する時は、0x0000 0000から処理が始まります。
前の処理でinituvm関数を呼び出し、仮想アドレス0x0000 0000〜0x0000 9FFFのメモリ領域にinitcode.Sのバイナリファイルの内容をロードしているので、プロセス1がユーザモードで処理を開始する時は、initcode.Sのプログラムが実行されます。

プロセス1に名前をつける

safestrcpy(p->name, "initcode", sizeof(p->name));

safestrcpy関数を呼び出して、デバッグ用としてプロセスに名前をつけます(“initcode”の値をp->nameに書き込みます)。

プロセス1のカレントワーキングディレクトリを設定する

p->cwd = namei("/");

namei関数を呼び出してルートディレクトリ("/")に対応するiノードを取得し、取得したiノードをプロセスディスクリプタのcwdに記録することで、プロセス1のカレントワーキングディレクトリをルートディレクトリ("/")として設定しています。

プロセス1の状態をRUNNABLEに設定する

acquire(&ptable.lock);

p->state = RUNNABLE;

release(&ptable.lock);

クリティカルセクション内で、プロセス1の状態をRUNNABLEに設定します(p->state = RUNNABLE;)。




次回
jupiteroak.hatenablog.com