第13章 まじめな FreeBSD ハッカーだけの話題

訳: 岩崎 満 、 1997 年 11 月 8 日

13.1. SNAP とか RELEASE とかは何?
13.2. 自分用のカスタムリリースを構築するには?
13.3. カスタムのインストールディスクを作るにはどうすればいいのですか?
13.4. 「make world」 を行なうと既存のバイナリを上書きしてしまうのですが。
13.5. システム起動時に 「(bus speed defaulted)」 とメッセージが出ます。
13.6. インターネットアクセスに制限があっても current を追いかけられますか?
13.7. どのようにして配布ファイルを 240KB に分割しているのですか?
13.8. 私はカーネルに拡張を行ないました。 誰に送ればいいですか?
13.9. PnP ISA カードの検出と初期化はどのように行なうのですか?
13.10. FreeBSD は、他のアーキテクチャをサポートしないんですか?
13.11. デバイスドライバを開発したので、メジャー番号が欲しいのですが。
13.12. 代替のディレクトリ配置ポリシー
13.13. カーネルパニックを最大限に利用する
13.14. dlsym() が ELF 実行形式では動作しなくなります!
13.15. カーネルアドレス空間を大きくしたり、 小さくするにはどうしたら良いのですか?

13.1.

SNAP とか RELEASE とかは何?

現在、FreeBSD の CVS リポジトリ には、三つのアクティブ/準アクティブなブランチがあります (アクティブな開発ブランチは三つしか存在しないため、 おそらく RELENG_2 ブランチの変更は年に 2 回だけになるでしょう)。

  • RELENG_2_2 通称 2.2-STABLE

  • RELENG_3 通称 3.X-STABLE

  • RELENG_4 通称 4-STABLE

  • HEAD 通称 -CURRENT あるいは 5.0-CURRENT

HEAD は他の二つと違って、 実際のブランチタグではなく、 「current、 分岐していない開発本流」のための単なるシンボリックな定数です。 私たちはこれを -CURRENT と呼んでいます。

現在、 -CURRENT は 5.0 の開発本流であり、 4.0-STABLE ブランチ、 つまり RELENG_4 は 2000 年 3 月に -CURRENT から分岐しています。

2.2-STABLE ブランチ、 RELENG_2_2 は 1996 年 11 月に -CURRENT から分岐しました。 これは保守が完全に終了しています。

13.2.

自分用のカスタムリリースを構築するには?

リリースを構築するには三つのことが必要です。まず、 vn(4) ドライバが組み込まれたカーネルを実行させている必要があります。 以下をカーネルコンフィグレーションファイルに追加し、 カーネルを作り直してください。

pseudo-device vn         #Vnode driver (turns a file into a device)

次に、CVS リポジトリ全体を手元においておく必要があります。 これを入手するには CVSUP が使用できますが、supfile で release の名称を cvs にして 他のタグや date フィールドを削除する必要があります。

*default prefix=/home/ncvs
*default base=/a
*default host=cvsup.FreeBSD.org
*default release=cvs
*default delete compress use-rel-suffix

## Main Source Tree
src-all
src-eBones
src-secure

# Other stuff
ports-all
www
doc-all

そして cvsup -g supfile を実行して自分のマシンに CVS リポジトリ全体をコピーします…。

最後に、ビルド用にかなりの空き領域を用意する必要があります。 そのディレクトリを /some/big/filesystem として、 上の例で CVS リポジトリを /home/ncvs に置いたものとすると、 以下のようにしてリリースを構築します。

# setenv CVSROOT /home/ncvs
 # or export CVSROOT=/home/ncvs
# cd /usr/src
# make buildworld
# cd /usr/src/release
# make release BUILDNAME=3.0-MY-SNAP CHROOTDIR=/some/big/filesystem/release
      

注記:

ただし、すでに /usr/obj 以下に構築物が存在しているなら、buildworld の必要はありません

処理が終了すると、 リリース全体が /some/big/filesystem/release に構築され、完全な FTP インストール用の配布物が /some/big/filesystem/release/R/ftp に作成されます。 -current 以外の開発ブランチの SNAP を自分で構築したい場合は、 RELEASETAG=SOMETAG を上の make release のコマンドラインに追加します。 たとえば、RELEASETAG=RELENG_2_2 とすると最新の 2.2-STABLE snapshot が構築されます。

