xv6-armv7 mmu (3)
前回やった、最新式のgoで落ちるやつ、fedoraでも確認してみた。 go 1.5.4 と、ちょっと古いやつ。
実験対象は、昔作った血圧アプリ。グラフ書きのパッケージ取り寄せ先が 変更になってたので、下記のように変更。
// blood check go version package main import ( "github.com/gonum/plot" "github.com/gonum/plot/plotter" "github.com/gonum/plot/plotutil" "encoding/csv" "flag" :
お取り寄せすると、途中でパッケージのコンパイルエラーですよ。
[sakae@fedora src]$ go get github.com/gonum/plot # bitbucket.org/zombiezen/gopdf/pdf run compiler with -v for register allocation sites bitbucket.org/zombiezen/gopdf/pdf/image.go:89: internal compiler error: out of fixed registers
該当の89行目は、配列への書き込みをしてる所。
func encodeRGBAStream(w io.Writer, img *image.RGBA) error { buf := make([]byte, 3*img.Rect.Dx()*img.Rect.Dy()) var a uint16 for i, j := 0, 0; i < len(img.Pix); i, j = i+4, j+3 { a = uint16(img.Pix[i+3]) if a != 0 { buf[j+0] = byte(uint16(img.Pix[i+0]) * 0xff / a) buf[j+1] = byte(uint16(img.Pix[i+1]) * 0xff / a) buf[j+2] = byte(uint16(img.Pix[i+2]) * 0xff / a) } } _, err := w.Write(buf) return err }
難の変哲も無い所、レジスターの使い回しに苦労しそうには見えない。世のお悩み 相談室に問うてみる。
internal compiler error: out of fixed registers - Google 検索
出るわ、出るわ、世界で、142万件も悩みが登録されてたぞ。
落ちるのは、pdfを作っている部分なので、pdfなんていらないわいっと、該当部分を コメントアウト。
@ github.com/gonum/plot/vg/canvas.go 16// "github.com/gonum/plot/vg/vgpdf" : 281// case "pdf": 282// c = vgpdf.New(w, h)
そうしておいて、
$ go build -v bld.go github.com/gonum/internal/asm github.com/gonum/floats : github.com/gonum/plot/palette github.com/gonum/plot/plotter github.com/gonum/plot/plotutil command-line-arguments # command-line-arguments ./bld.go:319: cannot use width (type float64) as type vg.Length in argument to p.Save ./bld.go:319: cannot use height (type float64) as type vg.Length in argument to p.Save
まだエラーかよ。該当部分を参照。
: width := 6.0 height := 4.0 p.Save(width, height, "zzz.png")
Saveの所で、こけている。2年ぐらいすると仕様が変わってしまうものかなあ。 plot - GoDoc
同胞のサンプル Golearnとplotで散布図を書くでは、難しい事をやってたけど、普通に整数(ピクセル単位)で指定したら、いけたよ。 やれやれ。
Example plots ・ gonum/plot Wikiこちらがgoのプロット例。以前より進化してるな。
グラフと言えば、統計。犬も歩けば棒に当った!
A curated list of awesome Go frameworks, libraries and software
kmain
前回に続いて、xv6のコードを追って行く。今回は、mmuがonになって、カーネルが 本格的に活動を始めた所。
// interrrupt vector table is in the middle of first 1MB. We use the left // over for page tables vectbl = P2V_WO ((VEC_TBL & PDE_MASK) + PHY_START);
割り込みヴェクターは中位の1M中に有るよ。ページテーブルの使い残しを 割り当てるよでいいのかな。
(gdb) macro expand (VEC_TBL & PDE_MASK) + PHY_START expands to: (0xFFFF0000 & ((1 << 20) - 1)) + 0x80000000 (gdb) p/x (0xFFFF0000 & ((1 << 20) - 1)) + 0x80000000 $1 = 0x800f0000
マクロ展開、なかなか便利。
// add some memory used for page tables (initialization code) void kpt_freerange (uint32 low, uint32 hi) { => while (low < hi) { _kpt_free ((char*)low); low += PT_SZ; } }
(gdb) p/x low $2 = 0xc00b1000 (gdb) p/x hi $3 = 0xc00f0000 (gdb) info macro PT_SZ Defined at /home/sakae/src/xv6-armv7/src/mmu.h:59 included at /home/sakae/src/xv6-armv7/src/vm.c:6 #define PT_SZ (NUM_PTE << 2) (gdb) p/x (NUM_PTE << 2) $4 = 0x400
これ、1kづつ、リンクリストに繋いでる。
(gdb) s paging_init (phy_low=2148532224, phy_hi=2281701376) at vm.c:445 445 mappages (P2V(&_kernel_pgtbl), P2V(phy_low), phy_hi - phy_low, phy_low, AP_KU); (gdb) p/x phy_low $6 = 0x80100000 (gdb) p/x phy_hi $7 = 0x88000000
最初に大雑把に割り当てたのを、4K毎に再度割り当てし直し、2段階変換 方式にするんだな。
// 1:1 map the memory [phy_low, phy_hi] in kernel. We need to // use 2-level mapping for this block of memory. The rumor has // it that ARMv6's small brain cannot handle the case that memory // be mapped in both 1-level page table and 2-level page. For // initial kernel, we use 1MB mapping, other memory needs to be // mapped as 4KB pages void paging_init (uint phy_low, uint phy_hi)
(gdb) s mappages (pgdir=0xc0014000, va=0xc0100000, size=133169152, pa=2148532224, ap=3) at vm.c:125 125 a = (char*) align_dn(va, PTE_SZ); (gdb) p/x size $8 = 0x7f00000 (gdb) p/x pa $9 = 0x80100000
こういう事をやるとな。
// Create PTEs for virtual addresses starting at va that refer to // physical addresses starting at pa. va and size might not // be page-aligned. static int mappages (pde_t *pgdir, void *va, uint size, uint pa, int ap)
// Return the address of the PTE in page directory that corresponds to // virtual address va. If alloc!=0, create any required page table pages. static pte_t* walkpgdir (pde_t *pgdir, const void *va, int alloc)
PDEが1次テーブルで、PTEは2次テーブルって呼んでいるんだな。 実際にPTEの下位ビットで、アクセス権とかが設定されてた。
kmem_init2
上記操作が終了すると、kmem_init2へと制御が移ってくる。 ソースは、buddy.cに置いてある。
(gdb) s kmem_init2 (vstart=0xc0100000, vend=0xc8000000) at buddy.c:82 82 kmem.start = (uint)vstart;
これは、ヒープ領域の管理をしてるんだな。多分、きっとそうだろう。 読んでみたらそれっぽい。空きメモリーを色々なサイズ(2のべき乗単位)で 取り出せるような工夫がされてる。そして検索を素早く行うために、ビットマップも 使ってる。
初期値では、4Kブロックをどんどんと切り出して、集めているようだ。 余り、深入りしてもあれなんで、これぐらいにしておく。
user page table
カーネルをスタートする時、カーネルが使うページテーブルが初期化されてるけど、 ユーザー用のやつは、設定してる素振りが無い。
何処でやってるの? コードをブラウジング。vm.cって、バーチャルメモリーだろう って、根拠の無い希望を抱いて見てくと、TTBR0なんてコメントが出てきた。 そう、switchuvmって関数ね。
ここにBPを置いて、走らせてみると
(gdb) bt #0 switchuvm (p=0xc00ae640 <ptable+52>) at vm.c:166 #1 0xc0025b94 in scheduler () at proc.c:341 #2 0xc0024b10 in kmain () at main.c:54 #3 0x80010520 in start () at start.c:195
此処に飛び込んできた時点で、pgdirが確定してる。
(gdb) p *p $1 = { sz = 4096, pgdir = 0xc00dfc00, kstack = 0xc7fde000 "", state = RUNNABLE, pid = 1, parent = 0x0, tf = 0xc7fdefb8, context = 0xc7fdef88, chan = 0x0, killed = 0, ofile = {0x0 <repeats 16 times>}, cwd = 0xc00ad508 <icache+52>, name = "initcode\000\000\000\000\000\000\000" }
と言うことは、もっと前の段階で、ユーザー用のpgtblを設定してると思われな。 まあいい、ここへ来たんだから、先を見ておくと、
// Switch to the user page table (TTBR0) void switchuvm (struct proc *p) { uint val; B pushcli(); if (p->pgdir == 0) { panic("switchuvm: no pgdir"); } => val = (uint) V2P(p->pgdir) | 0x00; asm("MCR p15, 0, %[v], c2, c0, 0": :[v]"r" (val):); flush_tlb(); popcli(); }
割り込みを禁止にして、論理アドレスから物理アドレスを求め、それをTTBR0に セットして、TLBをクリアって動作ね。
(gdb) p/x val $2 = 0x800dfc00 (gdb) x/4 $2 0x800dfc00: 0x800df801 0x00000000 0x00000000 0x00000000
上記は、ユーザープロセスを走らせる為の卵initだけど、gdbをcontすると、 今度は本式のinitプロセスが動く
(gdb) p *p $3 = { sz = 16384, pgdir = 0xc00df400, kstack = 0xc7fde000 "", state = RUNNING, pid = 1, parent = 0x0, tf = 0xc7fdefb8, context = 0xc7fdef88, chan = 0x0, killed = 0, ofile = {0x0 <repeats 16 times>}, cwd = 0xc00ad508 <icache+52>, name = "init\000\000de\000\000\000\000\000\000\000" }
(gdb) p/x val $4 = 0x800df400 (gdb) x/4 $4 0x800df400: 0x800df001 0x00000000 0x00000000 0x00000000
そして、何回かcontすると、待望のshが生まれる
(gdb) p *p $7 = { sz = 20480, pgdir = 0xc00dec00, kstack = 0xc7fdf000 "$\020\237\345$ \237\345\a", state = RUNNING, pid = 2, parent = 0xc00ae640 <ptable+52>, tf = 0xc7fdffb8, context = 0xc7fdff88, chan = 0x0, killed = 0, ofile = {0xc00acb24 <ftable+52>, 0xc00acb24 <ftable+52>, 0xc00acb24 <ftable+52>, 0x0 <repeats 13 times>}, cwd = 0xc00ad508 <icache+52>, name = "sh", '\000' <repeats 13 times>
(gdb) p/x val $8 = 0x800dec00 (gdb) x/4 $8 0x800dec00: 0x800de801 0x00000000 0x00000000 0x00000000
これで、shが動き出す。lsなんてコマンドを叩くと、sh用のtblが参照され、 やがて
(gdb) p *p $18 = { sz = 16384, pgdir = 0xc00de400, kstack = 0xc7fe4000 "\b\260-\345\004\340\215\345\004\260\215\342\b\320M\342\002\020\240\343d\001\001\343", state = RUNNING, pid = 3, parent = 0xc00ae6bc <ptable+176>, tf = 0xc7fe4fb8, context = 0xc7fe4f88, chan = 0x0, killed = 0, ofile = {0xc00acb24 <ftable+52>, 0xc00acb24 <ftable+52>, 0xc00acb24 <ftable+5\ 2>, 0x0 <repeats 13 times>}, cwd = 0xc00ad508 <icache+52>, name = "ls", '\000' <repeats 13 times>
(gdb) p/x val $19 = 0x800de400 (gdb) x/4 $19 0x800de400: 0x800de001 0x00000000 0x00000000 0x00000000
この時、呼び出しの歴史を辿ると
(gdb) bt #0 switchuvm (p=0xc00ae738 <ptable+300>) at vm.c:174 #1 0xc002260c in exec (path=0x2230 "ls", argv=0xc7fe4f08) at exec.c:131 #2 0xc002753c in sys_exec () at sysfile.c:473 #3 0xc0026528 in syscall () at syscall.c:152 #4 0xc0027af4 in swi_handler (r=0xc7fe4fb8) at trap.c:12 #5 0xc0027974 in trap_swi () #6 0x00000354 in ?? ()
このexecの中で、ユーザーメモリーの確保と、テーブルのセットアップが 行われているんだな。
execの中でのpgtblの用意
最初
42 pgdir = 0; (gdb) 44 if ((pgdir = kpt_alloc()) == 0) {
// Allocate a PT page if no inital pages is available => if ((r == NULL) && ((r = kmalloc (PT_ORDER)) == NULL)) { panic("oom: kpt_alloc"); } memset(r, 0, PT_SZ); return (char*) r;
kpt_alloc()は、リンクリストの頭をrに取り出してきて、メモリーを1k確保。 それを0に初期化してる。PT=ORDERは10、PT_SZは、0x400。すなわち、PTE用の 配列サイズは1024って事だな。1ページが4Kって事を思い出せば、4Mまでの メモリーをアクセス出来るとな。
(gdb) p/x r $26 = 0xc00de400
(gdb) s allocuvm (pgdir=0xc00de400, oldsz=0, newsz=5280) at vm.c:234
// Allocate page tables and physical memory to grow process from oldsz to // newsz, which need not be page aligned. Returns new size or 0 on error. int allocuvm (pde_t *pgdir, uint oldsz, uint newsz)
こうして確保したエリアに、
loaduvm (pgdir=0xc00de400, addr=0x0, ip=0xc00ad5a8 <icache+212>, offset=84, sz=5252) at vm.c:202
指定されたpgdir(PDE)で、PTEの先頭を探し出し(これが論理アドレスで表現される 空きメモリーになる)そこに、ファイルから呼び出したプログラムを格納する。
プログラムは、addrで示されるユーザーメモリの0番地から格納。プログラムの 実際の在り処はipが示していて、実際には、そこからoffset分ずれた所から、sz分 転送してねって、依頼だな。
// Load a program segment into pgdir. addr must be page-aligned // and the pages from addr to addr+sz must already be mapped. int loaduvm (pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz )
実際のデータをロードするとな。 そして、次のページをユーザースタック用に確保。サイズは決め打ちで、8k。
そして、execの中で、このユーザーイメージを確定、switchuvmへ飛んで、 今ロードしたユーザーエリアをMMU的に有効にするとな。長い道のりであったなあ。
etc
0から作るOS開発 ページングその1 ページとPTEとPDE