xv6-armv7(8)
前回取り上げた囲碁。gnugoが有るそうなので、fedoraに入れてみた。初心者って事で、19路盤は 止めて、9路盤で立ち上げてみた。路って、台湾とか中国あたりでの道を意味する言葉だよなあ。 何となく、整然と区画された街(札幌とか京都)を思い出すな。
将棋の駒で有名な山形県天童市では、人間を駒に見立てた将棋イベントが行われているけど、 これって、豊臣秀吉が始めたたしい。囲碁なら、札幌を通行止めにして行えばよい。
白石の代わりに、雪達磨、黒石の変わりに石炭。交通局の協力を得て、トラックで運ぶのさ。 白C7とかの変わりに、雪達磨 東2条とかね。雪祭りとペアで囲碁好きを呼べるぞ。
[sakae@fedora ~]$ gnugo --boardsize=9 GNU Go 3.8 Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 2008 and 2009 by the Free Software Foundation, Inc. See http://www.gnu.org/software/gnugo/ or contact Beginning ASCII mode game. Board Size: 9 Handicap 0 Komi: 0.0 Move Number: 0 To Move: black Computer player: White White (O) has captured 0 pieces Black (X) has captured 0 pieces A B C D E F G H J Last move: White F4 9 . . . . . . . . . 9 8 . . . . . . . . . 8 7 . . + . . . + . . 7 6 . . . . . O . . . 6 5 . . . . + . . . . 5 4 X . . . .(O). . . 4 3 . . X . . . + . . 3 2 . . . . . . . . . 2 1 . . . . . . . . . 1 A B C D E F G H J black(5): f5
CUIな端末では、上記のようなASCII表示だけど、X Window上のemacsを使うと、リアルな碁盤と 白黒石で打てるようになる。
付属のgnugo.elはちと古くてemacs24系には対応していない。 gnugo.elを取ってきて、gnugo-xpms.elと 供に、load-fileする。それから、M-x gnugoでゲームスタート。最初はASCIIモードで起動 してくるけど、iコマンドで、画像を使うようになる。コマンドヘルプは ? で表示される。
自分では囲碁してる積もりなんだけど、いつの間にか、五目並べに頭が切り替わっている。 そうすると、gnugoは、そんな定石無いぞ、新手かと警戒して、長考モードに入っちゃう。 今、考え中ーーーって、頭を巡らしているみたい。どんなコードになってるか。詳しい説明が infoにあった。一度読んで見れ。
つらつらinfoとか説明書を見てると、 GMPとかGTPとかってのが出て来る。これは何? GMPはコンピュータ対戦をする時の 通信プロトコルみたい。GTPはそれを進化させたものみたい。SGFなんて言葉も有るな。 こちらは碁譜を記録するフォーマットのようだ。これを使って名人戦を観賞するとか、 途中までプレイしたのを保存して、後でそこから継続するとかが出来るとな。
詳しい資料は、YSSと彩のページに有る。 モンテカルロ法で囲碁、将棋って、何も 考えずに乱数で手を決めるやつだな。乱数はメルセンヌ乱数あたりを使うのでしょうか。
将棋も囲碁も実は今まで何も知らなかったし、プレイも出来なかった。将来の自分のために 囲碁を覚えておこう。何故、囲碁か? 駒が2種類と言う単純な理由です。石の変わりに 1円と10円で代用できるからね。(老人ホームには碁セットぐらいは置いてあるぞ。)
やさしい囲碁入門講座 用語解説が載ってて便利。
日本棋院が、日本の総本山?
NetBSD カーネルモジュール
NetBSDカーネル内部で動作するLuaを試すによると、 結構昔から取り組みが始まっていて、7.0版で正式にサポートされたんだな。
NetBSD-7.0_RC3でLuaカーネルモジュールを試してみる こちらはフライングぽいけど、新しい事を試すのは楽しいよね。
NetBSDでもモジュールが使えるそうなので、本当かチェックしてみました。
nb7$ modstat NAME CLASS SOURCE REFS ADDRESS SIZE REQUIRES accf_dataready misc builtin 0 0 - - accf_httpready misc builtin 0 0 - - acpiacad driver builtin 0 0 - - acpibat driver builtin 0 0 - - : xc3028 driver builtin 1 0 - i2cexec xc5k driver builtin 1 0 - i2cexec zlib misc builtin 1 0 - -
普通に使われています。ドライバーだけでなく、zlibみたいなカーネルだってその機能欲しいって のが登録されてます。カーネル内でzlibしたいって、ちと無謀な気がしないでもないですが、 Javaとお近づきになりたい人がねじ込んだんでしょうか。
そんじゃ、本題。
nb7# pwd /usr/src/sys/modules/lua nb7# modload lua nb7# luactl create hoge hoge created nb7# luactl load hoge ./test.lua ./test.lua loaded into hoge nb7# modstat | grep lua lua misc filesys 1 d94c0000 153592 - luasystm misc filesys 0 d8dc6000 1900 lua nb7# luactl destroy hoge hoge destroyed nb7# modunload luasystm nb7# modunload lua
使い方の例。テストケースが/usr/src/sys/modules/lua/test.luaに用意されてた。luaモジュールをロード。 インスタンス hoge を作って、そこにテストケースをロード。modstatで見ると、関係者が ロードされた。後はインスタンスを壊してから、それぞれのモジュールをアンロードして カーネル空間を綺麗にする。
実行結果は、/var/log/messageに出て来る。
Nov 13 06:56:44 nb7 /netbsd: lua0: Lua 5.3.0 (kernel) Copyright (c) 2015, Lourival Vieira Neto <lneto@NetBSD.org>. Copyright (C) 1994-2015 Lua.org, PUC-Rio Nov 13 06:58:25 nb7 /netbsd: hello, kernel world! Nov 13 06:59:12 nb7 /netbsd: I am about to be closed Nov 13 07:00:21 nb7 /netbsd: lua0: detached
なんでもLuaはヤマハのルーターに採用されていたり、gnuplotにこっそり忍び込ませてあったり して、組み込み言語にうってつけ。オイラーも実務で使ってるぞ。awesomeって言うWindow manager でね。そう言えば、日本発の組み込み言語、mrubyはその後発展を 遂げているのでしょうか?
NetBSDな人が、perlでもなくpythonでもなくrubyでもなくluaを選んだって所に慧眼を 感じますよ。一時perlがシステム管理ツールに採用され、BSD業界はperlの追い出しに 多大な努力を要しました。今はLinux業界がpythonを2から3系に上げるのに苦労して ますなあ。
fedoraは頑張ってpythonで書かれたyumから決別してdnfに移行しつつありますけど、その他の 大物はpythonなんでしょ。
Lua 5.3 リファレンスマニュアル 一応Luaも教養として押さえておきましょうかね。 C語との相互運用が簡単そうですよ。
Lua in NetBSD
NetBSDでは、インストールした瞬間から普通にluaが使えます。
nb7$ which lua /usr/bin/lua nb7$ lua Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio > ^D
しっかり食い込んでと言うか、組み込まれています。ちょっと間が有ったので某pkgを コンパイルしたら、perlとpython2.7もこっそり忍び入ってきましたけど、場所は/usr/pkg/bin って事で、外様扱い。いつでもご主人の気まぐれで、お家断絶出来ますからね。
折角、luaが取り入ってくれているので、Cとの親和性を確認。wikiに有ったやつ。
nb7$ cat test.c #include <stdio.h> #include <stdlib.h> #include <lua.h> #include <lauxlib.h> int my_add(lua_State* L) { int x = (int)lua_tonumber(L, 1); // 第1引数の取得。 int y = (int)lua_tonumber(L, 2); // 第2引数の取得。 lua_settop(L, 0); // スタックのクリア。 int ret = x + y; // C/C++ 側での演算。 lua_pushnumber(L, ret); // 返却値をプッシュ。 return 1; } int main(void) { lua_State* L = luaL_newstate(); // Lua VM の初期化。 luaL_openlibs(L); // Lua の標準ライブラリを使えるようにする。 lua_register(L, "my_add", my_add); // Lua VM に C/C++ 関数を登録。 if (luaL_dostring(L, "print(my_add(5, 3))")) { // Lua スクリプトを実行。 lua_close(L); // Lua VM を閉じる。 exit(EXIT_FAILURE); // エラー終了。 } lua_close(L); return 0; }
上記はC語の関数をLuaに登録して、それをLuaのスクリプトから呼び出す例とか。下記が 実行例。
nb7$ cc test.c -llua nb7$ ./a.out 8.0 nb7$ ldd ./a.out ./a.out: -llua.5 => /usr/lib/liblua.so.5 -lm.0 => /usr/lib/libm.so.0 -lgcc_s.1 => /usr/lib/libgcc_s.so.1 -lc.12 => /usr/lib/libc.so.12
どんなlua語がサポートされてるか、lua.hとluauxlib.hを参照。#ifdef _KERNELとかが あちこちに取り混ぜてあって、kernelとの親和性を確保してる事が良く分かる。
Luaが組み込みって事は、そのソース一式も何処かに置いてあるに違いない。探してみたら、
nb7$ ls /usr/src/external Makefile broadcom/ ibm-public/ mit/ README bsd/ intel-fw-eula/ public-domain/ apache2/ cddl/ intel-fw-public/ realtek/ atheros/ historical/ lgpl3/ zlib/
ここに集積されてるようです。はてluaは何処でしょうか? って、世界の果てまでいってQ みたいな口ぶりだな。一発で答えられた人はOSSに造詣がありますね。それじゃヒント。 各dirはライセンス名になってます。おいらは、それすら気が付かず、READMEを見たよ。
nb7$ ls /usr/src/external/mit/ Makefile expat/ lua/ xorg/
luaって、ユルフワなMITライセンスなんです。どこかのライセンスみたいに、感染は しませんから、大手を振って組み込みに使えます。
ここにあるのをそうのまま利用すると、NetBSD用に改変されてるだろうから、 素直にtar玉をLuaをリナにでも入れて、確認してみるかな。
メモリー管理
wikipediaをうろうろしてたら、メモリー管理なんてのに行き当たり、面白そうなのでちょっと 調べてみたよ。
メモリ管理、Buddyシステム、kmalloc、スラブアロケータ
その4 「メモリ管理問題」懐かしい人に出会った。
Linux2.2 on x86 でのメモリ管理機構忌まわしい糞石擁護の解説有り。
色々有りすぎるって事は、それだけ大きな問題って事ですな。カーネルにもGCをって言ったら、 笑われる?
mkfs
再び、xv6の話。ユーザーランド側のアプリとかファイルは、fs.imgってdataにまとめられて、 カーネル中に挿入される。今回は、そのfs.imgはどうやって作成されるか見ていく。
tools/mkfs.cがそれ。単独で動くアプリ。
[sakae@fedora tools]$ ./mkfs zz.img README used 29 (bit 1 ninode 26) free 29 log 10 total 512 balloc: first 47 blocks have been allocated balloc: write bitmap block at sector 28 [sakae@fedora tools]$ ./mkfs zz.img README _cat _usertests used 29 (bit 1 ninode 26) free 29 log 10 total 512 balloc: first 176 blocks have been allocated balloc: write bitmap block at sector 28
要はdiskをイニシャライズしてファイルを書きましょってアプリ。最初の例はREADMEってファイルだけを置いた。 次の例は、その他のデータも置いてみた。
ソースを見て行く。大事なのは、fs.h
// Block 0 is unused. // Block 1 is super block. // Blocks 2 through sb.ninodes/IPB hold inodes. // Then free bitmap blocks holding sb.size bits. // Then sb.nblocks data blocks. // Then sb.nlog log blocks. #define ROOTINO 1 // root i-number #define BSIZE 512 // block size // File system super block struct superblock { uint size; // Size of file system image (blocks) uint nblocks; // Number of data blocks uint ninodes; // Number of inodes. uint nlog; // Number of log blocks };
コメントではブロック番号って言ってるけど、巷ではセクター番号とかLBAとか言う事がある。 512Byte(1セクター)単位で0から番号が付いている。
1ブロック目に大事なデータが4つ格納されてる。DISKの大きさ、データエリアの大きさ、 iノードの大きさ、そして、ログエリア。このログエリアは、データベースで使われる技術、 ログを取っておいてコミットするってのに使う。ジゃーナリングファイルシステムを実現 してるんだな。
なお、ここでは使われていないけど、先頭 セクターには、プログラムのロード用コード(通称、ブートローダー)が置かれる事がある。
#define NDIRECT 12 #define NINDIRECT (BSIZE / sizeof(uint)) #define MAXFILE (NDIRECT + NINDIRECT) // On-disk inode structure struct dinode { short type; // File type short major; // Major device number (T_DEV only) short minor; // Minor device number (T_DEV only) short nlink; // Number of links to inode in file system uint size; // Size of file (bytes) uint addrs[NDIRECT+1]; // Data block addresses };
iノードの構造。一つのファイルの最大サイズは、12 + 512 / 4 ブロックまで。これに512を 乗ずると、バイト数になる。
// Inodes per block. #define IPB (BSIZE / sizeof(struct dinode)) ;; = 8 // Bitmap bits per block #define BPB (BSIZE*8) // Directory is a file containing a sequence of dirent structures. #define DIRSIZ 14 struct dirent { ushort inum; char name[DIRSIZ]; };
後は、mkfs.cに出てきそうなやつ。Bitmapは、どのブロックが使われているかをbitで表現した 表。direntは、iノード番号とファイル名の対応表。このデータがづらっと収められているのが dirの実体だったりする。ファイル名の長さは、 伝統的に14文字。
mkfs.cの冒頭部分で、作成するDISKの構造を宣言してある。
16int nblocks = 985 - 512; 17int nlog = LOGSIZE; ;; = 10 18int ninodes = 200; 19int size = 1024 - 512;
変数の初期化の時、引き算をしてるけど、これはDISKサイズを縮小させてgdbの応答を良く する為に行っただけである。同時に2箇所を修正しないと、プログラム中で矛盾してるって 言われて落ちるから注意。
gdbは10進/16進数な電卓だー
早速、diskを作って、その内容をdumpしてみる。作る材料は次の通り
[sakae@fedora tools]$ ./mkfs zz.img README _cat _usertests used 29 (bit 1 ninode 26) free 29 log 10 total 1024 balloc: first 176 blocks have been allocated balloc: write bitmap block at sector 28
[sakae@fedora tools]$ bmore zz.img 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * 00000200 00 04 00 00 D9 03 00 00 C8 00 00 00 0A 00 00 00 ................ 00000210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * 00000440 01 00 00 00 00 00 01 00 00 02 00 00 1D 00 00 00 ................ 00000450 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bvi付属のbmoreを使うと、ずっとZEROデータが続く所は省略してくれて便利です。 で、最初のZEROで無いデータが現れる所は、ファイルの先頭から数えて0x200の所。
結構、dumpとかやると、16進数とか10進数が入り乱れた数値計算が必要。こんな時は どんな計算機を使う? お手軽なのは超身近にあるgdb。
(gdb) p 0x200 $1 = 512 (gdb) p/x 512 $2 = 0x200
最初の例は、0x200って10進にすると幾ら? 次の例は、逆演算っていうか、表示させる時に、/xを 付けて、16進で表示ヨロって指示だ。
(gdb) p/x (0x200 * 28) $4 = 0x3800
このように、16進と10進の混合演算もお手の物。どうやら、上の例では、Bitmapが28セクターから 始まってるそうなので、みると
00003800 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................ 00003810 FF FF FF FF FF FF 00 00 00 00 00 00 00 00 00 00 ................ 00003820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
確かにそれっぽいデータが並んでた。もっと真面目に検討すると、 8bit/byte x 22Byte = 176 って事で、先の表示結果、最初から176ブロック割り当てたってのに 符合してる。
まあ、gdbは計算も出来るし、プログラマーの強ーーいみかたって事ですな。 ついでに、もう少しみておくと
00003A00 01 00 2E 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00003A10 01 00 2E 2E 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00003A20 02 00 52 45 41 44 4D 45 00 00 00 00 00 00 00 00 ..README........ 00003A30 03 00 63 61 74 00 00 00 00 00 00 00 00 00 00 00 ..cat........... 00003A40 04 00 75 73 65 72 74 65 73 74 73 00 00 00 00 00 ..usertests..... 00003A50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
ここは、どうやらdir領域っぽい。3A00って?
(gdb) p (0x3a00 / 0x200) $8 = 29
セクター番号に直せば、29か。ここから、データ領域なんだな。ってな具合にすいすいと 解析が進めばいいな。
gdbで調査
gdbは計算機だけでなく、プログラムの調査にも威力を発揮する。が、ちょいと不便な 所がある。C語のマクロがどうなってるか調べるのが面倒。で、 C言語のプリプロセッサマクロ なんてのがサポートされているようだ。 使うには、おまじないが必要との事。
[sakae@fedora tools]$ make gcc -Werror -Wall -gdwarf-2 -g3 -O0 -iquote ../ -o mkfs mkfs.c
g3とgdwarf-2を付けてコンパイルしておく。すると
(gdb) info macro BSIZE Defined at /home/sakae/xv6-armv7/tools/../fs.h:12 included at /home/sakae/xv6-armv7/tools/mkfs.c:10 #define BSIZE 512 (gdb) info macro IPB Defined at /home/sakae/xv6-armv7/tools/../fs.h:37 included at /home/sakae/xv6-armv7/tools/mkfs.c:10 #define IPB (BSIZE / sizeof(struct dinode))
gdbの中からマクロ定義を参照出来るようになる。またマクロ展開も出来るぞ。minってマクロが ソースの中ほどに定義されてた。きっとアドホックに追加したものだろうな。
#define min(a, b) ((a) < (b) ? (a) : (b))
(gdb) macro expand min(12, 44) expands to: ((12) < (44) ? (12) : (44))
これをgdb君にevalってもらうと
(gdb) p ((12) < (44) ? (12) : (44)) $1 = 12
正しい答えが返ってきた。gdbは隠れlispです。(違っう、いつもこんなに旨くはいかないよ)
さて、mkfsをemacs上のgdbで解析したいんだけど、こやつ、実行中に色々な報告をしてくるんで、 画面がごちゃごちゃしちゃう。そこでハロワ本に習って、gdbserverの登場ですよ。
[sakae@fedora tools]$ gdbserver localhost:1234 ./mkfs README _cat _usertests Process ./mkfs created; pid = 16551 Listening on port 1234 Remote debugging from host 127.0.0.1 used 29 (bit 1 ninode 26) free 29 log 10 total 1024 ;;; run from emacs balloc: first 159 blocks have been allocated balloc: write bitmap block at sector 28 Child exited with status 0
一方emacsからは、M-x gdb して、mkfsを指定する
Reading symbols from mkfs...done. (gdb) target extended-remote localhost:1234 Remote debugging using localhost:1234 Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done. 0xb7fdda70 in _start () from /lib/ld-linux.so.2 (gdb) b main Breakpoint 1 at 0x804877d: file mkfs.c, line 72. (gdb) c Continuing. Breakpoint 1, main (argc=4, argv=0xbffff0e4) at mkfs.c:72 warning: Source file is more recent than executable. 72 if(argc < 2){ (gdb) c Continuing. [Inferior 1 (process 16551) exited normally]
cするとmainの所で止まるので後は普通にgdb操作をすれば良い。緊急で、相手方を終了させたい 時は、monitor exit する。
実録 gdbで解析
以上で、gdbの発展的使い方は分かった。実際に応用してみる。上記のようにして起動。 気になる所で止めて、確認していけばよい。
91 bitblocks = size/(512*8) + 1; 92=>usedblocks = ninodes / IPB + 3 + bitblocks;
91行目で、bitblocのサイズを計算してる。1ブロックに512Byteであるから、8倍すれば bit数になる。それでサイズを割れば、必要なbitblock数が出てくる。プラス1してるのは、 割り算が切り捨てだから。
92行目は、必要なブロック数の算出。ninodeってのは、幾つファイルを保持するかって値。 それをIPBで除すれば、必要なブロック数になる。プラス3は、使ってない0ブロック目と、 1ブロック目のスーパーブロックと、割り算結果の切り上げ。これらは、DISKの先頭からの 必要ブロック数。
所で、IPBって幾つ何?
(gdb) info macro IPB Defined at /home/sakae/xv6-armv7/tools/../fs.h:37 included at /home/sakae/xv6-armv7/tools/mkfs.c:10 #define IPB (BSIZE / sizeof(struct dinode)) (gdb) info macro BSIZE Defined at /home/sakae/xv6-armv7/tools/../fs.h:12 included at /home/sakae/xv6-armv7/tools/mkfs.c:10 #define BSIZE 512 (gdb) p sizeof(struct dinode) $1 = 64
こんな風にすると、後は512/64の暗算で求まるな。いちいちfs.hを参照しなくてもOk。
100 for(i = 0; i < nblocks + usedblocks + nlog; i++) 101=> wsect(i, zeroes);
ここは、全ブロックをZEROクリアしてる部分。1024(=size)回もループを回すのはたまらんな。 早くこのループを脱出したい。こういう時はu(util)コマンド使うのが定石。uを数回叩くと、 ループを脱出してくれる。
別解もあって、次の行(この場合は、103行目)にテンポラリィーブレークを置いて、cする方法。 tb 103で貼れる。
107 rootino = ialloc(T_DIR); 108=>assert(rootino == ROOTINO); 109 110 bzero(&de, sizeof(de)); 111 de.inum = xshort(rootino); 112 strcpy(de.name, "."); 113 iappend(rootino, &de, sizeof(de));
dir用のinodeを一つ用意それは、ROOTINOって事で1になってるはず。それを使って、 dir情報を登録。113行目に到達した時
(gdb) p de $5 = {inum = 1, name = ".", '\000' <repeats 12 times>}
そしてiappendを追っていくと、最後にそれを書き込むところに到達。どんなデータに なってるかと言うと
(gdb) p din $8 = { type = 1, major = 0, minor = 0, nlink = 1, size = 16, addrs = {29, 0 <repeats 12 times>} }
同様にして親dir情報を書き、次はいよいよ、指定したファイル類の書き込みルーチンだ。 ちょっと飛ばして、最後のbitmapを書き込むballocルーチン。
239 printf("balloc: first %d blocks have been allocated\n", used); 240 assert(used < 512*8); 241 bzero(buf, 512); 242=>for(i = 0; i < used; i++){ 243 buf[i/8] = buf[i/8] | (0x1 << (i%8)); 244 } 245 printf("balloc: write bitmap block at sector %zu\n", ninodes/IPB + 3); 246 wsect(ninodes / IPB + 3, buf);
ループ変数 i は、ブロック番号になる。それをbitt番号に変換して、書き込むデータを bufに溜め込む。assertでも分かるように、このシステムの最大収録可能ブロック数は、4096 ブロックまでだ。最後に、246行目で、セクターに書き込んでいる。
途中ちょっと省略しちゃったけど、以上で実録モード終了。
tips
ハロワ攻略本を補間するような内容。大学でもこういう事に注目してくれるようになったんだ。
Linuxであるパッケージを(debugのために)自前コンパイルしようとすると、結構リナの いやがらせを受ける事がある。そういう時はいやがらせを跳ね除ける知恵があるらしい。
$ sudo apt-get build-dep pkg-name
使うかも知れないので、 Git/gitlabで共同作業をするための最小限の知識 を得ておく。
そして、昔懐かしい、 机上デバッグ なんてのを見つけた。昔、出入りの業者さんが、プログラムをプリンター用紙に出して、 それを床に広げて、赤鉛筆と青鉛筆を耳に挟んで(近頃、こういうの見ないね)、怪しい 所は赤線(それは楽しい所って、いつの時代だ? 今だと、新風俗かな)で、チェック済みの 所は、青線を引いていた。ああ、青線の方が赤線よりも怪しいし危険か? 詳しい事は、 80歳以上の年寄りにでも聞いてみれ。
こういうの、机上デバックじゃなくて、床上デバッグって言うんだぞ。床上技が得意な人は 尊敬(羨ましがられた)されたんだぞ。
etc
アプルからipad pro買えるようになったから見にこいってCMが流れてきた。iMacも新装 したらしいしな。見に行くと欲しくなる。じっと我慢。
とか思っていたら、とうとうipad用にfirefox まで出たみたい。このタイミングで出るとは、firefox財団もアプルから献金を受けて いませんかね?
取りあえず、ipadに入れてミレ。