<!-- The FreeBSD Documentation Project The FreeBSD Japanese Documentation Project Original revision: 1.25 $FreeBSD: doc/ja_JP.eucJP/books/handbook/internals/chapter.sgml,v 1.10 2001/04/03 17:17:21 hrs Exp $ --> <chapter id="internals"> <title>FreeBSD の内部</title> <!-- Commented out because this is now outdated. It's kept as a placeholder in case we decide to resurract this with the right information. If nothing happens by (say) October 2000 then we can drop this. <sect1 id="memoryuse"> <title>PC におけるメモリの利用</title> <para><emphasis>原作: &a.joerg;. 1995 年 4 月 16 日.</emphasis></para> <para><emphasis>訳: &a.jp.tomo;. 1996 年 10 月 29 日.</emphasis></para> <para><emphasis>FreeBSD が i386 プラットフォーム上でどのようにメモリを使うかに ついての説明です. </emphasis></para> <para>ブート部分は<literal>0:0x7c00</literal>にロードされ, すぐに自分自身を <literal>0x7c0:0</literal>に移します. (これは手品ではなく, 単なる<literal>%cs</literal> セレクタのための調節であり, <literal>ljmp</literal>により行われます. )</para> <para>それから最初の 15 セクタを <literal>0x10000</literal> (biosboot の Makefile のなかの <makevar>BOOTSEG</makevar>部分)にロードし, 作業領域のスタックを <literal>0x1fff0</literal>以下に セットします. このあと, boot2 に飛びます. つまり, boot1 自身と (ダミーの) DOS パーティションテーブルを飛び越えて, %csセレクタを 調節します — この時点ではまだ16ビットモードです.</para> <para>boot2 はブートファイルを要求し, <filename>a.out</filename>ヘッダを調べます. <literal>0x00ffffff</literal>によってファイルエントリポイントを (通常は<literal>0xf0100000</literal>に)マスクし, ロードします. このため, 通常のロードポイントは 1MB (<literal>0x00100000</literal>) になります. ロードしている間, リアルモードでBIOSを使うため, ブートコードは, リアルモードとプロテクトモードの間を行ったり来たりします (訳注: これは, BIOSがリアルモード用に書かれていて, ロードすべき領域がリアルモードではアクセスできない1MBより上位の アドレスであることから, ブートコードがリアルモードと プロテクトモードを切り替えながら動作するためです).</para> <para>ブートコード自身はプロテクトモードで <literal>%cs</literal>と<literal>%ds/%es</literal> 用に セグメントセレクタ <literal>0x18</literal> と <literal>0x20</literal> を使い, リアルモードに戻るのに<literal>0x28</literal>を使います. 最終的にカーネルはアドレス空間全体をカバーできるようなダミーの ディスクリプタを参照して<literal>%cs</literal> <literal>0x08</literal>と <literal>%ds/%es/%ss</literal> <literal>0x10</literal>でスタートします.</para> <para>カーネルはそのロードポイントで起動されます. 別の(高位)アドレスにリンクされるので, ページテーブルやページディレクトリなどが適切に設定され, ページングが有効になり, カーネルがリンクされたアドレスで 動作するようになるまでは, カーネルはロードアドレスからの 相対アドレス (PIC: position independent code) を用いて 実行されなければなりません.</para> <para><emphasis>寄稿: &a.dg;. 1995 年 4 月 16 日.</emphasis></para> <para>カーネルの BSS セグメントの直後の物理ページ (実メモリ) に proc0 (訳注: プロセス番号 0, swapper) のページディレクトリや ページテーブル, Uページが配置されます. 仮想記憶機構が初期化された少しあと, <literal>0x1000-0x9ffff</literal>の実メモリとカーネル (text + data + bss + 上記の proc0 に関わるもの + その他) の後ろの実メモリは, 通常の仮想記憶ページの形で利用可能となり, グローバルな空きページリストに追加されます.</para> </sect1> --> <sect1 id="dma"> <title>DMAとはどういったものでどういう働きをするのか</title> <para>原作: <emphasis>Copyright © 1995,1997 &a.uhclem;, All Rights Reserved. 1996 年 10 月 10 日. 最終更新日 1997 年 10 月 8 日.</emphasis></para> <para><emphasis>訳: &a.jp.yasu;</emphasis></para> <para>Direct Memory Access (DMA)は, 中央演算処理装置 (CPU)からの干渉なく データを計算機中である場所から別の場所に動かすための手法です. </para> <para>DMA 機能の実装の方法はそれぞれの 計算機アーキテクチャ間で異なるもので あるため, ここでの議論はIBMパーソナルコンピュータ(PC), PC/AT とその互換機における DMA サブシステムの実装と働きに限定します.</para> <para>PCの DMAサブシステムは, Intelの 8237 DMAコントローラをベースにして います. 8237はそれぞれ独立にプログラムできる4つのDMAチャネルを持ち, それぞれどのチャネルもいつでもアクティブにできます. これらのチャネルは順に 0, 1, 2, 3となっています. PC/ATからは, セカンド 8237 チップが追加され,それらは 4, 5, 6, 7と なっています.</para> <para>オリジナルの DMAコントローラ(0, 1, 2, 3)は, 1回の転送で1バイト 転送します. セカンドDMAコントローラ(4, 5, 6, 7)は1回で 隣接する2つのメモリ番地から 16ビット転送します. ここで, 最初のバイトは通常偶数のアドレスになります. 2つのコントローラは全く同じものであり, 転送量が異なるのは セカンドコントローラがシステムに直結しているためです.</para> <para>8237 は個々のチャネルについて, DRQと-DACKという2つの電気信号を 持っています. その他に, HRQ (Hold Request), HLDA (Hold Acknowledge), -EOP (End of Process)があり, バス制御信号として -MEMR (Memory Read), -MEMW (Memory Write), -IOR (I/O Read), and -IOW (I/O Write)があります.</para> <para>8237 DMACは, いわゆる<quote>fly-by</quote> DMAコントローラです. これは, データの移動を行う際に, データは DMACチップを通過せず, DMACチップに格納されないことを意味します. また, DMACはI/Oポートとメモリアドレス間でのみデータを 転送することができますが, 2つのI/Oポートもしくは2つのメモリアドレス 間ではできません.</para> <note> <para>8237 は, 非 <quote>fly-by</quote>モードでは, 互いに接続された 2つのチャネルでのメモリ-メモリ間でのDMA操作を許可します. しかし, PC メーカは, ただでさえ乏しいこのリソースをこんなふうに 使ったりしません. なぜなら, CPUを使用してメモリ間のデータを動かす方が早いからです.</para> </note> <para>PC アーキテクチャでは, それぞれのDMAチャネルは, 通常 与えられた DMA チャネルを使用するハードウェアがそのチャネルについて DRQ線を使って転送を要求した時のみ動作します.</para> <sect2> <title>DMA転送の例</title> <para>DMA転送の発生と処理の手順の例をあげてみましょう. この例では, フロッピーディスクコントローラ (FDC)が ディスケットから1バイト読み込んで, DMAを使って,メモリの0x00123456番地に 格納したいとします. 処理は, FDCが, DRQ2信号(DMAチャンネル2に 対するDRQ線)を有効にして DMAコントローラに要求を伝えることで開始されます.</para> <para>DMAコントローラは DRQ2 シグナルが有効になったことを記録します. するとDMAコントローラはDMAチャネル2がプログラムされ, マスクが かかっていない(有効になっている)ことを確認します. 同様に, DMAコントローラは, 他のDMAチャネルがアクティブまたは アクティブになろうとしていないこと, そしてより高い優先度を持って いないことを確認します. 一旦これらのチェックが完了すると, DMACはDMACがバスを使うために バスを開放するようにCPUに要求します. DMACはCPUにHRQ信号を送ってバスを要求します.</para> <para>CPUはHRQ信号を検出し, 現在の指示の実行を完了します. 一旦プロセッサがバスを開放することができる状態になると, 解放を 行います. 通常は CPU により駆動される信号 (-MEMR, -MEMW, -IOR, -IOW, その他)を すべてハイインピーダンス (ハイともローとも指定しない)状態にした後, CPUは HLDA信号を有効にして DMAコントローラにバスを明け渡したことを 伝えます.</para> <para>プロセッサによっては, CPUはバスを使用しないいくつかの 命令を追加して実行することもできますが, しかし,プロセッサの内部キャッシュや パイプライン以外のメモリから 何か読み出すといった指示に到達したら結局 CPU は待たなくてはなりません.</para> <para>ここで,DMACが バスを<quote>託される</quote>と, DMACはその -MEMR, -MEMW, -IOR, -IOW 出力信号をアクティブにし, DMACから出力されるアドレスは 0x3456にセットされます.これは 転送しようとする特定のメモリ番地をバイトで 指示するのに使われます.</para> <para>すると DMAC は DMA 転送をリクエストしたデバイスに転送が始まることを 知らせます.これは -DACK 信号をアクティブにすることで行われます. フロッピーディスクコントローラの場合は, -DACK2を アクティブにすることで行われます.</para> <para>バスのデータ線に転送されるバイトにを出力することについては フロッピーディスクコントローラが責任をもつことになります. もし,フロッピーディスクコントローラがバス上にバイトデータを 出力するのに余計な時間を必要としなければ (もし周辺装置がもっと時間を必要とする場合には, READY信号を 経由してDMACに通知します), DMAは 1 DMAクロック待ち, メモリにバス上のバイトデータを格納するために -MEMW および -IOR 信号を解除します. そして FDCはバイトデータが転送されたことを認識します.</para> <para>DMAサイクルは1度に1バイトしか転送しないので, FDCはDRQ2信号を止めて, DMACに転送が終了したことを知らせます. DMACは-DACK2信号を解除して, FDCはバス上へのデータ出力を 停止しなくてはならないことを知らせます.</para> <para>次にDMACは他のDMAチャネルのいずれかに要求がきていないか チェックを行います. もしどのチャネルのDRQも有効になっていなければ, DMAコントローラは処理を完了して, -MEMR, -MEMW, -IOR, -IOW および アドレス信号をハイインピーダンス状態にします.</para> <para>最後に, DMAはHRQ信号を解除します. CPUはこれを見ると,HOLDA信号を 解除します. そしてCPUは自らの -MEMR, -MEMW, -IOR, -IOW 信号および アドレス線を有効にし, 命令の実行やメインメモリや周辺機器へのアクセスを 再開します.</para> <para>典型的なフロッピーディスクの1セクタについては, 上記のプロセスが それぞれのバイトについて1回行われ, 全部で512回繰り返されます. 1 バイト転送される毎に, DMAC 内のアドレスレジスタはインクリメントされ, 同じくDMAC内にある, 何バイト転送すればよいかを示すカウンタが デクリメントされます.</para> <para>カウンタが0になると, DMAはEOP信号を送ります. この信号は カウンタが0であり, DMAコントローラがCPUによって再び プログラムされるまで, これ以上データは転送されないことを 示すものです.</para> <para>このイベントはターミナルカウント(TC)とも呼ばれます. EOP信号は1本しかありません. そして, 一度にアクティブにできる DMAチャネルは一本だけなので, 現在アクティブであるDMAチャネルこそが, たった今処理を終了したDMAチャネルだと言うことができます.</para> <para>もし, バッファの転送が完了した時に周辺機器から割り込みを発生させたい とき, 周辺機器は -DACKn信号およびEOP信号の両方が同時に発信されたか どうかをテストします. その場合, DMACはCPUの介在がなければ これ以上はその周辺機器についての情報を転送しません. その後で, 周辺機器はプロセッサに割り込みを生じさせるために, 何らかの割り込み信号を発生させることができます. PCアーキテクチャ においては, DMAチップ自身が割り込みを生じさせることはできません. 周辺機器とそれに関連するハードウェアが割り込みを生成する責任を 持ちます. また, DMAを使用する周辺機器が割り込みを使用しない 可能性もあります.</para> <para>DMAC が要求を出したときには CPU は常にバスを DMAC に開放しますが, この動作は, DMAC がアクティブになった時にプロセッサが命令を実行するのに かかる時間がわずかに変化することを除いては, アプリケーション, オペレーティングシステムの両方からはわからないということを 理解することが重要です. そのため, プロセッサが確かにDMA転送が完了したことを知るためには, 周辺装置や DMA チップ中のレジスタを調べたり,周辺装置からの割り込みを 受け取る必要があります.</para> </sect2> <sect2> <title>DMA ページレジスタ および 16メガ アドレス空間制限</title> <para>これまで述べたのとは異なり, DMACはアドレス線を 0x0123456 にセットする 代わりに 0x3456 だけをセットすることにあなたは気づいたかも しれません. この理由について少し説明します.</para> <para>オリジナルのIBM PCがデザインされた時, IBMは, DMACと割込み制御チップの 両方を, 8085(8ビットプロセッサで, 16ビットのアドレス空間(64k)を持つ)と 組み合わせて使うように設計されたチップを使うことを選びました. IBM PCが64k以上のメモリをサポートしていたため, DMACが64kを越えるメモリ番地に読み込み又は書き込みを行うために 変更を行う必要が生じました. この問題を解決するためにIBMが行ったのは, それぞれのDMAチャネルに, 読み込み元または書き込み先のアドレスの 上位ビットを保持するための 外部的なラッチを追加することでした. DMAチャネルがアクティブな時はいつでも, このラッチの内容はアドレスバスに書かれて, そのチャネルのDMA操作が 終了するまでそこに保持されます. IBM はこれらのラッチを <quote>ページレジスタ</quote> と呼んでいます.</para> <para>そのため上記に示した例では, DMACはアドレスの0x3456の部分をバス上に 置き, DMAチャネル2に対するページレジスタは, 0x0012xxxxをバス上に 置きます. これらの2つの値が組み合わされてアクセスされるメモリ中の完全な アドレスを形成します.</para> <para>ページレジスタのラッチはDMAチップとは独立であるので, 読み込まれる又は書き込まれるメモリ領域は, 64kの物理的境界を またいではなりません. 例えば, もし DMACがメモリの0xffff番地をアクセスした場合, データの転送後, DMACはアドレスレジスタをインクリメントし, 0x0000番地にある次のバイトを アクセスします. 0x10000番地ではありません. これはおそらく意図されたものとは異なっているでしょう.</para> <note> <para><quote>物理的な</quote> 64Kの境界を 8086モードの 64k <quote>セグメント</quote>と混同してはいけません. セグメントは, セグメント レジスタに数学的にオフセットレジスタを 加算して作られるものです. ページレジスタにはアドレスのオーバーラップも無く, 数学的に OR を取られることもありません.</para> </note> <para>さらに複雑なことには, PC/ATでは外部のDMAアドレスのラッチは 8ビットしか保持しません. よって8+16で24ビットになり, これは DMAが0から16メガの間のメモリ番地しか指し示せないことを 意味します. 16メガ以上のメモリを持ったより新しいマシンにおいても, 標準的なPCコンパチブルなDMAでは16メガ以上のメモリ番地には アクセスできません.</para> <para>この制限を避けるために, オペレーティングシステムは 16 メガ以下にある物理的な 64k の境界をまたがない領域に RAM バッファを 予約します. そして, DMACはデータを周辺機器からそのバッファに 転送するようにプログラムされます. 一旦DMACがこのバッファに データを動かすと, オペレーティングシステムは本当にデータを 格納したいアドレスにバッファからデータをコピーします.</para> <para>16メガを越えるアドレスからDMAベースの周辺機器にデータを 書き込む際には, データは16メガ以下に位置したバッファから最初に コピーされなくてはならず, その後, DMACはバッファからハードウェアに データをコピーすることができます. FreeBSDでは, これらの予約バッファは <quote>バウンスバッファ</quote>と呼ばれます. MS-DOSの世界では, これらは<quote>スマートバッファ</quote>などと呼ばれます.</para> <note> <para>82374と呼ばれる8237の新しい実装においては, ページレジスタを16ビットで指定して, バウンスバッファを使用しなくても, 32 ビットのアドレス空間全体にアクセスすることが可能です.</para> </note> </sect2> <sect2> <title>DMA操作モードとその設定</title> <para>8237 DMA はいくつかのモードで動作します. 主なモードは, 以下のとおりです.</para> <variablelist> <varlistentry><term>シングル転送モード</term> <listitem> <para>シングルバイト(もしくはワード)が転送されます. DMAは1バイト毎にバスを開放し, 再び要求しなくてはなくてはなりません. これは一般に, すぐにはデータのブロック全てを転送できないデバイスに よって使用されます. 周辺装置は次の転送の準備ができる毎にDMAを要求します. </para> <para>標準的な PC コンパチブルなフロッピーディスクコントローラ(NEC 765)は 1バイトのバッファしか持たないので, このモードを使用します.</para> </listitem> </varlistentry> <varlistentry><term>ブロック/デマンド転送モード</term> <listitem> <para>一旦 DMAC がシステムバスを取得すると, 最大64kまでのデータブロック 全体が転送されます. もし周辺装置が余分に時間を必要とするときは, 転送を一時中断するためにREADY信号を有効にします. READY信号は過度に使われるべきではなく, 遅い周辺装置の転送の場合は シングル転送モードを代わりに使うべきです.</para> <para>ブロック転送モードとデマンド転送モードの違いは, 一旦ブロック転送が 始まると, 転送カウンタか 0 になるまでそれが行われるところです. DRQ は -DACK が有効になるまでの間は有効でなければなりません. デマンドモードは DRQ が有効な間転送が続けられます. DRQが有効でなくなった場合, DMA はその時点で転送を中断し, バスを解放して CPU に返します. その後, DRQが有効になると, 転送は中断したところから再開されます.</para> <para>データの転送, 特に転送に使われるメモリ番地が16Mを越える場合に, CPU を使った方が効率がよくなるまで CPU の速度が向上する以前の 古いハードディスクコントローラはデマンドモードを 使っていました.</para> </listitem> </varlistentry> <varlistentry><term>カスケード転送モード</term> <listitem> <para>このメカニズムは DMA チャネルがバスを要求することを許可する ものですが, 接続されたデバイスはバス上のアドレス情報の配置に ついてDMACに代わって責任を持ちます. これは<quote>バスマスタ</quote> と呼ばれる技術の実装に利用されます.</para> <para>カスケードモードの DMA チャネルがバスのコントロールを受け取ると, DMA は通常行われるようなバス上のアドレスと I/O コントロール信号の 出力を行いません. 代わりに, DMAはアクティブなチャネルの -DACK信号を 有効にします.</para> <para>この時点で, アドレスとバスコントロール信号の供給は DMAチャネルに接続された周辺機器が担当します. 周辺機器はシステムバスの完全なコントロールを行い, 16 メガ以下の任意のアドレスの読み込みおよび書き込みを 行うことが できます. 周辺機器はバスの使用を終えると DRQ 線を無効にするので, DMA コントローラは CPU もしくは他のDMAチャネルに制御を返すことが できます.</para> <para>カスケードモードは複数の DMA コントローラを相互接続するのに 使われます. PC内ではDMAチャネル4がまさにこの用途に使われています. 周辺機器がDMAチャネル0, 1, 2, 3でバスを要求すると, スレーブDMAコントローラは HLDREQ を有効にしますが, この線はCPUではなく, 実際にはプライマリDMAコントローラのDRQ4に 接続されています. その後, チャンネル4になにか仕事があるものと見なしたプライマリの DMAコントローラは HLDREQ を使ってCPUにバスを 要求します. バスが与えられると, -DACK4が有効になりますが, この線は実際にはスレーブDMAコントローラの HLDA信号に 接続されています. スレーブDMAコントローラはその後要求したDMAチャネル (0, 1, 2, 3) に対してデータを転送するか, SCSIコントローラのような バスマスタリングを要求する周辺機器にバスを許可します. </para> <para>このような配線がおこなわれているため, PC/ATシステムの 周辺機器ではDMAチャネルは 0, 1, 2, 3, 5, 6, 7のみが使用できます.</para> <note> <para>初期のIBM PCコンピュータでは, DMAチャネル0は操作の リフレッシュのために予約されていますが, 最近のシステムでは通常, 周辺機器によって使用することができます.</para> </note> <para>周辺機器がバスマスタリングを行っている時は, システムバスを保持している間絶えずメモリに もしくはメモリから データを転送することが重要です.もし, 周辺機器がこのように できないときは, システムがメインメモリのリフレッシュを 行なえるようにしばしばバスを開放しなくては なりません.</para> <para>全ての PC でメインメモリとして使われるダイナミック RAM は, 中身が <quote>満たされている</quote> ビットを保持するため 頻繁にアクセスされなくてはなりません. ダイナミック RAM は, それぞれが 1 ビットのデータを記憶するコンデンサが たくさん集まって構成されています. これらのコンデンサは充電された 状態で <literal>1</literal>, 充電されていない状態で <literal>0</literal> を表します. 全てのコンデンサは放電するため, <literal>1</literal> の値を保持するために, 一定の間隔で電力を加える必要があります. 実際に RAM チップは RAM の適切な場所に電力を送る作業を行ないますが, メモリのリフレッシュ作業が RAM を普通にアクセスする時と 衝突しないように, それをいつ行なうかを コンピュータが休止状態の時に知らせなくてはなりません. もしコンピュータがメモリのリフレッシュを 行なえない場合は, メモリの中身はわずか数ミリ秒で壊れてしまいます.</para> <para>メモリの読み込みと書き込みのサイクルは リフレッシュサイクルとして <quote>カウントされる</quote> (ダイナミック RAM のリフレッシュサイクルは 実際には不完全なメモリ読み込みサイクルになります)ので, 周辺機器のコントローラが連続するメモリ番地から データの読み込み または書き込みを行う間は, メモリの全てがリフレッシュされます.</para> <para>バスマスタリングはいくつかの SCSI ホストインターフェースやその他の ハイパフォーマンスな周辺機器コントローラに 見られます.</para> </listitem> </varlistentry> <varlistentry><term>自動初期化転送モード</term> <listitem> <para>このモードにおいてDMAはバイト, ブロック, デマンド転送を行いますが, DMA転送カウンタが0になると, カウンタとアドレスはDMAチャネルが もともとプログラムされた時のものに戻されます. これは, 周辺機器が転送を要求している間は転送が続けられることを 意味します. 転送領域としてDMACにプログラムされた固定バッファの中で, 出力操作でDMACがデータを読み出す前もって新しいデータを 書き込んだり入力操作でDMACが書き込んだあとに, そこから新しいデータを読み出す作業は CPU が受け持ちます.</para> <para>このテクニックは, <quote>サンプリング</quote> 用のバッファが小さいもしくは それを持たないオーディオデバイスによく使われます. この<quote>環状</quote>バッファの管理は更なる CPU オーバーヘッドになりますが, DMAカウンタが0になり, 再プログラムされるまでDMAが停止してしまう ことによって起きる遅延は, この方法でしかなくす事ができない 場合もあります.</para> </listitem> </varlistentry> </variablelist> </sect2> <sect2> <title>DMAのプログラミング</title> <para>プログラムされるDMAチャネルは, 通常, 設定を行う前に <quote>マスクする</quote>べきです. これはハードウェアが予期せずそのチャンネルに対してDRQを有効に した場合, たとえ全てのパラメータが 満たされてない場合や更新されていない場合でも, DMACは それに応答してしまう可能性があるからです.</para> <para>マスクを行ってから,ホストは転送の方向(メモリからI/O, もしくはI/Oからメモリ)と, 転送に使用するDMA操作のモード (シングル, ブロック, デマンド, カスケードなど)を設定し, 最後に アドレスや転送の長さを設定します. 設定される長さはDMACに転送させたい量よりも1少なくなります. アドレスや転送長のLSBとMSBは同じ8ビットI/O ポートに書き込まれます. そのためDMACが最初のバイトをLSBとして, 2番目のバイトをMSBとして 受け取ることを保証するために, 最初に別のポートに書き込みを行なって LSBとMSB の判別を行なうフリップフロップをクリアしておく必要があります. </para> <para>そして,DMAのページレジスタを更新します. これはDMACの外部にあり I/O ポートの別のセットを通してアクセスされます.</para> <para>すべての設定ができると, DMAチャネルはマスクを解除することができます. そのDMAチャネルは<quote>準備ができた</quote>とみなされ, そのチャンネルのDRQが 有効になると応答します.</para> <para>8237のプログラミングの正確な詳細については, ハードウェアデータブックを参照してください. PCシステムにおける I/O マップについても参照する必要があるでしょう. このマップには DMA およびページレジスタのポートがどこに位置するのかを 書いてあります. 以下に完全なポートのマップテーブルを示します.</para> </sect2> <sect2> <title>DMAポートのマップ</title> <para>IBM-PCとPC/ATに基づくすべてのシステムでは, 同じI/Oポートに配置された DMAハードウェアを持っています. その完全なリストを以下に示します. DMAコントローラ2に割り当てられたポートは, AT以外のデザインでは 未定義になっています.</para> <sect3> <title>0x00 – 0x1f DMA コントローラ #1 (Channels 0, 1, 2 and 3)</title> <para>DMA アドレス および カウントレジスタ</para> <informaltable frame="none"> <tgroup cols="3"> <tbody> <row> <entry>0x00</entry> <entry>write</entry> <entry>Channel 0 starting address</entry> </row> <row> <entry>0x00</entry> <entry>read</entry> <entry>Channel 0 current address</entry> </row> <row> <entry>0x01</entry> <entry>write</entry> <entry>Channel 0 starting word count</entry> </row> <row> <entry>0x01</entry> <entry>read</entry> <entry>Channel 0 remaining word count</entry> </row> <row> <entry>0x02</entry> <entry>write</entry> <entry>Channel 1 starting address</entry> </row> <row> <entry>0x02</entry> <entry>read</entry> <entry>Channel 1 current address</entry> </row> <row> <entry>0x03</entry> <entry>write</entry> <entry>Channel 1 starting word count</entry> </row> <row> <entry>0x03</entry> <entry>read</entry> <entry>Channel 1 remaining word count</entry> </row> <row> <entry>0x04</entry> <entry>write</entry> <entry>Channel 2 starting address</entry> </row> <row> <entry>0x04</entry> <entry>read</entry> <entry>Channel 2 current address</entry> </row> <row> <entry>0x05</entry> <entry>write</entry> <entry>Channel 2 starting word count</entry> </row> <row> <entry>0x05</entry> <entry>read</entry> <entry>Channel 2 remaining word count</entry> </row> <row> <entry>0x06</entry> <entry>write</entry> <entry>Channel 3 starting address</entry> </row> <row> <entry>0x06</entry> <entry>read</entry> <entry>Channel 3 current address</entry> </row> <row> <entry>0x07</entry> <entry>write</entry> <entry>Channel 3 starting word count</entry> </row> <row> <entry>0x07</entry> <entry>read</entry> <entry>Channel 3 remaining word count</entry> </row> </tbody> </tgroup> </informaltable> <para>DMA コマンドレジスタ</para> <informaltable frame="none"> <tgroup cols="3"> <tbody> <row> <entry>0x08</entry> <entry>write</entry> <entry>Command Register</entry> </row> <row> <entry>0x08</entry> <entry>read</entry> <entry>Status Register</entry> </row> <row> <entry>0x09</entry> <entry>write</entry> <entry>Request Register</entry> </row> <row> <entry>0x09</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0x0a</entry> <entry>write</entry> <entry>Single Mask Register Bit</entry> </row> <row> <entry>0x0a</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0x0b</entry> <entry>write</entry> <entry>Mode Register</entry> </row> <row> <entry>0x0b</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0x0c</entry> <entry>write</entry> <entry>Clear LSB/MSB Flip-Flop</entry> </row> <row> <entry>0x0c</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0x0d</entry> <entry>write</entry> <entry>Master Clear/Reset</entry> </row> <row> <entry>0x0d</entry> <entry>read</entry> <entry>Temporary Register (新しいバージョンでは利用不可)</entry> </row> <row> <entry>0x0e</entry> <entry>write</entry> <entry>Clear Mask Register</entry> </row> <row> <entry>0x0e</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0x0f</entry> <entry>write</entry> <entry>Write All Mask Register Bits</entry> </row> <row> <entry>0x0f</entry> <entry>read</entry> <entry>Read All Mask Register Bits (Intel 82374にのみ存在する)</entry> </row> </tbody> </tgroup> </informaltable> </sect3> <sect3> <title>0xc0 – 0xdf DMA コントローラ #2 (Channels 4, 5, 6 and 7)</title> <para>DMA アドレス および カウントレジスタ</para> <informaltable frame="none"> <tgroup cols="3"> <tbody> <row> <entry>0xc0</entry> <entry>write</entry> <entry>Channel 4 starting address</entry> </row> <row> <entry>0xc0</entry> <entry>read</entry> <entry>Channel 4 current address</entry> </row> <row> <entry>0xc2</entry> <entry>write</entry> <entry>Channel 4 starting word count</entry> </row> <row> <entry>0xc2</entry> <entry>read</entry> <entry>Channel 4 remaining word count</entry> </row> <row> <entry>0xc4</entry> <entry>write</entry> <entry>Channel 5 starting address</entry> </row> <row> <entry>0xc4</entry> <entry>read</entry> <entry>Channel 5 current address</entry> </row> <row> <entry>0xc6</entry> <entry>write</entry> <entry>Channel 5 starting word count</entry> </row> <row> <entry>0xc6</entry> <entry>read</entry> <entry>Channel 5 remaining word count</entry> </row> <row> <entry>0xc8</entry> <entry>write</entry> <entry>Channel 6 starting address</entry> </row> <row> <entry>0xc8</entry> <entry>read</entry> <entry>Channel 6 current address</entry> </row> <row> <entry>0xca</entry> <entry>write</entry> <entry>Channel 6 starting word count</entry> </row> <row> <entry>0xca</entry> <entry>read</entry> <entry>Channel 6 remaining word count</entry> </row> <row> <entry>0xcc</entry> <entry>write</entry> <entry>Channel 7 starting address</entry> </row> <row> <entry>0xcc</entry> <entry>read</entry> <entry>Channel 7 current address</entry> </row> <row> <entry>0xce</entry> <entry>write</entry> <entry>Channel 7 starting word count</entry> </row> <row> <entry>0xce</entry> <entry>read</entry> <entry>Channel 7 remaining word count</entry> </row> </tbody> </tgroup> </informaltable> <para>DMA コマンドレジスタ</para> <informaltable frame="none"> <tgroup cols="3"> <tbody> <row> <entry>0xd0</entry> <entry>write</entry> <entry>Command Register</entry> </row> <row> <entry>0xd0</entry> <entry>read</entry> <entry>Status Register</entry> </row> <row> <entry>0xd2</entry> <entry>write</entry> <entry>Request Register</entry> </row> <row> <entry>0xd2</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0xd4</entry> <entry>write</entry> <entry>Single Mask Register Bit</entry> </row> <row> <entry>0xd4</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0xd6</entry> <entry>write</entry> <entry>Mode Register</entry> </row> <row> <entry>0xd6</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0xd8</entry> <entry>write</entry> <entry>Clear LSB/MSB Flip-Flop</entry> </row> <row> <entry>0xd8</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0xda</entry> <entry>write</entry> <entry>Master Clear/Reset</entry> </row> <row> <entry>0xda</entry> <entry>read</entry> <entry>Temporary Register (Intel 82374には存在しない)</entry> </row> <row> <entry>0xdc</entry> <entry>write</entry> <entry>Clear Mask Register</entry> </row> <row> <entry>0xdc</entry> <entry>read</entry> <entry>-</entry> </row> <row> <entry>0xde</entry> <entry>write</entry> <entry>Write All Mask Register Bits</entry> </row> <row> <entry>0xdf</entry> <entry>read</entry> <entry>Read All Mask Register Bits (Intel 82374にのみ存在する)</entry> </row> </tbody> </tgroup> </informaltable> </sect3> <sect3> <title>0x80 – 0x9f DMA ページレジスタ</title> <informaltable frame="none"> <tgroup cols="3"> <tbody> <row> <entry>0x87</entry> <entry>r/w</entry> <entry>Channel 0 Low byte (23-16) page Register</entry> </row> <row> <entry>0x83</entry> <entry>r/w</entry> <entry>Channel 1 Low byte (23-16) page Register</entry> </row> <row> <entry>0x81</entry> <entry>r/w</entry> <entry>Channel 2 Low byte (23-16) page Register</entry> </row> <row> <entry>0x82</entry> <entry>r/w</entry> <entry>Channel 3 Low byte (23-16) page Register</entry> </row> <row> <entry>0x8b</entry> <entry>r/w</entry> <entry>Channel 5 Low byte (23-16) page Register</entry> </row> <row> <entry>0x89</entry> <entry>r/w</entry> <entry>Channel 6 Low byte (23-16) page Register</entry> </row> <row> <entry>0x8a</entry> <entry>r/w</entry> <entry>Channel 7 Low byte (23-16) page Register</entry> </row> <row> <entry>0x8f</entry> <entry>r/w</entry> <entry>Low byte page Refresh</entry> </row> </tbody> </tgroup> </informaltable> </sect3> <sect3> <title>0x400 – 0x4ff 82374 Enhanced DMA Registers</title> <para>Intel 82374 EISA System Component (ESC)は1996年の初めに発表されました. この中 には機能的には8237のスーパーセットであり, 1つのパッケージの中にその他の PC 互換機のコアとなる周辺コンポーネントをも含んだ DMA コントローラも含まれています. このチップはEISAとPCI 両方のプラットホームをターゲットにしたものであり, scatter-gather I/O やリングバッファを始めとして, システムDMAをして32ビットの アドレス空間全体に直接アクセスする能力も提供しています. </para> <para>これらの機能を使用する場合でも, 過去16年間のPC互換機で利用されてきた 同等機能を提供するコードも含めておく必要があります. 互換性の問題から, 82374の レジスタの一部は, 従来の8237のレジスタをプログラムした <emphasis>後</emphasis> に, 転送の度にプログラムされる必要があります. 8237のレジスタに書き込みを行うとき, ソフトウェアの下位互換性のために, 82374で追加された一部のレジスタの内容が 強制的に0にクリアされるからです.</para> <informaltable frame="none"> <tgroup cols="3"> <tbody> <row> <entry>0x401</entry> <entry>r/w</entry> <entry>Channel 0 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x403</entry> <entry>r/w</entry> <entry>Channel 1 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x405</entry> <entry>r/w</entry> <entry>Channel 2 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x407</entry> <entry>r/w</entry> <entry>Channel 3 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x4c6</entry> <entry>r/w</entry> <entry>Channel 5 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x4ca</entry> <entry>r/w</entry> <entry>Channel 6 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x4ce</entry> <entry>r/w</entry> <entry>Channel 7 High byte (bits 23-16) word count</entry> </row> <row> <entry>0x487</entry> <entry>r/w</entry> <entry>Channel 0 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x483</entry> <entry>r/w</entry> <entry>Channel 1 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x481</entry> <entry>r/w</entry> <entry>Channel 2 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x482</entry> <entry>r/w</entry> <entry>Channel 3 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x48b</entry> <entry>r/w</entry> <entry>Channel 5 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x489</entry> <entry>r/w</entry> <entry>Channel 6 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x48a</entry> <entry>r/w</entry> <entry>Channel 6 High byte (bits 31-24) page Register</entry> </row> <row> <entry>0x48f</entry> <entry>r/w</entry> <entry>High byte page Refresh</entry> </row> <row> <entry>0x4e0</entry> <entry>r/w</entry> <entry>Channel 0 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4e1</entry> <entry>r/w</entry> <entry>Channel 0 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4e2</entry> <entry>r/w</entry> <entry>Channel 0 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x4e4</entry> <entry>r/w</entry> <entry>Channel 1 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4e5</entry> <entry>r/w</entry> <entry>Channel 1 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4e6</entry> <entry>r/w</entry> <entry>Channel 1 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x4e8</entry> <entry>r/w</entry> <entry>Channel 2 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4e9</entry> <entry>r/w</entry> <entry>Channel 2 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4ea</entry> <entry>r/w</entry> <entry>Channel 2 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x4ec</entry> <entry>r/w</entry> <entry>Channel 3 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4ed</entry> <entry>r/w</entry> <entry>Channel 3 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4ee</entry> <entry>r/w</entry> <entry>Channel 3 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x4f4</entry> <entry>r/w</entry> <entry>Channel 5 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4f5</entry> <entry>r/w</entry> <entry>Channel 5 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4f6</entry> <entry>r/w</entry> <entry>Channel 5 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x4f8</entry> <entry>r/w</entry> <entry>Channel 6 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4f9</entry> <entry>r/w</entry> <entry>Channel 6 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4fa</entry> <entry>r/w</entry> <entry>Channel 6 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x4fc</entry> <entry>r/w</entry> <entry>Channel 7 Stop Register (bits 7-2)</entry> </row> <row> <entry>0x4fd</entry> <entry>r/w</entry> <entry>Channel 7 Stop Register (bits 15-8)</entry> </row> <row> <entry>0x4fe</entry> <entry>r/w</entry> <entry>Channel 7 Stop Register (bits 23-16)</entry> </row> <row> <entry>0x40a</entry> <entry>write</entry> <entry>Channels 0-3 Chaining Mode Register</entry> </row> <row> <entry>0x40a</entry> <entry>read</entry> <entry>Channel Interrupt Status Register</entry> </row> <row> <entry>0x4d4</entry> <entry>write</entry> <entry>Channels 4-7 Chaining Mode Register</entry> </row> <row> <entry>0x4d4</entry> <entry>read</entry> <entry>Chaining Mode Status</entry> </row> <row> <entry>0x40c</entry> <entry>read</entry> <entry>Chain Buffer Expiration Control Register</entry> </row> <row> <entry>0x410</entry> <entry>write</entry> <entry>Channel 0 Scatter-Gather Command Register</entry> </row> <row> <entry>0x411</entry> <entry>write</entry> <entry>Channel 1 Scatter-Gather Command Register</entry> </row> <row> <entry>0x412</entry> <entry>write</entry> <entry>Channel 2 Scatter-Gather Command Register</entry> </row> <row> <entry>0x413</entry> <entry>write</entry> <entry>Channel 3 Scatter-Gather Command Register</entry> </row> <row> <entry>0x415</entry> <entry>write</entry> <entry>Channel 5 Scatter-Gather Command Register</entry> </row> <row> <entry>0x416</entry> <entry>write</entry> <entry>Channel 6 Scatter-Gather Command Register</entry> </row> <row> <entry>0x417</entry> <entry>write</entry> <entry>Channel 7 Scatter-Gather Command Register</entry> </row> <row> <entry>0x418</entry> <entry>read</entry> <entry>Channel 0 Scatter-Gather Status Register</entry> </row> <row> <entry>0x419</entry> <entry>read</entry> <entry>Channel 1 Scatter-Gather Status Register</entry> </row> <row> <entry>0x41a</entry> <entry>read</entry> <entry>Channel 2 Scatter-Gather Status Register</entry> </row> <row> <entry>0x41b</entry> <entry>read</entry> <entry>Channel 3 Scatter-Gather Status Register</entry> </row> <row> <entry>0x41d</entry> <entry>read</entry> <entry>Channel 5 Scatter-Gather Status Register</entry> </row> <row> <entry>0x41e</entry> <entry>read</entry> <entry>Channel 5 Scatter-Gather Status Register</entry> </row> <row> <entry>0x41f</entry> <entry>read</entry> <entry>Channel 7 Scatter-Gather Status Register</entry> </row> <row> <entry>0x420-0x423</entry> <entry>r/w</entry> <entry>Channel 0 Scatter-Gather Descriptor Table Pointer Register</entry> </row> <row> <entry>0x424-0x427</entry> <entry>r/w</entry> <entry>Channel 1 Scatter-Gather Descriptor Table Pointer Register</entry> </row> <row> <entry>0x428-0x42b</entry> <entry>r/w</entry> <entry>Channel 2 Scatter-Gather Descriptor Table Pointer Register</entry> </row> <row> <entry>0x42c-0x42f</entry> <entry>r/w</entry> <entry>Channel 3 Scatter-Gather Descriptor Table Pointer Register</entry> </row> <row> <entry>0x434-0x437</entry> <entry>r/w</entry> <entry>Channel 5 Scatter-Gather Descriptor Table Pointer Register</entry> </row> <row> <entry>0x438-0x43b</entry> <entry>r/w</entry> <entry>Channel 6 Scatter-Gather Descriptor Table Pointer Register</entry> </row> <row> <entry>0x43c-0x43f</entry> <entry>r/w</entry> <entry>Channel 7 Scatter-Gather Descriptor Table Pointer Register</entry> </row> </tbody> </tgroup> </informaltable> </sect3> </sect2> </sect1> <sect1 id="internals-vm"> <title>FreeBSD VM システム</title> <para><emphasis>原作: &a.dillon;. 6 Feb 1999</emphasis></para> <sect2> <title>物理メモリ管理 — <literal>vm_page_t</literal></title> <para>物理メモリはページ単位に, <literal>vm_page_t</literal>構造体を用いて管理されます. 物理メモリのページは, ページキューの一つに存在する, それぞれの <literal>vm_page_t</literal> 構造体の配置によって分類されます.</para> <para>ページは, wired(ワイヤード), active(活性状態), inactive(非活性状態), cache(キャッシュ状態), free(使われていない状態)の 各状態をとります. wired 状態を除いて, ページは通常 その状態を示す二重連結リストのキューに置かれます. wired 状態のページがキューに置かれることはありません.</para> <para>FreeBSD は, ページカラーリング(page coloring)を実装するため, cache 状態, free 状態にあるページ用に, さらに複雑なページキューを実装しています. その各々の状態は, プロセッサの L1, L2 キャッシュサイズに応じて最適化された 多重キューを利用します. FreeBSD は, 新たなページを確保(allocate)することが 必要になった場合に確保される VM オブジェクトのために, L1, L2 キャッシュに対して合理的にアライン(align)されたページを 得ようと試みます.</para> <para>加えて, ページは参照カウントとともに保持され, ビジーカウントとともにロックされます. VM システムは, ページフラグとして PG_BUSY を使う<quote>完全ロック状態</quote> も実装しています. </para> <para>一般的には, 各々のページキューは最長不使用 (LRU) 方式で動作します. ページは普通, 最初に wired, もしくは active 状態に置かれます. wired 状態の場合, そのページはどこかにあるページテーブルに 関連づけられています. VM システムはアクティブなキュー内のページをスキャンし, wired 状態のページにエイジング (訳注: ページ参照頻度を量る手法の一つ; aging) を施します. そして, そのページはあまりアクティブでないキューへ 移動することになります. cache キューに移動させられたページは, 再利用の候補になっている VM オブジェクトに割り付けられています. free キューにあるページは, 完全に自由の状態にあります. FreeBSD は, free キューにあるページ数を最小限にとどめようと 試みますが, 割り込み発生時のページ確保を融通するため, 完全に自由なページをいくつか持っていなければなりません. </para> <para>プロセスがページテーブルに存在しない, ページキューの一つ(例えば, inactive, cache キュー等)に 存在するページをアクセスしようとしたとき, 比較的負荷の小さなページ再活性化フォールトが起こります. システムメモリに全く存在していないページの場合は, ディスクからページを読み出す間, そのプロセスはブロック(block)されます.</para> <para>FreeBSD は, ページキューを動的に調節し, 同期済(clean)のページ, 同期していない(dirty)ページの分類を 合理的に保つのと同様に, それぞれのキューにあるページが合理的な 比率に保つように試みます. 再バランス化処理が起こる量は, システムのメモリ負荷に依存します. この再バランス化処理は ページアウトデーモンによって実装されていて, (補助記憶とページを同期して)同期していないページの クリーニングすることや, (LRU キュー内でのページ位置を再配置したり, ページをキューの間を移動することで)ページが頻繁に 参照状態にあることに注目すること, キューを均等にするための キュー間ページ移動等を伴います. ページが実際にどれだけ使われているかを決定するために, FreeBSD の VM システムは, ページの再活性化フォールトを 自発的に, 合理的な数だけ発生します. これは, ページをスワップアウトしたり, クリーニングする時期を より良く決めることに繋がります.</para> </sect2> <sect2> <title>統合バッファキャッシュ — <literal>vm_object_t</literal></title> <para>FreeBSD は, 一般化した <quote>VM オブジェクト</quote> という考え方を実装しています. VM オブジェクトは, 様々な種類の補助記憶(backing store) — 補助記憶なし, スワップ, 物理デバイス, ファイル, に割り付けられます. ファイルシステムは ファイルと関連するインコアデータを管理するのに, 同じ VM オブジェクトを利用するため, 統合バッファキャッシュと呼ばれます.</para> <para>VM オブジェクトは, <emphasis>シャドウ化</emphasis> することができます. シャドウ化とは, オブジェクトがそれぞれ互いの上に スタック(stack)されるということです. 例えば, MAP_PRIVATE mmap() の 動作を実装するために, ファイルに割り付けられた VM オブジェクトの上にスタックされた, スワップに割り付けられた VM オブジェクトが存在しているでしょう. このスタッキングは, fork されたアドレス空間のための 様々な共有属性, コピーオンライト(訳注: ページ共有のための 手法の一つ; cow,copy-on-write) を実装するのにも利用されています.</para> <para><literal>vm_page_t</literal> は, 同時に一つの VM オブジェクトしか割り付けられることが できないことに注意しなければなりません. VM オブジェクトのシャドウ化は, 複数のインスタンスが同じページに 共有できるように実装されています.</para> </sect2> <sect2> <title>ファイルシステム I/O — <literal>struct buf</literal> </title> <para>補助記憶にファイルを使う VM オブジェクトのように, v ノードを使う VM オブジェクトは通常, 処理されているかどうかという情報を, VMシステムが管理する処理情報から独立して 管理される必要があります. 例えば, VM システムが物理ページと補助記憶を同期させようとしたとき, VM システムは, 実際に書き戻す前に, ページがクリーニング済であるという マークを付ける必要があるわけです. さらに, ファイルシステムは, KVM 内で操作できるように, ファイルや, ファイルメタデータの一部分を KVM にマッピングすることが できなくてはなりません.</para> <para>これを管理するために使われる実体は, ファイルシステムバッファ, <literal>struct buf</literal>, <literal>bp</literal> として知られています. ファイルシステムに VM オブジェクトの一部を操作することが 必要となるときは通常, オブジェクトの部分が struct buf に マッピングされ, KVM に struct buf 内のページがマッピングされます. 同じ方法で, ディスク I/O はオブジェクトの部分を バッファ構造体内にマッピングし, その時バッファ構造体上の I/O を 発行することで発行されます. 基礎となっている vm_page_t は, I/O 処理の間 ビジー(busy)状態になります. ファイルシステムにも 独立したビジー状態があり, それはハードウェア上の VM ページの代わりに ファイルシステムバッファで動作する ファイルシステムドライバのコードに とって有用です.</para> <para>FreeBSD は, マッピングを保持するためにある量に制限された KVM を 予約していますが, KVM がマッピングを保持するためだけに使われ, キャッシュデータの能力を制限しないということは 明確にされるべきでしょう. 物理データキャッシュを行うことは厳密に <literal>vm_page_t</literal> の機能になっており, ファイルシステムバッファの機能ではありません. しかし, ファイルシステムバッファは placehold I/O に使われるため, それは実質的に同時処理可能な I/O 処理量を制限します. 通常は二, 三千のファイルバッファが利用可能ですから, このことは問題にならないでしょう.</para> </sect2> <sect2> <title>マッピングページテーブル — <literal>vm_map_t</literal>, <literal>vm_entry_t</literal></title> <!-- kuriyama - Eng should use literal and mdash --> <para>FreeBSD は, 物理ページテーブルの形態を VM システムと分離しています. ハードウェア上にある全てのプロセス毎のページテーブルは, その場その場で再構成され, 通常, 使い捨てだとみなされています. KVM を管理するような特殊なページテーブルは, 最初に永続的な確保が 行われ, これらのページテーブルが破棄されることはありません. </para> <para>FreeBSD は, vm_objects の部分を, 仮想メモリのアドレス範囲に <literal>vm_map_t</literal> と <literal>vm_entry_t</literal> 構造体を通して割り付けます. ページテーブルは, <literal>vm_map_t</literal> /<literal>vm_entry_t</literal>/<literal>vm_object_t</literal> という階層から 直接つくられます. “物理ページは, 直接一つの <literal>vm_object</literal> に 割り付けられる” と私が述べたことを思い出して下さい. ええと, そうですね, しかしそれはいつでも完全に当てはまる, というわけでもないのです. <literal>vm_page_t</literal> のは, 実際に割り付けられた ページテーブルにもリンクされています. 一つの <literal>vm_page_t</literal> は ページテーブルが呼ばれた時, いくつかの <emphasis>pmaps</emphasis> と リンクされることがあります. しかし, そのような階層的な割り付けは, 同じ <literal>vm_page_t</literal> を参照するオブジェクト内の, 同じページへの参照全てを保持しているため, その結果, 常にバッファキャッシュの統合を得ることができるわけです. </para> </sect2> <sect2> <title>KVM メモリマッピング</title> <para>FreeBSD は, 様々なカーネル構造体を保持するため, KVM を利用します. ファイルシステムバッファキャッシュは, KVM 内で最も大きなものです. それはつまり, <literal>struct buf</literal> の実体に対するマッピングに他なりません.</para> <para>Linux と異なり, FreeBSD は全ての物理メモリを KVM にマッピングしません. これは, FreeBSD が 32 ビットプラットフォームで 4G バイトまでの メモリを扱える, ということを意味します. 実際, MMU がそれを可能にしているならば, 理論上, FreeBSD は 32 ビットプラットフォームで 8TB までのメモリを扱うことができることになります. しかし, 大部分の 32 ビットプラットフォームは 4G バイトの RAM しか マッピングできないようになっている, ということには議論の余地があるでしょう. </para> <!-- kuriyama - This translate is difficult to understand --> <para>KVM は, いくつかのメカニズムによって管理されています. 中心となっているのは, <emphasis>ゾーンアロケータ(zone allocator)</emphasis>です. ゾーンアロケータは, 特定の構造体型を確保するために KVM の部分(chunk)を得て, 一定の大きさのメモリブロックに分割します. <command>vmstat -m</command> コマンドで, ゾーンによって 分割された, 現在の KVM 利用状況一覧を得ることができます. </para> </sect2> <sect2> <title>FreeBSD VM システムのチューニング</title> <para>FreeBSD カーネルでは, 動的に自分自身をチューニングするために, 協調的な努力が行なわれています. 普通は, <literal>maxusers</literal> と <literal>NMBCLUSTERS</literal> という カーネルオプション, つまり, <filename>/usr/src/sys/i386/conf/<replaceable>CONFIG_FILE</replaceable></filename> で 指定されるもの以外, 変更する必要はありません. 可能なカーネルオプションの一覧は, <filename>/usr/src/sys/i386/conf/LINT</filename> に 記載されています.</para> <para>大きなシステムに対しては, <literal>maxusers</literal> を増やしたいと思うかも知れませんね. この値は普通, 10 から 128 の間の値にします. <literal>maxusers</literal> を増やしすぎるとシステムの利用可能な KVM がオーバフローしてしまい, 予測できない動作に陥ってしまうことに注意して下さい. <literal>maxusers</literal> はある適度な値にとどめておいて, 特定のリソースを制御する <literal>NMBCLUSTERS</literal> のような, 他のオプションを増加させる方が良いでしょう. </para> <para>もし, システムが負荷の高いネットワーク用途に使われるなら, <literal>NMBCLUSTERS</literal> を増やしたいと望むことでしょう. この値は普通, 1024 から 4096 の間です. </para> <para><literal>NBUF</literal> パラメータも, 伝統的にシステムの規模を決めるのに使われます. これは, システムがファイルシステムバッファを I/O のために マッピングするのに使われる, KVA の大きさを決めるのに使われます. このパラメータは, 統合バッファキャッシュには何の影響も与えません. これは 3.0-RELEASE 以降のカーネルでは動的にチューニングされるため, 普通は手作業で調整されるべきものではありません. <literal>NBUF</literal> パラメータは, 指定しようとしないことを推奨します. システムに選択させれば良いのです. 小さすぎる値は極端に非効率的なファイルシステム動作を招き, 一方で, 大きすぎる値は wired 状態のページを数多くつくりだし, ページキューを枯渇させてしまうでしょう.</para> <para>デフォルトでは, FreeBSD カーネルは最適化されていません. カーネルコンフィグにある <literal>makeoption</literal> ディレクティブを使って 最適化とデバッグフラグをセットすることができます. ただし, それによって得られる大きな (7MB 超の)カーネルを相手にするのが嫌なら, <option>-g</option> オプションは使ってはいけません.</para> <programlisting>makeoptions DEBUG="-g" makeoptions COPTFLAGS="-O -pipe"</programlisting> <para>sysctl は, 実行時にカーネルパラメータをチューニングする 手段を提供しています. しかし, 普通は sysctl 変数, 特に VM に関連したものを変更する必要が 生じるようなことはありません.</para> <para>実行時の VM とシステムのチューニングは, 比較的単純です. まず, 可能ならば UFS/FFS ファイルシステムで softupdates を使いましょう. <filename>/usr/src/contrib/sys/softupdates/README</filename> のファイルに, 設定方法に関する手順(と制限)について書かれています. </para> <para>次に, 十分なスワップを設定します. <quote>作業</quote> ディスクを含む 各物理ディスク装置毎に一つずつ (最大四つまで)のスワップパーティションを 設定すべきです. 少なくとも, メインメモリの 2 倍の スワップ空間が望ましく, メモリがあまりない場合には, おそらくそれより多く必要になります. また, スワップパーティションのサイズは, 後でパーティションをつくり直しする必要がないように マシンに設定したいメモリ設定の最大値を基準に 決めるべきでしょう. もし, クラッシュダンプをとりたい場合, スワップパーティションは最低限メインメモリと同じの大きさで, <filename>/var/crash</filename> にはダンプを保持するのに十分な 空きがなければなければなりません.</para> <para>NFS 経由のスワップは, -4.x 以降のシステムで完全に動作しますが, NFS サーバ側では, ページングがその負荷の主な原因になることに 注意しなければなりません.</para> </sect2> </sect1> <sect1 id="ipv6-implementation"> <title>IPv6/IPsec の実装</title> <para><emphasis>原作: &a.shin;, 2000 年 3 月 5 日.</emphasis></para> <para><emphasis>訳: &a.jp.hino;, 2001 年 3 月 19 日.</emphasis></para> <note> <title>訳注</title> <para>訳語については <quote>IPv6 次世代インターネット・プロトコル</quote>, クリスチャン・ウイテマ著, 村井純監修, WIDE プロジェクト IPv6 分科会監訳, 松島栄樹訳, 1997, ISBN4-88735-010-4 を参考にさせていただきました.</para> </note> <para>本章では, IPv6 と IPsec に関連する実装の詳細について説明します. これらの機能は <ulink url="http://www.kame.net">KAME プロジェクト</ulink>の成果を取り込んだものです.</para> <sect2 id="ipv6"> <title>IPv6</title> <sect3> <title>規格適合性</title> <para>わたしたちの IPv6 関連の機能は, 最新の IPv6 仕様に準処してい るか, または準処しようと努力しています. 今後の参考のために以下 に参考文献を挙げておきます (<emphasis>注</emphasis>: これ は完全なリストではありません - それを維持するのは大変ですか ら...).</para> <para>詳しくは, 本文書の各章や, RFC, マニュアル ページ, そしてソースコード中のコメントを参照してください.</para> <para>規格適合テストは, TAHI プロジェクトにより KAME STABLE kit に対して行われました. その結果は, <ulink url="http://www.tahi.org/report/KAME/">http://www.tahi.org/report/KAME/</ulink> で見ることができます. わたしたちはまた, ニューハンプシャー大学で行われた IOL テスト (<ulink url="http://www.iol.unh.edu/">http://www.iol.unh.edu/</ulink>) に以前のバージョンで参加したこともあります.</para> <itemizedlist> <listitem> <para>RFC1639: FTP Operation Over Big Address Records (FOOBAR) (大きなアドレスレコードを用いる FTP 操作)</para> <itemizedlist> <listitem> <para>RFC2428 は RFC1639 よりも推奨されます. FTP クラ イアントは, まず RFC2428 を試し, もしそれが失敗したな ら RFC1639 を試します.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC1886: DNS Extensions to support IPv6 (IPv6 をサポー トするための DNS 拡張)</para> </listitem> <listitem> <para>RFC1933: Transition Mechanisms for IPv6 Hosts and Routers (IPv6 ホストおよびルータのための移行機構)</para> <itemizedlist> <listitem> <para>IPv4 互換アドレスはサポートされません.</para> </listitem> <listitem> <para>自動トンネリング (RFC の 4.3 で述べられています) はサポートされません.</para> </listitem> <listitem> <para>&man.gif.4; インタフェースは IPv[46]-over-IPv[46] トンネルを包括的な方法で実装しており, それは仕様で述べ られている "configured tunnel (構成されたトンネル)" を カバーしています. 詳細は本文書の <link linkend="gif">23.5.1.5</link>をご覧ください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC1981: Path MTU Discovery for IPv6 (IPv6 における 経路 MTU 探索)</para> </listitem> <listitem> <para>RFC2080: RIPng for IPv6 (IPv6 のための RIPng)</para> <itemizedlist> <listitem> <para>usr.sbin/route6d がこれをサポートしています.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2292: Advanced Sockets API for IPv6 (IPv6 のため の拡張ソケット API)</para> <itemizedlist> <listitem> <para>サポートされているライブラリ関数やカーネルの API については, <filename>sys/netinet6/ADVAPI</filename> をご覧ください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2362: Protocol Independent Multicast-Sparse Mode (PIM-SM) (プロトコル独立マルチキャスト - 疎モード (sparse mode))</para> <itemizedlist> <listitem> <para>RFC2362 は PIM-SM のパケットフォーマットを定義し ています. <filename>draft-ietf-pim-ipv6-01.txt</filename> はこれ に準じて書かれています.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2373: IPv6 Addressing Architecture (IPv6 アドレス 体系)</para> <itemizedlist> <listitem> <para>ノードに必須のアドレス群をサポートし, スコープ要 求に準処しています.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2374: An IPv6 Aggregatable Global Unicast Address Format (IPv6 集約可能グローバルユニキャストアドレス形式)</para> <itemizedlist> <listitem> <para>64 ビット長のインタフェース ID をサポートしていま す.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2375: IPv6 Multicast Address Assignments (IPv6 に おけるマルチキャストアドレス割り当て)</para> <itemizedlist> <listitem> <para>ユーザランドのアプリケーション群は本 RFC で割り 当てられている well-known なアドレスを使用しています. </para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2428: FTP Extensions for IPv6 and NATs (IPv6 と NAT のための FTP 拡張)</para> <itemizedlist> <listitem> <para>RFC2428 は RFC1639 よりも推奨されます. FTP クラ イアントは, まず RFC2428 を試し, もしそれが失敗したな ら RFC1639 を試します.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2460: IPv6 specification (IPv6 仕様)</para> </listitem> <listitem> <para>RFC2461: Neighbor discovery for IPv6 (IPv6 における 近隣探索)</para> <itemizedlist> <listitem> <para>詳しくは本文書の <link linkend="neighbor-discovery">23.5.1.2</link> をご覧く ださい.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2462: IPv6 Stateless Address Autoconfiguration (IPv6 におけるステートレスアドレス自動設定)</para> <itemizedlist> <listitem> <para>詳しくは本文書の <link linkend="ipv6-pnp">23.5.1.4</link> をご覧ください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2463: ICMPv6 for IPv6 specification (IPv6 のため の ICMPv6 仕様)</para> <itemizedlist> <listitem> <para>詳しくは本文書の <link linkend="icmpv6">23.5.1.9</link> をご覧ください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2464: Transmission of IPv6 Packets over Ethernet Networks (イーサネット上での IPv6 パケットの転送方式)</para> </listitem> <listitem> <para>RFC2465: MIB for IPv6: Textual Conventions and General Group (IPv6 用 MIB: 文字列的変換法と一般グループ)</para> <itemizedlist> <listitem> <para>必要な統計情報はカーネルにより集計されています. 実際の IPv6 MIB サポートは ucd-snmp に対するパッチキッ トとして提供されています.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2466: MIB for IPv6: ICMPv6 group (IPv6 用 MIB: ICMPv6 グループ)</para> <itemizedlist> <listitem> <para>必要な統計情報はカーネルにより集計されています. 実際の IPv6 MIB サポートは ucd-snmp に対するパッチキッ トとして提供されています.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2467: Transmission of IPv6 Packets over FDDI Networks (FDDI ネットワーク上での IPv6 パケットの転送方式)</para> </listitem> <listitem> <para>RFC2497: Transmission of IPv6 packet over ARCnet Networks (ARCnet ネットワーク上での IPv6 パケットの転送方 式)</para> </listitem> <listitem> <para>RFC2553: Basic Socket Interface Extensions for IPv6 (IPv6 のための 基本的ソケットインタフェースの拡張)</para> <itemizedlist> <listitem> <para>IPv4 射影アドレス (3.7) と IPv6 ワイルドカードバ インドソケットの特別な動作 (3.8) をサポートしています. 詳しくは本文書の <link linkend="ipv6-wildcard-socket">23.5.1.12</link> をご覧ください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2675: IPv6 Jumbograms (IPv6 巨大パケット)</para> <itemizedlist> <listitem> <para>詳しくは本文書の <link linkend="ipv6-jumbo">23.5.1.7</link> をご覧ください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para>RFC2710: Multicast Listener Discovery for IPv6 (IPv6 のためのマルチキャスト受信者探索)</para> </listitem> <listitem> <para>RFC2711: IPv6 router alert option (IPv6 ルータ警報オ プション)</para> </listitem> <listitem> <para><filename>draft-ietf-ipngwg-router-renum-08</filename>: IPv6 におけるルータの再番号付け</para> </listitem> <listitem> <para><filename>draft-ietf-ipngwg-icmp-namelookups-02</filename>: ICMP による IPv6 名前検索</para> </listitem> <listitem> <para><filename>draft-ietf-ipngwg-icmp-name-lookups-03</filename>: ICMP による IPv6 名前検索</para> </listitem> <listitem> <para><filename>draft-ietf-pim-ipv6-01.txt</filename>: IPv6 用 PIM</para> <itemizedlist> <listitem> <para>&man.pim6dd.8; は密モード (dense mode) を実装しています. &man.pim6sd.8; は疎モード (sparse mode) を実装しています.</para> </listitem> </itemizedlist> </listitem> <listitem> <para><filename>draft-itojun-ipv6-tcp-to-anycast-00</filename>: IPv6 エニーキャストアドレス向けに TCP 接続を切断</para> </listitem> <listitem> <para><filename>draft-yamamoto-wideipv6-comm-model-00</filename></para> <itemizedlist> <listitem> <para>詳細は本文書の <link linkend="ipv6-sas">23.5.1.6</link> を参照してください.</para> </listitem> </itemizedlist> </listitem> <listitem> <para><filename>draft-ietf-ipngwg-scopedaddr-format-00.txt </filename>: IPv6 のスコープ化アドレス形式の拡張</para> </listitem> </itemizedlist> </sect3> <sect3 id="neighbor-discovery"> <title>近隣探索</title> <para>近隣探索はきわめて安定しています. 現在, アドレス検索 (Address Resolution), 重複アドレス検出 (Duplicated Address Detection), 近隣到達不能性検出 (Neighbor Unreachability Detection) がサポートされています. もうすぐ, カーネルに代理近 隣通知 (Proxy Neighbor Advertisement) サポートを加え, 管理者ツー ルとして近隣探索取消要請 (Unsolicited Neighbor Advertisement) を送出するコマンドを加える予定です.</para> <para>DAD (重複アドレス検出) が重複を検出した場合, そのアドレ スは "重複している" という印が付けられ, syslog にメッセージが 記録されます (そして通常はコンソールに表示されます). "重複して いる" 印は &man.ifconfig.8; で調べることができます. 重複検出の チェックおよび重複状態を解消することは管理者の責任となります. この動作は近いうちに改良するべきです.</para> <para>いくつかのネットワークドライバは, そうしないようにと指示 された場合でもマルチキャストパケットを自分自身に送り返してしま います (特に無差別 (promiscuous) モードでは). このような状況で は DAD は重複を検出するでしょう. なぜなら DAD を実行するプログ ラムは NS パケットの到着を検出し (それが自ノードが出力したもの であるにもかかわらず) それが重複を表しているとみなすからです. この問題に対処する方法は, sys/netinet6/nd6_nbr.c:nd6_dad_timer() 中の "heuristics" とマー クされた #if 条件のあたりを見てください ("heuristics" 部分のプ ログラムコードは仕様に反していることに注意してください).</para> <para>近隣探索の仕様 (RFC2461) では, 以下に述べる状況での近隣 情報キャッシュの取り扱いについて記述されていません:</para> <orderedlist> <listitem> <para>近隣情報キャッシュが空の時に, 下位層 (リンク層) アド レスを持たない RS/NS/NA/redirect 削除要請パケットを受信 </para> </listitem> <listitem> <para>下位層アドレスを持たない通信メディアにおける近隣情報 キャッシュの取り扱い (IsRouter ビットのために一つの近隣情 報キャッシュエントリが必要です)</para> </listitem> </orderedlist> <para>一つめの場合については, IETF ipngwg メーリングリストで行 われた議論に基づく対応策を実装しています. より詳しくは, ソース コード中のコメントと, 1999 年 2 月 6 日の (IPng 7155) というメー ルから始まったスレッドを参照してください.</para> <para>IPv6 における同一リンク上かどうかの判断ルール (RFC2461) は, BSD のネットワークコードが想定している条件と全く異なります. とりあえず, デフォルトルータのリストが空の時には同一リンク上か どうかの判断ルールはサポートしていません (RFC2461 の 5.2 章, 第二文節の最後の文 - この仕様のこの章では何カ所かで "host" と "node" という単語の間違った使い方をしていることに注意).</para> <para>サービス妨害攻撃と無限ループをさけるために, ND パケット 中のオプションは 10 個だけが受け付けられます. そのため, もし RA に 20 個のプレフィックスを載せたとしても先頭の 10 個のプレ フィックスしか理解されません. もしこのことが問題となるのなら, FREEBSD-CURRENT メーリングリストに質問するか, または <filename>sys/netinet6/nd6.c</filename> 中の nd6_maxndopt を変 更してください. もし多くの要求があるようなら, この変数を変更す る sysctl 変数を用意できるでしょう.</para> </sect3> <sect3 id="ipv6-scope-index"> <title>スコープ番号</title> <para>IPv6 はスコープ化されたアドレスを使います. そのため, IPv6 アドレスにスコープ番号 (リンクローカルアドレスではインタ フェース番号, サイトローカルアドレスではサイト番号) を指定する ことは非常に重要です. スコープ番号が無いと, カーネルにとってス コープ化された IPv6 アドレスは曖昧なものとなり, そしてカーネル はそのパケットを出力するインタフェースを選ぶことができないでしょ う.</para> <para>ユーザランドのアプリケーションは, スコープ番号またはイン タフェース番号を指定するために, 通常は拡張 API (RFC2292) を使 うべきです. 同様の目的のために, RFC2553 において sockaddr_in6 構造体にsin6_scope_id メンバが定義されています. しかしながら, sin6_scope_id の意味づけは少々曖昧です. もし, あなたが自分のア プリケーションの移植性について気をつけたいのなら, sin6_scope_id ではなく拡張 API を使うことをお勧めします.</para> <para>カーネル中では, リンクローカルスコープのアドレスに対応す るインタフェース番号は IPv6 アドレスの二番目の 16 ビット語 (三 番めと四番めのバイト) に埋め込まれます. たとえばルーティングテー ブルとインタフェースアドレス構造体 (struct in6_ifaddr) の中で, 以下のような例を見ることができるでしょう:</para> <screen>fe80:1::200:f8ff:fe01:6317</screen> <para>上で述べたアドレスはリンクローカルなユニキャストアドレス で, それはインタフェース番号が 1 であるネットワークインタフェー スに属するものです. 埋め込まれた番号によって, 複数のインタフェー スに対する IPv6 リンクローカルアドレス群を, 効率的に, かつ少々 のプログラムの対応だけで, 識別することができます.</para> <para>&man.route6d.8; や &man.ifconfig.8; のような, ルーティン グデーモンと設定プログラムは "埋め込まれた" スコープ番号を取り 扱う必要があります. これらのプログラムはルーティング用のソケッ トと (SIOCGIFADDR_IN6 のような) ioctl 群を使います. そしてカー ネル API は二番目の 16 ビット語を書き込んでから IPv6 アドレス を返します. これらの API はカーネル内部構造体を操作するための ものです. これらの API を利用するプログラムは, どちらにしろカー ネル間の違いについて意識する必要があります.</para> <para>コマンドラインでスコープ化されたアドレスを指定するときに は, (ff02:1::1 や fe80:2::fedc のような) 埋め込まれた形式を * 絶対に* 使わないでください. たぶんこれでは動きません. インタ フェースを指定するコマンドラインオプション (たとえば <command>ping6 -I ne0 ff02::1</command>) を使うときには, 必ず ff02::1 や fe80::fedc のような標準形式を使ってください. 一般的 にいって, コマンドが出力インタフェースを指定するコマンドライン オプションを持っていないのならば, そのコマンドはスコープ化され たアドレスを受け付けることはできないでしょう. このことは IPv6 の, "歯医者のオフィス" をサポートするという前提に反するように 思えるでしょう. この問題に関して, わたしたちは仕様に何らかの改良を 加える必要があると信じています.</para> <para>いくつかのユーザランドのツールは, <filename>draft-ietf-ipngwg-scopedaddr-format-00.txt</filename> で文書化されている, IPv6 拡張数値記法をサポートしています. "fe80::1%ne0" というように, 出力インタフェースの名前を使って, パケットを出力するリンクを指定することができます. この方法によっ て, 特に問題なくリンクローカルなスコープ化されたアドレスを指定 することができるでしょう.</para> <para>あなたのプログラムでこの拡張数値記法を使うためには, &man.getaddrinfo.3;, と &man.getnameinfo.3; を NI_WITHSCOPEID をつけて使用する必要があります. 現在の実装では, リンクとインタ フェースとが一対一に対応していることを想定しています. これは仕 様で述べられているよりも強い想定です.</para> </sect3> <sect3 id="ipv6-pnp"> <title>プラグ & プレイ</title> <para>ほとんどの IPv6 ステートレスアドレス自動設定はカーネル中 に実装されています. 近隣探索機能は全てカーネル内に実装されてい ます. ルータ通知 (RA: Router Advertisement) のホストへの入力は カーネル内で実装されています. ルータ要請 (RS: Router Solicitation) の終端ホストからの出力, RS のルータへの入力, そ してルータでの RA の出力はユーザランドで実装されています.</para> <sect4> <title>リンクローカルアドレスと特別アドレスの割り当て</title> <para>IPv6 リンクローカルアドレスは IEEE802 アドレス (イーサ ネットの MAC アドレス) から生成されます. 各インタフェースに は, そのインタフェースが利用可能になったとき (IFF_UP) に自動 的にリンクローカルアドレスが一つ割り当てられます. 同時に, そ のリンクローカルアドレスに対する直接のルートがルーティングテー ブルに加えられます.</para> <para>以下に netstat コマンドの出力を示します:</para> <screen>Internet6: Destination Gateway Flags Netif Expire fe80:1::%ed0/64 link#1 UC ed0 fe80:2::%ep0/64 link#2 UC ep0</screen> <para>IEEE802 アドレスを持っていないインタフェース (トンネル インタフェースのような疑似インタフェースや, ppp インタフェー ス) は, できる限り, イーサネットインタフェースなどの他のイン タフェースから IEEE802 アドレスを借用します. もし IEEE802 ア ドレスを持ったハードウェアが一つもなければ, 最後の手段として (MD5(ホスト名)で計算される) 疑似乱数値がリンクローカルアドレ スの元として使われます. もしこれがあなたの用途に合わないなら, リンクローカルアドレスを手動で設定する必要があるでしょう.</para> <para>もしあるインタフェースで IPv6 を使えない (たとえばマルチ キャストをサポートしない, 等の理由で) ならば, そのインタフェー スにはリンクローカルアドレスは割り当てられません. 詳細は 2 章をご覧ください.</para> <para>各インタフェースは要請されたマルチキャストアドレスとリ ンクローカルな「全ノード」マルチキャストアドレスに加わります (すなわち, そのインタフェースがつながれたリンク上で, それぞ れ fe80::1:ff01:6317 と ff02::1 です). リンクローカルアドレ スに加え, ループバックアドレス (::1) がループバックインタフェー スに割り当てられます. また, ::1/128 と ff01::/32 が自動的に ルーティングテーブルに追加され, そしてループバックインタフェー スはノードローカルマルチキャストグループである ff01::1 に加 わります.</para> </sect4> <sect4> <title>ホスト上でのステートレスアドレス自動設定</title> <para>IPv6 仕様では, ノードは二種類に分類されます: <emphasis>ルータ</emphasis>と<emphasis>ホスト</emphasis>です. ルータは他宛のアドレスがついたパケットを転送します. ホストは パケットの転送を行いません. net.inet6.ip6.forwarding によっ て, このノードがルータであるかホストであるかが決定されます (1 ならルータで, 0 ならホストです).</para> <para>ホストがルータ通知 (Router Advertisement) をルータから 受信すると, ホストはステートレスアドレス自動設定によって自ら を自動設定することができます. この動作は net.inet6.ip6.accept_rtadv によって制御できます (1 にセット されているとホストは自らを自動設定します). 自動設定によって, この受信したインタフェースにネットワークアドレスプリフィック ス (通常はグローバルアドレスプリフィックス) が追加されます. デフォルトルートも同時に設定されます. ルータは定期的にルータ 通知パケットを送出します. 隣接するルータに RA パケットを送出 するよう要求するために, ホストはルータ要請 (Router Solicitation) を送信することができます. 好きなときに RS パケッ トを送出するためには, <emphasis>rtsol</emphasis> コマンドを 使います. また, &man.rtsold.8; デーモンもあります. &man.rtsold.8; は必要なときにはいつでもルータ要請を送出しま す. これは移動体のような使い方 (ノート型やラップトップ型コン ピュータ) をするときにとても調子良く働きます. もしルータ通知 を無視したいのなら, sysctl で net.inet6.ip6.accept_rtadv を 0 にしてください.</para> <para>ルータでルータ通知を送出するには &man.rtadvd.8 デーモ ンを使います.</para> <para>IPv6 仕様では, 以下の事柄を想定しており, それらに合致 しないケースに関しては仕様化されていないことに注意してくださ い:</para> <itemizedlist> <listitem> <para>ホストだけがルータ通知を待ち受けている</para> </listitem> <listitem> <para>ホストは (ループバック以外には) ネットワークインタ フェースを一つだけ持つ</para> </listitem> </itemizedlist> <para>以上のことより, ルータや複数のインタフェースを持つホス トで net.inet6.ip6.accept_rtadv を有効にすることは賢いことで はないことが分かります. 設定を失敗したノードは変な動作をする 可能性があります (経験をしてみたい人のために仕様にあわない設 定も許されています).</para> <para>sysctl 変数を整理すると:</para> <screen> accept_rtadv forwarding ノードの役割 --- --- --- 0 0 ホスト (手動で設定された) 0 1 ルータ 1 0 自動設定されたホスト (仕様ではホストはインタフェー スを一つのみ持つことを想定して いるので, 複数のインタフェース を持つホストの自動設定は想定外) 1 1 不正, または実験的 (仕様の想定外)</screen> <para>RFC2462 の 5.5.3 (e) には到着した RA プリフィックス情 報オプションの検証ルールが書いてあります. これは悪意のある (または設定を失敗した) ルータが非常に短いプリフィックス生存 期間を通知してくることに対してホストを防御するためのものです. ipngwg メーリングリストで Jim Bound による更新があり (メーリ ングリストアーカイブの "(ipng 6712)" をご覧ください), ここで の実装はこの Jim の更新を取り入れたものです.</para> <para>DAD と自動設定との関係については, 本文書の <link linkend="neighbor-discovery">23.5.1.2</link> をご覧ください. </para> </sect4> </sect3> <sect3 id="gif"> <title>包括的トンネルインタフェース (Generic tunnel interface)</title> <para>GIF (包括的インタフェース: Generic Interface) は構成された トンネルのための疑似インタフェースです. 詳細は &man.gif.4; に 述べられています. 現在,</para> <itemizedlist> <listitem> <para>v6 in v6</para> </listitem> <listitem> <para>v6 in v4</para> </listitem> <listitem> <para>v4 in v6</para> </listitem> <listitem> <para>v4 in v4</para> </listitem> </itemizedlist> <para>が利用できます. gif インタフェースに物理的な (外側の) 始 点と終点アドレスを割り当てるためには &man.gifconfig.8; を使い ます. 内側と外側の IP ヘッダで同じアドレスファミリを使う (v4 in v4, または v6 in v6) 設定は危険です. 無限段数のトンネルを作っ てしまうようにインタフェースとルーティングテーブルを設定するの はとても簡単だからです.<emphasis>注意してください! </emphasis></para> <para>gif は ECN (Explicit Congestion Notification: 明示的輻輳 通知) とともに使えるように設定することができます. ECN との親和 性については <link linkend="ipsec-ecn">23.5.4.5</link> をご覧ください. また, 設定方法に関しては &man.gif.4; をご覧ください.</para> <para>もし IPv4-in-IPv6 トンネルを gif インタフェースを使って 設定しようと思っているなら, &man.gif.4; を注意深く読んでくださ い. gif インタフェースに自動的に割り当てられる IPv6 リンクロー カルアドレスを削除する必要があるでしょう.</para> </sect3> <sect3 id="ipv6-sas"> <title>始点アドレスの選択</title> <para>現在の始点選択ルールはスコープ指向です (いくつかの例外も あります - 以下を参照してください). ある終点アドレスに対する始 点 IPv6 アドレスは以下の規則に従って選択されます:</para> <orderedlist> <listitem> <para>もし始点アドレスがユーザにより明示的に指定 (たとえば拡 張 API を通じて) されていたならば, その指定されたアドレス が使われる.</para> </listitem> <listitem> <para>出力インタフェース (通常はルーティングテーブルを検索 することにより決定される) にアドレスが割り当てられており, そのアドレスが終点アドレスと同一のスコープを持っているなら ば, そのアドレスが使われる.</para> <para>これがもっとも一般的な場合です.</para> </listitem> <listitem> <para>もし上記の場合を満たすアドレスがなかったときには, 送 出するノードが持つインタフェースのうちのどれか一つに割り当 てられているグローバルアドレスを選択する.</para> </listitem> <listitem> <para>もし上記の場合を満たすアドレスがなかったときで, 終 点アドレスがサイトローカルスコープであった場合には, 送出す るノードが持つインタフェースのうちのどれか一つに割り当てら れているサイトローカルアドレスを選択する.</para> </listitem> <listitem> <para>もし上記の場合を満たすアドレスがなかったときは, 終 点に向かっているルーティングテーブルエントリに関連づけられ たアドレスを選択する. これは最後の手段であり, スコープ違反 を引き起こす可能性がある.</para> </listitem> </orderedlist> <para>たとえば, ff01::1 に対しては ::1 が選択され, fe80:1::2a0:24ff:feab:839b に対しては fe80:1::200:f8ff:fe01:6317 が選択されます (埋め込まれたインタ フェース番号に注意 - <link linkend="ipv6-scope-index">23.5.1.3</link> で述べられている - この番号により正しい始点アドレスが選択できます. これらの埋め込 まれた番号は通信路 (wire) 上では存在しません). もし出力インタ フェースが該当するスコープに対して複数のアドレスを持っていた場 合は, 始点は最長一致法 (規則 3) で選択されます. たとえば, 出力イ ンタフェースに, 3ffe:501:808:1:200:f8ff:fe01:6317 と 3ffe:2001:9:124:200:f8ff:fe01:6317 が付けられていたとします. 終点アドレスが 3ffe:501:800::1 であるとすると, 始点アドレスと しては 3ffe:501:808:1:200:f8ff:fe01:6317 が選択されます.</para> <para>上記の規則は IPv6 仕様では文書化されていないことに注意し てください. これは「実装に任された」項目と考えられているのです. 上記の規則に則らないケースがいくつかあります. 一つの例は, 接続 済みの TCP セッションで, この場合は tcb に保存されているアドレ スを始点とします. 他の例としては, 近隣通知 (Neighbor Advertisement: NA) の際の始点アドレスです. 仕様 (RFC2461 7.2.2) によれば, NA の始点アドレスは対応する NS (近隣要請) の ターゲットアドレスであるべきとされています. この場合は, 上記の 最長一致規則ではなく仕様に従います.</para> <para>新規に接続を行うとき (規則 1 に当てはまらないとき) に, 他に選択肢がある限り, 推奨有効期間切れアドレス (preferred lifetime が 0 であるアドレス)を始点アドレスとして選択すること はありません. もし他の選択肢がなければ, 最後の手段として推奨有 効期限切れアドレスが使われます. もし複数の推奨有効期間切れアド レスがあるときには, 上記のスコープ規則に従ってそれらのアドレス からの選択が行われます. もし何らかの理由により推奨有効期間切れ アドレスの使用を禁止したいのならば, net.inet6.ip6.use_deprecated を 0 に設定してください. 推奨有効 期間切れアドレスに関連する問題は, RFC2462 5.5.4 に記述されてい ます (注意: IETF の ipngwg では推奨有効期間切れアドレスをどの ように使うべきかの議論がいくつか進行中です).</para> </sect3> <sect3 id="ipv6-jumbo"> <title>巨大ペイロード</title> <para>巨大ペイロード中継点毎オプションは実装されており, 65,535 バイトよりも長いペイロードを持つ IPv6 パケットを送信するときに 利用できます. しかし, MTU が 65,535 よりも大きな物理インタフェー スは現在サポートされていませんから, そのようなペイロードはルー プバックインタフェース (たとえば lo0) 上でのみ利用できます.</para> <para>もし巨大ペイロードを試してみたいのならば, まず始めに, ルー プバックインタフェースの MTU が 65,535 バイトよりも大きくなる ようにカーネルの再構成を行わなければなりません; 以下の行をカー ネル構成ファイルに追加してください:</para> <para><literal> options "LARGE_LOMTU" #To test jumbo payload </literal></para> <para>そして新しいカーネルをコンパイルしてください.</para> <para>次に, &man.ping6.8; コマンドを -b と -s オプション付きで 使うことで巨大ペイロードを試してみることができます. -b オプショ ンはソケットバッファの大きさを拡大するために指定しなければなリ ません. -s オプションによりパケット長を指定します. その値は 65,535 よりも大きくなるでしょう. たとえば以下のように入力してく ださい:</para> <para><userinput> &prompt.user; <command>ping6 -b 70000 -s 68000 ::1</command> </userinput></para> <para>IPv6 仕様では, 巨大ペイロードオプションは分割された (fragtment) ヘッダを持つパケットでは使えないことになっています. この条件が破られると ICMPv6 Parameter Problem メッセージが送信 元に送られるはずです. この仕様には従っているのですが, 通常はこ の ICMPv6 エラーを見ることはできないでしょう.</para> <para>IPv6 パケットが受信されると, そのフレーム長が調べられ, そして IPv6 ヘッダ中のペイロード長フィールド, またはもしあれば 巨大ペイロードオプションの値, で指定された長さと比較されます. もし前者の方が後者よりも短ければ, パケットは廃棄され統計情報が 更新されます. この統計情報は, &man.netstat.8; コマンドを `-s -p ip6' オプション付きで実行すると見ることができます:</para> <screen> &prompt.user; <command>netstat -s -p ip6</command> ip6: (snip) 1 with data size < data length </screen> <para>それ故, カーネルはエラーを起こしたパケットが本当に巨大ペ イロードである, すなわち, そのパケット長が 65,535 バイトよりも 長い場合にしか ICMPv6 エラーを送りません. 上で述べたように, 現 在そのような巨大な MTU をもつ物理インタフェースはサポートされ ていませんので, IPMPv6 エラーが返ることは滅多にないでしょう. </para> <para>現状では, 巨大パケット上の TCP/UDP はサポートされていま せん. これはテストできる通信メディアが (ループバック以外) 存在 しないためです. もしこれを必要としているなら, わたしたちに連絡して ください.</para> <para>IPsec は巨大パケットの上では動作しません. これは, 巨大パ ケットと AH を同時にサポートする場合の仕様の「ねじれ」のせいで す (AH ヘッダサイズはペイロード長に影響を及ぼし, そしてこのこ とが, 巨大ペイロードオプションと AH が両方付いた到着パケッ トを認証することを本当に困難にしてしまうのです).</para> <para>*BSD において巨大パケットをサポートするには基本的な問題 がいくつかあります. それらをここで指摘したいのですが, このリス トを完成させるためにはもっと時間が必要です. その中のいくつかを 以下に挙げます:</para> <itemizedlist> <listitem> <para>4.4BSD では mbuf の pkthdr.len フィールドは "int" 型 ですから, 32 ビットアーキテクチャの CPU 上では 2 ギガバイ ト以上の巨大パケットを保持できません. 巨大パケットを正しく サポートするには, このフィールドは, 4 ギガバイト + IPv6 ヘッ ダ + 下位層ヘッダを保持できるように拡張されなければなりま せん. そのため, 少なくとも int64_t に拡張する必要がありま す (u_int32_t では十分では*ありません*).</para> </listitem> <listitem> <para>多くの場所で, パケット長を格納するための場所を誤って "int" としてしまっています. それらをもっと大きな整数型に変 換する必要があります. この作業には細心の注意が必要です. な ぜならパケット長の計算をするときにオーバフローを起こしてし まうかもしれないからです.</para> </listitem> <listitem> <para>たくさんの場所で, パケットのペイロード長を調べるのに, 間違って IPv6 ヘッダの ip6_plen フィールドをチェックしてい ます. そうではなくて, mbuf の pkthdr.len を調べなければな りません. ip6_input() が入力時に巨大ペイロードオプションの 健全性をチェックするようにすれば, その後は mbuf の pkthdr.len を安全に使うことができるでしょう.</para> </listitem> <listitem> <para>TCP のコードには, もちろん, たくさんの場所の注意深い更 新が必要でしょう.</para> </listitem> </itemizedlist> </sect3> <sect3> <title>ヘッダ処理中のループ防止</title> <para>IPv6 仕様はパケットに任意の数の拡張ヘッダがつくことを許 しています. もし IPv6 パケット処理コードを BSD の IPv4 コード が実装されているのと同様の方法で実装すると, 何重もの関数呼び出 しのせいでカーネルスタックがオーバーフローしてしまうでしょう. sys/netinet6 のコードは, カーネルスタックオーバフローを回避す るように注意深く設計されています. そのために, sys/netinet6 の コードでは独自のプロトコルスイッチ構造体を "struct ip6protosw" (<filename>netinet6/ip6protosw.h</filename> を参照してください) として定義しています. 互換性のために, IPv4 の部分 (sys/netinet) にはこの変更を行っていませんが, しかし, 小さな変 更が pr_input() のプロトタイプについて行われています. そのため "struct ipprotosw" も定義されています. 以上の理由により, 非常 に多くの IPsec ヘッダを持つ IPsec-over-IPv4 パケットを受け取っ たときにカーネルスタックがオーバーフローする可能性があります. IPsec-over-IPv6 は大丈夫です. (もちろん, それら全ての IPsec ヘッ ダが全て処理されるには, 一つ一つの IPsec ヘッダがそれぞれ IPsec の試験をパスする必要があります. そのため, 外部の攻撃者が そのような攻撃をすることは不可能です.)</para> </sect3> <sect3 id="icmpv6"> <title>ICMPv6</title> <para>RFC2463 が公開された後, IETF の ipngwg は, ネットワーク メディア上の ICMPv6 ストームを引き起こさないように, ICMPv6 向 け直し (redirect) に対して ICMPv6 エラーパケットを出さないよう に決定しました. これはすでにカーネル内で実装されています.</para> </sect3> <sect3> <title>アプリケーション</title> <para>ユーザランドのプログラミングのために, RFC2553, RFC2292, そして発行準備中の internet draft で定義されている, IPv6 ソケッ ト API をサポートしています.</para> <para>IPv6 上の TCP/UDP は利用可能であり, きわめて安定していま す. &man.telnet.1;, &man.ftp.1;, &man.rlogin.1;, &man.rsh.1;, &man.ssh.1, 等を試してみてください. これらのアプリケーションは プロトコル非依存となっています. つまり, DNS に従って自動的に IPv4 と IPv6 を選択します.</para> </sect3> <sect3> <title>カーネルの内部</title> <para>ip_forward() は ip_output() を呼びだしていますが, ip6_forward() は ip_output() を直接呼びだします. これは, ルー タは IPv6 のパケットを断片に分割してはいけないからです.</para> <para>ICMPv6 は最大 1280 まで, できる限り元のパケットをコピー して含んでおく必要があります. たとえば, UDP6/IP6 ポート不到達 ICMPv6 パケットは, 全ての拡張ヘッダと, *未変更の* UDP6 と IP6 ヘッダを含まなければなりません. そのため, TCP を除く全ての IP6 関数はオリジナルのパケットを保存しておくために, ネットワー クバイト順をホストバイト順に変換しません.</para> <para>tcp_input() と udp6_input(), icmp6_input() は, 拡張ヘッ ダがあるために, IP6 ヘッダのすぐ後ろにトランスポートヘッダがあ ることを仮定できません. そのため, in6_cksum() は IP6 ヘッダと トランスポートヘッダが連続しないようなパケットを処理できるよう に実装されています. チェックサム計算のための TCP/IP6 ヘッダ構 造体も, UDP6/IP6 ヘッダ構造体も存在しません.</para> <para>IP6 ヘッダと, 拡張ヘッダ, トランスポートヘッダを容易に処 理できるように, ネットワークドライバに対する新たな要求事項とし て, パケットを一つの内部 mbuf に納めるか, または, 一つ以上の外 部 mbuf に納める必要性を追加しました. 典型的な古いドライバは, 96 から 204 バイトのデータ用の二つの内部 mbuf を用意しますが, 今後はそのようなパケットデータは一つの外部 mbuf に記録されるこ とになります.</para> <para><command>netstat -s -p ip6</command> を実行すると, ドラ イバが上記の要求を満たしているかどうかが分かります. 以下の例で は "cce0" は要求を満たしていません (詳細は 2 章をご覧ください.)</para> <screen> Mbuf statistics: 317 one mbuf two or more mbuf:: lo0 = 8 cce0 = 10 3282 one ext mbuf 0 two or more ext mbuf </screen> <para>各入力関数は始めの段階で, IP6 とそのヘッダが連続した領域 にあるかどうかを調べるために IP6_EXTHDR_CHECK を呼び出します. IP6_EXTHDR_CHECK は mbuf が M_LOOP フラグを持っているときのみ m_pullup() を呼び出します. M_LOOP フラグは, パケットがループバッ クインタフェースからきたことを示します. 物理的なネットワークイ ンタフェースからきたパケットに対しては m_pullup() が呼ばれるこ とはありません.</para> <para>IP と IP6 両方の再構成 (reassemble) 機能は m_pullup() を 呼び出すことはありません.</para> </sect3> <sect3 id="ipv6-wildcard-socket"> <title>IPv4 射影アドレスと IPv6 ワイルドカードソケット</title> <para>RFC2553 では, IPv4 射影 (mapped) アドレス (3.7) と IPv6 ワイルドカードバインドソケットの特別な振る舞い (3.8) が記述さ れています. 仕様では以下の動作が許されています:</para> <itemizedlist> <listitem> <para>AF_INET6 ワイルドカードバインドソケットで IPv4 接続 を受け付ける.</para> </listitem> <listitem> <para>特別な形式のアドレス, たとえば ::ffff:10.1.1.1 を使うこ とで AF_INET6 ソケットから IPv4 のパケットを送出する. </para> </listitem> </itemizedlist> <para>しかし, 仕様自体が非常に込み入っており, またこの仕様では ソケット層がどう動作すべきかということについて何も規定していま せん. ここでは前者を "待ち受け側" と呼び, 後者を "開始側" と呼 ぶことにします.</para> <para>同一のポート上で, 両アドレスファミリのワイルドカードバイ ンドを実行することができます.</para> <para>以下の表に FreeBSD 4.x の動作を示します.</para> <screen> 待ち受け側 開始側 (AF_INET6 ワイルド (::ffff:10.1.1.1 への接続) カードソケットが IPv4 接続を受ける.) --- --- FreeBSD 4.x 設定可能 サポートされている デフォルト: 有効 </screen> <para>以下の章で, より詳細を述べると共に, どうやってこの動作を 設定するかを示します.</para> <para>待ち受け側に対するコメント:</para> <para>RFC2553 は, ワイルドカードバインドの問題についてあまりに も少ししか議論していません. 特に, ポート空間の問題, 失敗モード, そして AF_INET/INET6 ワイルドカードバインド間の関連について. この RFC に関しては, これに準処しているにもかかわらず異なった 動作をするいくつかの別々の解釈が成り立ちます. そのため, 移植可 能なアプリケーションを実装する際には, カーネルの動作について一 切の仮定をおくべきではありません. &man.getaddrinfo.3; を使う のがもっとも安全な方法です. ポート番号空間とワイルドカードバイ ンドの問題は, 1999 年 3 月半ばに ipv6imp メーリングリストにお いてつっこんだ議論がなされましたが, 最終的な合意は得られなかっ たようです (つまり, 実装次第ということです). メーリングリスト のアーカイブを調べてみてはいかがでしょうか.</para> <para>サーバアプリケーションで, IPv4 と IPv6 の両方の接続を受 けたいのなら, 別の方法が二つあります.</para> <para>一つは, AF_INET ソケットと AF_INET6 ソケットを使う方法で す (二つのソケットが必要です). &man.getaddrinfo.3; を ai_flags に AI_PASSIVE を設定して使い, そして, 返ってきた全て のアドレスに対して &man.socket.2; と&man.bind.2; を使います. 複数のソケットを開くことで, 適切なアドレスファミリを持つソケッ ト上で接続を受けることができます. IPv4 接続は AF_INET ソケット で受けられ, そして IPv6 接続は AF_INET6 ソケットで受けられるで しょう.</para> <para>もう一つの方法は, AF_INET6 のワイルドカードバインドソケッ トを使う方法です. ai_flags にAI_PASSIVE を, ai_family に AF_INET6 を設定し, そして一つ目の引数であるホスト名を NULL に して &man.getaddrinfo.3; を使ってください. そして返ってきたア ドレス (IPv6 の未指定アドレスであるはずです) に対して &man.socket.2; と &man.bind.2; を行います. この一つのソケット で, IPv4 と IPv6 のパケットを受けることができます.</para> <para>移植可能な方法で, AF_INET6 ワイルドカードバインドソケッ トで IPv6 のみをサポートするには, 待ち受けしている AF_INET6 ソ ケットに対して接続要求をしてきた相手アドレスを毎回チェックする ようにしてください. もしそのアドレスが IPv4 射影アドレスであっ たなら, その接続を遮断する必要があるかもしれません. この条件を 判定するのに, IN6_IS_ADDR_V4MAPPED() マクロを使うことができま す.</para> <para>この問題をもっと簡単に解決するために, システム依存の &man.setsockopt.2; オプション, IPV6_BINDV6ONLY があります. そ の使い方を以下に示します.</para> <screen> int on; setsockopt(s, IPPROTO_IPV6, IPV6_BINDV6ONLY, (char *)&on, sizeof (on)) < 0)); </screen> <para>この呼び出しが成功すれば, このソケットは IPv6 パケットの みを受信します.</para> <para>開始側についてのコメント:</para> <para>アプリケーション実装者へのアドバイス: 移植可能な (複数種 の IPv6 カーネル上で動作する) IPv6 アプリケーションを実装する には, 以下に述べる点が成功への鍵となると信じます:</para> <itemizedlist> <listitem> <para>*絶対に* AF_INET も AF_INET6 もハードコードしない. </para> </listitem> <listitem> <para>システムを通じて &man.getaddrinfo.3; と &man.getnameinfo.3; を使う. gethostby*() や, getaddrby*(), inet_*(), getipnodeby*() を絶対に使わない (既存アプリケー ションを簡単に IPv6 対応とするために, getipnodeby*() が便 利なこともあるでしょう. しかし, 可能であれば, コードを書き 直して &man.getaddrinfo.3; と &man.getnameinfo.3; を使うよ うに努力してみてください.)</para> </listitem> <listitem> <para>ある終点 (destination) に対して接続したいときは, &man.telnet.1; のように, &man.getaddrinfo.3; を使って, 返っ てきた全ての終点について試すようにしてください.</para> </listitem> <listitem> <para>いくつかの IPv6 プロトコルスタックは, バグありの &man.getaddrinfo.3; 付きで出荷されています. 最低限きちんと 動作するバージョンをあなたのアプリケーションと一緒に出荷し, それを最後の手段として使いましょう.</para> </listitem> </itemizedlist> <para>もし AF_INET6 ソケットから IPv4 と IPv6 の両方の接続を行 いたいのなら, &man.getipnodebyname.3; を使う必要があるでしょう. 既存のアプリケーションを工数最小で IPv6 対応に更新したいのなら ば, この方法がよいでしょう. しかし, これは一時的な解であること に注意してください. なぜなら, スコープ化された IPv6 アドレスを 全く扱うことができないという欠点があるので &man.getipnodebyname.3; 自身を推薦できないからです. IPv6 の名 前検索には, &man.getaddrinfo.3; が推奨される API です. ですか ら, 時間があるときには, アプリケーションを &man.getaddrinfo.3; を使うように書き換えるべきです.</para> <para>外部に出ていく接続を行うアプリケーションを書くときに, も し AF_INET と AF_INET6 とを完全に別々のアドレスファミリとして 取り扱うのならば, 話は非常に単純になります. {set,get}sockopt の問題は単純になり, DNS の問題も単純になるでしょう. IPv4 射影 アドレスに依存する手法は推薦できません.</para> <sect4> <title>tcp と inpcb の統合コード</title> <para>FreeBSD 4.x では, tcp に関しては IPv4 と IPv6 でコード を共有しています (sys/netinet/tcp* で). そして udp4/6 では別々 のコードです. 統合した inpcb 構造体を使っています.</para> <para>このプラットフォームは IPv4 射影アドレスをサポートする ように設定できます. カーネルの設定は以下のようにまとめられま す:</para> <itemizedlist> <listitem> <para>デフォルトでは, AF_INET6 ソケットはある条件下では IPv4 接続をハンドリングできます. そして IPv4 射影の IPv6 アドレス中に入っている IPv4 の終点へ向けて接続を開始する ことができます.</para> </listitem> <listitem> <para>以下のように sysctl を使ってシステム全体でそれを無 効にできます.</para> <para> <command>sysctl -w net.inet6.ip6.mapped_addr=0</command> </para> </listitem> </itemizedlist> <sect5> <title>待ち受け側</title> <para>各ソケットは特別な AF_INET6 ワイルドカードバインドを サポートするように設定できます (デフォルトで有効です). 以 下のように, ソケット毎に &man.setsockopt.2; を使ってこれを 無効にできます.</para> <screen> int on; setsockopt(s, IPPROTO_IPV6, IPV6_BINDV6ONLY, (char *)&on, sizeof (on)) < 0)); </screen> <para>ワイルドカード AF_INET6 ソケットは, 以下の条件が成り 立つときのみ, IPv4 接続をハンドリングできます:</para> <itemizedlist> <listitem> <para>その IPv4 接続にマッチする AF_INET ソケットが存 在しない</para> </listitem> <listitem> <para>その AF_INET6 ソケットは IPv4 通信を受け付けるよ うに設定されている, すなわち getsockopt(IPV6_BINDV6ONLY) が 0 を返す.</para> </listitem> </itemizedlist> <para>open/close の順番は問題となりません.</para> </sect5> <sect5> <title>開始側</title> <para>FreeBSD 4.x では, ノードが IPv4 射影アドレスをサポー トするように設定されているときには, IPv4 射影アドレス (::ffff:10.1.1.1) へ向けての接続がサポートされています. </para> </sect5> </sect4> </sect3> <sect3> <title>sockaddr_storage</title> <para>RFC2553 が最後の仕上げにかかっている頃, sockaddr_storage 構造体のメンバにどのように名前を付けるかという議論がありました. 一つの提案は, それがいじってはいけないものであるのだから, メン バ名の前に "__" を付ける ("__ss_len" のように), というものでし た. 他の提案は, それらのメンバを直接操作する必要があるのだから, なにも付けない ("ss_len" のように), というものでした. この件に 関する明確な合意は得られませんでした.</para> <para>その結果として, RFC2553 では sockaddr_storage 構造体は以 下のように定義されました:</para> <screen> struct sockaddr_storage { u_char __ss_len; /* address length */ u_char __ss_family; /* address family */ /* and bunch of padding */ }; </screen> <para>これに反して, XNET ドラフトでは以下のように定義されまし た:</para> <screen> struct sockaddr_storage { u_char ss_len; /* address length */ u_char ss_family; /* address family */ /* and bunch of padding */ }; </screen> <para>1999 年 12 月に, RFC2553bis では後者 (XNET) の定義を採用 することで合意がなされました.</para> <para>現在の実装は, RFC2553bis の議論の結果, XNET の定義に適合 するようになっています.</para> <para>複数の IPv6 実装を調べたならば, 両方の定義を見ることにな るでしょう. ユーザランドのプログラマとして, この件に対応するもっ とも移植性が高い方法は:</para> <orderedlist> <listitem> <para>GNU autoconf を使って, ss_family と/または ss_len がこのプラットフォームで利用可能であることを確かめる, </para> </listitem> <listitem> <para>-Dss_family=__ss_family として (ヘッダファイルも含め て) 全てを __ss_family に統合してしまう, または</para> </listitem> <listitem> <para>__ss_family には絶対に手を出さない. sockaddr * へキャ ストして, 以下の例のように sa_family を使う:</para> <screen> struct sockaddr_storage ss; family = ((struct sockaddr *)&ss)->sa_family </screen> </listitem> </orderedlist> </sect3> </sect2> <sect2> <title>ネットワークドライバ</title> <para>ここで, 以下の二項目を標準的なドライバでサポートすることが必須 となります:</para> <orderedlist> <listitem> <para>mbuf クラスタリングが要求されます. この安定版リリース においては, 全てのドライバが期待通りに動くように, 全てのオペ レーティングシステムにおいて MINCLSIZE を MHLEN+1 に変更しま した.</para> </listitem> <listitem> <para>マルチキャスト. もしあるインタフェースについて &man.ifmcstat.8; が一つもマルチキャストグループを示さないな ら, そのインタフェースは修正が必要です.</para> </listitem> </orderedlist> <para>もしあるドライバが上記要求をサポートしないなら, そのドラ イバでは IPv6 と/または IPsec 通信には使えません. もしあなた のカードで IPv6/IPsec の使用に関して何か問題を見つけたなら, 是 非それを <email>freebsd-bugs@FreeBSD.org</email> に報告してくだ さい.</para> <para>(注: かつて, 全ての PCMCIA ドライバに in6_ifattach() を呼 ぶことを要求したことがありました. 現在では, もはやこの要求は 取り下げています)</para> </sect2> <sect2> <title>トランスレータ</title> <para>ここでは IPv4/IPv6 トランスレータを四つに分類します:</para> <itemizedlist> <listitem> <para><emphasis>トランスレータ A</emphasis> --- 移行の初期 の段階で使われるもので, IPv6 島上の IPv6 ホストから IPv4 海上の IPv4 ホストへの接続を確立できるようにするものです. </para> </listitem> <listitem> <para><emphasis>トランスレータ B</emphasis> --- 移行の初期 の段階で使われるもので, IPv4 海上の IPv4 ホストから IPv6 島上の IPv6 ホストへの接続を確立できるようにするものです. </para> </listitem> <listitem> <para><emphasis>トランスレータ C</emphasis> --- 移行の後期 の段階で使われるもので, IPv4 島上の IPv4 ホストから IPv6 海上の IPv6 ホストへの接続を確立できるようにするものです. </para> </listitem> <listitem> <para><emphasis>トランスレータ D</emphasis> --- 移行の後期 の段階で使われるもので, IPv6 海上の IPv6 ホストから IPv4 島上の IPv4 ホストへの接続を確立できるようにするものです. </para> </listitem> </itemizedlist> <para>分類 A のための TCP 中継トランスレータはサポートされてい ます. これは "FAITH" と呼ばれます. 分類 A のための IP ヘッダト ランスレータも提供しています. (後者は FreeBSD 4.x ではまだ取り 込まれていません.)</para> <sect3> <title>FAITH TCP 中継トランスレータ</title> <para>FAITH システムはカーネルの助けを借りた &man.faithd.8; と 呼ばれる TCP 中継デーモンを利用します. FAITH は IPv6 アドレス プリフィックスを一つ予約し, そのプリフィックスに向かう TCP 接 続を IPv4 終点に向けて中継します.</para> <para>たとえば, 予約された IPv6 プリフィックスが 3ffe:0501:0200:ffff:: で, TCP 接続の IPv6 終点が 3ffe:0501:0200:ffff::163.221.202.12 であるならば, その接続は 163.221.202.12 という IPv4 終点に向けて中継されます.</para> <screen> 終点 IPv4 ノード (163.221.202.12) ^ | 163.221.202.12 へ向けた IPv4 tcp FAITH-中継 二重スタックノード ^ | 3ffe:0501:0200:ffff::163.221.202.12 へ向けた IPv6 TCP 始点 IPv6 ノード </screen> <para>FAITH-中継 二重スタックノードでは &man.faithd.8; を起動 しておく必要があります.</para> <para>より詳細な情報は, <filename>src/usr.sbin/faithd/README</filename> をご覧ください.</para> </sect3> </sect2> <sect2 id="ipsec-implementation"> <title>IPsec</title> <para>IPsec は主に三つの構成要素からなります.</para> <orderedlist> <listitem> <para>ポリシ管理</para> </listitem> <listitem> <para>鍵管理</para> </listitem> <listitem> <para>AH と ESP のハンドリング</para> </listitem> </orderedlist> <sect3> <title>ポリシ管理</title> <para>現在のカーネルには実験的なポリシ管理コードが実装されてい ます. セキュリティポリシを管理するには二つの方法があります. 一 つは, &man.setsockopt.2; を使ってソケット一つずつにポリシを設 定する方法です. この場合のポリシ設定は &man.ipsec.set.policy.3; で説明されています. もう一つの方法は, &man.setkey.8; によって PF_KEY インタフェース経由でカーネル内 のパケットフィルタをもとにしたポリシを設定する方法です.</para> <para>ポリシエントリはその番号によって並び替えられることはない ので, エントリを追加するときの順番がとても重要になります.</para> </sect3> <sect3> <title>鍵管理</title> <para>このキットで実装されている鍵管理コード (sys/netkey) は, 自家製の PFKEY v2 実装です. これは RFC2367 に準処しています. </para> <para>自家製の IKE デーモンである "racoon" がこのキットに含ま れています (kame/kame/racoon). 基本的に, racoon はデーモンとし て走らせる必要があり, それから鍵を要求するポリシをセットアップ します (たとえば, <command>ping -P 'out ipsec esp/transport//use'</command> というように). カーネルは鍵を交 換するために, 必要に応じて racoon デーモンにアクセスします.</para> </sect3> <sect3> <title>AH と ESP のハンドリング</title> <para>IPsec のモジュールは, 標準の IPv4/IPv6 処理中の "フック" として実装されています. パケットを送信するときに, ip{,6}_output() は 一致する SPD (Security Policy Database: セ キュリティポリシデータベース) があるかどうかをチェックして ESP/AH 処理が必要かどうかを判断します. もし ESP/AH が必要なら, {esp,ah}{4,6}_output() が呼ばれ, mbuf が適切に更新されます. パ ケットを受信したときは, プロトコル番号に従って, すなわち (*inetsw[proto])() という形で {esp,ah}4_input() が呼ばれます. {esp,ah}4_input() はそのパケットの認証情報を解読 / 試験し, そ して数珠繋ぎのヘッダと ESP/AH のためのパディングを取り除きます. パケット受信時に ESP/AH ヘッダを取り除いてしまっても安全です. なぜなら受信したパケットを "あるがまま" の形で使うことは絶対に ないからです.</para> <para>ESP/AH を使うことによって, TCP4/6 における実効データセグ メント長は ESP/AH によって挿入される数珠繋ぎのヘッダの増加分だ け影響を受けます. わたしたちのコードはこの場合を考慮に入れています. </para> <para>基本的な暗号機能は "sys/crypto" ディレクトリの中にありま す. ESP/AH 変換は wrapper 関数と一緒に {esp,ah}_core.c に記述され ています. もしアルゴリズムを追加したかったら, wrapper 関数を {esp,ah}_core.c に追加し, そして追加する暗号アルゴリズムを実装 したコードを src/crypto に追加してください.</para> <para>このリリースでは, トンネルモードは以下の制限と共に部分的 にサポートされています:</para> <itemizedlist> <listitem> <para>IPsec トンネルは GIF による包括的トンネリングインタ フェースと結合されていません. ip_output() と tunnelifp->if_output() の間で無限ループを構成してしまうか もしれないので, 非常に注意深く作業する必要があります. 統合 した方がよいか, よくないか, 意見はいろいろ出ています.</para> </listitem> <listitem> <para>MTU と 分割禁止ビット (IPv4) についての考慮はさらな るチェックを必要としています, が, 基本的にはうまく動いてい ます.</para> </listitem> <listitem> <para>AH トンネルのための認証モデルは再考する必要があるで しょう. 今後, ポリシ管理エンジンを改良する必要が出てくると 思われます.</para> </listitem> </itemizedlist> </sect3> <sect3> <title>RFC と ID への準処</title> <para>カーネル内の IPsec コードは以下の標準に準処しています (もしくは, 準処しようと努力しています):</para> <para><filename>rfc182[5-9].txt</filename> で文書化されている "old IPsec" 仕様</para> <para><filename>rfc240[1-6].txt</filename> と, <filename>rfc241[01].txt</filename>, <filename>rfc2451.txt</filename>, <filename>draft-mcdonald-simple-ipsec-api-01.txt</filename> (このドラフトは期限切れですが, <ulink url="ftp://ftp.kame.net/pub/internet-drafts/"> ftp://ftp.kame.net/pub/internet-drafts/</ulink> から入手できま す) で文書化されている "new IPsec" 仕様. (注: IKE 仕様, <filename>rfc241[7-9].txt</filename> はユーザラ ンドの "racoon" IKE デーモンとして実装されています)</para> <para>現在サポートしているアルゴリズムは:</para> <itemizedlist> <listitem> <para>old IPsec AH</para> <itemizedlist> <listitem> <para>空の暗号チェックサム (文書化されていません. デ バッグのためだけのものです)</para> </listitem> <listitem> <para>128 ビットの暗号チェックサムと鍵付き MD5 (<filename>rfc1828.txt</filename>)</para> </listitem> <listitem> <para>128 ビットの暗号チェックサムと鍵付き SHA1 (文書化されていません)</para> </listitem> <listitem> <para>128 ビットの暗号チェックサムと HMAC MD5 (<filename>rfc2085.txt</filename>)</para> </listitem> <listitem> <para>128 ビットの暗号チェックサムと HMAC SHA1 (文書化されていません)</para> </listitem> </itemizedlist> </listitem> <listitem> <para>old IPsec ESP</para> <itemizedlist> <listitem> <para>空の暗号化 (文書化されていません, <filename>rfc2410.txt</filename> と似たものです)</para> </listitem> <listitem> <para>DES-CBC モード (<filename>rfc1829.txt</filename>)</para> </listitem> </itemizedlist> </listitem> <listitem> <para>new IPsec AH</para> <itemizedlist> <listitem> <para>空の暗号チェックサム (文書化されていません. デ バッグのためだけのものです)</para> </listitem> <listitem> <para>96 ビットの暗号チェックサムと鍵付き MD5 (文書化されていません)</para> </listitem> <listitem> <para>96 ビットの暗号チェックサムと鍵付き SHA1 (文書化されていません)</para> </listitem> <listitem> <para>96 ビットの暗号チェックサムと HMAC MD5 (<filename>rfc2403.txt</filename>)</para> </listitem> <listitem> <para>96 ビットの暗号チェックサムと HMAC SHA1 (<filename>rfc2404.txt</filename>)</para> </listitem> </itemizedlist> </listitem> <listitem> <para>new IPsec ESP</para> <itemizedlist> <listitem> <para>空の暗号化 (<filename>rfc2410.txt</filename>)</para> </listitem> <listitem> <para>DES-CBC with derived IV (<filename>draft-ietf-ipsec-ciph-des-derived-01.txt</filename>, draft expired)</para> </listitem> <listitem> <para>DES-CBC with explicit IV (<filename>rfc2405.txt</filename>)</para> </listitem> <listitem> <para>3DES-CBC with explicit IV (<filename>rfc2451.txt</filename>)</para> </listitem> <listitem> <para>BLOWFISH CBC (<filename>rfc2451.txt</filename>)</para> </listitem> <listitem> <para>CAST128 CBC (<filename>rfc2451.txt</filename>)</para> </listitem> <listitem> <para>RC5 CBC (<filename>rfc2451.txt</filename>)</para> </listitem> <listitem> <para>上記のそれぞれは, 以下と結合可能:</para> <itemizedlist> <listitem> <para>HMAC-MD5(96 ビット) による ESP 認証</para> </listitem> <listitem> <para>HMAC-SHA1(96 ビット) による ESP 認証</para> </listitem> </itemizedlist> </listitem> </itemizedlist> </listitem> </itemizedlist> <para>以下のアルゴリズムはサポートされて *いません*:</para> <itemizedlist> <listitem> <para>old IPsec AH</para> <itemizedlist> <listitem> <para>128 ビットの暗号チェックサムと HMAC MD5 + 64 ビット の再送攻撃防止 (<filename>rfc2085.txt</filename>)</para> </listitem> <listitem> <para>160 ビット暗号チェックサムと鍵付き SHA1 + 32 ビッ トのパディング (<filename>rfc1852.txt</filename>)</para> </listitem> </itemizedlist> </listitem> </itemizedlist> <para>IPsec (カーネル内) と IKE (ユーザランドの "racoon") は, 何回もの相互接続性試験イベントで試験されていて, そこでの結果とし て, 多くの他の実装とうまく相互接続可能であることがわかっていま す. また, 現在の IPsec 実装は, RFC に記述された IPsec 暗号ア ルゴリズムを非常に広くカバーしています (知的所有権の問題がない アルゴリズムに限ってカバーしています).</para> </sect3> <sect3 id="ipsec-ecn"> <title>IPsec トンネルにおける ECN の考察</title> <para>ECN と親和性のある IPsec トンネルは, <filename>draft-ipsec-ecn-00.txt</filename> で述べられている方 法を用いてサポートされています.</para> <para>通常の IPsec トンネルは RFC2401 で記述されています. カプ セル化されるときに, 内側の IP ヘッダの IPv4 TOS フィールド (ま たは, IPv6 トラフィッククラスフィールド) が外側の IP ヘッダに コピーされます. カプセル解放の時には外側の IP ヘッダは単に削ら れるだけです. 外側の IP ヘッダの TOS / トラフィッククラスフィー ルド中の ECN ビットが失われてしまうため, このカプセル解放規則 は ECN と互換性がありません.</para> <para>IPsec トンネルを ECN と親和性があるようにするために, カ プセル化手順とカプセル解放手順を変更する必要がありました. これ については, <ulink url="http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt"> http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt</ulink> の 3 章で記述されています.</para> <para>net.inet.ipsec.ecn (または net.inet6.ipsec6.ecn) に設定 する値によって, IPsec トンネルの実装は三通りの動作を行います:</para> <itemizedlist> <listitem> <para>RFC2401: ECN を考慮しない (sysctl の値が -1 の時)</para> </listitem> <listitem> <para>ECN 禁止 (sysctl の値が 0 の時)</para> </listitem> <listitem> <para>ECN 許可 (sysctl の値が 1 の時)</para> </listitem> </itemizedlist> <para>この振る舞いはノード毎に設定可能であり SA 毎ではないこ とに注意してください (draft-ipsec-ecn-00 では SA 毎の設定を要 求していますが, それは大変すぎます).</para> <para>ここで述べた動作をまとめると以下のようになります (より詳 しくはソースコードをご覧ください):</para> <screen> カプセル化 カプセル解放 --- --- RFC2401 内側から外側へ 外側の TOS ビットを落とす 全ての TOS ビットをコピー. (内側の TOS ビットをそのまま使う) ECN 禁止 内側から外側へ ECN ビット以外 外側の TOS ビットを落とす (0xfc でマスク) の TOS ビット (内側の TOS ビットをそのまま使う) をコピー. ECN ビットを 0 に. ECN 許可 内側から外側へ ECN CE 以外 内側の TOS ビットを変更して使う. (0xfe でマスク) の TOS ビット もし外側の ECN CE ビットが 1 な をコピー. ECN CE ビットを 0 に. ら, 内側の ECN CE ビットを有効に. </screen> <para>設定方法に関する一般的な戦略は以下のようになります:</para> <itemizedlist> <listitem> <para>もし IPsec トンネルの両端が ECN に親和性がある動作を するのなら, その両方を "ECN 許可" と設定した方がよいでしょ う (sysctl の値を 1 に設定).</para> </listitem> <listitem> <para>もし反対側の端が TOS ビットに関して非常に厳密に動作 するなら, "RFC2401" を使ってください (sysctl の値を -1 に 設定).</para> </listitem> <listitem> <para>その他の場合, "ECN 禁止" (sysctl の値を 0 に設定).</para> </listitem> </itemizedlist> <para>デフォルトの動作は, "ECN 禁止" (sysctl の値が 0) です.</para> <para>より詳しくは, 以下を参照してください:</para> <para><ulink url="http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt"> http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt</ulink>, RFC2481 (Explicit Congestion Notification: 明示的輻輳通知), src/sys/netinet6/{ah,esp}_input.c</para> <para>(詳しい分析をしてくれた, 長 健二朗 氏 <email>kjc@csl.sony.co.jp</email> に感謝します)</para> </sect3> <sect3> <title>相互接続性</title> <para>以下に, これまでに KAME と IPsec/IKE の相互接続性試験を 行ったプラットフォーム (の一部) を挙げます. これらのプラット フォームも KAME も, 試験以降に何らかの実装の変更を行っているで しょうから, 以下のリストは参考にとどめてください.</para> <para>Altiga, Ashley-laurent (vpcom.com), Data Fellows (F-Secure), Ericsson ACC, FreeS/WAN, HITACHI, IBM AIX, IIJ, Intel, Microsoft WinNT, NIST (linux IPsec + plutoplus), Netscreen, OpenBSD, RedCreek, Routerware, SSH, Secure Computing, Soliton, Toshiba, VPNet, Yamaha RT100i</para> </sect3> </sect2> </sect1> </chapter> <!-- Local Variables: mode: sgml sgml-declaration: "../chapter.decl" sgml-indent-data: t sgml-omittag: nil sgml-always-quote-attributes: t sgml-parent-document: ("../book.sgml" "part" "chapter") End: -->