map.pdf の Running a process のメモ (2)

e*lipse がメモリ大量消費するお陰で披露困憊。なんとか頑張ってみます。

とりあえず Process creation の項から

ざくっと再度確認。ええと、struct proc の kstack な属性についての説明が意味分からんぞ。_for example in a system call_という記述がありますな。あ、_xv6 allocates one kernel stack for each process._という記述もある。
IO wait とか system call からの復帰アドレスとかを持っとく、って理解で良いのかな。

userinit 手続き

属性の説明はスルーして userinit のあたりの記述を確認。
allocproc の仕事は_to allocate a slot (a struct proc) in the process table and to initialize the parts of the process’s state required for its kernel thread to execute._とあります。
あ、と思ったら kstack 属性をもごもごしてます。kstack って何だ。ってマンガを引用したのは 8/6 なエントリらしい。

Here is the state of the new process’s kernel stack:
---------- <-- top of new process’s kernel stack
|   esp   |
|   ...   |
|   eip   |
|   ...   |
|   edi   | <-- p->tf (new proc’s user registers)
| trapret | <-- address forkret will return to
|   eip   |
|   ...   |
|   edi   | <-- p->context (new proc’s kernel registers)
|         |
| (empty) |
|         |
---------- <-- p->kstack

allocproc 手続きでは上記 p->kstack の領域を確保して上のマンガの通り、p->tf が指す領域、p->context が指す領域、を設定してたり trapret を stack に格納してたりしますね。あと、p->context->eip は forkret の開始アドレスが格納されるのかな。
上記マンガによれば trapframe が user 側で context が kernel 側なのかどうか。
ということで確認したところでは Process creation は userinit 手続きの中身の解説になっているようです。セグメントレジスタ云々なあたりは若干微妙でしたが、なんとか理解ができているのかどうか。

Running a process

mpmain の中で呼ばれている scheduler 手続きに関する解説なのかどうか。task state segment に関する記述がちょろっと出てきますが、別途再確認する模様。
あ、あと swtch ですが callee 側の記述が以下。

      swtch(&cpu->scheduler, proc->context);

で、swtch の中ですが、まず引数をレジスタに確保して

swtch:
  movl 4(%esp), %eax
  movl 8(%esp), %edx

次にレジスタを stack に格納。

  # Save old callee-save registers
  pushl %ebp
  pushl %ebx
  pushl %esi
  pushl %edi

stack を switch らしい

  # Switch stacks
  movl %esp, (%eax)
  movl %edx, %esp

これ、元が cpu->scheduler に格納されて proc->context が新たな stack になる模様。で、そこから pop して

  # Load new callee-save registers
  popl %edi
  popl %esi
  popl %ebx
  popl %ebp

ret します。

  ret

で、上述の通り、proc->context->eip は forkret なアドレスが入ってて、ret でそれが eip にセットされるはず。このあたりの記述も mem.pdf にありますね。
forkret は以下の通り

forkret(void)
{
  // Still holding ptable.lock from scheduler.
  release(&ptable.lock);
  
  // Return to "caller", actually trapret (see allocproc).
}

なので再度 ret 命令が実行されるんですが、eip の次は trapret の関数ポインタが設定されております。ので、trapret に jmp する、ということなのか。で、trapret の手続き定義が以下。

  # Return falls through to trapret...
.globl trapret
trapret:
  popal
  popl %gs
  popl %fs
  popl %es
  popl %ds
  addl $0x8, %esp  # trapno and errcode
  iret

popal してtrapframe なソレを pop して iret されておる。てか、なんでここまで面倒なことをしないといけないのかと小一時間。。
このあたりが佳境だとは思うのですが意味不明。カギは trap frame ッスか? んーと proc.c の userinit 手続きの以下あたりがそうなの? と言いつつ trapframe 構造体の定義確認してびっくり。上の命令がそのまんま通用する形で定義されとるやんけ。

なるほど

ここまでは腑にオチました。が、次の _%eip holds zero and %esp holds 4096. These are virtual addresses in the process's address space._ というあたりがナニ。もうちょいですが、ここで中断。

再開

allocuvm 手続きが PTE を用意する云々な記述がありますが、allocuvm 手続きを呼び出すのは相当限定された手続き。ええと、って思っていたらどうも load される initcode.S なソレから exec な system call が呼び出されてて、exec から allocuvm が起動されているのかどうか。
あ、initcode の page table は inituvm でセットされているのか。で、initcode が exec するのは init という実行ファイルなのかな。init.c というソースファイルがありますね。

Exercises

  1. swtch で breakpoint 設定して forkret とか trapret のあたりをもごもごしなさい、とあります
  2. 実際の OS でどうやってメモリサイズを確定させてるかを見れ

とのこと。明日以降で確認してみます。