天泣記

2014-05-20 (Tue)

#1

qemu で armhf な Debian GNU/Linux (wheezy) を動かしてみた。

ホスト環境は Debian GNU/Linux (jessie) で、qemu-system-arm をインストールしておく。

  1. Debian の armhf 用の installer をとってくる。

    今回は qemu-system-arm でエミュレートする vexpress-a9 というマシンを使うので、それ用の installer をとってくる。(vexpress-a9 はメモリが 1G まで使えるのが良い。versatilepb は 256M までしか使えなくて厳しい)

    % wget ftp://ftp.jp.debian.org/debian/dists/wheezy/main/installer-armhf/20130613+deb7u2+b1/images/vexpress/netboot/{vmlinuz-3.2.0-4-vexpress,initrd.gz}

    Linux kernel と initrd であるが、これが installer である。CD や USB メモリのイメージではない。

  2. disk image をつくる

    以下で作っているのは raw で 8G なファイルという単純なものだが、LVM を使うとか他の選択肢もあるだろう。

    % qemu-img create debian-arm.img 8G
    Formatting 'debian-arm.img', fmt=raw size=8589934592
  3. installer を起動する

    kernel と initrd をどうやって使うかというと、qemu-system-arm のオプションで指定する。まぁ、仮想マシンのメモリ内に適切に配置して実行できるなら特別に boot loader を使わなくてもいいのだろう。

    % qemu-system-arm \
    -M vexpress-a9 -m 1G \
    -kernel vmlinuz-3.2.0-4-vexpress \
    -initrd initrd.gz \
    -drive file=debian-arm.img,if=sd,cache=writeback

    起動すると、ウインドウが出てきて、Debian installer が起動する。

    以下のようにオプションを足せば、curses を使うモードにもできる。(遠くのマシンでやるには便利)

    % qemu-system-arm \
    -M vexpress-a9 -m 1G \
    -kernel vmlinuz-3.2.0-4-vexpress \
    -initrd initrd.gz \
    -append 'console=ttyAMA0' \
    -drive file=debian-arm.img,if=sd,cache=writeback \
    -curses -serial stdio

    なお、vexpress の kernel は PCI をサポートしていないようで、-hda を使って disk を指定すると No disk drive was detected とか出てきてインストールできない。そのため、SDカードとして扱う。(参考: <URL:https://gist.github.com/bdsatish/7476239>) 単に -sd debian-arm.img じゃなくて -drive ... としているのは耐えがたいほど遅かったからである。

  4. Debian installer と対話する

    基本的に、普通にやればいい。

    最後のほうで、No boot loader installed というメッセージが出て、/dev/mmcblk0p1 の /vmlinuz に root=/dev/mmcblk0p2 と指定して 起動するようにいわれる。

    というわけで、install された kernel と initrd を外に取り出さないといけない。これをやる方法はいろいろあるだろうが、今回は reboot する前に (Alt-F2 で) shell を動かして、nc で取り出した。

    • 外部の端末:

      nc -l 9999 > boot.tar
    • QEMU中の端末

      cd /target
      tar cf - boot | nc 外部のIPアドレス 9999
    • 外部の端末:

      tar xf boot.tar

    (他の方法としては、qemu を止めた後に debian-arm.img から取り出すことが考えられる。)

    取り出したら installer から reboot し、qemu を適当なところで Ctrl-C で止める。

  5. install されたものを起動する

    取り出した kernel と initrd を使って起動する。

    % qemu-system-arm \
    -M vexpress-a9 -m 1G \
    -kernel boot/vmlinuz \
    -initrd boot/initrd.img \
    -append root=/dev/mmcblk0p2 \
    -drive file=debian-arm.img,if=sd,cache=writeback \
    -net nic -net user,hostfwd=tcp:127.0.0.1:3022-:22

    これで動くのはごく普通の Debian で、(installer で外していなければ) sshd が動いている。qemu に hostfwd=tcp:127.0.0.1:3022-:22 と指定して、外側の 127.0.0.1:3022 から内側のマシンの 22 に port forward しているので、外側で ssh 127.0.0.1 -p 3022 とすると中にログインできる。

  6. ウインドウを出さないで動かす

    ウインドウが出るのは試すのにはいいのだが、動かしっぱなしにするにはうれしくない。

    qemu は標準入出力を仮想マシンのシリアルと接続できるので、そのようにしてみる。

    まず、arm な GNU/Linux では、シリアルは /dev/ttyAMA0 というデバイスのようである。なので、/etc/inittab に以下の行を足して ttyAMA0 にログインプロンプトを出すようにしておく。

    AMA0:2345:respawn:/sbin/getty 38400 ttyAMA0

    そして、qemu を以下のように起動する。

    % qemu-system-arm \
    -M vexpress-a9 -m 1G \
    -kernel boot/vmlinuz \
    -initrd boot/initrd.img \
    -append 'root=/dev/mmcblk0p2 console=ttyAMA0' \
    -nographic \
    -drive file=debian-arm.img,if=sd,cache=writeback \
    -net nic -net user,hostfwd=tcp:127.0.0.1:3022-:22

    こうすると、qemu-system-arm の標準入出力が内部の仮想マシンの ttyAMA0 と接続され、起動した端末から普通にログインできる。

    とりあえずこれを screen の中で飼えばいいかな。

2014-05-24 (Sat)

#1

file descriptor passing で、fd が受け取られないで消えてしまったらどうなるか試してみた。

端末1:

% uname -mrsv
Linux 3.14-1-amd64 #1 SMP Debian 3.14.4-1 (2014-05-13) x86_64
% ruby -rsocket -e '
Socket.unix_server_loop("/tmp/s") {|s|
  p s
  p :before_sleep; t = Time.now
  sleep 3
  p :after_sleep; p Time.now-t
  s.close
}'
#<Socket:fd 8>
:before_sleep
:after_sleep
3.000132032

端末2:

% ruby -rsocket -e '
s = Socket.unix("/tmp/s")
r, w = IO.pipe
s.sendmsg("\0", 0, nil, Socket::AncillaryData.unix_rights(w))
w.close
p :before_read; t = Time.now
p r.read
p :after_read; p Time.now - t
'
:before_read
""
:after_read
3.000255096

なにをやっているかというと、端末1で起動した Unix domain socket server に、端末2で起動した client が接続し、その接続を通してパイプの書き込み側を送る。しかし、server は受け取れるパイプを受け取らずに、3秒後に接続されたソケットを close する。

client では、パイプの書き込み側は送った後にすぐに close する。そうすると、書き込み側はどのプロセスにも存在せず、kernel の中にしかない状態になる。その状態で読み込み側からデータを読み出そうとするとどうなるか。

結果としては、(だれもデータを書き込まないので) データは読み出せず、書き込み側を取り出せるプロセスが存在しなくなった時点で EOF が検出される。

まぁ、順当な結果だろう。


[latest]


田中哲