13.3.

カスタムのインストールディスクを作るにはどうすればいいのですか?

/usr/src/release/Makefile のいろいろなターゲットとしてインストールディスク、 ソース、バイナリアーカイブを作る完全な処理を自動的に行なうようになっています。 Makefile に十分な情報があります。 しかし、実行には make world が必要で、 多くの時間とディスクの容量が必要です。

13.4.

make world を行なうと既存のバイナリを上書きしてしまうのですが。

ええ、それが一般的な考え方です。名前が示しているように make world はすべてのシステムのバイナリを最初から作り直しますので、結果として、 クリーンで一貫性のある環境を得ることができます (これがそれだけ長い時間がかかる理由です)。

環境変数 DESTDIRmake worldmake install を実行する時に定義しておくと、新しく作られたバイナリは ${DESTDIR}root とみなしたディレクトリツリーにインストールされます。 あるでたらめな共有ライブラリの変更やプログラムの再構築によって make world は失敗することもあります。

13.5.

システム起動時に (bus speed defaulted) とメッセージが出ます。

Adaptec の 1542 SCSI ホストアダプタは、 ユーザがソフトウェア的にバスアクセス速度の設定を行なうことができます。 以前のバージョンの 1542 ドライバは、 使用可能な最大の速度を求めてアダプタをその設定にしようとしました。 これは特定のユーザのシステムでは問題がある事がわかり、 現在ではカーネルコンフィグオプションに TUNE_1542 が加えられています。 これを使用すると、これが働くシステムではディスクが速くなりますが、 データの衝突が起きて速くはならないシステムもあるでしょう

13.6.

インターネットアクセスに制限があっても current を追いかけられますか?

はい、 CTM システムを使って、 ソースツリー全体のダウンロードを行なわずに追いかけることができます。

13.7.

どのようにして配布ファイルを 240KB に分割しているのですか?

比較的新しい BSD ベースのシステムでは、 split に任意のバイト境界で分割する -b オプションがあります。

以下は /usr/src/Makefile からの例です。

bin-tarball:
              (cd ${DISTDIR}; \
              tar cf - . \
              gzip --no-name -9 -c | \
              split -b 240640 - \
              ${RELEASEDIR}/tarballs/bindist/bin_tgz.)

13.8.

私はカーネルに拡張を行ないました。 誰に送ればいいですか?

FreeBSD ハンドブックの「FreeBSD への貢献」を参照してください。

あなたのアイディアに感謝します!

13.9.

PnP ISA カードの検出と初期化はどのように行なうのですか?

Frank Durda IV 氏 より:

要点は、ホストが認識されていないボードを探す時に、すべての PnP ボードが応答することのできる少数の I/O ポートがあるということです。 それにより、PnP プローブルーチンが開始したとき、PnP ボードが存在するなら、すべての PnP ボードは自分のモデル番号を返します。 そのポートを I/O read するとプローブルーチンは問いに対するワイアード-OR された yes を得ます。この場合は 少なくとも 1 ビットが ON になります。 そして、プローブルーチンはモデル ID (Microsoft/Intel によって割り当てられています)が X より小さいボードを オフライン にすることができます。 この操作を行ない、問い合わせに応答しているボードがまだ 残っているかどうかを調べます。 もし 0 が返ってくるなら X より大きな ID を持つボードはないことになります。 今度は X よりも小さな値を持つボードについて問い合わせます。 もしあるのであれば、 プローブルーチンはモデル番号が X より小さいことを知ります。 今度は、X-(limit/4) より大きな値を持つボードをオフラインにして問い合わせを繰り返します。 この ID の範囲による準バイナリサーチを十分繰り返すことにより、 プローブルーチンはマシンに存在するすべての PnP ボードの値を最終的に得ることができます。その繰り返しの回数は 2^64 よりはるかに少ない回数です。

