xv6-armv7

『人口知能・人類最悪にして最後の発明』(ダイヤモンド社)なんて本を読んだ。

この分野の人気者は、Lispの父マッカーシー先生でもなければ、マービン・ミンスキー教授でもなく、 レイ・カーツワイルらしい。彼はブラックホールであるググルXに飲み込まれて、いろいろと やっているらしい。

そして彼のご宣託によれば、人口知能の能力が2045年に人間のそれを超えるそうだ。 技術的特異点(シンギュラリティ)ってのがあって、それを超えると自己成長が指数関数的に 速まる。

昔、2001年宇宙の旅ってSF映画があって、そこにHAL9000ってコンピュータが出てきた。主人公の 公の少年に説教して、うるさく思った少年が電源プラグを抜こうとすると、それだけはヤメテって 懇願してた。シンギュラリティを一旦超えてしまうと、そういうのどかな事も無くなるらしい。

だから、最悪にして最後の発明。原子力が平和にも戦争にも役立った、、けど、今は何とか 平衡状態を保っている。人口知能はそんなものでは無いと言う。

金さえつぎ込めば、その臨界点に早く到達出来る。世界を握れる。だから、国防省が膨大な 予算をつぎ込んでいる。金が余っているウォール街の某は、金儲けマシンを一生懸命に 開発してる。富が一箇所に集まるようになったら、その分野の人口知能は技術的特異点を 超えたと思って間違いないだろう。

国防省は、勿論兵器への応用、戦争勝利への方法論と実践が最終目標。何もドンパチだけが 戦争にあらず。相手国のインフラを破壊するだけで勝利決着。ある試算によると、送電網を 混乱させるだけで、1歳以下の乳幼児は2週間以内に離乳食が食べれなくなって(製造も輸送も医療も みんな電気のおかげ)死亡。成人でも1年以内に90%の人が死ぬと言う。

こういう迷惑はマルウェアにおまかせ。サイバー兵器をイランに適用して、原子力開発を STOPさせたのは、USAのDが仕掛けたテロですよ。恐いねぇ。なんでも、シーメンス製の コントローラを狙ったとか。オムロン製とかのシーケンサーは大丈夫か?

これもそれも、コンピュータの能力向上が寄与してるのはまちがいない。第二世代のipadは 一昔のスパコンの能力をとっくに超えているとか。一昔のスパコンと言うと、世界一高価な 椅子こと、CRAY-1しか思い付かないんだけど、どうよ。 CRAY manualでも、眺めて みますかな。 2240004C_CRAY-1_Hardware_Reference_Nov77.pdf いろいろあるけど、まずはハードから。

前回だかに書いたやつ、みんな気になるね。 国勢調査、37%の世帯がオンラインで回答 日本もやっとネットに関しては韓国に近づいた?

iOS 9へのアップグレード、一部で端末が文鎮化 不幸は続くよ、どーこまでも!!

xv6

なにげに、 カーネル/VM探検隊なんて集積地を見ている。 みなさんお盛んで。んなわけで、昔やったuv6なんてのも出てくるんですが、、、、ちらっと xv6も言及されたような。。。 オイラーも2013年にやってたね。またひっくり返してみるか。

本家はこちら、 Xv6, a simple Unix-like teaching operating system ウブだとすいすいかと思ったら、起動の段階でSDLが無いとか言われた。どうする? Running qemu remotely (via ssh) そんなVGAなカードは無視するのが鉄則。

そんなこんなで、起動スクリプトは下記。某OpenBSDの起動スクリプトをパクッタだけですけど。

qemu-system-i386 -m 256M -net nic -net user -s -nographic \
      -redir tcp:2022::22  -hdb fs.img xv6.img

本来、make qemuすれば起動してくるんだけど、こちらは豪華にsmpなマシン用に なってるんで、貧乏な人は上記で十分。

