vm.c void switchuvm(struct proc *p)

トップページ
jupiteroak.hatenablog.com


vm.c
https://github.com/mit-pdos/xv6-public/blob/master/vm.c#L156

void
switchuvm(struct proc *p)
{
  if(p == 0)
    panic("switchuvm: no process");
  if(p->kstack == 0)
    panic("switchuvm: no kstack");
  if(p->pgdir == 0)
    panic("switchuvm: no pgdir");

  pushcli();
  mycpu()->gdt[SEG_TSS] = SEG16(STS_T32A, &mycpu()->ts,
                                sizeof(mycpu()->ts)-1, 0);
  mycpu()->gdt[SEG_TSS].s = 0;
  mycpu()->ts.ss0 = SEG_KDATA << 3;
  mycpu()->ts.esp0 = (uint)p->kstack + KSTACKSIZE;
  // setting IOPL=0 in eflags *and* iomb beyond the tss segment limit
  // forbids I/O instructions (e.g., inb and outb) from user space
  mycpu()->ts.iomb = (ushort) 0xFFFF;
  ltr(SEG_TSS << 3);
  lcr3(V2P(p->pgdir));  // switch to process's address space
  popcli();
}

switchuvm関数は、現在使用されているページディレクトリから、プロセスディスクリプタpに対応するプロセス(ユーザプログラム)において使用されるページディレクトリへ切り替えます。

引数 struct proc *p
切り替え先ページディレクトリが使用されるプロセス(ユーザプログラム)に関連したプロセスディスクリプタの先頭アドレスです。


処理の内容

プロセスディスクリプタがない場合

if(p == 0)
    panic("switchuvm: no process");

p == 0 が真となる場合→プロセスディスクリプタがない場合は、panic関数を呼び出してメッセージを出力します。

カーネルスタックのトップアドレスがない場合

if(p->kstack == 0)
   panic("switchuvm: no kstack");

p->kstack が真となる場合→カーネルスタックのトップアドレスがない場合は、panic関数を呼び出してメッセージを出力します。

ページディレクトリがない場合

if(p->pgdir == 0)
   panic("switchuvm: no pgdir");

p->pgdir == 0 がない場合は、ページディレクトリがない場合は、panic関数を呼び出してメッセージを出力します。

割り込みを無効化する

  pushcli();

pushcli関数を呼び出して、ハードウェア割り込みを無効化(マスク)します。
これから操作するプロセスディスクリプタに関連しているプロセスがスケジューリングされないようにします。

タスク状態セグメントに関わる設定を行う

  mycpu()->gdt[SEG_TSS] = SEG16(STS_T32A, &mycpu()->ts,
                                sizeof(mycpu()->ts)-1, 0);
  mycpu()->gdt[SEG_TSS].s = 0;
  mycpu()->ts.ss0 = SEG_KDATA << 3;
  mycpu()->ts.esp0 = (uint)p->kstack + KSTACKSIZE;
  // setting IOPL=0 in eflags *and* iomb beyond the tss segment limit
  // forbids I/O instructions (e.g., inb and outb) from user space
  mycpu()->ts.iomb = (ushort) 0xFFFF;
  ltr(SEG_TSS << 3);

タスク状態セグメントディスクリプタを設定する

mycpu()->gdt[SEG_TSS] = SEG16(STS_T32A, &mycpu()->ts, sizeof(mycpu()->ts)-1, 0);
mycpu()->gdt[SEG_TSS].s = 0;


mycpu()->gdt[SEG_TSS] = SEG16(STS_T32A, &mycpu()->ts, sizeof(mycpu()->ts)-1, 0);
タスク状態セグメントについての設定を行います。タスク状態セグメントの設定は、グローバルディスクリプタテーブル(cpu構造体のメンバであるgdt)の6つ目(SEG_TSS→インデックス5)のセグメントディスクリプタに、SEGマクロ16を使って記述します。

1つ目の引き数(type)は、セグメントの種類を指定する4bitの値です。
設定値は STS_T32A→0x9→0b 1001なので、GDTの6つ目にあるセグメントディスクリプタは、タスク状態セグメントディスクリプタになります。

2つ目の引数(base)は、セグメントベース(タスク状態セグメントの先頭アドレス)となる32bitの値です。
設定値は &mycpu()->ts(cpu構造体のtsメンバの先頭アドレス)です。

3つ目の引数(lim)は、セグメントのサイズを指定するリミット値(セグメントのサイズ-1)です。
設定値は、 sizeof(mycpu()->ts)-1なので、cpu構造体のtsメンバの先頭アドレスから終端アドレスまでがタスク状態セグメントの範囲となります)。

4つ目の引数(dpl)は、セグメントへのアクセスに必要なCPUの特権レベルを指定する2bitの値です。
カーネルモードで使用されるセグメントなので、設定値は0(ディスクリプタ特権レベル0)が設定されます。


mycpu()->gdt[SEG_TSS].s = 0;
タスク状態セグメントディスクリプタのsフラグに0を設定します(タスク状態セグメントディスクリプタとしてセグメントディスクリプタを利用する場合は、sフラグに0を設定します)。

タスク状態セグメントに値を設定する

  mycpu()->ts.ss0 = SEG_KDATA << 3;
  mycpu()->ts.esp0 = (uint)p->kstack + KSTACKSIZE;
  // setting IOPL=0 in eflags *and* iomb beyond the tss segment limit
  // forbids I/O instructions (e.g., inb and outb) from user space
  mycpu()->ts.iomb = (ushort) 0xFFFF;


mycpu()->ts.ss0 = SEG_KDATA << 3;
タスク状態セグメントに、カーネルモードで使用されるssレジスタの値を保存しておきます。
カーネルモードで使用されるssレジスタの値は、カーネルデータセグメントのセレクタ値SEG_KDATA << 3です。
タスク状態セグメントに保存されたssレジスタの値は、割り込み・例外・システムコールなどで、ユーザモードからカーネルモードへ切り替わる時に使用されます。


mycpu()->ts.esp0 = (uint)p->kstack + KSTACKSIZE;
タスク状態セグメントに、カーネルモードで使用されるspレジスタ(スタックポインタ)の値を保存しておきます。
カーネルモードで使用されるspレジスタ(スタックポインタ)の値は、プロセスが持つカーネルスタックのボトムアドレスp->kstack + KSTACKSIZEの値です。
タスク状態セグメントに保存されたspレジスタの値は、割り込み・例外・システムコールなどで、ユーザモードからカーネルモードへ切り替わる時に使用されます。


mycpu()->ts.iomb = (ushort) 0xFFFF;
ユーザーモードプロセスがin/out命令を使ってI/Oポートを利用できないようにするために、タスク状態セグメントのI/Oパーミッションビットマップを全て1に設定します(iombメンバに0xFFFFを設定します)。

タスクレジスタにロードする

  ltr(SEG_TSS << 3);

ltr関数を呼び出して、タスク状態セグメントディスクリプタセレクタ値SEG_TSS << 3をタスクレジスタにロードします。

CR3レジスタにロードする

  lcr3(V2P(p->pgdir));  // switch to process's address space

lcr3関数を呼び出して、プロセスディスクリプタpに対応しているプロセス(ユーザープログラム)において使用されるぺージディレクトリの先頭アドレス(物理アドレス)をCR3レジスタに設定します。

割り込みを有効化する

  popcli();

popcli関数を呼び出して、ハードウェア割り込みを有効化します。