最近xv6のシミュレーションにあまり手をつけれていなかったが、久し振りに進化したシミュレータを使ってやってみた。
mpmain()までは追い掛けることが出来たので、mpmain以降、プロセスの切り替えなどに焦点を当ててみる。
// Common CPU setup code. static void mpmain(void) { cprintf("cpu%d: starting\n", cpu->id); atomic_swap(&cpu->started, 1); // tell startothers() we're up scheduler(); // start running processes }
schedulerの動作
scheduler()の構造は以下のようになっている。ソースコードとプロセスツリーを表示してみる (プロセスツリーはちょっと読み難いが...)
void scheduler(void) { struct proc *p; for(;;){ // Enable interrupts on this processor. enableinterrupt(); // Loop over process table looking for process to run. acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; // Switch to chosen process. It is the process's job // to release ptable.lock and then reacquire it // before jumping back to us. proc = p; switchuvm(p); p->state = RUNNING; swtch(&cpu->scheduler, proc->context); switchkvm(); // Process is done running for now. // It should have changed its p->state before coming back. proc = 0; } release(&ptable.lock); } }
まず、ptableをサーチして、プロセステーブル上で実行可能なプロセスを探す。今回の例では、どのプロセスが当たったのだろう。
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; cprintf("<scheduler : %s was selected>\n", p->name); ...
proc構造体を見れば分かるとおり、nameという変数があるね。表示させてみよう。
xv6... cpu0: starting <scheduler : initcode was selected>
initcodeか。これはどこで登録されているのかな?
// Set up first user process. 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, p->asid, _binary_initcode_start, (int)_binary_initcode_size); p->sz = PGSIZE; memset(p->tf, 0, sizeof(*p->tf)); p->tf->sp = PGSIZE; p->tf->status = (read_cop0_status() | STATUS_KSU_USER | STATUS_EXL | STATUS_IE) & ~STATUS_ERL; p->tf->epc = (uint)0; // beginning of initcode.S safestrcpy(p->name, "initcode", sizeof(p->name)); p->cwd = namei("/"); p->state = RUNNABLE; }
この中で何をやっているかというと、
- allocproc()
ptableの中を線形に探索していき、UNUSEDな領域を探している。UNUSEDな領域を発見すると、状態(state)をEMBRYOに設定、新しいPIDを割り当てる。 次にカーネルスタックを割り当てるのだが、これにはkallocを使う。kallocは、kmemというフリーリストの中から、空いている場所を確保する関数のようだ。 kmem自体は空っぽのポインタチェーンだが、何を格納するのだろう。
struct run { struct run *next; }; struct { struct spinlock lock; int use_lock; struct run *freelist; } kmem;
その後、p->kstackにfreelistから取り出したポインタを格納し、KSTACKSIZEを加算することでカーネルスタックを確保している。 次に確保されるトラップフレームの領域は、トラップ処理に入ったときに退避するレジスタを格納する領域のようだ。
x86の実装では、以下のようにトラップフレームが定義されていた。x86.hの中だ。
struct trapframe { // registers as pushed by pusha uint edi; uint esi; uint ebp; uint oesp; // useless & ignored uint ebx; uint edx; uint ecx; uint eax; // rest of trap frame ushort gs; ushort padding1; ushort fs; ushort padding2; ushort es; ushort padding3; ushort ds; ushort padding4; uint trapno; // below here defined by x86 hardware uint err; uint eip; ushort cs; ushort padding5; uint eflags; // below here only when crossing rings, such as from user to kernel uint esp; ushort ss; ushort padding6; };
最後に、戻り先アドレスをforkretに設定することで、プロセスを終了したときにforkretを通じて戻ってこれるようになる。
// Set up new context to start executing at forkret, // which returns to trapret. sp -= sizeof *p->context; p->context = (struct context*)sp; memset(p->context, 0, sizeof *p->context); p->context->ra = (uint)forkret;
付録 関数コール構造
<FunctionCall 59149660: scheduler(0x80106964)> <FunctionCall 59149666: enableinterrupt(0x80105e8c)> <FunctionCall 59149672: is_interruptible(0x80105e4c)> <FunctionCall 59149678: read_cop0_status(0x80105df0)> <Return: read_cop0_status> <Return: is_interruptible> <FunctionCall 59149703: read_cop0_status(0x80105df0)> <Return: read_cop0_status> <FunctionCall 59149721: write_cop0_status(0x80105e1c)> <Return: write_cop0_status> <Return: enableinterrupt> <FunctionCall 59149744: acquire(0x801072e0)> <FunctionCall 59149751: pushcli(0x80107554)> <FunctionCall 59149758: disableinterrupt(0x80107198)> <FunctionCall 59149764: is_interruptible(0x80107158)> <FunctionCall 59149770: read_cop0_status(0x801070fc)> <Return: read_cop0_status> <Return: is_interruptible> <Return: disableinterrupt> <FunctionCall 59149811: is_interruptible(0x80107158)> <FunctionCall 59149817: read_cop0_status(0x801070fc)> <Return: read_cop0_status> <Return: is_interruptible> <Return: pushcli> <FunctionCall 59149851: holding(0x801074f8)> <Return: holding> <FunctionCall 59149873: atomic_swap(0x80107254)> <Return: atomic_swap> <FunctionCall 59149902: getcallerpcs(0x80107408)> <Return: getcallerpcs> <Return: acquire> <FunctionCall 59150136: cprintf(0x801007fc) ...> <Return: cprintf> <FunctionCall 59154917: switchuvm(0x8010a9b0)> <FunctionCall 59154924: pushcli(0x80107554)> <FunctionCall 59154931: disableinterrupt(0x80107198)> <FunctionCall 59154937: is_interruptible(0x80107158)> <FunctionCall 59154943: read_cop0_status(0x801070fc)> <Return: read_cop0_status> <Return: is_interruptible> <Return: disableinterrupt> <Return: pushcli> <FunctionCall 59155008: tlbwi(0x8010a340)> <Return: tlbwi> <FunctionCall 59155040: popcli(0x801075c0)> <FunctionCall 59155046: is_interruptible(0x80107158)> <FunctionCall 59155052: read_cop0_status(0x801070fc)> <Return: read_cop0_status> <Return: is_interruptible> <Return: popcli> <Return: switchuvm>