NetBSDでLinuxエミュレーションを利用する

NetBSDのLinuxエミュレーションは、仮想マシン上でLinuxカーネルを動作させるものではない。NetBSDのカーネル上でLinuxバイナリを動作させるものだ。Linuxエミュレーションを使うと、NetBSDネイティブでは動作しない有用なプログラムを数多く動かすことができる。たとえばSunの1.4 Java Runtime EnvironmentやJDKなどだ。

Linuxエミュレーションをセットアップするには、NetBSDカーネルのカーネルオプションCOMPAT_LINUXの指定と、いくつかのローカルファイルが必要だ。COMPAT_LINUXオプションは、デフォルトのGENERIC設定では最初から指定されている。他の設定を使用している場合は、カーネル設定ファイルに”options COMPAT_LINUX”という行が記述されているかどうか確認すること。また、Linuxプログラムの多くはダイナミック・リンクを使用するため、Linuxの共有ライブラリが必要となる。これらは/emul/linuxにインストールすることになる。

必要なライブラリおよびサポート・ファイルをセットアップするのに最も簡単なのは、pkgsrcに含まれるパッケージを利用して、さまざまなバイナリをきちんとインストールするという方法だ。柱になるのはsuse_baseというパッケージだが、他にもいくつかある。それらは、suse_linuxパッケージをインストールすれば、一度にまとめて導入することができ、X Windowのサポートを含む、一般的なライブラリの大半がインストールされる。本記事の執筆時点では、このパッケージはSUSE Linux バージョン7.3を基にしている。なおこれは、SUSEをインストールするということではない。共有ライブラリおよびサポート・ファイルをいくつか展開するだけだ。

場合によっては、/procファイルシステムのマウントが必要となる。このファイルシステムは、さまざまなシステム・データ構造やプロセス固有のデータ構造にアクセスできるようにするためのものだ。このファイルシステムが既にfstabに含まれている場合は、オプションのフィールドに”linux”オプションを追加する。含まれていない場合は、次の例のような行を記述する。

/proc /proc procfs rw,linux 0 0

/procディレクトリはあらかじめ作成しておく必要がある。所有者はroot、アクセス権は755とする。

以上の手順が済めば、たいていのLinuxバイナリはそのまま動くようになるはずだ。Linuxの実行可能ファイルを起動すると、NetBSDのカーネルはそれに対し、Linuxエミュレーションが必要というフラグを立てる。これにより、いくつかのシステム・コールの挙動が変わり、Linuxカーネルと互換性のある動作が実現される。

パッケージによっては、Linuxエミュレーション・モードで奇妙なエラー・メッセージが出るものがある。たとえば、StarOffice 7では、起動中に”file_image_pagein: Function not implemented”というメッセージが幾度となく出てくるが、それでもきちんと動作する。また、SunのJREやJDKといったパッケージは問題なく動作するし、LinuxビルドのNetscapeもそうだ。動作しないプログラムを見付けるには、もう少し広い範囲に目を向ける必要がある。ビデオ・ゲームは問題が生じる可能性がもっと高いし、エミュレーションが難しいシステム・コールを使用する一部のプログラムについても同じことが言える。たとえば、WINEのようなプログラムのLinuxビルドを動作させるには、かなりの苦労が必要となる可能性が非常に高いし、全く動作しないことも考えられる。

Linuxエミュレーションのコードは、NetBSDプロジェクトのスタッフによるメンテナンスが積極的に行われているため、サポートはどんどん向上している。少し前は不安定だったり信頼性に欠けたりした部分が、今では改善されている場合もある。また、表面上はエミュレーションに起因するように見える問題が、実はエミュレーションのもとで動作するプログラム自体の問題ということも時としてある。たとえば、マルチスレッド・プログラムでSunのJavaランタイムがハングするというバグが以前あったのだが、Sunがこのバグを修正するまでは、私はエミュレーションの問題だと思っていた。実際はJavaランタイムのバグだったのだ。

動作のしくみ

NetBSDのカーネルには、さまざまな種類の実行可能プログラムについて定義されたexecsw構造体が多数用意されている。execsw構造体のメンバの1つにstruct emulオブジェクトがある。これは、実行するプロセスに対応付けられるもので、この中には、システム・コールやエラー・コードなど、システム間でのやり取りを変換するためのさまざまなテーブルが格納されている。バイナリを実行しようとするとき、カーネルは、利用可能なexecsw構造体を1つずつ調べていき、実行対象のバイナリはそのexecsw構造体で処理できるものかどうかをチェックする。たとえば、Linuxバイナリかどうかのチェックでは、そのファイルのELFヘッダをLinuxエミュレータが調べる。ファイルを処理できるexecsw構造体が見付かったら、カーネルはそれを利用する。execsw構造体にstruct emulが関連付けられている場合には、そのエミュレーション層を使ってプロセスの処理が行われる。

このしくみを、具体例で説明するとこうなる。ユーザがLinuxのELFバイナリを起動しようとしたとする。するとまず、NetBSDの実行ファイル用のexecsw構造体のチェック・ルーチンによってファイルが調べられる。そして、この構造体は当該ファイルを扱えないとの結論に達し、この構造体での処理は却下される。次にexecシステム・コールは、他の構造体(エミュレータ用の構造体や、シェル・スクリプトで使うデフォルトの「インタプリタ」構造体など)でチェックを試みる。そして、Linuxエミュレーションのexecsw構造体のチェック・ルーチンで、対象のファイルを処理できるということが確認される。続いて、この構造体が保持するstruct emulが当該プロセスに対応付けられる。これ以降、このプロセスは、Linuxエミュレータのコードによって実行されることになる。システム・コールやerrno値をはじめ、カーネルとユーザ・コードの間のAPIで使用されるものはすべて、Linuxエミュレータにより変換される。

ファイルシステムのからくり

明らかに問題となる点が1つある。大半のプログラムは動的なリンクを使うため、共有Cライブラリを見付け出す必要があり、したがって共有Cライブラリがロードされている必要があるということだ。LinuxシステムとBSDシステムではライブラリが大きく異なるので、Linuxの実行ファイル用の追加的な共有ライブラリが必要だ。

さまざまなシステム・ファイルの中から「正しい」バージョンをロードする処理を簡便化するために、カーネルは、エミュレーション下のバイナリから行われたファイル探索をすべてインターセプトする。そしてまず、”shadow root”(LinuxシステムとBSDシステムとで差異があるファイルが格納されている/emul/linux配下のディレクトリ・ツリー)で探索を行う。この中に目的のファイルがない場合は、通常のファイルシステム・ツリーで探索を行う。この結果、libc.soはshadow rootからロードされる一方、ユーザのホーム・ディレクトリは通常のファイルシステム・ツリーのものが使われることになる。/emul/linuxツリーを見て、その中のどのファイルがロードされているかを確認すると、Linuxエミュレーションのコードを理解するうえで大いに役立つ。

このしくみは、全く完璧というわけではなく、たとえばシンボリック・リンクはshadow rootを使用しない。だが実際上は、このしくみで特に問題はない。

LinuxシステムとNetBSDシステムでは、/procファイルシステムで使用されるフォーマットが少し異なる。NetBSDのmount_procfsコマンドでは、-o linuxというオプションを指定することができる。これを使うと、Linuxとの互換性が高いフォーマットに変わるほか、NetBSDの通常の/procファイルシステムにはないLinux向けの機能がいくつか使えるようになる(たとえば”stat”というファイルである。これは、NetBSDが通常使用する”status”というファイルと似たデータを保持するが、フォーマットが異なるというものだ)。すべてのプログラムでこれが必要というわけではないが、多くのものには必要である。

うまく動作しないプログラム

エミュレーション・モードで、すべてのアプリケーションがきちんと動作するとは限らない。特によく見られるのが、プログラムが必要とする共有ライブラリがシステムに入っていないことがあるという問題だ。pkgsrcからのデフォルトのインストールで、一般的に広く使用されるライブラリはインストールされるものの、インストールされていないライブラリが必要となってしまう可能性はあるのだ。代替用のライブラリを手に入れるのは、必ずしも簡単とは限らない。使えそうな手の1つとしては、目的のプログラムをLinuxマシンにインストールしたうえで、lddコマンドを実行して、そのプログラムがリンクしているライブラリのリストを得るという方法がある(そのとき使うLinuxマシンは、他のすべての共有ライブラリの入手元と同じディストリビューションおよびバージョンのものが理想だ)。 リストが得られたら、目的のファイルをコピーしてくればよい。

注意すべき点が1つある。ライブラリによっては、複数のバージョンが用意されていて、それぞれ異なるシステムでの使用を意図しているケースがあるという点だ。Linuxシステムのダイレクト・レンダリング・サポートを使用するレンダリング・ライブラリは、大きく異なるメモリ・アーキテクチャを持つNetBSDシステムでは動作しない可能性が高い。ハードウェア・バージョンとソフトウェア・バージョンの両方のレンダリング・ライブラリがある場合には、ソフトウェア・バージョンの方が動作する可能性は高いが、かなり遅くなってしまうかもしれない。

プログラムによってオペレーティング・システムがクラッシュに至ることはごくまれだ。プログラムが落ちてしまう可能性の方が高く、その際にまぎらわしいエラー・メッセージが表示されることもある。とはいえ、私が見てきた中にも、NetBSDシステムを実際にハングさせてしまうプログラム(ビデオ・ゲーム)が1つあった。したがって、他の処理を並行して行っている高負荷なシステムでプログラムを実行する前には、そのプログラムの動作確認を必ず何度か行っておくことが必要だ。新しいアプリケーションを初めてテストする際には、まずディスクの同期を取り、重要な処理やリソースを食う処理はすべて停止したうえで行うようにする。

