* ソフトウェアシステム論でxv6読み会をした時のメモ(2018/04/16) [#v9d0fb30]

** xv6のdebug † [#n9cfe161]
- qemu
- /mnt/dalmore-home/one/src/xv6-rpi/src
 cd /mnt/dalmore-home/one/src/xv6-rpi/src
 /net/open/Linux/arm/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gdb 
 kernel.elf
 (gdb) b _start
 (gdb) target remote :1234
 qemu再起動
 make clean;make -f makefile-armclang qemu-debug
 CtrlA-Xで抜けれる

** Boot Sequence [#efd8496f]

***Boot [#t1a6269e]
         Kernel load
 Page    Table(Boot用のpagetableの設定)
 initialize(memory / IO / process / file)
 ini process

- Kernel load
-- x86ならEFIがある
-- ARMだとVersatile(fimware)

- pagingを設定しなければ動かない

- Boot用のpage table の設定ができた後一連の初期化を行う
--memory
-- I/O
-- process
-- file

- 初期化が終わった後
--init process(process番号1番)
--init process は linux だと rc.d/~ の下にある

** ls 時の User と System [#ka6c601e]
 User Sys
 fork
 exec ls (lsを探す)
      opendir
      open ls
      (load ls (loadの後にmemory空間の初期化を行う) 
      page table reset
      goto User Mode
 opendir
      system call (file IO) (VM)
      (filesystemを読んでまたUserに値を返す)

- 10行目 VM の説明
--lsは自分で仮装メモリにアクセスしていくので memory allocation をやる
--malloc は User library だが memory の要求は System でやらなきゃいけない 
--break とい System --call で memory を増やす
--memory を増やすと OS は最初に ls のバイナリを生成
--すると、break で取った領域が別にできる
--breakで 取った memory が全部 リアル memory に割り当てられるわけではない 
--いくつかは仮想メモリに行く
--memory にアクセスした時に仮装メモリだったらtrapしてmemoryを割り当てる 
--memoryが割り当てられなければ、他の実メモリを追い出して書き換える 
--VM関係の一連の処理がある

-fork
--forkすると process structure ができる 
--process自体は active や waitの状態を持っている
--複数のactiveがあると順に実行していく(scheduler)

-kernel の要素
--process management 
--scheduler
--file IO
--Virtual Memory

この4つの Kernel の要素を読んでいく

** X.v6 の trace [#hb1fbcdd]
- arm 用の gdb で kernel.elf を開く
- dalmoreに入って
 cd /mnt/dalmore-home/one/src/xv6-rpi/src
 /net/open/Linux/arm/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gdb kernel .elf

qemuを立ち上げる 見たいのは Boot Sequence だから qemu-debug を使う
 make clean; make -f makefile-armclang qemu-debug

***makefile-armclang の qemu-debug 部分 [#cfdfbc81]

 qemu-debug : kernel.elf
         @clear
         @echo "Press Ctrl-A and then X to terminate QEMU session\n"
         export QEMU_AUDIO_DRV=none ; $(QEMU)  -M versatilepb -m 128 -cpu ${QEMUCPU
 } -nographic -singlestep -d exec,cpu,guest_errors -D qemu.log -kernel kernel.elf -s -S

- -M versatilepb
--仮想メモリにこの firmwere を使うという指示 
--versarilepb は Raspi(ARM) のfirmwereの一種

- -m 128
-- memory の量
- -cpu ${QEMUCPU} 
-- cpu の種類
-- アーキテクチャによって命令違う 
-- armのxv6を作る時のcompile時にcpuに教えるcpuの種類と合わせる必要がある 
-- 名前がqemu側とcompile側のcpuの名前が違うので試行錯誤して合わせる

- -s -S
-- Boot時に debugger が接続するまで止めるようにする

 ** gdb
 (gdb)b _start
 # remote 接続
 (gdb) target remote :1234
 
 # bt: stackを参照して、stack上のfanction callの履歴を調べる
 (gdb)bt
 
 (gdb)info registarts (初期のレジスタ) 
 r0 ~ r12 (ARMのレジスタは0~12)
 sp (stack pointer)
 lr (link regester)
 pc (program counter)
 cpsr (cpu statusのレジスタ)
 
 (gdb)disass
 # disassemblerの略
 (gdb)x/20x $pc+48+4
 0x100030
 # entry.Sのr1,=edata_entryやend_entryが$pc+48に変わってる # pcは次の命令を指している 10030->10038だから48からさらに+4
 (gdb)p (void*) edata_entry
 $1 = (void *0) 0x10588 <edata_entry>  
 # 10588がレジスタのr1に入る
 (gdb)stepi
 (gdb)info registers
 r1 0x10588 66952
 (gdb)stepi
 (gdb)info registers
 r1  0x10588 66952
 r2  0x19000 102400
 (gdb)define ni
 >x/1i $pc
 >x/1i $pc
 >end
 # こうするとentry.Sを1命令ずつ実行できる
 
 (gdb)si
 0x0001000c in _start ()
 => 0x1000c <_start+12>: cmp r1,r2
 (gdb)si
 0x0001000c in _start ()
 => 0x1014c <_start+16>: cmp r1,{r3}
 
 (gdb)disass
 =>  0x00010014 <*20>: blt
 0x00010018 <*24>: msr
 # blt でループしてることがわかる
 (gdb)tb *0x00010018 (gdb)c
 # bltのループを抜ける
 (gdb)disass
 (gbd)info registers
 r1 0x19000 102400
 r2 0x19000 102400
 # r2の値になるまでr1をincrementすることでループを抜けてる事がわかる # 19000までmemoryがある
 # CPUのコントロール
 (gdb)si
 (gdb)info registers
 cpsr    0x600001d3
 # entry.Sの MSRの行を実行 (gdb)si
 (gdb)info registers cpsr 0xd3
 # CPUのコントロールレジスタを指定している  # supervisorじゃないとアクセスできない
 # entry.Sの LDR の行を実行 (gdb) si
 (gdb) info registers
 sp 0x12000
 # 0x0 -> 0x12000
 # stack ponterが設定されたのでサブルーチンコールが使えるようになる
 # サブルーチンコール 戻り先をstackに覚えておいてそこに飛ぶ
 # lr(link register)に覚えさせる事で1回だけアクセスせずに飛ぶ事ができる # BL breach & link 
 
 (gdb)si
 (gdb)info registers
 lr  0x10024
 (gdb)disass _start
    0x00010024 <+36>: b
 #startまできたのでここ以降はstart.c (gdb)l
 # cなので next が使える
 (gdb)next
 (gdb)next
 (gdb)s
 set_bootpgtbl(... lent=1048576 ...)
 (gdb)p (void*) len
 $2 = (void *) 0x100000
 # 1048576 は16進数で100000

- memory に直接 load できる値は 長い値だとmemory一旦置かないといけない
- 一番最初に呼び出すのは C で書かれたファイルじゃなくてアセンブラ
-- entry.S で行なっている

*** entry.S [#m1f4210a]
 _start:
      LDR     r1, =edata_entry
      LDR     r2, =end_entry
      MOV     r3, #0x00
 # BLT までをループ 1:
      CMP r1, r2
      STMLTIA r1!, {r3} # {}はレジスタのリスト r1にセーブする # !は セーブした数だけr1を進める(++とかと一緒)
      # Memclear とかと一緒
      BLT 1b
      # initialize stack pointers for svc modes # CPUのコントロールレジスタを指定
      # cpsr 0xd3
      MSR CPSR_cxsf, #(SVC_MODE|NO_INT)
      LDR     sp, =svc_stktop
      BL start 
      B.

- B . まで行くとそこを永遠とループする
-- ARM fault持ってるので Bではなくfaultにすべき

*** start.c [#nb867326]
166行目
 void start (void)
 {
         uint32  vectbl;
     _puts("starting xv6 for ARM...\n");
     // double map the low memory, required to enable paging
     // we do not map all the physical memory
     set_bootpgtbl(0, 0, INIT_KERNMAP, 0);
     set_bootpgtbl(KERNBASE, 0, INIT_KERNMAP, 0);

69行目
 for (idx = 0; idx < len; idx++) {
    pde = (phy << PDE_SHIFT);
    if (!dev_mem) {
    // normal memory, make it kernel-only, cachable, bufferable pde |= (AP_KO << 10) 
    |PE_CACHE | PE_BUF | KPDE_TYPE;
    // |(or) を使って足して行く
    } else {
        // device memory, make it non-cachable and non-bufferable
        pde |= (AP_KO << 10) | KPDE_TYPE;
 }

- ARMv6 page table entry
-- https://developer.arm.com/docs/ddi0211/h/memory-management-unit/hardware-page-table-translation/armv6-page-table-translation-subpage-ap-bits-disabled

* ソフトウェアシステム論でxv6読み会をした時のメモ(2018/05/27) [#v9d0fb30]

** 読んだとこ [#le8b9f37]
- exec
- initcode
- swtch
- flush_tlb
-- userスペースにだけflush する(systemにはしない)
-fetchint
--kernel 側から指定されたプロセスのアドレスから integer を取ってくるサブルーチン
-trapret
--特権モードを抜ける
-coppyout
--current page table に乗ってたら memcopy でできるけど、乗ってないからcopyout必要

** exec [#k515474c]
- xv6-rpi/src  から grep で exec を探す
-- exec.c 
-- sysecxec の2つがある
sysファイルの中で exec 呼んでるので exec から読む

- gdb側

 $ /net/open/Linux/arm/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gdb kernel.elf
 (gdb) b _start
 (gdb) target remote :1234
 (gdb) l exec
 (gdb) b exec
 (gdb) c
 (gdb) bt
 #0  exec (path=0x1c "/init", argv=0x87fdeef0) at exec.c:16  
 #1  0x800272fc in sys_exec () at sysfile.c:473
 #2  0x80026310 in syscall () at syscall.c:152
 #3  0x800277d4 in swi_handler (r=0x87fdefb8) at trap.c:14
 #4  0x80027634 in trap_swi ()
 #5  0x00000010 in ?? ()
   			
 // 最初のexecはinit 
 // trap_swi はソフトウェアインタラクト(armのsyscallの命令)
 
 (gdb) l start   
 // l _start は アンセブラ
 
  169  _puts("starting xv6 for ARM...\n");
  // OS経由じゃなく直接 uart を叩いて出力している
 
 183  set_bootpgtbl(VEC_TBL, 0, 1 << PDE_SHIFT, 0);
 184  set_bootpgtbl(KERNBASE+DEVBASE, DEVBASE, DEV_MEM_SZ, 1);
 // 一番最初の page table を設定
 
 186 load_pgtlb (kernel_pgtbl, user_pgtbl);
 // 仮想メモリで動くようになる
 // ただし、実アドレスと仮想アドレスが同じアドレスで走る 
 
  192         kmain ();
  // void kmain (void) { に飛ぶ
 
 (gdb) l kmain
 
 29          init_vmm ();
 30          kpt_freerange (align_up(&end, PT_SZ), vectbl);
 31          kpt_freerange (vectbl + PT_SZ, P2V_WO(INIT_KERNMAP));
 32          paging_init (INIT_KERNMAP, PHYSTOP);
 // 仮想メモリマネージャ(vmm) を初期化
 // freepage を仮想メモリ(vmm)に登録
 // 次のkmem_init までが仮想メモリの初期化
 
 37          trap_init (); // vector table and stacks for models
 // (ソフト|ハード)インタラクトの飛び先の vector table を初期化
 
 38          pic_init (P2V(VIC_BASE));   // interrupt controller
 // 割り込みできるようにする
  
 39          uart_enable_rx ();                  // interrupt for uart
 //  ic(集積回路) を割り込めるようにする
 // ユーザーが入力した key は割り込みで kernel に伝えられる
 
 40          consoleinit ();                             // console
 // uart が初期化されたので console が起動できる
 
 50          sti ();
 // sti(Set Interrupt Flag) で 割り込みが許可される
 // uart_enableはic(集積回路)の許可
 // cpu の割り込みを許可 
 
 52          userinit(); 
 // init のプロセスをロードして入れてるだけ
 // 実行されるのは scheduler が起動されてから
 
 (gdb) l userinit
 
 122         p = allocproc();
 // process 構造体を取ってくる
 // kernel の alloc
  
 125         if((p->pgdir = kpt_alloc()) == NULL) {
 126             panic("userinit: out of memory?");
 127         }
 // kpt_alloc は kernelの page table の allocate
 // つまり process と process のメモリ空間を allocate
 
 129         inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size
 );
 // user の仮想メモリを allocate
 
 136         p->tf->r14_svc = (uint)error_init;
 // trap flame(tf) を設定している
 // trap flame は processの実行に必要
 // process の中に current directory 入っている
 // working directory は shell が持ってればいい
 
 148         p->state = RUNNABLE;
 // OS の p->state と同じ 
 
 // userinit() {ではprocess構造体をつくっただけ
 // process に接続しただけで実行されてない
 
 // 実際の実行は scheduler から行われる
 (gdb) b scheduler
 (gdb) c
 (gdb) l
  // この段階で sti(cpuの割り込み許可) されている
 
** swtch [#e1e72660]
 (gdb) l swtchuvm
 166         pushcli();
 177         popcli();
 // pushcli と popcli の間で割り込みを禁止している
 
 174         asm("MCR p15, 0, %[v], c2, c0, 0": :[v]"r" (val):);
 // MCR がpagetable を設定するアセンブラ 
  
 175         flush_tlb();
 // userスペースにだけflush する(systemにはしない)
 // gcc llvm の syntax
  
 154         asm("MCR p15, 0, %[r], c8, c7, 0" : :[r]"r" (val):);
 // asm も  gdb 使えば追えるが大事なのは switch  
 
 (gdb) l swtch 
 // swtch はアセンブラなので出て来ない
 
 (gdb) l scheduler
 346                 swtch(&cpu->scheduler, proc->context);
 // swtch の引数はこの2つ
 
 $ grep swtch * 
 // swtch はアセンブラなのでCでかけない
 
- src 側

 $ vi swtch.S
  zx
 swtch:
    STMFD   r13!, {r4-r12, lr}  // push svc r4-r12, lr to the stack
 
    # switch the stack
    STR     r13, [r0]           // save current sp to the old PCB (**old)
    // store register 
    // r0に格納 
    // swtch の引数 cpu->scheduler  がr0に入る
 
    MOV     r13, r1             // load the next stack
    // swtch の引数 proc->context  がr1に入る
  
    # load the new registers. pc_usr is not restored here because
    # LDMFD^ will switch mode if pc_usr is loaded. We just simply
    # pop it out as pc_usr is saved on the stack, and will be loaded
    # when we return from kernel to user space (swi or interrupt return)
 
    LDMFD   r13!, {r4-r12, lr}  // pop svc r4-r12, lr
    // ! マークは store を load したあと、r13の値を書き終わったとこに合わせる
    
    # return to the caller
    bx      lr
    // bx が抜けるためのコードかも?
    // system call から戻るときはここは使わない
    // 特権モードかはgdbでは確認できないけど、registerの状態を見れば何かわかるかも

- gdb側

 (gdb) si
 (gdb) x/1i $pc
 => 0x80025fd4 <swtch+16>:       bx      lr
 (gdb) info register
 cpsr           0x200000d3       536871123
 // cpsr にcpu の値が入っている
 
 (gdb) si
 (gdb) info register
 0x80025c98 in forkret () at proc.c:397
 // forkret() に入る
 (gdb) info register
 // register の値は変わらない
 // register の記述は C ではかけない
 
 (gdb) l
 395     // will swtch here.  "Return" to user space.
 // ここで切り替わる
 
 403         if (first) {
 // ここでif 文で分岐してるのはバグよけ
 // 本来は省略できる
 
 (gdb) n 
  0x80027634 in trapret ()
 // n を 繰り返すと trapret に戻る
 
** trapret [#d3af8d11]

 (gdb) bt
 #0  0x80027634 in trapret ()
 #1  0x8002077c in popcli () at arm.c:83
 #2  0xe59f2024 in ?? ()
 // popcli から trapret に来ている
 // user スペースに切り替えたから信用はできない
 
- src 側

 $ grep trapret *
 //  trap_asm.S がある
  
 $ less trap_asm.S
 trapret:
    LDMFD   r13, {sp, lr}^      // restore user mode sp and lr
    ADD     r13, r13, #8
    LDMFD   r13!, {r14}         // restore r14
    LDMFD   r13!, {r2}          // restore spsr
    MSR     spsr_cxsf, r2
    // 特権モードを抜ける
  
    LDMFD   r13!,{r0-r12, pc}^  // restore context and return
    // pc も r13 から取って来ている
  
   // trapret 全体が割り込みから抜けるルーチンになっている
   // user スペースに戻る

- gdb側

 (gdb) b trapret 
 (gdb) c
 (gdb) p *proc
  $2 = {sz = 4096, pgdir = 0x800dfc00, kstack =  0x87fde000 "", 
  state = RUNNING, pid = 1, parent = 0x0, tf =  0x87fdefb8, 
  context = 0x87fdef88, chan = 0x0, killed = 0, ofile =  {
    0x0 <repeats 16 times>}, cwd = 0x800ac508 <icache+52>, 
  name = "initcode\000\000\000\000\000\000\000"}
  
 // プロセスの構造体
 // アセンブラで書いたinitcode は中でexecされる
 
 (gdb) b exec 
 (gdb) si
 (gdb) x/20i $pc
 => 0x4: ldr     r2, [pc, #36]   ; 0x30
   0x8: mov     r0, #7
   0xc: svc     0x00000000
   0x10:        mov     r0, #2
   0x14:        svc     0x00000000
   0x18:        b       0x10
   0x1c:        stmdbvs lr!, {r0, r1, r2, r3, r5, r8, r11, sp, lr}^
   0x20:        andeq   r0, r0, r4, ror r0
   0x24:        andeq   r0, r0, r12, lsl r0
   0x28:        andeq   r0, r0, r0
   0x2c:        andeq   r0, r0, r12, lsl r0
   0x30:        andeq   r0, r0, r4, lsr #32
   0x34:        andeq   r0, r0, r0
   0x38:        andeq   r0, r0, r0
   0x3c:        andeq   r0, r0, r0
   0x40:        andeq   r0, r0, r0
   0x44:        andeq   r0, r0, r0
   0x48:        andeq   r0, r0, r0
   0x4c:        andeq   r0, r0, r0
   0x50:        andeq   r0, r0, r0
 
 // これが出るまで si と x/20i $pc 繰り返す
 // svc が software interact   

-src側

 $ vi initcode.S
 start:
    LDR     r1, =init
    LDR     r2, =argv
    MOV     r0, #SYS_exec
    SWI     0x00
 // これが一番最初に動くコード

-gdb側

 (gdb) x/s $r1
 0x1c:   "/init" 
 // $r1 で init にexecする
 
 (gdb) si     
 0x80027614 in trap_swi ()
 // system call に切り替わった
 // trap したので interact の vector に入ったはず
  
 (gdb) x/20i $pc 
 // user プロセスはスタートした状態でsystem に戻って来てる
  
 (gdb) si
 swi_handler (r=0x0) at trap.c:10 
 // ここにくるまで si
  
 (gdb) l
 // 割り込みはかかっている状態
 
 (gdb) info register 
 // init 探せない
 // initcode.asm は user 側には入ってない
  
 (gdb) s
 syscall () at syscall.c:147
 147         num = proc->tf->r0;
 //  proc の trapframe に色々入っている
  
 (gdb) p *proc->tf
 $4 = {sp_usr = 4096, lr_usr = 0, r14_svc = 16, spsr = 1610612816, r0 = 7, 
  r1 = 28, r2 = 36, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, 
  r9 = 0, r10 = 0, r11 = 0, r12 = 0, pc = 16}
 // こうすると register の値がみれる
 // r1 = 28 に initcode が入っている
 
 (gdb) x/s 28
 0x1c:   "/init"
 // 確認
 
- src側(initcode.S)

  9   LDR     r1, =init
 // user 空間で コンパイルされている
 // kernel 空間はもっと高いアドレスにあるはず

- gdb

 (gdb) s
 151         if((num > 0) && (num <= NELEM(syscalls)) && syscalls[num]) { 
 // C のコードに移ってるので s でいける
 // syscalls[] 配列は ポインタ
 
 (gdb) s
 sys_exec () at sysfile.c:448
 448         if(argstr(0, &path) < 0 || argint(1, (int*)&uargv) < 0){ 
 // sys_exec まで来た
 // argv はポインタ
  
 (gdb) l
 473         return exec(path, argv);
 // exec へ リターン

** fetchint [#c0829cf0]
 (gdb) l fetchint
 // kernel 側から指定されたプロセスのアドレスから integer を取ってくるサブルーチン
 
 36          *pp = (char*)addr;
 // *だから userプロセスのアドレスを*pp に入れてる
  
 (gdb) s 
 27          if ((ip = namei(path)) == 0) {
 (gdb) p path
 $5 = 0x1c "/init"
 // nameiのパスはuser空間にある
 // 割り込みは解除してないのでkernelにはいかない
 
 (gdb) l
 33          // Check ELF header
 34          if (readi(ip, (char*) &elf, 0, sizeof(elf)) < sizeof(elf)) {
 35              goto bad;
 36          }
 // elf : linux のa.out 
 // namei はファイルシステムだから 書かないとexec 動かない
 // 長いコードなのでここから先のテストはかなり難しい
 
 78          if ((sz = allocuvm(pgdir, sz, sz + 2 * PTE_SZ)) == 0) {
 // exec した時の引数をプロセス空間と別のとこにコピってる
 // 再利用できない
 // 正しい場所にコピって実行する必要がある
 
 82          clearpteu(pgdir, (char*) (sz - 2 * PTE_SZ));
 // allocate してから クリアしていく
 // allocateuvm で物理アドレスは確保されてるが、page entry が切り替わってない(argvはuser空間に残っている)
  
 94              if (copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0) {
 // copyoutでkernelにコピーしてる
   
** copyout [#p6e8b9d3]
 
 (gdb) l copyout
 // current page table に乗ってたら memcopy でできるけど、乗ってないからcopyoutする
  
 414             pa0 = uva2ka(pgdir, (char*) va0);
 // uva2ka で allocate した物理メモリを計算
 
 426             memmove(pa0 + (va - va0), buf, n);
 // uva2ka で計算したとこに memmoveする
 
  66              if (loaduvm(pgdir, (char*) ph.vaddr, ip, ph.off, ph.filesz) < 0) {
 // allocate して読み込んでそれを loaduvmでコピってる
 
 71          iunlockput(ip);
  // ip は namei から
 // exec の途中で書き換えられないように lockしてたのを解除
  
 130         freevm(oldpgdir);
 // oldpgdir exec する前のもの
 // execで新しいバイナリで古いバイナリをかき潰すことはしない(再利用しない)
 // 新しく allocate してそこでloadしてる
 
 (gdb) b 131
 Breakpoint 4 at 0x80022548: file exec.c, line 131.
 (gdb) c
 // user 空間に init が load された状態
 
 (gdb) si
 0x80026310 in syscall () at syscall.c:152
 152             ret = syscalls[num]();
 // syscall から return される
  
 (gdb) l 157 
 // copyout されてない
 // さっきcopyout したのは新しいtrap frame に書き込まなきゃ行けなかったから
 // exec の場合はr0に入れちゃ行けない(argc,argvが入ってる)
 
 (gdb) n 
 swi_handler (r=0x87fdefb8) at trap.c:15
 15          if (proc->killed)
 
 (gdb) n
 Breakpoint 2, 0x80027634 in trapret ()
 // trapret () はアセンブラで書かれてるので C で breakpoint かけないと行き過ぎる
 
 (gdb) si
 0x00000004 in ?? ()
 // trapret 抜けるまでsi
 (gdb) x/20i 0
  //ここでようやく最初のinit が動く

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS