xv6-armv7 mmu

『競馬の世界史』(中公新書)なんてのを読んでみた。

巻末に、馬の名前の索引が出てて、オイラーの知ってる名前、ハイセイコーと オグリキャップもちゃんと網羅されてたから、世界でも認められた馬なんだなーと 安心した次第。

世界史の上で覚えておかなければならない馬は、エクリスプって言う馬らしい。 イギリスで金持ちの道楽として始まった、競争に勝とうとして、アラブの馬を 導入したらしい。

その頃は地産の馬が主流だったそうだけど、アラブの馬って事で、お取り寄せ。 国王も馬競争が大好きで、外交官とかを使って、速い馬を漁っていたとか。 で、アラブの馬から、丁度日蝕の日に馬が生まれた。それで、エクリスプって 名前が付いたらしい。

その頃は、馬が成熟して馬力が出る6-7才ぐらいの奴が、競争に参加。 競争距離も6kmとか長いのが普通。こうなると、余り番狂わせは期待出来ず、 出れば勝つって状態だったらしい。エクリスプ強かったんだ。

そう言えば、いにしえのミニコンに、エクリスプって名前のやつが居たな。 速い馬にあやかって、付けた名前だったのだろうか? だとしたら、相当な 馬好きだな。

この本の著者さんは、勿論馬好き。プロローグで、来年から外国の競馬馬券も 日本で買えるようになるんで、井の中の蛙は止めて、世界を知ろうって 煽っている。決してJRAの回し者ではなく、れっきとした大学教授さん。

イギリスの競馬場にもよく行くそうで、決して勝てるわけではないけど、 歴史の瞬間に立ち会えるのが、ことのほか嬉しいとの事。 数々のエピソードが載ってて、オイラーみたいな門外漢でも楽しいぞ。

馬用語で、全弟とか、兄弟、半兄弟なんてのが有るってのも初めて知った。 血統の世界なんだなあと、驚く次第。

関数型言語 Objective Camel 入門

散歩の時、よくお使いを言い付かる。色々言っても忘れるだろうからって言う 配慮からか、買い物リストを渡されるのだ。

時には、買うもののメーカーと個別商品名の指定が有ったりする。例えば、 マヨネーズなら、キューピーのカロリーOFFのやつ300gバージョンとか。 込み入った物になると、現有物を目の前に持ってきて、これと同じのとか言われる。 そんなに信用ないなら、自分で行けばいいのにと、思っちゃうぞ。

で、最近、メモ用紙にしてるチラ裏が無くなってきて、オイラーからメモ用紙の 拠出しろって要求が来た。

ええ、今まで何度か、A4の紙を八つ裂きして出したことはありますよ。でも、もう 出せそうなのないな。家捜ししてみた。

そしたら、20ページの小冊子が出てきた。題名は、関数型言語うんぬんという奴。 名残おしそうに、再読してみたよ。

そしたら、なんとOcamlでグラフを書く例が出てきた。そんな便利なモジュール なんて有ったっけ?

OCaml 窶・OCaml

Develop with Ocaml

Jacques Garrigue の講義情報

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レジスタが担当。

下記が参考になる。

仮想メモリ・・・その準備

MMU有効化

etc

OCaml(関数型言語)

BuckleScript(OCaml→JavaScriptコンパイラ)

OCamlDebugが熱い

lambda.pdf

Windows Anniversary UpdateのLinux機能を探る