もっとわかりにくい種類のエラーとしては、インストール・スクリプトが#!/bin/shで始まるが、実際にはbashスクリプトであるようなプログラムで生じるエラーがある。これは以前に比べて少なくなってきてはいるものの、現在でもよく見られ、イライラの原因にもなる。shadow root(/emul/linux)に/bin/shがきちんと入っているだけでは十分ではない。他のプログラムからそのスクリプトが起動された場合に、Linuxエミュレーション・フラグが立たずに実行される可能性があるからだ。表面上は標準のシェル・スクリプトとしてきちんと動作するように見えるためだ。対処策の1つとしては、/bin/shを一時的に、何らかのバージョンのbashへのリンクに置き換えるという方法がある。もう1つは、スクリプトの「#!…」行に手を加えて、/bin/sh以外(たとえば/bin/bash)を参照するように修正するという方法だ。

このほか、プログラムがうまく動作しないときに役立つテクニックとしては、ktraceを使ってそのプログラムを動かしてみて、どんな出力が生成されるかを確認するという方法がある。ktraceコマンドは、プログラムから行われたシステム・コールをすべて記録し、そのログをファイル(デフォルトではktrace.out)に書き込む。記録された一連のシステム・コールは、kdumpを使って確認できる。次に示すのはその出力例だ。ls /fooコマンドの場合の例である。

  4653 ls       CALL  __stat13(0x80510c0,0xbfbff4e0)
  4653 ls       NAMI  “/foo”
  4653 ls       RET   __stat13 -1 errno 2 No such file or directory 

この出力からわかるのは、namei(名前をinodeに変換するカーネル内部関数)が”/foo”を探そうとし、”No such file or directory”という応答を得たということだ。Linuxプログラムが異常終了した場合に、kdumpで調べてみた結果、何らかのファイルが見つからないという状況になっているとわかったときには、そのファイルは標準インストールに含まれるものかどうかをLinuxシステムで調べてみて、もしそうなら、/emul/linuxツリーにコピーすればよい。

Linuxのデバイス・ドライバは、NetBSDでは一切動作しない。ひょっとすると、動作できるようにするためのコードを、誰かがいつの日か作ってくれるかもしれないが、今のところは、Linuxシステムのデバイス・ドライバやその他のカーネル・モジュールをBSDシステムで使用する方法はない。

遜色のないパフォーマンス

現実に利用されているアプリケーションの大多数は、Linuxエミュレーション・モードでも通常のパフォーマンスで動作する。Mac用のPCエミュレータ(VirtualPCなど)になじみのある人は、エミュレーションと聞くと処理が遅いという印象を持っていることが多いが、ここでは当てはまらないのだ。エミュレーションによる荷重を、OSによる他のオーバーヘッドやCライブラリのパフォーマンスの違いと切り分けるのは難しいが、実際上は、ダイレクト・レンダリングへの依存度がさほど大きくなくてLinux上で普通に使えるプログラムであれば、同等のハードウェア上でLinuxエミュレーションを使って動かした場合でも、使用感は変わらない。たとえばStarOffice 7はスムーズに動作し、反応も速い。Netscape 7はネイティブ・ブラウザと同じくらいの信頼性で動作し、Flashアニメーションの類を実に見事に再生できる。高パフォーマンスのプロジェクトではLinuxエミュレーションのコードはおそらく使わないほうがよいだろうが、特定のアプリケーションやモジュールを利用する目的であれば、Linuxエミュレーションはかなり有用だ。

Linux実行ファイルをエミュレーション・モードで動作できるのは、Alpha、ARM、i386、m68k、PowerPCの各システム上においてである。a.outとELFの両方の形式の実行可能ファイルがサポートされている。pkgsrcインストーラを使用すれば、重要なファイルすべてをきわめて簡単にセットアップできるし、GENERICカーネルでは、必要なオプションはすべて含まれている。したがって、Linuxエミュレーションをそのまますんなりと動作させることができるはずだ。Linuxバイナリを動作させるようにシステムを設定するためにはroot権限が必要だが、エミュレータとサポート・ファイルのセットアップが済めば、アプリケーションは一般ユーザできちんと動作する。Linuxエミュレーションのコードを使うと、標準的なアプリケーションの多くを、さほど苦労せずに動作させることができる。また、Linuxエミュレーションのコードは、別のOSを動かしたり、プロプライエタリ・コードを使用したりする必要があるユーザにとっての標準的な環境を確立するのに役立つ可能性もある。決して万能ではないが、現実的かつ実用的なツールとしては十分なものだ。

Peter Seebachは数年前からNetBSD上でLinuxバイナリを動かしているが、逮捕された経験はまだないので、これはおそらく許されている行為なのであろう。