xv6はそもそもuv6を移植したもの。pdp-11で動いてたやつね。古典を読まないでどうするって、 Plan9日記も言いつつ、微妙なトーン。 綺麗にPlan9はどうでっしゃろっと仰ってる。なんでも、Plan9の一部がxv6に取り込まれてる とか。どこだろう?

もう一度、 xv6ソースコードリーディング かなあ。糞石32のせいで、脳に余計な負荷がかかるよ。そういう場合は、

xv6のブートシーケンスメモ

xv6を読む:アドレススペースの作成

xv6を読む:メモリアロケータ

xv6-armv7

カーネル探検隊の過去ログを見てたら、各種CPUの変態度と処理能力の関係図が出てた。 それによると、処理能力が高いけど変態度が一番高いのは、糞石でした。次に酷い変態度の 石はarmでしたよ。変態が世の中を席巻してるね。

素直で良い子は、PowerPCとMIPSだった。オイラーもそう思うぞ。残念ながら、良い子は 長く生き長らえる運命になにのね。処理能力は落ちるけど素直なのもいて、ルネサスのshが 上がっていたね。激同意。

糞石から逃れるとしたら、次の候補は消極的選択でarmになるな。ラズパイも流行って いるし、そこらのねーちゃんが持ってるスマホにも入ってるしね。

奇特な方が、 xv6-armv7を公表されてます。そこからFORKして ラズパイ用もあるようです。試しに入れてみました。

debugが出来ればいいなってんで、

sudo apt-get install gdb-arm-none-eabi
cd /usr/bin
sudo ln -s arm-none-eabi-gdb agdb

を入れました。arm用のgdb名が長いので短い名前も登録しました。 お決まりのgdb用設定をしてからrun-debug.sh を走らせておいて、M-x gdbして、

Run gdb (like this): agdb -i=mi kernel.elf

すれば

(gdb) bt
#0  0xc0020624 in pushcli () at arm.c:59
#1  0xc0025e2c in acquire (lk=0xc00ad60c <ptable>) at spinlock.c:29
#2  0xc0025880 in scheduler () at proc.c:330
#3  0xc00248a8 in kmain () at main.c:54
#4  0x800104e8 in start () at start.c:195
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

ふーん、こういう所を回っているんだ。emacsからgdb使うのはヤダって場合は、debug.shを 素直につかいます。ただ、ちょっと改変が必要でしたが。

sakae@uB:~/xv6-armv7/src$ cat debug.sh
agdb -q -n -x connect.gdb kernel.elf

やはり、run-debug.shを走らせておいてから、debug.shを起動します。

sakae@uB:~/xv6-armv7/src$ ./debug.sh
Reading symbols from kernel.elf...done.
0x80010000 in _start ()
(gdb) b start
Breakpoint 1 at 0x800103f8: file start.c, line 169.
(gdb) c
Continuing.

Breakpoint 1, start () at start.c:169
169         _puts("starting xv6 for ARM...\n");
(gdb) n
173         set_bootpgtbl(PHY_START, PHY_START, INIT_KERN_SZ, 0);

このあたりからハードウェアのセットアップが始まるのかな。init386()相当だな。 後でじっくり見るとして、先へ進みます。

