xv6-armv7 (5)
『AIは心を持てるのか』(日経BP社)なんて本を読んだ。これで何冊目なんだろう。この系統の本。 ブームを煽って、CPUの利用を促す戦略。これもひょっとして、尼のEC戦略に乗せられて、 本を書けば売れ末世なんかな。
最近は富が1%の人に集中しちゃってる。これは資本主義のなせる業。富が富を生む循環に 入ってしまった為だ。こんな事は許されるのかって、いつぞやのTVで池上先生がまとめてた。
これを打破するには、政府に超協力なAIマシンを装備させ、全体が豊かになるように、AIに 采配してもらえばいいんでないかい。AIじゃないオイラーが思うには、遺産相続禁止に しちゃえばいいと思うぞ。そうすれば、金儲けする意味無くなるじゃん。これは、ちと浅はかな 考えかな?
上で超協力って誤変換しちゃったけど、本意は超強力ね。でも、よく考えたら超協力ってのも ある意味間違いじゃないね。世の中の趨勢がそういう方向を目指してる。たとえば飛行機。 各種のコンピュータが搭載されてて、AIってかある程度の自動化は実現されてるけど、最後の 判断は人に任せてる。
そんな事が如実に分かる取り組みがなされるようだ。今まで、人間の棋士とコンピュータが 対戦するってのが話題をさらってた。曰く、コンピュータは人を越えられるか?って構図 だったけど、今度は、協力しあったら、パワーがブーストされるんじゃねぇって事。
電王戦タッグマッチが 始まるらしい。どうなるかなあ?要注目。あっ、これも面白い記事だな。 電王戦と独機墜落から考える人工知能の今後 そして、情報処理学会からは、 コンピュータ将棋プロジェクトの終了宣言 なんてのが出された。高らかな勝利宣言でしょうか。それとも、AIは遊びじゃないのよーーー、 もっと儲かる口を見つけろーーと、産業界から圧力がかかったから? いろいろ考えてみよう。
自動車が馬の延長ではなく、飛行機が鳥の延長ではなかったように、 コンピューター将棋も人間の思考の延長ではなかっただけですが、 応用が利かず、研究の隘路にはまったように感じました。
案外、真実は上記のような事かも知れないな。
かのデカルトさんも言ってるじゃないですか。『我思う、ゆえに我あり』って。AIにこの心が 分かるのでしょうか? おっと、心は今の所未定義じゃったわい。
富って事で、ちと書いてみたけど、こちらは 貧困についてグーグル先生に聞いてみた そうです。ぐぐる先生が提示したものが世の中の全てか? 都合の悪い事は隠してしまえ 引力が、どこからか放射されていないか? 疑ってかかれ。相手は人口知能だぞ。
fedoraのあれ
って、何ちゅうタイトルだ。fedoraでxv6を動かした時エラーで落ちる。落ちた時に 出してくれるスタック状況には再現性がある。
starting xv6 for ARM... cpu0: panic: popcli - interruptible 15: 0x0 : 6: 0x0 5: 0xc0020750 ;; after sti() in popcli() 4: 0xc0025e74 ;; if (first) in forkret() 3: 0xc00262c0 ;; after popcli() in release() 2: 0xc00206c0 ;; if (--cpu->ncli < 0) in popcli() 1: 0xc0021c68 ;; after show_callstk() in panic()
これ、マシン語のアドレスなんで、kernel.asmをgrepして、何処に居たか調べておいた。
panicするとarm.c/show_callstk()で、stackの中を辿ってくれるんだけど、そのルーチンが 面白い。まるでgdbのbacktraceコマンドみたい(多分)。
// Record the current call stack in pcs[] by following the call chain. // In ARM ABI, the function prologue is as: // push {fp, lr} // add fp, sp, #4 // so, fp points to lr, the return address void getcallerpcs (void * v, uint pcs[]) { uint *fp; int i; fp = (uint*) v; for (i = 0; i < N_CALLSTK; i++) { if ((fp == 0) || (fp < (uint*) KERNBASE) || (fp == (uint*) 0xffffffff)) { break; } fp = fp - 1; // points fp to the saved fp pcs[i] = fp[1]; // saved lr fp = (uint*) fp[0]; // saved fp } :
このルーチンには、現在のフレームポインターが引数として渡ってくるんだけど、 どうやって取り出しているかと思ったら、asm.S内にひっそりと置かれていた。
# return the frame pointer for the current function get_fp: MOV r0, fp bx lr
get_fp()って事で、呼び出されるサブルーチンなんだけど、実質は、返り値用のレジスター r0に fpの値を代入してるだけ。こういうのは、きっとC語の中にまぜこぜ出来ちゃうはず。
インライン アセンブル
前回やったasm構文、引数が大文字で始まっていたものだからてっきりマクロかと思っていた んだけど、いろいろ調べてみると、そうではなかった。C語にアセンブラーを埋め込むいわゆる、 インライン・センブリらしい。この際だと思って調べてみた。
GCCでインラインアセンブリを使用 する方法と留意点等 for x86
ARMな石に特化した説明としては、 ARM GCC Inline Assembler Cookbook が、良く参照されてるみたい。論文でもそうだけど、良く参照されるのは優れた文献。 リンクが多数なされているのは佳きWeb。ググル様はこれを定式化して大儲けしたけど、 なんの事は無い。学術的な方法をインターネットに応用しただけじゃん。これで特許に なるんですかね。と、凡人が申しております。
最近、ググルの社提で有名だった、「Don't Be Evil」(悪をなすな)が、最近発足したググルの 持ち株会社の規範から消えたらしい。これはぐぐる帝国の邁進を意味しますよ。何か検索 する度に、何を考えているか筒抜けになる。そしてそれを金儲けに利用する。 裏に控えるは、ぐぐるXが持ってる膨大なコンピュータ資源。人口知能で何でも、おみとうし。 その上前をはねるのは、本当の帝国。そう、田舎単位を使って当たり前と思ってる国。 英語の席巻で、インターネットの世界を我が物にしたあの国ですな。
いかん、いかん、つい横道に逸れてしまった。gccに毒されないように、別の道も探っておこう。 そう、 llvm界隈では、どうなっているか? 余り資料は見当たらないけど、逃げ道って事で。。。
差を取って儲ける
これ、世間の常識。安い時に買って、高い時に売る。株屋さんじゃ無くても知ってる。 安いコスト(利息)で、資金調達、高い金利で金を貸す。おまけに、手数料でがっぽりが銀行。
差は大事だよーーて所で、 fedoraで、xv6-armv7が動かないのを追い駆けてみます。こういう場合は、ちゃんと動いて いる系と比べてみるのが手っ取り早い。そう、diffを取るのね。
まずは正常系なウブの入り口と出口
|0xc0025a78 <forkret> push {r11, lr} | |0xc0025a7c <forkret+4> add r11, sp, #4 | B+>|0xc0025a80 <forkret+8> movw r0, #54800 ; 0xd610 | |0xc0025a84 <forkret+12> movt r0, #49162 ; 0xc00a | |0xc0025a88 <forkret+16> bl 0xc0025e58 <release> | : |0xc0025ab0 <forkret+56> bl 0xc0024204 <initlog> | >|0xc0025ab4 <forkret+60> pop {r11, pc} |
そして、fedora
|0xc0025e5c <forkret> str r11, [sp, #-8]! | |0xc0025e60 <forkret+4> str lr, [sp, #4] | |0xc0025e64 <forkret+8> add r11, sp, #4 | B+>|0xc0025e68 <forkret+12> movw r0, #58696 ; 0xe548 | |0xc0025e6c <forkret+16> movt r0, #49162 ; 0xc00a | |0xc0025e70 <forkret+20> bl 0xc002629c <release> | : |0xc0025e98 <forkret+60> bl 0xc00244f0 <initlog> | >|0xc0025e9c <forkret+64> nop ; (mov r0, r0) | |0xc0025ea0 <forkret+68> sub sp, r11, #4 | |0xc0025ea4 <forkret+72> ldr r11, [sp] | |0xc0025ea8 <forkret+76> add sp, sp, #4 | |0xc0025eac <forkret+80> pop {pc} ; (ldr pc, [sp], #4) |
見た通りウブの方は非常に素直。入り口でr11(fp)とlr(サブルーチンの戻り先)をpushし、 fpの値を調整してる。出口も素直に、pushした値をpop(lrのpop先をpcに向けるってのは、 ARMの定番技法)して抜けてる。
それに対して、異常なfedoraの方は、異常に複雑な事をやって、墓穴を掘っている。
で、案の定、forkretを抜けた時に、何処へ飛んで行くか追ってみると、正常なウブは、
>|0xc00275a8 <trapret> ldm sp, {sp, lr}^ | |0xc00275ac <trapret+4> add sp, sp, #8 | |0xc00275b0 <trapret+8> ldmfd sp!, {lr} | |0xc00275b4 <trapret+12> ldmfd sp!, {r2} | |0xc00275b8 <trapret+16> msr SPSR_fsxc, r2 | |0xc00275bc <trapret+20> ldm sp!, {r0, r1, r2, r3, r4, r5, r6, r7|
異常なfedoraは、
>|0xc0025e60 <forkret+4> str lr, [sp, #4] | |0xc0025e64 <forkret+8> add r11, sp, #4 | B+ |0xc0025e68 <forkret+12> movw r0, #58696 ; 0xe548 |
forkretに舞い戻りしてますよ。
正常な方は、trapretで何処に戻るかと言うと、usr/init.cのmainに戻る。随分端折った 言い方だな。userinit()中で、initcodeがロードされる。スケジューラーがそれを検知と 言うか、実行可として検知。実行。それは、ユーザーランドのinit.cを実行する コードだ。
まだ、端折っているな。initcodeの中では、システムコールexecが 実行(ユーザーランドのinit.cの起動)されるのだ。システムコールの帰結として、trap_swiが 実行されてるので、それに対応するtrapretが巡って来たって訳。
ああ、userinit()の中では、初めて起動するユーザー側のプロセスが、ちゃんと動けるように 下記のトラップフレームをセットアップしてるね。
(gdb) p/x *p->tf $1 = { sp_usr = 0x1000, // user stack size? lr_usr = 0x0, r14_svc = 0xc002510c, // lr for error_init spsr = 0x60000050, // IRQ=enable & user-mode r0 = 0x0, r1 = 0x0, r2 = 0x0, r3 = 0x0, r4 = 0x0, r5 = 0x0, r6 = 0x0, r7 = 0x0, r8 = 0x0, r9 = 0x0, r10 = 0x0, r11 = 0x0, r12 = 0x0, pc = 0x0 }
修正するぞ
万策尽きた。だってarm用のgccが変なコードを吐くんだもん。まてまて、こういう時はgccの 挙動を変えてミレ。どうやって?
makefile.incを覗いたら、コンパイルの仕方が、-g 付きになってた。これに-Oを付けて、 吐き出すコードを変えてみればよい。で、-O1 から始めて-O2 ,,,やってみたら、-Os の時に、ちゃんと動き出した。 まあ、その前に一波乱があったんだけどね。
それは、exec.cのコンパイル中、pgdirって変数が初期化されていない状態で使われてるって エラーになっちゃうの。コードを見ると、エラーを検出してbadへ飛んできた所で使ってた。 正常ルートの時は、ちゃんと初期化してる。んな訳で、初期化を関数の最初でやってあげたら、 ちゃんとコンパイル出来た。
オプチマイザーレベルを変えると、エラーの検出度も変わるのね。たまにやってみると、隠れた BUGのあぶり出しに有効かも。
で、動いたって言って喜んでいては、血肉にならないので、どんな風にgccがコードを 吐いたか、眺めておく。以下、kernel.asmからの抜粋。
void forkret(void){ c002286c: e92d4010 push {r4, lr} static int first = 1; // Still holding ptable.lock from scheduler. release(&ptable.lock); c0022870: e59f0020 ldr r0, [pc, #32] ; c0022898 <forkret+0x2c> c0022874: eb0001ce bl c0022fb4 <release> if (first) { c0022878: e59f301c ldr r3, [pc, #28] ; c002289c <forkret+0x30> c002287c: e5932004 ldr r2, [r3, #4] c0022880: e3520000 cmp r2, #0 c0022884: 08bd8010 popeq {r4, pc} first = 0; c0022888: e3a02000 mov r2, #0 c002288c: e5832004 str r2, [r3, #4] initlog(); } // Return to "caller", actually trapret (see allocproc). } c0022890: e8bd4010 pop {r4, lr} if (first) { // Some initialization functions must be run in the context // of a regular process (e.g., they call sleep), and thus cannot // be run from main(). first = 0; initlog(); c0022894: eafffe1f b c0022118 <initlog> c0022898: c00a94c4 .word 0xc00a94c4 c002289c: c0026000 .word 0xc0026000
ちら見、入り口と出口がウブ風になったな。こうなったらか、ちゃんと正しい所に 戻れて、動くようになったんか。
冒頭にある、static int first = 1; のコード相当が、消えてる。こんな事していいのか? >gcc。 よく考えたら、永遠に残る変数でっせ(static)宣言がある変数なんで、関数外で初期値を 設定しておける。だから、コード上からは消えた。
そして、if(first)が、末尾最適化(もどき)されて、この関数の出口方向へ行くのが先に来、initlog の関数呼び出しが、ジャンプ命令に置き換えられている。gcc賢いな。本当か? 末尾呼び出し最適化 (Tail Call Optimization)
forkretは何処から呼ばれる?
それは、allocproc()で、procの雛形を作った時に、セットされてた。
// skip the push {fp, lr} instruction in the prologue of forkret. // This is different from x86, in which the harderware pushes return // address before executing the callee. In ARM, return address is // loaded into the lr register, and push to the stack by the callee // (if and when necessary). We need to skip that instruction and let // it use our implementation. p->context->lr = (uint)forkret+4;
注意書きにあるように、関数の入り口部(プロローグ)は飛ばして設定してる。これは、 ウブなgcc用だ。fedoraの場合は、もう一命令先を帰りアドレスに設定すればいいんで ないかい。やってみた。動いたぞ。
gccと言えども、同じコードを吐くわけではないのね。こういう違いはどこで発生するの? gccのバージョン違いかな?
[sakae@fedora xv6-armv7]$ arm-linux-gnu-gcc -v 組み込み spec を使用しています。 COLLECT_GCC=arm-linux-gnu-gcc : スレッドモデル: single gcc バージョン 5.1.1 20150618 (Red Hat Cross 5.1.1-3) (GCC)
sakae@uB:~/xv6-armv7/src$ arm-linux-gnueabi-gcc -v Using built-in specs. COLLECT_GCC=arm-linux-gnueabi-gcc : Thread model: posix gcc version 4.7.4 (Ubuntu/Linaro 4.7.4-2ubuntu1)
exec
ここで、最初のユーザーランドプロセスであるinitの起動を見ておく。initcodeから、 execがシステムコールされるはず。
(gdb) bt #0 exec (path=0x1c "/init", argv=0xc7fdef08) at exec.c:27 #1 0xc002715c in sys_exec () at sysfile.c:473 #2 0xc00261cc in syscall () at syscall.c:154 #3 0xc0027724 in swi_handler (r=0xc7fdefb8) at trap.c:12 #4 0xc00275a8 in trap_swi () #5 0x00000010 in ?? ()
まず、お目当てのプログラム名から、nameiを使って、iノード番号を割り出し、それを 元に、elfのヘッダーを読み出す。マジックをみて、elfファイルである事を確認後、
42 pgdir = 0; 43 44=> if ((pgdir = kpt_alloc()) == 0) { 45 goto bad; 46 } 47 48 // Load program into memory. 49 sz = 0; 50 51 for (i = 0, off = elf.phoff; i < elf.phnum; i++, off += sizeof(ph)) { 52 if (readi(ip, (char*) &ph, off, sizeof(ph)) != sizeof(ph)) { 53 goto bad; 54 } : 64 if ((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0) { 65 goto bad; 66 } 67 68 if (loaduvm(pgdir, (char*) ph.vaddr, ip, ph.off, ph.filesz) < 0) { 69 goto bad; 70 } 71 }
メモリーページを用意。そして読み込み。続いて、
76 // Allocate two pages at the next page boundary. 77 // Make the first inaccessible. Use the second as the user stack. 78 sz = align_up (sz, PTE_SZ); 79 80 if ((sz = allocuvm(pgdir, sz, sz + 2 * PTE_SZ)) == 0) { 81 goto bad; 82 } 83 84 clearpteu(pgdir, (char*) (sz - 2 * PTE_SZ)); 85 86 sp = sz;
BSS領域? と、スタック領域を用意。次は、argc、argv関係をstack上に用意。それが、main()の 引数になるように、proc内の変数領域にセット。(r0,r1)
最後の最後に、
124 // Commit to the user image. 125 oldpgdir = proc->pgdir; 126 proc->pgdir = pgdir; 127 proc->sz = sz; 128 proc->tf->pc = elf.entry; 129 proc->tf->sp_usr = sp; 130 131 switchuvm(proc); 132 freevm(oldpgdir); 133 return 0;
proc内の変数をセットして終了。なお、switchvvmは、
161// Switch to the user page table (TTBR0) 162void switchuvm (struct proc *p) 163{ 164 uint val; 165 166=> pushcli(); 167 168 if (p->pgdir == 0) { 169 panic("switchuvm: no pgdir"); 170 } 171 172 val = (uint) V2P(p->pgdir) | 0x00; 173 174 asm("MCR p15, 0, %[v], c2, c0, 0": :[v]"r" (val):); 175 flush_tlb(); 176 177 popcli(); 178} 179
ユーザー用のページテーブルに切り替えてるようです。(まだ、追いきれていない)
こうして、initが起動すると、そこからユーザーとのインターフェースになるshellが 起こされる事になる。
祝20周年 OpenBSD
年に2回、淡々と版を重ねるOpenBSDだが、今回は特に目出度いリリース。
20周年を迎えたOpenBSD、「OpenBSD 5.8」を公開
10月に入った所で、一部の成果物が上がっていた事は知っていたけど、肝心の本体が 未公開。親分のTheo de Raadt氏をめぐり内紛でも有ったのかと心配してたけど、 10月18日の佳き日を待ってたって訳ね。納得。
sudoコマンドがdoasコマンドに置き換えられたそうだけど、いつ試してみるかなあ。