ID は二つの 32-bit (つまり 64bit) フィールド + 8 bit チェックサムからなります。最初の 32 bits はベンダの識別子です。 これは公表されてはいませんが、 同一のベンダから供給されている異なるタイプのボードでは異なる 32-bit ベンダ ID を持つことができるように考えられます。 製造元を特定するだけのために 32-bit はいくらか過剰です。

下位の 32-bit はシリアル番号、 イーサネットアドレスなどのボードを特定するものです。 ベンダは上位 32 bits が異なっていないのであれば、 下位 32-bit が同一である 2枚目のボードを製造することはありません。 したがって、同じタイプの複数のボードをマシンにいれることができ、 この場合でも 64-bit 全体ではユニークです。

32-bit のフィールドはすべてを 0 にすることはできません。 これは初期化のバイナリサーチの間ワイアード-OR によって 0 ではない ビットを参照するからです。

システムがすべてのボードの与えられた ID を認識すると、 それぞれのボードに対応した処理を一つずつ (同一の I/O ポートを通して) 行ないます。 そして、利用できる割り込みの選択などのボードが必要とするリソースを検出します。 すべてのボードについてこの情報を集めます。

この情報はハードディスク上の ECU ファイルなどの情報とまとめられ、 マザーボードの BIOS にも結合されます。 マザーボード上のハードウェアへの ECU と BIOS PnP のサポートは通常は統合されていますが、 周辺機器については真の PnPであるとはいえません。 しかし、BIOS の情報に ECU の情報を加えて調査することで、 プローブルーチンは PnP デバイスが再配置できなくなることを避けることができます。

それから、再度 PnP デバイスにアクセスし、I/O、DMA、IRQ、 メモリマップアドレスの設定をします。 デバイスはこのアドレスに見えるようになり、 次に再起動するまでこの位置を占めます。しかし、 あなたの望む時に移動させることが不可能である、 といっているわけではありません。

以上の話では大きく単純化をしてありますが、 基本的な考え方は得られたでしょう。

マイクロソフトは、ボードのロジックが対立する I/O サイクルではデコードしていない (訳注: おそらく read 時しかデコードされていず write 時はポートが空いているという意味でしょう)、 プライマリプリンタのステータスポートのいくつかを PnP のために占有しました。 私は初期の PnP の提案レビュー時に IBM 純正のプリンタボードでステータスポートの write のデコードがされているということに気がつきましたが、 MS は tough (頑固、不運、無法な) と言っています。 そしてプリンタのステータスポートへアドレスの設定のために write を行なっています。また、 そのアドレス + 0x800 と read のための 3番目の I/O ポートが 0x200 から 0x3ff の間のどこかに置かれるでしょう。

13.10.

FreeBSD は、他のアーキテクチャをサポートしないんですか?

いくつかのグループの人々が、FreeBSD の他のアーキテクチャへの移植に関心を示しており、 FreeBSD/AXP (ALPHA) はこれらの成果としてはとても成功したものの一つです。 FreeBSD/AXP は現在 ftp://ftp.FreeBSD.org/pub/FreeBSD/alpha から入手できます。 ALPHA への移植版が現在動く機種は増えつつあり、 その中には AlphaStation、AXPpci、PC164、Miata そして Multia といったモデルが含まれています。 現状についての情報を得るには メーリングリストに参加してください。

その他に FreeBSD の SPARC アーキテクチャへの移植があります。 プロジェクトへの参加に興味がある方は メーリングリスト に参加してください。 進行中のプラットホームのリストにもっとも最近追加されたのが IA-64 と PowerPCです。詳細は および/あるいは メーリングリストに参加してください。 新しいアーキテクチャに関する一般的な議論については 新しいアーキテクチャに関する一般的な議論については メーリングリスト へ参加してください。

13.11.

デバイスドライバを開発したので、メジャー番号が欲しいのですが。

これは、開発したドライバを公開するかどうかに依存します。 公開するのであれば、ドライバのソースコード、 files.i386 の変更、 コンフィグファイルのサンプル、 デバイスが使うスペシャルファイルを作成する MAKEDEV(8) のコードを私たちに送ってください。 公開するつもりがない場合、ライセンスの問題により公開できない場合は、 キャラクタメジャー番号 32 および、 ブロックメジャー番号 8 がこのような目的のために予約されています。 これらの番号を使用してください。 どちらの場合であれ、ドライバに関する情報を FreeBSD technical discussions メーリングリスト に流して頂けると助かります。

