This is an archive of past FreeBSD releases; it's part of the FreeBSD Documentation Archive.
このセクションは, ほとんどが <chat@FreeBSD.org> メーリングリストに投稿された Terry Lambert<tlambert@primenet.com> 氏のメール(Message ID: <199906020108.SAA07001@usr09.primenet.com>) に基づいています.
FreeBSD は, ``実行クラスローダ(execution class loader)'' と呼ばれる抽象的な機構を持っています. これは execve(2) システムコールに追加される形で実装されています.
FreeBSD は, シェルインタプリタやシェルスクリプトを実行するための #! ローダを持った単一のプログラムローダではなく, プログラムローダのリストを備えています.
歴史的に言って, UNIX プラットフォームでマジックナンバ(一般的にファイル先頭の 4 ないし 8 バイト部分)の検査を行なうのはプログラムローダだけです. プログラムローダは, それがシステムで実行できるバイナリなのか確認して, それが確認できればバイナリローダを呼び出します.
もし, それがそのシステム用のバイナリでない場合には, execve(2) システムコールの呼び出しは失敗の戻り値を返し, シェルがシェルコマンドとして実行しようと試みるわけです.
この仮定は, ``現在利用しているシェルがどのようなものであっても''変わりません.
後になって, sh(1) に変更が加えられました. それは先頭の 2 バイトを検査し, :\n だったら シェルとして代わりに csh(1) を呼び出す, というものです(この変更は SCO が最初に行なったと思われます).
現在の FreeBSD は, プログラムローダリストを走査します. その際, 空白文字までの文字列をインタプリタとして認識する, 通常の #! ローダを用いるため, 該当するものが存在しなければ最終的に /bin/sh がロードされます.
Linux ABI をサポートするため, FreeBSD は ELF バイナリを示すマジックナンバを確認します. (ただし, この段階で FreeBSD, Solaris, Linux, そしてその他にも存在する ELF イメージ形式を使っている OS を区別することはできません).
ELF ローダは, 特殊なマーク(brand)があるかどうか探します. このマークとは, ELF イメージのコメントセクションのことです. SVR4/Solaris の ELF バイナリには, このセクションは存在しません.
Linux バイナリを実行するためには, ELF バイナリに brandelf(1) で説明されている Linux のマークが 付けられていなければなりません.
# brandelf -t Linux file
上のようにすることで, 指定されたファイルは Linux のマークが付けられ, ELF ローダが認識できるようになります.
ELF ローダが Linux マークを確認すると, ローダは proc 構造体内の ある一つのポインタを置き換えます. システムコールは全て, この置き換えられたポインタ(伝統的な UNIX システムでは, システムコールが sysent[] 構造体の配列として実装されています)を基準に呼び出されます. またそのプロセスには, Linux カーネルモジュールに必要な シグナルトランポリンコード(訳注: シグナルの伝播を実現するコード)用の特殊なトラップベクタの設定や, 他の(細かな)調整のための設定が行なわれます.
Linux システムコールベクタは, さまざまなデータに加えて カーネルモジュール内のアドレスを指す sysent[] エントリのリストを含んでいます.
Linux バイナリがシステムコールを発行する際, トラップコードは proc 構造体を用いてシステムコール関数ポインタを 解釈します. そして, FreeBSD ではなく Linux 用の システムコールエントリポイントを得るわけです.
さらに Linux モードは, 状況に応じて ファイルシステム本来のルートマウントポイントを置き換えて ファイルの参照を行ないます. これは, union オプションを指定して マウントされたファイルシステム(unionfs ではありません!)が 行なっていることと同じです. ファイルを検索する際にはまず /compat/linux/original-path ディレクトリを, それから見つけられなかったときにのみ, /original-path を調べます. こうすることで, 他のバイナリを要求するバイナリの実行を可能にしています (したがって, Linux 用プログラムツールは Linux ABI サポート環境下で完全に動作するわけです). またこれは, もし対応する Linux バイナリが存在しない場合に Linux バイナリが FreeBSD バイナリをロードしたり, 実行したりすることが可能であること, その Linux バイナリに自分自身が Linux 上で実行されていないことを 気付かせないようにする目的で, uname(1) コマンドを /compat/linux ディレクトリに 置くことができる, ということを意味します.
要するに, Linux カーネルが FreeBSD カーネルの内部に存在しているわけです. カーネルによって提供されるサービス全ての実装の基礎となるさまざまな関数は, FreeBSD システムコールテーブルエントリと Linux システムコールテーブルエントリの 両方で共通に利用されています. これらにはファイルシステム処理, 仮想メモリ処理, シグナル伝送, System V IPC などが含まれますが, FreeBSD バイナリは, FreeBSD グルー(訳注: glue; 二者の間を仲介するという意味)関数群, そして Linux バイナリは Linux グルー関数群を用いる, という点だけが異なります(過去に存在したほとんどの OS は, 自分自身のためのグルー関数群しか備えていません. 前述したように, システムコールを発行する際, 各々のプロセスの proc 構造体内にある, ローダによって動的に初期化されるポインタを参照してアドレスを得る代わりに, 静的でグローバルな sysent[] 構造体の配列に システムコール関数のアドレスが直接格納されているのです).
さて, どちらを本来の FreeBSD ABI(訳注: Applications Binary Interface; 同じ CPU を利用したコンピュータ間でバイナリを共有するための規約のこと) と呼ぶべきなのでしょうか? 実は, どちらが本来のものであるかということを論ずることに意味はありません. 基本的に, FreeBSD グルー関数群はカーネルの中に静的にリンクされていて, Linux グルー関数群は静的にリンクすることも, カーネルモジュールを介して利用することもできるようになっている, という違いがあるだけ(ただしこれは現時点においての話です. これは将来のリリースで変更される可能性がありますし, おそらく実際に変更されるでしょう)です.
あ, 「でもこれは本当にエミュレーションと呼べるのか」って? 答えは「いいえ」です. これは一つの ABI 実装にすぎず, エミュレーションとは異なります. エミュレータ(シミュレータでもないことを あらかじめ断っておきましょう)が呼び出されているわけではありません.
では, これが良く ``Linux エミュレーション'' と呼ばれるのは何故でしょうか? それはもちろん FreeBSD の売りにするため 8-) でもあるのですが, 実際には, 次のような理由によります. この機能が初めて実装された頃, 動作原理を説明する以外に この機能を表現する言葉はありませんでした. しかし, コードをコンパイルしたりモジュールをロードしない場合, 「FreeBSD 上で Linux バイナリを実行する」言う表現は, 厳密に考えると適切ではありません. そこで, その際にロードされているもの自身を表現する言葉---すなわち ``Linux エミュレータ''が必要だったのです.