xv6-armv7 mmu
『競馬の世界史』(中公新書)なんてのを読んでみた。
巻末に、馬の名前の索引が出てて、オイラーの知ってる名前、ハイセイコーと オグリキャップもちゃんと網羅されてたから、世界でも認められた馬なんだなーと 安心した次第。
世界史の上で覚えておかなければならない馬は、エクリスプって言う馬らしい。 イギリスで金持ちの道楽として始まった、競争に勝とうとして、アラブの馬を 導入したらしい。
その頃は地産の馬が主流だったそうだけど、アラブの馬って事で、お取り寄せ。 国王も馬競争が大好きで、外交官とかを使って、速い馬を漁っていたとか。 で、アラブの馬から、丁度日蝕の日に馬が生まれた。それで、エクリスプって 名前が付いたらしい。
その頃は、馬が成熟して馬力が出る6-7才ぐらいの奴が、競争に参加。 競争距離も6kmとか長いのが普通。こうなると、余り番狂わせは期待出来ず、 出れば勝つって状態だったらしい。エクリスプ強かったんだ。
そう言えば、いにしえのミニコンに、エクリスプって名前のやつが居たな。 速い馬にあやかって、付けた名前だったのだろうか? だとしたら、相当な 馬好きだな。
この本の著者さんは、勿論馬好き。プロローグで、来年から外国の競馬馬券も 日本で買えるようになるんで、井の中の蛙は止めて、世界を知ろうって 煽っている。決してJRAの回し者ではなく、れっきとした大学教授さん。
イギリスの競馬場にもよく行くそうで、決して勝てるわけではないけど、 歴史の瞬間に立ち会えるのが、ことのほか嬉しいとの事。 数々のエピソードが載ってて、オイラーみたいな門外漢でも楽しいぞ。
馬用語で、全弟とか、兄弟、半兄弟なんてのが有るってのも初めて知った。 血統の世界なんだなあと、驚く次第。
関数型言語 Objective Camel 入門
散歩の時、よくお使いを言い付かる。色々言っても忘れるだろうからって言う 配慮からか、買い物リストを渡されるのだ。
時には、買うもののメーカーと個別商品名の指定が有ったりする。例えば、 マヨネーズなら、キューピーのカロリーOFFのやつ300gバージョンとか。 込み入った物になると、現有物を目の前に持ってきて、これと同じのとか言われる。 そんなに信用ないなら、自分で行けばいいのにと、思っちゃうぞ。
で、最近、メモ用紙にしてるチラ裏が無くなってきて、オイラーからメモ用紙の 拠出しろって要求が来た。
ええ、今まで何度か、A4の紙を八つ裂きして出したことはありますよ。でも、もう 出せそうなのないな。家捜ししてみた。
そしたら、20ページの小冊子が出てきた。題名は、関数型言語うんぬんという奴。 名残おしそうに、再読してみたよ。
そしたら、なんとOcamlでグラフを書く例が出てきた。そんな便利なモジュール なんて有ったっけ?
20ページの冊子は、講義情報の中の筑波大学編の中に有った。ここから頂いてきたんだな。
で、何となくグラフィク系が必要そうなので、 ocaml-graphics-4.03.0p0.tgzを入れてから走らせてみると
[ob: plot]$ ocamlc -c plot.mli [ob: plot]$ ocamlc -c plot.ml [ob: plot]$ ocaml all.ml Exception: Graphics.Graphic_failure "Cannot open display unix:0".
こんな具合に、見事にエラーですよ。ちゃんとDISPLAY変数はセットしてあって、 gnuplotでは、Windows側にグラフが表示されると言うのに。
こう、文句を言っても始まらないので、冷静に file:///usr/local/share/doc/ocaml/html/libgraph.html を覗いてみたよ。そしたら、
Here are the graphics mode specifications supported by Graphics.open_graph on the X11 implementation of this library: the argument to Graphics.open_graph has the format "display-name geometry", where display-name is the name of the X-windows display to connect to, and geometry is a standard X-windows geometry specification. The two components are separated by a space. Either can be omitted, or both. Examples: Graphics.open_graph "foo:0" connects to the display foo:0 and creates a window with the default geometry Graphics.open_graph "foo:0 300x100+50-0" connects to the display foo:0 and creates a window 300 pixels wide by 100 pixels tall, at location (50,0) Graphics.open_graph " 300x100+50-0" connects to the default display and creates a window 300 pixels wide by 100 pixels tall, at location (50,0) Graphics.open_graph "" connects to the default display and creates a window with the default geometry.
こんな有り難い説明が有った。デフォルトでは、環境変数DISPLAYを見てくれない ように受け取れるな。ならば、スクリプトの中に設定して野郎。
[ob: plot]$ cat all.ml #load "graphics.cma" ;; #load "plot.cmo" ;; open Plot ;; Graphics.open_graph "Windows7-IPAddress:0" ;; adjust_size [-5., 0.; 5., 0.] ;; let curve = create_curve (fun x -> (sin x) ** 2. -. 0.5) ;; adjust_size curve ;; draw_curve curve ;; read_line () ;;
プログラムの中に、Xmingが動いているWindows7のIPを決め打ち。read_line ()は、 ocamlが終了しないように待ち合わせさせるもの。これで動いたわい。
折角、最新式ocamlを入れたんだから、 OCamlでJavaScriptコードが書ける「BuckleScript」が登場 にでも、挑戦してみるか。npmを使ってインストールするって事は、nodeとか 言うのを入れるんかな。調べたら、これを入れるとgccもくっ付いてくるようだ。 どうしよっかな。
それに、詳しいinstallの項を見ると、ocamlも新し過ぎると駄目みたいで、opamを 入れて、指定バージョンのocamlで試せって言ってる。
全くもって、難儀な事だのう。
難儀ついでに、グラフィックパッケージの事を少し調べてみた。OpenBSDのパッケージ になってるけど、それに対するパッケージの作成手順は無い。いわゆる偽パッケージ のようだ。実体は、ocaml本体の一部として存在してる。(パッキングリストが、 本体用とグラフィック用に分かれている。)ocamlでグラフィックという人は 余り居ないからだろうな。
plotルーチンは、ガリグ先生作のもの。クラスとかが宣言してあって、ocamlの oの部分を強調してるっぽい。
作画は、ペンを上げての移動、movetoとペンを下げて(描画状態にして)の移動、 linetoという、Graphicsから提供されるものを使っている。 最大描画サイズが、16000x16000以内に収まらないとエラーにしてる。
arm mmuの威力
上記の冊子と供に、pdp-11の命令表と仮想記憶の説明を印刷したのも出てきた。 今更、pdpでもないので、armにスイッチする。以前にやって、っむ mmuを そのまま変換しると、っむ って、考えこませる結果になるな。
これは、up を うp と変換するより、ずっと意味深だな。(by国語審議会)
fedoraで動いていたxv6-armv7をOpenBSDに持ってきて走らせたら、panicしちゃった。 こんな経験、以前したな。proc.cの中の関数 allocproc(void)の最後returnの直近で forkret+8に変更。これで、動いた。
p->context->lr = (uint)forkret+8; return p;
出来上がったkernel.asm いわゆる、kernel.elfのobjdumpしたものを見ると、
Disassembly of section .start_sec: 80010000 <_start>: 80010000: e59f1030 ldr r1, [pc, #48] ; 80010038 <jump_stack+0x10> 80010004: e59f2030 ldr r2, [pc, #48] ; 8001003c <jump_stack+0x14> 80010008: e3a03000 mov r3, #0 : 80010420 <start>: void start (void) { 80010420: e52db008 str fp, [sp, #-8]! 80010424: e58de004 str lr, [sp, #4] 80010428: e28db004 add fp, sp, #4 8001042c: e24dd008 sub sp, sp, #8 uint32 vectbl; _puts("starting xv6 for ARM...\n"); : kmain (); 8001051c: eb000005 bl 80010538 <__kmain_veneer> } : 800105f0 <kernel_pgtbl>: 800105f0: 80014000 .@.. 800105f4 <user_pgtbl>: 800105f4: 80018000 .... 800105f8 <edata_entry>: ... 80012000 <svc_stktop>: ... 80014000 <_kernel_pgtbl>: ... 80018000 <_user_pgtbl>: ...
いわゆるカーネルが走り始めるまでの準備段階が、一つのセクションとして 用意されてる。まだ、mmuが有効になっていなくて、論理アドレスと物理アドレスが 同じものになってる。
そして、こちらは、mmuが有効になって、アドレス変換が行われた状態。 アセンブルリストは、CPUから見たアドレスになるので、論理アドレスを 表示してる。(ってか、それしか表示のしようはないよな)
Disassembly of section .text: c0020000 <memset>: #include "types.h" #include "arm.h" void* memset(void *dst, int v, int n) { c0020000: e52db004 push {fp} ; (str fp, [sp, #-4]!) c0020004: e28db000 add fp, sp, #0 : void kmain (void) { c0024a34: e52db008 str fp, [sp, #-8]! c0024a38: e58de004 str lr, [sp, #4] c0024a3c: e28db004 add fp, sp, #4 c0024a40: e24dd008 sub sp, sp, #8 uint vectbl; :
この、セクション2つの離れ業は、kernel.ldっていうリンカーが使う配置定義に ユーザーが行っているんだ。
SECTIONS { /* the entry point, before enabling paging. The code to enable paing needs to have the same virtual/physical address. entry.S and start.c run in this initial setting.*/ /* . = 0x10000; */ . = 0x80010000; .start_sec : { build/entry.o(.text) build/start.o(.text .text.*) : /*the kernel executes at the higher 2GB address space, but loaded at the lower memory (0x20000)*/ . = 0xC0020000; /* HCLIN: below text symbols is in VA space **/ .text : AT(0x80020000){ /** here to make code also copied into phymem **/ *(.text .text.* .gnu.linkonce.t.*)
この配置仕様を見ると、entry.oとstart.oは、0x80010000 から配置され、 それ以外のコードは、0xC0020000から配置って事のようだ。
ついでに、エントリーポイントも見ておく。まずは、lernel.elf
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: ARM Version: 0x1 Entry point address: 0x80010000 :
次はユーザープロセスの例として、usr/_ls
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: ARM Version: 0x1 Entry point address: 0x354
他にも、_initだと、エントリーpointが、0x0とかだったりする。ユーザー プロセスは、論理アドレスの非常に低位な所から実行が始まるのね。 これら、本に書いてあるような作りになってると、実感出来るよ。
観察の仕方
この、xv6-armv7はqemu上で走るんだけど、qemuがgdbserverの役割を果たしてくれる ようになってる。それを実現してるのが、
[ob: src]$ ./run-debug.sh
これを実行すると、gdb側から制御が出来るようになる。 gdb側からは、remote接続するんだけど、毎回同じ事を入力しなければならないので、 src直下に、下記のような .gdbinitを用意して、それを使えるように$HOMEの下の gdbinitをイネーブルにしとけばよい。
[ob: src]$ cat ~/.gdbinit add-auto-load-safe-path /home/sakae/src/xv6-armv7/src/.gdbinit [ob: src]$ cat .gdbinit target remote tcp::1234 symbol kernel.elf set print pretty
まず、run-debug.shを起動。別端末でarm用のagdb(長い名前なので短くしてる)を 引数と供に起動。BPをセットしてから、contすれば、目的の所で止まる。
[ob: src]$ agdb kernel.elf GNU gdb (GDB) 7.9.1 : Reading symbols from kernel.elf...done. 0x80010000 in _start () (gdb) b start Breakpoint 1 at 0x80010430: file start.c, line 169. (gdb) c Continuing. Breakpoint 1, start () at start.c:169 169 _puts("starting xv6 for ARM...\n");
現在のPCの値を確認。まだ、物理アドレスを指している。
(gdb) p $pc $1 = (void (*)()) 0x80010430 <start+16>
続いて、本番カーネルが動く所にBPを置いてcont。PCを確かめてみると、 確かに、論理アドレスになってるな。
(gdb) b kmain Breakpoint 2 at 0xc0024a44: file main.c, line 21. (gdb) c Continuing. Breakpoint 2, kmain () at main.c:21 21 cpu = &cpus[0]; (gdb) p $pc $2 = (void (*)()) 0xc0024a44 <kmain+16>
あと、毎度の事だけど、gdbの中でマクロの参照と展開が出来るように、 makefile.incの部分を変更してからコンパイルすると、楽出来るよ。
CFLAGS = -march=armv7-a -mtune=cortex-a15 -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -Werror -I. -O0 -gdwarf-2 -g3
コプロのレジスタ
メモリー関係の制御は、コプロ番号15のプロセッサーが担当する。 このコプロには16個の専用レジスタが用意されてる。
arm側からコプロへの転送は、mcr 命令を使って行う。逆方向は、mrc命令。
変換テーブルはメモリー上に設定するが、そのアドレスをコプロ側でも知っている 必要がある。その値はc2レジスタに設定する。
変換のスタート/ストップ等の制御は、c1レジスタが担当。
下記が参考になる。