13.12.

代替のディレクトリ配置ポリシー

現在使われているディレクトリの配置ポリシーは、 私が 1983 年に書いたものから全く変更されていません。 私は当初の配置ポリシーを、オリジナルの fast filesystem のために書き、 まったく改定していません。 このポリシーはシリンダグループを使い尽くすのを防ぐにはうまくいきましたが、 お気づきの方もいる通り find の動作には不適切です。 ほとんどのファイルシステムの内容は、 深さ優先検索 (ftw とも呼ばれます) によって作られたアーカイブから、 抽出 (restore) して作成されます。この際、 ディレクトリは、シリンダグループにまたがって配置され、 以降の深さ優先検索を行うには、 考え得る限り最悪の状態になります。 もし作成するディレクトリの総数がわかっていれば、 解決方法はあります。(総数/シリンダグループ数) 個のディレクトリを、 シリンダグループごとにまとめて作成すれば良いのです。 もちろん最適なディレクトリ配置になるように、 総数を予測する方法を考えなければなりません。 しかし仮にシリンダグループあたりのディレクトリ数を 10 くらいの小さな数に固定してしまったとしても、 大幅な改善が望めるでしょう。 このポリシーを用いるべきリストア作業を、通常の作業 (おそらく既存のポリシーを使用したほうが良いでしょう) を区別するには、 10 秒間の間に作成されたディレクトリを最大 10 個までまとめて単一のシリンダグループに書き込むという手順が使えるでしょう。 とにかく私の結論は、そろそろ実験を始めて見る時期だろうということです。

13.13.

カーネルパニックを最大限に利用する

注記:

この節は、freebsd-current メーリングリストに Bill Paul 氏が投稿したメールを、 Dag-Erling C. Smørgrav 氏が校正し、[] 内のコメントを追加して引用したものです。

From: Bill Paul <wpaul@skynet.ctr.columbia.edu>
Subject: Re: the fs fun never stops
To: ben@rosengart.com
Date: Sun, 20 Sep 1998 15:22:50 -0400 (EDT)
Cc: current@FreeBSD.ORG

[<ben@rosengart.com> が以下のパニックメッセージを投稿しました。]

> Fatal trap 12: page fault while in kernel mode
> fault virtual address   = 0x40
> fault code              = supervisor read, page not present
> instruction pointer     = 0x8:0xf014a7e5
                                ^^^^^^^^^^
> stack pointer           = 0x10:0xf4ed6f24
> frame pointer           = 0x10:0xf4ed6f28
> code segment            = base 0x0, limit 0xfffff, type 0x1b
>                         = DPL 0, pres 1, def32 1, gran 1
> processor eflags        = interrupt enabled, resume, IOPL = 0
> current process         = 80 (mount)
> interrupt mask          =
> trap number             = 12
> panic: page fault

このようなメッセージが表示された場合、問題が起きる状況を確認して、 情報を送るだけでは十分ではありません。 下線をつけた命令ポインタ値は重要な値ですが、 残念ながらこの値は構成に依存します。つまり、 この値は使っているカーネルのイメージに依存するのです。 もしスナップショットなどの GENERIC カーネルを使っているのであれば、 他の人間が問題のある関数について追試をすることができますが、 カスタマイズされたカーネルの場合は、 使っている本人にしか問題の起こった場所は特定できないのです。

何をすれば良いのでしょう?

  1. 命令ポインタ値をメモします。 0x8: という部分は今回必要ありません。 必要なのは 0xf0xxxxxx という部分です。

  2. システムが再起動したら、以下の操作を行います。

    % nm -n /kernel.that.caused.the.panic | grep f0xxxxxx

    ここで、f0xxxxxx は命令ポインタ値です。 カーネルシンボルのテーブルは関数のエントリポイントを含み、 命令ポインタ値は、関数内部のある点であり最初の点ではないため、 この操作を行っても完全に一致するものが表示されない場合もあります。 この場合は、 最後の桁を省いてもういちどやってみてください。 このようになります。

    % nm -n /kernel.that.caused.the.panic | grep f0xxxxx

    これでも一致しない場合は、 桁を減らしながら何らかの出力があるまで繰り返してください。 何か出力されたら、 それがカーネルパニックを引き起こした可能性のある関数のリストです。 これは、問題点を見付ける正確な方法ではありませんが、何もないよりましです。

