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のプロセスディスクリプタの割り当てとカーネルスタックを用意する
- プロセス1のページディレクトリを作成する
- ユーザモードで使用されるハードウェアコンテキストの設定を行う
- プロセス1に名前をつける
- プロセス1のカレントワーキングディレクトリを設定する
- プロセス1の状態をRUNNABLEに設定する
プロセス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の場合は、外部割り込みを許可していることを示しています。
プロセス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;)。