(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0xc0025894 in scheduler () at proc.c:333
333                 if(p->state != RUNNABLE) {
(gdb) p *p
$2 = {
  sz = 0,
  pgdir = 0x0,
  kstack = 0x0,
  state = UNUSED,
  pid = 0,
  parent = 0x0,
  tf = 0x0,
  context = 0x0,
  chan = 0x0,
  killed = 0,
  ofile = {0x0 <repeats 16 times>},
  cwd = 0x0,
  name = '\000' <repeats 15 times>
}

これが、大事な大事なプロセスの雛形か。これの意味する所を理解すればいいんだな。 ああ、コードをチラ見してたら、

$ 1 sleep  init
2 sleep  sh
procdump:

15: 0x0
14: 0x0
13: 0x0
12: 0x0
11: 0x0
10: 0x0
9: 0x0
8: 0x0
7: 0xc0011fbc
6: 0xc0027574
5: 0xc00276d0
4: 0xc00293a4
3: 0xc0028be4
2: 0xc0021be4
1: 0xc0025dc4

CTRL-p で、プロセスリストとスタックの内容を表示してくれるのね。ソース読むと徳するぞ。

それはそうと、 どんな風にイメージを作っているかとmakeのログを紐解くと

../tools/mkfs ../build/fs.img  _cat _echo _grep _init _kill _ln _ls _mkdir _rm _sh _stressfs _usertests _wc _zombie  UNIX
used 29 (bit 1 ninode 26) free 29 log 10 total 1024
balloc: first 510 blocks have been allocated
balloc: write bitmap block at sector 28
arm-none-eabi-objdump -S usys.o > usys.asm
rm wc.o grep.o mkdir.o rm.o ln.o stressfs.o kill.o echo.o init.o zombie.o cat.o sh.o usertests.o ls.o
make[1]: Leaving directory '/home/sakae/xv6-armv7/src/usr'
cp -f build/initcode initcode
cp -f build/fs.img fs.img
  LINK     kernel.elf
arm-none-eabi-objdump -S kernel.elf > kernel.asm
arm-none-eabi-objdump -t kernel.elf | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel.sym
rm -f initcode fs.img

一番面白いリンクの所が隠されているな。Makefileを覗くと何やらcallなんてのを使ってる。 なんじゃらほいと思って調べてみると、 8.6 callファンクション なんか、おいらには悪夢だな。いつの間にrakeに喧嘩売るようになった。ちっともLLじゃないぞ。 全く、GNUの連中といったら節度を知らないな。しょうがないので、脳内変換すると、

kernelは普通のアプリケーション、その後ろにfs.imgをくっつけたものなんだな。これって、 組み込みの方式か。

sakae@uB:~/xv6-armv7/src$ arm-linux-gnueabi-nm kernel.elf | grep fs_img
c00aa0c0 D _binary_fs_img_end
00080000 A _binary_fs_img_size
c002a0c0 D _binary_fs_img_start

これが使われている所は、memide.c。あこがれのメモリーDISKだ。そこらのSSDよりも速いかな。 そんな事は無いだろう。qemu上のメモリーだからな。

まてまて、一応makeのコマンド調べて、使えそうなオプションを試してみると、

sakae@uB:~/xv6-armv7/src$ make clean
sakae@uB:~/xv6-armv7/src$ make -n
echo "  CC       build/device/gic.o" && arm-none-eabi-gcc -march=armv7-a -mtune=cortex-a15 -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -Werror -I. -g  -c -o build/device/gic.o device/gic.c
echo "  AS       build/entry.o" && arm-none-eabi-gcc -march=armv6    -c -o build/entry.o entry.S
echo "  AS       build/initcode.o" && arm-none-eabi-gcc -march=armv6   -nostdinc -I. -c -o build/initcode.o initcode.S
echo  "  LINK     build/initcode" && arm-none-eabi-ld -L.  -N -e start -Ttext 0 -o build/initcode.out build/initcode.o
echo  "  OBJCOPY  build/initcode" && arm-none-eabi-objcopy -S -O binary --prefix-symbols="_binary_build/initcode" build/initcode.out build/initcode
arm-none-eabi-objdump -S build/initcode.o > initcode.asm
make -C tools
make -C usr
cp -f build/initcode initcode
cp -f build/fs.img fs.img
echo  "  LINK     kernel.elf" && arm-none-eabi-ld -L. -T  kernel.ld -o  kernel.elf  build/lib/string.o build/arm.o build/asm.o build/bio.o build/buddy.o build/console.o build/exec.o build/file.o build/fs.o build/log.o build/main.o build/memide.o build/pipe.o build/proc.o build/spinlock.o build/start.o build/swtch.o build/syscall.o build/sysfile.o build/sysproc.o build/trap_asm.o build/trap.o build/vm.o build/device/timer.o build/device/uart.o build/device/gic.o build/entry.o /usr/lib/gcc/arm-none-eabi/4.8/libgcc.a -b binary  initcode fs.img
arm-none-eabi-objdump -S kernel.elf > kernel.asm
arm-none-eabi-objdump -t kernel.elf | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel.sym
rm -f initcode fs.img

やっぱりね。-nは別名 --just-print 動きを表示だけするって事で、こんな所に展開方法が 隠れていたよ。ああ、これですっきりしたよ。

起動の流れ

どんな風に起動するか?

kernel.elfを作る、リンカースクリプト、kernel.ldを見ると、冒頭に、

OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)

ENTRY_SVC_STACK_SIZE = 0x1000;

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;

ふむ、ページング機構がまだ働いていないんで、仮想/物理アドレスは等しいよ。entry.Sを 見てから、start.cを見れ。

その前に、このプログラム(kernel.elf)はどうやってメモリーにロードされるの? いわゆるローダー問題。 それは、qemu様を起動する時、-kernelオプションを付けているから、qemuがやってくれる んだよ。糞石の場合は、IBMと結託してやれmbrとか五月蝿い事があるんだな。

entry.Sではbssをクリアして、カーネルスタックをセットしてから、start.c/startへと 行くんだな。startの中では、

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(PHY_START, PHY_START, INIT_KERN_SZ, 0);
    set_bootpgtbl(KERNBASE+PHY_START, PHY_START, INIT_KERN_SZ, 0);

いきなり、シリアルポートを叩いて大丈夫ってのはあるけど、取りあえず動き始め ましたよのご挨拶。そしてそろそろとページングを有効にしてきます。 そして、ちゃんと設定が済んだら、それを有効にして、スタックも仮の場所から 正式な場所に変更、通常のBSS領域をクリアしてから、main.c/kmainへと飛んで行く。

kmainの中では、メモリー系のイニシャライズ、トラップベクターの設定、armの割り込み 関係gicのイニシャライズ、ええい面倒なので

      :
    kmem_init ();
    kmem_init2(P2V(INIT_KERNMAP), P2V(PHYSTOP));

    trap_init ();                               // vector table and stacks for models

    gic_init(P2V(VIC_BASE));    // arm v2 gic init

    uart_enable_rx ();                  // interrupt for uart
    consoleinit ();                             // console
    pinit ();                                   // process (locks)

    binit ();                                   // buffer cache
    fileinit ();                                // file table
    iinit ();                                   // inode cache
    ideinit ();                                 // ide (memory block device)
    timer_init (HZ);                    // the timer (ticker)


    sti ();
    userinit();                                 // first user process
    scheduler();                                // start running processes

タイマーは10Hz駆動。stiでページのread/modify/writeを許可。userinitの中で、ユーザーの 役にたつプロセスが起動されるとな。後は、スケジューラーがそれらを監視して公平に 扱えるように務めるのか。

なおP2Vとかのマクロは、仮想/物理アドレスの変換用マクロ、memlayout.hに定義 されてる。

// Key addresses for address space layout (see kmap in vm.c for layout)
#define KERNBASE    0x40000000   // First kernel virtual address
               // P:0x80000000  (PHY_START) => V:0xc0000000

#define V2P(a) (((uint) (a)) - KERNBASE)
#define P2V(a) (((void *) (a)) + KERNBASE)
#define V2P_WO(x) ((x) - KERNBASE)    // same as V2P, but without casts
#define P2V_WO(x) ((x) + KERNBASE)    // same as V2P, but without casts

initの起動

userinitにBPを置いて、起動してみた。

(gdb) p *p
$4 = {
  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 = 0xc00ac508 <icache+52>,
  name = "initcode\000\000\000\000\000\000\000"
}

やがてinitcodeがinitになり、shを生む。コンソールには、

starting xv6 for ARM...
hello man:)
init: starting sh
$

この時に呼ばれるのはusr/init.c

ずっとinitは生き続けて、forkしてshをexecしてる。zombieの検出もやってるのね。 shが死んだら、また生まれるように手配してるのか。