このようなパニックメッセージを投稿している人はよく見掛けますが、 このように、命令ポインタ値を、 カーネルシンボルテーブルの中の関数とつき合わせて調べている人はまれです。

パニックの原因を突き止める最良の方法は、クラッシュダンプをとり、 gdb(1) でスタックトレースを行うことです。

どっちにしろ、私は普通以下のようにします。

  1. カーネルコンフィグファイルを作ります。 カーネルデバッガが必要そうであれば options 'DDB' を加えても良いです (私は永久ループが起こっていそうな場合に、 ブレークポイントを設定するのに使っています)。

  2. config -g KERNELCONFIG としてビルドディレクトリを設定します。

  3. cd /sys/compile/KERNELCONFIG; make を実行します。

  4. カーネルのコンパイルが終了するのを待ちます。

  5. make install を実行します。

  6. 再起動します。

make(1) プロセスは2つのカーネル、 kernelkernel.debug をビルドします。 kernel/kernel としてインストールされ、 kernel.debuggdb(1) のデバッグ用シンボル情報を取り出すために利用されます。

確実にクラッシュダンプをとるには、/etc/rc.conf を編集して dumpdev を使用しているスワップパーティションに指定する必要があります。 こうすると rc(8) スクリプトから dumpon(8) コマンドが実行され、 クラッシュダンプ機能が有効になります。 手動で dumpon(8) コマンドを実行してもかまいません。 パニックの後、クラッシュダンプは savecore(8) コマンドを使用して取り出すこと ができます。 dumpdev/etc/rc.conf で設定されていれば、 rc(8) スクリプトから savecore(8) が自動的に実行され、クラッシュダンプを /var/crash に保存します。

注記:

FreeBSD のクラッシュダンプのサイズは、 ふつう物理メモリサイズと同じです。 つまり 64MB のメモリを積んでいれば、 64MB のクラッシュダンプが生成されることになります。 /var/crash に十分な空き容量があることを確認してください。手動で savecore(8) を実行すれば、 もっと空き容量のあるディレクトリにクラッシュダンプを保存できます。 options MAXMEM=(foo) という行をカーネルコンフィグファイルに追加することで、 カーネルのメモリ使用量を制限できます。 たとえば 128MB のメモリがある場合も、 カーネルのメモリ使用量を 16MB に制限し、クラッシュダンプのサイズも 128MB ではなく 16MB にすることができます。

クラッシュダンプを取り出せたら、 以下のように gdb(1) を使ってスタックトレースをとります。

% gdb -k /sys/compile/KERNELCONFIG/kernel.debug /var/crash/vmcore.0
(gdb) where

必要な情報が 1 画面に収まらないことも多いので、できれば script(1) を使って出力を記録します。 strip していないカーネルイメージを使うことで、 すべてのデバッグシンボルが参照でき、 パニックの発生したカーネルのソースコードの行が表示されているはずです。 通常、正確なクラッシュへの過程を追跡するには、 出力を最後の行から逆方向に読まなければなりません。 また gdb(1) を使って、 変数や構造体の内容を表示させ、 クラッシュした時のシステムの状態を調べられます。

もしあなたがデバッグ狂で、同時に別のコンピュータを利用できる環境にあれば、 gdb(1) をリモートデバッグに使うこともできます。 リモートデバッグを使うと、あるコンピュータ上の gdb(1) を使って、 別のコンピュータのカーネルをデバッグできます。 ブレークポイントの設定、カーネルコードのステップ実行など、 ふつうのプログラムのデバッグと変わりません。 コンピュータを 2 台並べてデバッグするチャンスにはなかなか恵まれないので、 私はまだリモートデバッグを試したことはありません。

Bill による追記:

