map.pdf の Running a process のメモ

以下にて。メモということでご容赦願います。

Running a process

  • scheduler は state 属性が RUNNABLE なプロセスを探すんですが、現時点ではそれは userinit で生成されるプロセスなオブジェクト
  • switchuvm 手続きは target process な page table を使って start することを hardware に教えるよ、とあります
    • たしかにプロセス構造体の pgdir 属性を cr3 にロードしてますね
    • gdt もリセットされてますがスルー? って思ったら次の文で云々な模様
  • 以下な記述は確認が必要
  // Setup TSS
  cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0);
  cpu->gdt[SEG_TSS].s = 0;
  cpu->ts.ss0 = SEG_KDATA << 3;
  cpu->ts.esp0 = (uint)proc->kstack + KSTACKSIZE;
  ltr(SEG_TSS << 3);
    • 記述として_switchuvm also creates a new task state segment SEG_TSS that instructs the hardware to handle an interrupt by returning to kernel mode with ss and esp set to SEG_KDATA<<3 and (uint)proc->kstack+KSTACKSIZE, the top of this process’s kernel stack._というものがありますが、stack segment register が SEG_KDATA<<3 に、というのがナニ
    • SEG_* は proc.h で定義されてて以下らしい
// Segments in proc->gdt.
// Also known to bootasm.S and trapasm.S
#define SEG_KCODE 1  // kernel code
#define SEG_KDATA 2  // kernel data+stack
#define SEG_KCPU  3  // kernel per-cpu data
#define SEG_UCODE 4  // user code
#define SEG_UDATA 5  // user data+stack
#define SEG_TSS   6  // this process's task state
#define NSEGS     7
      • bootasm.S と trapasm.S で云々というコメントがありますね
  • TSS って何かと思い調べてみましたら Task State Segment というものらしく詳解 Linux Kernel なソレによれば以下とのこと
    • 80x86 CPU がユーザモードからカーネルモードに切り替わるとき、カーネルモードスタックのアドレスを TSS から読み込みますという記述があるけど、xv6 なコードとは整合してない風に見えます
      • とりあえず流して後で SEG16 なマクロとか確認の方向
  • scheduler はプロセスの state 属性を RUNNING にして swtch 手続きを呼び出す
    • _to perform a context switch to the target process's kernel thread_って記述があります
    • 呼び出し記述は _swtch(&cpu->scheduler, proc->context);_ となってます
    • swtch 手続きは swtch.S です
    • 定義が以下
# Context switch
#
#   void swtch(struct context **old, struct context *new);
# 
# Save current register context in old
# and then load register context from new.
      • old は current context なのか (値が変更される可能性あり?
      • new は新たに load される云々とありますね
.globl swtch
swtch:
  movl 4(%esp), %eax
  movl 8(%esp), %edx
      • 引数を順に eax と edx レジスタに格納
      • old が eax で new が edx で良いのかどうか
  # Save old callee-save registers
  pushl %ebp
  pushl %ebx
  pushl %esi
  pushl %edi
  # Switch stacks
  movl %esp, (%eax)
  movl %edx, %esp
      • current なスタックを eax に退避して edx を esp に load
  # Load new callee-save registers
  popl %edi
  popl %esi
  popl %ebx
  popl %ebp
  ret
      • switch した stack からレジスタを復帰して ret
      • ret って何するんでしたっけ
    • あら? struct context って stack なんスか?

とゆーことで

深みにハマらないうちに手を止めます。と言いつつちょっと確認。
ret 命令は上記の状態で ip が stack のてっぺんにあるこを前提にしていそげ。あー、cpu->scheduler とか proc->context なのか。ちなみに cpu->scheduler も context 構造体のポインタになってたりして。

struct context 型

うをを、コメントまで引用してやれ。

// Saved registers for kernel context switches.
// Don't need to save all the segment registers (%cs, etc),
// because they are constant across kernel contexts.
// Don't need to save %eax, %ecx, %edx, because the
// x86 convention is that the caller has saved them.
// Contexts are stored at the bottom of the stack they
// describe; the stack pointer is the address of the context.
// The layout of the context matches the layout of the stack in swtch.S
// at the "Switch stacks" comment. Switch doesn't save eip explicitly,
// but it is on the stack and allocproc() manipulates it.
struct context {
  uint edi;
  uint esi;
  uint ebx;
  uint ebp;
  uint eip;
};

もうそのまんまじゃないスか。てーかこいつらどこで設定されているのか。というのがアレなんですが、直前の Process creation から再確認しないとダメなことに気づいていたりしています。
forkret とか trapret のあたりがアレ? というか userinit から呼ばれる allocproc なのか。このへん? (@allocproc)

  sp -= sizeof *p->context;
  p->context = (struct context*)sp;
  memset(p->context, 0, sizeof *p->context);
  p->context->eip = (uint)forkret;
  return p;

ああ、やっぱ Process creation からもっかい見た方が良さげ。

Process creation

以下な記述があるよorz

Now allocproc must set up the new process’s kernel stack.

とほほ。やっぱ allocproc って特別なのだな。