DDB を有効にしていてカーネルがデバッガに 落ちたら、ddb のプロンプトで "panic" と入力すれば、強制的にパニックを起こしクラッシュダンプさせることができます。 パニックの途中で、再びデバッガに落ちるかもしれませんが、 "continue" と入力すれば、 クラッシュダンプを最後まで実行させられます。

13.14.

dlsym() が ELF 実行形式では動作しなくなります!

ELF のツール類は、 デフォルトでは実行形式の中に定義されているシンボルを、 ダイナミックリンカから見えるようにはしません。 このため、dlopen(NULL, flags) を呼び出して得られたハンドルに対して、 dlsym() で探索を行っても、 こういったシンボルを見つけられません。

もし、あなたがプロセスの中心にあたる実行形式の中にあるシンボルを探索したければ、 ELF リンカ (ld(1)) に -export-dynamic オプションを付けて実行形式をリンクする必要があります。

13.15.

カーネルアドレス空間を大きくしたり、 小さくするにはどうしたら良いのですか?

カーネルアドレス空間は、FreeBSD 3.X 上で 256MB、FreeBSD 4.X 上で 1GB がデフォルトになっています。 負荷の高いネットワークサーバ (たとえば大きな FTP、HTTP サーバ) を運用する場合は、256MB では足りないことに気付くかも知れません。

では、アドレス空間を大きくするにはどうしたら良いのでしょうか? それには、二つの段階を踏みます。まず、 より大きいアドレス空間を割り当てることをカーネルに知らせる必要があります。 次に、カーネルはアドレス空間の先頭にロードされるため、 アドレスの先頭が天井 (訳注:カーネルアドレス空間の最下端アドレスのこと) と ぶつかることのないように、ロードアドレスを今までより低位に設定する必要があります。

最初の段階は、src/sys/i386/include/pmap.h にある NKPDE の値を増加させることで行ないます。 ここに 1GB のアドレス空間にするために、どのようにすれば良いかを示します。

#ifndef NKPDE
#ifdef SMP
#define NKPDE                   254     /* addressable number of page tables/pde's */
#else
#define NKPDE                   255     /* addressable number of page tables/pde's */
#endif  /* SMP */
#endif

正確な NKPDE の値を計算するには、 望みのアドレス空間の大きさ (メガバイト単位) を 4 で割って、 それから単一プロセッサ (UP) なら 1、SMP なら 2 を引き算してください。

次の段階を行なうには、ロードアドレスを正確に計算することが必要です。 単純に、アドレス空間の大きさ (バイト単位) を 0x100100000 から引き算してください。 1GB アドレス空間の場合、その結果は 0xc0100000 になります。 そして、src/sys/i386/conf/Makefile.i386 にある LOAD_ADDRESS に、今計算した値を入れます。また、次のように src/sys/i386/conf/kernel.script のセクションの始めの方にあるロケーションカウンタにも同じ値を入れてください。

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(btext)
SEARCH_DIR(/usr/lib); SEARCH_DIR(/usr/obj/elf/home/src/tmp/usr/i386-unknown-freebsdelf/lib);
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  . = 0xc0100000 + SIZEOF_HEADERS;
  .interp     : { *(.interp)    }

それが完了したら、config し直してカーネルを再構築してください。 おそらく、ps(1)top(1) などに不具合が出るでしょう。 それらを正常にするために、make world (もしくは、変更した pmap.h/usr/include/vm/ にコピーした後に、 libkvmps および top を手動で再構築すること) を行なうべきです。

注記:

カーネルアドレス空間の大きさは、4MB の倍数である必要があります。

David Greenman 氏による補足:

カーネルアドレス空間は 2 の乗数である必要があると思いますが、 それが確かなことかどうかははっきりしていません。 昔の起動コードには、良く高位アドレスビットのトリックが使われていたため、 少なくとも 256MB の粒度であることが想定されていたと思います。

本文書、および他の文書は ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/ からダウンロードできます。

FreeBSD に関する質問がある場合には、 ドキュメント を読んだ上で <questions@FreeBSD.org> まで (英語で) 連絡してください。

本文書に関する質問については、 <doc@FreeBSD.org> まで電子メールを (英語で) 送ってください。