The Linux Kernel David A Rusling david.rusling@arm.com v0.8-3 January 25, 1999 JF Project v0.8-3 December 2000 *フレーム表示* 本書は、Linux カーネルの仕組みを知りたい Linux 愛好家の ためのものです。これは内部構造のマニュアルではありません。むしろ Linux で使用されている原理やメカニズムを解説したものであり、Linux の動作原理 とはどういうもので、なぜそれが採用されているのかを説明するものです。 Linux という対象は常に変化しています。本書がベースにしているのは現在の 安定版である 2.0.33 のカーネルソースですが、これは個人や法人の大部分で 使用されているのがこのバージョンだからです。また、本書は自由に配布して もらってかまわないので、一定の条件のもとにではありますが、複製や再配布 が可能です。詳細は、``著作権と配布条件''を参照して下さい。 (訳注: ご利 用に際して、``日本語訳について''をお読みください。) ______________________________________________________________________ 目次 1. はじめに 1.1 想定する読者 1.2 本書の構成 1.3 この文書での表記規則 1.4 商標について 1.5 著者について 1.6 謝辞 2. ハードウェアの基本 2.1 CPU 2.2 メモリ 2.3 バス 2.4 コントローラと周辺機器 2.5 アドレス空間 2.6 タイマー 3. ソフトウェアの基本 3.1 コンピュータ言語 3.1.1 アセンブリ言語 3.1.2 C 言語とコンパイラ 3.1.3 リンカ 3.2 オペレーティングシステムとは何か? 3.2.1 メモリ管理 3.2.2 プロセス 3.2.3 デバイスドライバ 3.2.4 ファイルシステム 3.3 カーネルのデータ構造 3.3.1 連結リスト 3.3.2 ハッシュテーブル 3.3.3 抽象インターフェイス 4. メモリ管理 4.1 仮想メモリの抽象モデル 4.1.1 デマンドページング 4.1.2 スワッピング 4.1.3 共有仮想メモリ 4.1.4 物理アドレスモードと仮想アドレスモード 4.1.5 アクセス制御 4.2 キャッシュ 4.3 Linux のページテーブル 4.4 ページの割り当てとページの解放 4.4.1 ページの割り当て 4.4.2 ページの解放 4.5 メモリマッピング 4.6 デマンドページング 4.7 Linux のページキャッシュ 4.8 スワップアウトとページの破棄 4.8.1 ページキャッシュとバッファキャッシュのサイズの縮小 4.8.2 System V 共有メモリページのスワップアウト 4.8.3 スワップアウトされたか破棄されたページ 4.9 スワップキャッシュ 4.10 スワップのページイン 5. プロセス 5.1 Linux のプロセス 5.2 識別子(identifiers) 5.3 スケジューリング 5.3.1 マルチプロセッサシステムでのスケジューリング 5.4 ファイル 5.5 仮想メモリ 5.6 プロセスの生成 5.7 時間とタイマー 5.8 プログラムの実行 5.8.1 ELF 5.8.1.1 ELF 共有ライブラリ 5.8.2 スクリプトファイル 6. プロセス間通信の仕組み 6.1 シグナル 6.2 パイプ 6.3 ソケット 6.3.1 System V IPC のメカニズム 6.3.2 メッセージキュー 6.3.3 セマフォ 6.3.4 共有メモリ 7. PCI 7.1 PCI アドレス空間 7.2 PCI コンフィグレーションヘッダ 7.3 PCI I/O と PCI メモリアドレス 7.4 PCI-ISA ブリッジ 7.5 PCI-PCI ブリッジ 7.5.1 PCI-PCI ブリッジ: PCI I/O と PCI メモリウィンドウ 7.5.2 PCI-PCI ブリッジ: PCI コンフィグレーションサイクルと PCI バスの番号付け 7.6 Linux の PCI 初期化方法 7.6.1 Linux カーネルの PCI データ構造 7.6.2 PCI デバイスドライバ 7.6.2.1 PCI-PCI ブリッジの設定 - PCI バス番号の割り当て 7.6.3 PCI BIOS 関数 7.6.4 PCI フィックスアップ(fixup) 7.6.4.1 デバイスが必要とする PCI I/O と PCI メモリ空間の容量を調べる 7.6.4.2 PCI-PCI ブリッジとデバイスに対して PCI I/O と PCI メモリを割り当てる 8. 割り込みと割り込み処理 8.1 プログラム可能な割り込みコントローラ 8.2 割り込み処理のデータ構造の初期化 8.3 割り込み処理 9. デバイスドライバ 9.1 ポーリングと割り込み 9.2 ダイレクトメモリアクセス(Direct Memory Access, DMA) 9.3 メモリ 9.4 デバイスドライバとカーネルとのインターフェイス 9.4.1 キャラクタデバイス 9.4.2 ブロックデバイス 9.5 ハードディスク 9.5.1 IDE ディスク 9.5.2 IDE サブシステムの初期化 9.5.3 SCSI ディスク 9.5.4 SCSI サブシステムの初期化 9.5.5 ブロックデバイスのリクエストを伝達する 9.6 ネットワークデバイス 9.6.1 ネットワークデバイスの初期化 10. ファイルシステム 10.1 Second Extended File System (EXT2) 10.1.1 EXT2 Inode 10.1.2 EXT2 スーパーブロック(Superblock) 10.1.3 EXT2 グループディスクリプタ 10.1.4 EXT2 ディレクトリ 10.1.5 EXT2 ファイルシステム上のファイルの検索 10.1.6 EXT2 ファイルシステム上のファイルサイズの変更 10.2 仮想ファイルシステム (Virtual File System, VFS) 10.2.1 VFS スーパーブロック 10.2.2 VFS inode 10.2.3 ファイルシステムの登録 10.2.4 ファイルシステムのマウント 10.2.5 仮想ファイルシステム内でのファイルの検索 10.2.6 仮想ファイルシステム上でのファイルの作成 10.2.7 ファイルシステムのマウント解除 10.2.8 VFS inode キャッシュ 10.2.9 ディレクトリキャッシュ 10.3 バッファキャッシュ 10.3.1 bdflush カーネルデーモン 10.3.2 update プロセス 10.4 /proc ファイルシステム 10.5 デバイススペシャルファイル 11. ネットワーク 11.1 TCP/IP ネットワーキングの概要 11.2 Linux の TCP/IP ネットワーク層 11.3 BSD ソケットインターフェイス 11.4 INET ソケット層 11.4.1 BSD ソケットの作成 11.4.2 アドレスを INET BSD ソケットに bind する 11.4.3 INET BSD ソケット上でコネクションを確立する 11.4.4 INET BSD ソケット上での待機(listen) 11.4.5 コネクション要求を受け入れる(accept) 11.5 IP 層 11.5.1 ソケットバッファ(socket buffer) 11.5.2 IP パケットの受信 11.5.3 IP パケットの送信 11.5.4 データの細分化(fragmentation) 11.6 Address Resolution Protocol (ARP) 11.7 IP ルーティング 11.7.1 ルートキャッシュ 11.7.2 フォワーディング情報データベース(Forwarding Information Database) 12. カーネルメカニズム 12.1 ボトムハーフハンドラ 12.2 タスクキュー(task queue) 12.3 タイマー(timer) 12.4 待ち行列(wait queue) 12.5 バズロック (Buzz Locks) 12.6 セマフォ(semaphore) 13. モジュール 13.1 モジュールのローディング 13.2 モジュールのアンロード 14. プロセッサ 14.1 x86 14.2 ARM 14.3 Alpha AXP プロセッサ 15. Linux カーネルソース 15.1 Linux カーネルソースの入手場所 15.2 カーネルソースの構造 15.3 どこから見るべきか 15.3.1 システムの起動と初期化 15.3.2 メモリ管理 15.3.3 カーネル 15.3.4 PCI 15.3.5 プロセス間通信 15.3.6 割り込み処理 15.3.7 デバイスドライバ 15.3.8 ファイルシステム 15.3.9 ネットワーク 15.3.10 モジュール 16. Linux データ構造 16.1 arp_table(訳注) 16.2 blk_dev_struct 16.3 buffer_head 16.4 device 16.5 device_struct 16.6 dma_chan(訳注) 16.7 ext2_inode(訳注) 16.8 ext2_inode_info(訳注) 16.9 fib_info(訳注) 16.10 fib_node(訳注) 16.11 fib_zone(訳注) 16.12 file 16.13 files_struct 16.14 file_system_type(訳注) 16.15 free_area(訳注) 16.16 fs_struct 16.17 gendisk 16.18 hh_cache(訳注) 16.19 ide_drive_t(訳注) 16.20 ide_hwif_t(訳注) 16.21 inode 16.22 ipc_perm 16.23 ipfrag(訳注) 16.24 ipq(訳注) 16.25 irqaction 16.26 irq_action 16.27 kdev_t(訳注) 16.28 linux_binfmt 16.29 mem_map_t 16.30 mm_struct 16.31 module(訳注) 16.32 msg(訳注) 16.33 msqid_ds(訳注) 16.34 packet_type(訳注) 16.35 pci_bus 16.36 pci_dev 16.37 pipe_inode_info(訳注) 16.38 proto_ops(訳注) 16.39 request 16.40 rtable 16.41 Scsi_Cmnd(訳注) 16.42 Scsi_Device(訳注) 16.43 Scsi_Device_Template 16.44 Scsi_Disk(訳注) 16.45 Scsi_Host(訳注) 16.46 Scsi_Host_Template(訳注) 16.47 sem(訳注) 16.48 semaphore 16.49 semid_ds(訳注) 16.50 sem_queue(訳注) 16.51 sem_undo(訳注) 16.52 shmid_ds(訳注) 16.53 sigaction(訳注) 16.54 signal_struct(訳注) 16.55 sk_buff 16.56 sk_buff_head(訳注) 16.57 sock 16.58 sockaddr(訳注) 16.59 socket 16.60 super_block(訳注) 16.61 swap_control(訳注) 16.62 swap_info_struct(訳注) 16.63 task_struct 16.64 timer_list 16.65 timer_struct(訳注) 16.66 tq_struct 16.67 vfsmount(訳注) 16.68 vm_area_struct 16.69 vm_operations_struct(訳注) 16.70 wait_queue(訳注) 17. 有益なウェブと FTP サイト 18. LDP 宣言(LDP Manifesto) 18.1 概要 18.2 現在のプロジェクトとその参加方法 18.3 LDP ウェブサイト 18.4 文書作成の方法 18.5 ライセンス上の必要事項 18.6 ライセンスの鋳型 18.7 LDP 文書の出版 19. The GNU General Public License 20. 用語集 21. 著作権と配布条件 22. Bibliography 23. 日本語訳について 23.1 参考文献 23.2 謝辞 ______________________________________________________________________ 1. はじめに Linux は、インターネットの産物である。ひとりの学生の趣味のプロジェクト から出発し、フリーに入手できる他のどんなオペレーティングシステムよりも ポピュラーなものに成長した。しかし、多くのひとにとって Linux は謎であ る。フリーなものに価値があるなどということがあり得るのか? 一握りの巨 大ソフトウェア企業に支配された世界において、いわゆる「ハッカー」連中の 書いたもので太刀打ちできるのか? 世界中の様々な国の多様な人間が関わっ て作られたソフトウェアに、安定性と効率を期待できるだろうか?だが、それ は安定と効率そのものであり、現に市販製品と競合している。多くの大学や有 名な研究機関において、Linux は日ごとの計算処理の要求を満たすために使用 されている。個々人が自宅の PC 上で走らせているし、おそらく大部分の会社 でも、かならずしも意識されているわけではないかもしれないが、使用されて いるだろう。 Linux の使用目的は、ウェブの閲覧、ウェブサイトのホスティ ング、そのウェブコンテンツの作成、電子メールの送信、そしてコンピュータ の常として、ゲームで遊ぶことなど様々だろう。だが、Linux は、断じておも ちゃではない。本当のプロフェッショナルによって書かれ、全世界に熱心なユ ーザを持つ、完全に成熟したオペレーティングシステムである。 Linux のルーツは、Unix の起源にまで遡る。1969 年、ベル研究所の研究グル ープの一員であった Ken Thompson は、当時誰も使っていなかった PDP-7 上 でマルチユーザ、マルチタスクのオペレーティングシステムの実験を始めた。 まもなく Dennis Richie がそれに加わり、ふたりは、研究グループの他のメ ンバーからの協力も得て Unix の初期バージョンを生み出した。Richie は、 それ以前の MULTICS プロジェクトから大きな影響を受けていたので、その Unix という名称自体も MULTICS という名前をもじったものである。初期のバ ージョンはアセンブリコードで記述されていたが、三度目のリリースでは C 言語で書き換えらている。C は、オペレーティングシステムを記述するための プログラミング言語として Richie が特別に作成したものであった。この書き 直しによって、 Unix は、よりパワフルな PDP-11/45 や 11/70 といった当時 DIGITAL(DEC) で生産されたばかりのコンピュータに移植することが可能に なった。彼ら自身も述べていることだが、それは歴史的事件にまで発展する。 Unix は研究所内に留まらず、コンピュータのメインストリームとなり、間も なくメジャーなコンピュータメーカの大部分が自社バージョンを製造するよう になった。 Linux は、必要の産物であった。Linux の作者であり監修者でもある Linus Torvalds にとって、当時入手可能な唯一のソフトウェアは Minix だっ た。Minix とは、 (当時)教育目的で広く使用されていた、シンプルで Unix ライクなオペレーティングシステムのことである。 Linus は Minix の機能に 魅力を感じなかったため、自分自身のソフトウェアを書くことで解決しようと した。彼は、学生としての日々の生活で使い慣れたオペレーティングシステム である Unix をモデルに選んだ。そして、Intel 386 ベースの PC を使って、 コードを書き始める。進歩はめざましく、それに勢いを得た Linus は、当時 動き始めた世界規模のネットワーク、主に研究者のコミュニティー内で使用さ れていたネットワーク経由で、その成果を他の学生たちに提供した。彼らは、 そのソフトウェアを見て、自分たちもそのプロジェクトに関わり始めた。彼ら が提供した新しいソフトウェアの多くは、提供者自身が直面した問題への解決 策であった。そしてまもなく、Linux はひとつのオペレーティングシステムと なるに到る。注意すべきなのは、Linux は Unix のコードを含んでいないとい うことである。Linux は出版された POSIX 規格をもとに書き直されたものだ からだ。また、Linux は、GNU(GNU's Not Unix の略称)の多くのソフトウェア によって構築されており、それを数多く実装している。GNU ソフトウェアは、 マサチューセッツのボストンにある Free Software Fundation が作成したも のである。 大部分の人にとって、Linux は単なる道具である。良く出来た CD-ROM ベース のディストリビューションは多数あるので、そのひとつをインストールするだ けで、たいていすぐに利用できる。多くの Linux ユーザは、それでアプリケ ーションを書いたり、他人が書いたアプリケーションを実行する。たくさんの ユーザが、HOWTO ``(脚注1)'' を熱心に読んで、システムの一部を正しく設定 できたときにはその成功を喜び、うまくいかないときは失敗でがっかりする。 小数の人は、大胆にもデバイスドライバを書いたり、カーネルパッチを作った りしてカーネル作成者であり管理者でもある Linus に送ったりする。 Linus は、どこの誰からでもカーネルソースへの追加や変更を受け付けている。この ような方法は、混乱を生み出すだけのように思えるかもしれない。しか し、Linus は厳格な品質管理を行っており、カーネルへの新しいコードの追加 は彼自身が行っている。とはいえ、どんなときも、Linux カーネルソースに貢 献する人々は小数だが存在する。 Linux ユーザの大半は、オペレーティングシステムの仕組みや内部での相互作 用を調べようとはしない。これは残念なことだ。Linux を調べることは、オペ レーティングシステムの仕組みについて多くを学ぶ非常によい方法だからであ る。Linux はよく書かれたオペレーティングシステムであるだけでなく、その すべてのソースコードを自由に入手して調べることが可能になっている。作者 はソフトウェアに対する著作権を保持しているが、Free Software Foundation の ``GNU General Public License'' のもとでソースの自由な配布を許してい るからである。しかし、初めてソースを眺めたときには混乱するかもしれな い。kernel, mm, net などのディレクトリがあるのはわかるが、それに何が含 まれるのか、どのコードがどういう役割を果たしているのか見当がつかない。 必要なのは、Linux 全体の構造と目的を鳥瞰して、理解することである。つま り、これが本書の目的である。すなわち、Linux というオペレーティングシス テムの仕組みについてはっきりとした理解を促進することである。ファイルを ある場所から別の場所にコピーしたり、電子メールを読んだりするときに、何 が起こっているのかを思い描けるような心象を提供することである。オペレー ティングシステムの実際の動きについて初めて理解できたときに感じた興奮を わたしは今も鮮明に覚えている。わたしが本書の読者に伝えたいのは、そのと きの興奮である。 わたしが Linux に関わり始めたのは、1994 年の終わり頃、当時 Alpha AXP ベースのシステムへの Linux の移植作業をしていた Jim Paradis を訪ねたと きだった。その頃、わたしは 1984 年以来 Digital Equipment Co.Limited で ネットワークとコミュニケーションに関する仕事をしていて、1992 年には、 新しく設立された電子半導体部門で働き始めていた。この部門の目的は、商用 チップ製造市場に完全に参入して、 Alpha AXP シリーズのマイクロプロセッ サを中心に、Alpha AXP システムボードを他社にも販売するということであっ た。Linux の話を初めて聞いたとき、わたしはすぐに面白いことができそうだ と思った。Jim の熱心さが伝染して、わたしも移植の手伝いをしはじめた。そ の作業をするうちに、わたしはそのオペレーティングシステムだけでなく、そ れを作り出したエンジニアのコミュニティーをもより深く敬愛するようになっ ていった。 しかし、Alpha AXP は、Linux が走る多くのハードウェアプラットフォームの ひとつでしかない。大部分の Linux カーネルは、Intel プロセッサベースの システムで動いているが、Intel 以外の Linux システムの数もだんだんと増 えていて、入手もしやすくなってきている。それらの中には、Alpha AXP, ARM, MIPS, Sparc, それに PowerPC などがある。それらのひとつを選んでこ の文書を書くこともできたのだが、わたしのバックグラウンドと技術的経験 は、Alpha AXP 上の Linux のものであり、それより少し程度は低いが ARM 上 のものである。本書で、重要な点について解説するときに Intel 以外のハー ドウェアを例に挙げるのは、そのためである。ただ、Linux カーネルのソース の約 95% はどのようなハードウェアプラットフォームにも当てはまるものと なっている。同様に、本書の約 95 % も、マシンに依存しない部分の Linux カーネルの解説となっている。 1.1. 想定する読者 この文書は、読者の知識や経験を前提としていない。この主題に興味があるな ら、必要なものは自分から学ぶようになると信じているからである。ただ、コ ンピュータ、できれば PC について知っていると、本書から実際に得るものが あるだろうし、C 言語の知識もあると役立つだろう。 1.2. 本書の構成 本書では、Linux 内部の作業マニュアルとして使用されることを意図していな い。むしろ、オペレーティングシステム一般の入門書であり、そのなかで特に Linux を取り上げている。各章は、わたしのルールである「一般から個別へ」 という原則に従って構成されている。つまり、実際にカーネルの内部で動いて いるコードの詳細についていきなり説明を始めるのではなく、まずその章で扱 うカーネルサブシステムの概要を確認することから始めている。 カーネルのアルゴリズムについての叙述は意図的に省いている。 routine_X() が routine_Y() を呼び出し、それが foo データ構造内の bar フィールドを インクリメントするといった動作方法は述べていない。そうしたことは、コー ドを読めばわかるからだ。コードの一部を理解したり、それを誰かに説明した りする必要があるとき、わたしはよくホワイトボードにデータ構造を描くこと から始める。したがって、わたしは、関係のあるカーネルのデータ構造とそれ らの相互関係についてはかなり詳細に述べている。 各章は完全に独立している。これは、各章で述べる Linux のカーネルサブシ ステムが独立しているのと同じである。しかし、ときには関連する事柄もあ る。たとえば、仮想メモリの仕組みを理解しないと、プロセスを解説できない といったことである。 ``「ハードウェアの基本」''の章は、現代の PC に関する簡単な入門となって いる。オペレーティングシステムは、ハードウェアと密接に関連して動作して おり、ハードウェアはオペレーティングシステムの基礎として働く。オペレー ティングシステムは、ハードウェアでしか提供できないサービスを必要とす る。Linux オペレーティングシステムを完全に理解するには、その基礎となっ ているハードウェアの基本を理解する必要がある。 ``「ソフトウェアの基本」''の章では、ソフトウェアの基本的な原理を説明 し、アセンブリと C 言語を概観する。Linux 等のオペレーティングシステム を構築するために使用されるツールについて述べ、オペレーティングシステム の目的と機能について概説する。 ``「メモリ管理」''の章では、システム上の物理メモリと仮想メモリを Linux がどう扱っているのかについて述べる。 ``「プロセス」''の章では、プロセスとはなにか、 Linux はシステム上でプ ロセスをどのように生成し、管理し、削除するのかを説明する。 プロセスは、互いに協調して働くため、他のプロセスやカーネルと通信してい る。 Linux は、いくつかのプロセス間通信(IPC)のメカニズムをサポートして いる。シグナルとパイプはそのうちのふたつだが、Linux は System V IPC も サポートしている。この名称は、初めてその仕組みが登場したのが Unix の当 該リリースの時だったからである。これらプロセス間通信のメカニズム は、``「プロセス間通信の仕組み」''の章で説明する。 PCI(Peripheral Component Interconnect) 規格は、PC 上の低価格・高性能の データバスとして今日ではその立場を確立している。``「 PCI 」''の章で は、Linux が、システム上で、PCI バスとそのデバイスを初期化して使用する 方法を説明する。 ``「割り込みと割り込み処理」''の章では、Linux の割り込み処理の方法につ いて説明する。カーネルは割り込み処理についての汎用メカニズムとインター フェイスを備えているが、割り込み処理の詳細の中には、ハードウェアやアー キテクチャに依存するものもある。 Linux の長所のひとつは、現代の PC 向けの多くのハードウェアデバイスをサ ポートしていることだ。``「デバイスドライバ」''の章では、システム上の物 理デバイスを Linux がどのように管理しているのか説明する。 ``「ファイルシステム」''の章では、Linux 上でサポートされているファイル システムをカーネルがどのように維持管理しているのかを説明している。仮想 ファイルシステム(Virtual File System)について述べ、 Linux カーネルの 実(real)ファイルシステムがどのようにサポートされているのかを解説してい る。 ネットワークと Linux とは、ほぼ同義語である。本当の意味で、Linux はイ ンターネットやウェブ(WWW)の産物なのである。開発者とユーザは、ウェブを 使って情報やアイデアやコードを交換しているし、Linux 自体が、しばしば組 織のネットワークへの要求を満たすために使用されている。``「ネットワー ク」''の章では、Linux が、TCP/IP と総称されているネットワークプロトコ ルをどのようにサポートしているかについて解説する。 ``「カーネルメカニズム」''の章では、カーネルの一部が他の部分と協調して 効率よく機能するために、Linux カーネルが提供しなければならない一般的な タスクとメカニズムについて解説する。 ``「モジュール」''の章では、カーネルが、たとえばファイルシステムのサポ ートなどの機能を、必要なときに動的にロードする仕組みについて説明する。 ``「プロセッサ」''の章では、Linux が移植されているいくつかのプロセッサ についての概要を説明する。 ``「Linux カーネルソース」''の章では、特定のカーネル関数を探すときソー スのどこから探し始めるべきかについて説明する。 1.3. この文書での表記規則 この文書では、次のような表記規則を採用している。 serif font コマンドや、ユーザが文字どおりにタイプすべきテキストの場合 type font データ構造およびそのデータ構造の中のフィールドを示す場合 (訳注: 訳文では、どちらも等幅フォントで示しています。) この文書のいたるところに、Linux カーネルのソースツリー内のコードの一部 への参照が付されている。(たとえば、文章のすぐそばにある、四角で囲った 記述など。) (訳注: 原著では HTML 版以外の PS 版などに付けられていま す。訳文では、[ ] で囲って、文中に記述しました。) それらは、読者が実際 にソースそのものを見たい場合を考えて付したものであり、すべての参照ファ イルは、/usr/src/linux からの相対位置にある。たとえば、/foo/bar.c を例 にとると、ファイルのフルネームは /usr/src/linux/foo/bar.c であ る。Linux を使っているなら(そうであって欲しいのだが)、コードを見ること は価値のある経験であり、本書をコードへの理解とデータ構造への手引きとし て利用することができる。 1.4. 商標について (訳注: 以下は、原文のままです。) ARM は、ARM Holdings PLC の登録商標です。 Caldera, OpenLinux, および C のロゴは、Caldera, Inc の登録商標です。 Caldera OpenDOS 1997 Caldera, Inc. DEC は、Digital Equipment Corporation の登録商標です。 DIGITAL は、Digital Equipment Corporation の登録商標です。 Linux は、Linus Torvalds の登録商標です。 Motif は、The Open System Fundation, Inc の登録商標です。 MSDOS は、Microsoft Corporation の登録商標です。 Red Hat, glint および Red Hat ロゴは、Red Hat Software, Inc の登録商標 です。 UNIX は、X/Open の登録商標です。 XFree86 は、XFree86 Project, Inc の登録商標です。 X Window System は、X Consortium と Massachusetts Institute of Technology の登録商標です。 1.5. 著者について わたしは、スプートニクが打ち上げられる数週間前の 1957 年に、イギリス北 部に生まれた。Unix と初めて出会ったのは、大学時代だった。講義でカーネ ルやスケジューリングその他のオペレーティングシステムの機能についての概 念を教わるときに、講師が教材として使ったからだ。わたしが卒業する年のプ ロジェクトとして PDP-11 が搬入されたのだが、その新品のマシンを使ってい たときのことはとても印象に残っている。卒業後(1982 年、コンピュータサイ エンス学科の First Class Honours の学位を得て)、Prime Computer (Primos) に就職し、その数年後に DEC (VMS, Ultrix) で働き始めた。DEC で は、いろいろなことに従事したが、最後の 5 年間は、半導体部門の Alpha と Strong ARM の評価委員会で働いた。 1998 年には、ARM に移籍し、ここで は、低レベルファームウェアを書いたりオペレーティングシステムを移植した りするエンジニアのグループを束ねている。わたしの子供たち(Esther と Stephen)は、わたしをコンピュータ狂い(geek)と呼ぶ。 職場や家庭で Linux について尋ねられることがしばしばあるが、そんなとき わたしは楽しさのあまり、つい聞かれてもいないことまで答えてしまう。仕事 上でも個人的にも Linux を使えば使うほど、わたしは Linux の熱心な愛好家 になってゆく。読者は気付いているかもしれないが、わたしは、狂信家ではな く、愛好家という言葉を使う。Linux 愛好家とは、他にもオペレーティングシ ステムがあることを理解しているが、それらを使うことを好まない情熱的な人 間のことだとわたしは考えている。Windows 95 を使っている妻の Gill は、 こう言ったことがある。「オペレーティングシステムが他にもあるなんて知ら なかったわ。」 エンジニアであるわたしにとって、Linux は自分の要求に完 璧に答えてくれるものである。それは、わたしが職場でも自宅でも使用する、 柔軟で応用範囲の広い非常に出来の良い道具である。フリーで手に入るほとん どのソフトウェアは、 Linux 上で簡単にビルドでき、しばしばあらかじめビ ルドされた実行ファイルをダウンロードすることもでき、それらを CD-ROM か らインストールすることもできる。 C++ や Perl でのプログラムを学ぶの に、あるいは Java について知るのに、Linux 以外にいったい何が自由に使え るというのだろう。 1.6. 謝辞 本書についてのコメントをメールで送ってくれた多くの親切なひとたちに感謝 したいと思う。それらのコメントは新しいバージョンを出すたびに含めるよう にしてきた。コメントをもらうことは、わたしには本当に嬉しいことなので、 是非わたしの新しいメールアドレスを目に留めてほしい。 何名かの講師が、わたしに質問を送り、コンピュータについて教えるためにこ の本の一部を使ってもよいかどうか尋ねてきた。これには当然快諾する。それ はわたしが特に意図していた本書の利用方法だからだ。そのクラスに未来の Linus Torvalds が座っていないと誰が断言できるだろうか。 この本全体を非常に詳細に批評してくれた John Rigby と Michael Bauer に は特に感謝している。大変な仕事だったと思う。Alan Cox と Stephen Tweedie はわたしの質問に辛抱強く答えてくれた。どうもありがとう。各章を すこし明るくするために Larry Ewing のペンギンをつかった。最後に、この 本を Linux Documentation Project に加えることに同意し、ウェブサイトに 載せてくれた Greg Hankins に感謝する。 (-- (脚注1)HOWTO とは、その名の通り、何かをする方法(how to)を述べた文 書である。Linux 向けに多数書かれており、非常に役に立つ。--) 2. ハードウェアの基本 オペレーティングシステムは、その基礎であるハードウェアシステムと密接に 協調しながら動作しなければならない。そして、オペレーティングシステムは ハードウェアでしか提供できない一定のサービスを必要としている。オペレー ティングシステムを完全に理解するには、その基礎となるハードウェアの基本 を理解しなければならない。この章では、そうしたハードウェア、すなわち現 代の PC について簡単に紹介する。 1975 年 1 月、「Popular Electrics」誌がその表紙に Altair 8080 のイラス トを載せたとき、革命が始まった。Altair 8080 は、初期のスタートレックの エピソードの目的地にちなんで名付けられていたのだが、家庭電子工作の愛好 家がわずか 397 ドルで組み立てられるというものだった。Intel 8080 プロ セッサと 256 バイトのメモリ、スクリーンもキーボードもないというそのマ シンは、今日の基準からすれば随分貧弱ではある。その発明者 Ed Roberts は、かれの発明品を呼ぶために「パーソナルコンピュータ(PC)」という言葉を 作り出した。しかし今日ではこの PC という言葉は、目の前にあるほとんどあ らゆるコンピュータに対して当たり前のように使われていている。この定義か らすると、非常にパワフルな Alpha AXP のシステムのいくつかでさえ PC と いうことになる。 熱心なハッカーたちは、Altair の潜在能力を理解し、そのためのソフトウェ アを書き、ハードウェアを作り始めた。そうした初期のパイオニアたちにとっ て、それは自由の象徴であった。その自由とは、エリート聖職者階級によって 守られて稼働していた、巨大なバッチプロセス方式のメインフレームシステム からの自由であった。一夜にして富豪となる「落ちこぼれ」大学生たちが現れ たのも、こうした現象、すなわち自宅のキッチンテーブルに置くことができる コンピュータという産物に魅了されてのことだった。多くのハードウェアが出 現し、それらは少しずつ違っていたから、ソフトウェアハッカーたちはそうし たマシンのソフトを喜んで書いていた。逆説的なのは、現代の PC の強固な鋳 型を作ったのは、IBM であったことだ。彼らは 1981 年に IBM PC を告示し て、1982 年はじめには顧客に出荷し始めた。Intel 8088 プロセッサ、64k の メモリ(256k まで拡張可能)、ふたつのフロッピーディスク、25 行 80 文字の カラーグラフィックアダプタ (CGA)というスペックは、今日の基準からすると 決してパワフルとは言えないが、売れ行きは好調であった。1983 年には IBM PC-XT が登場し、それには贅沢にも 10M バイトのハードドライブが付いてい た。まもなく、Compaq などの多くの会社によって IBM PC クローンが製造さ れはじめ、PC アーキテクチャは事実上の標準となった。この事実上の標準が 出来たことで、成長する市場で無数の会社が競争し、消費者にとっては幸運な ことに、価格は低く抑えられた。こうした初期 PC のシステムの構造上の多く の特徴が、現代の PC にも受け継がれている。たとえば、最も強力な Intel Pentium Pro ベースのマシンでも、起動時は Intel 8086 のアドレッシングモ ードで実行されている。 Linus Torvalds が将来 Linux となるはずのコード を書き始めたとき、最も普及していて妥当な値段のハードウェアとして彼が選 んだのが、Intel 80386 PC であった。 +--------------------------------------------------------------------+ | +-------+ +-------+ | | | power | | power | | | +-------+ +-------+ +-------------------+ | | +----------------+ | | | | | パラレルポート | |-------------------| | | +----------------+ | | | | +-------+ +-------+ |-------------------| | | | COM 1 | | COM 2 | | C P U | | | +-------+ +-------+ |-------------------| | | | | | | |-------------------| | | | | | | +-------------------+ | | _______________________ _____________________ | | | | | | | | +----------------------+ +--------------------+ | | _______________________ ______________________ | | | | | | | | +----------------------+ +--------------------+ | | SIMM メモリースロット | | +-------------------------------+ | | | | | | +-------------------------------+ PCI スロット | | +-------------------------------+ | | | | | | +-------------------------------+ | | +----------------------------------------------------+ | | | | | | +----------------------------------------------------+ | | +----------------------------------------------------+ | | | |ISA スロット | | +----------------------------------------------------+ | | +----------------------------------------------------+ | | | | | | +----------------------------------------------------+ | +--------------------------------------------------------------------+ (図表1.1) 典型的な PC マザーボード 外観からすると、PC を構成する部品として最も目立つのは、システムの本体 であり、キーボード、マウス、モニターである。本体の前面には、いくつかの ボタン、数字を表示する小さなランプ、それにフロッピードライブがある。最 近のシステムでは CD-ROM があり、データ保護の必要を感じているならバック アップ用のテープドライブもあるだろう。それらのデバイスの総称は、周辺機 器である。 CPU がシステム全体をコントロールしているのだが、情報処理機能を持つデバ イスはそれだけではない。たとえば、IDE コントローラのような周辺機器のコ ントローラはすべてあるレベルの情報処理能力を持っている。PC の内部に は(図表(1.1)参照) マザーボードがあり、 CPU もしくはマイクロプロセッ サ、メモリ、ISA や PCI の周辺機器コントローラ用のいくつかのスロットな どがある。IDE ディスクコントローラのようなコントローラの中には、システ ムボードに直接組み込まれているものもある。 2.1. CPU CPU もしくはマイクロプロセッサは、あらゆるコンピュータシステムの心臓部 である。マイクロプロセッサは、計算し、論理演算を行い、メモリから命令を 読み出してそれらを実行することでデータフローを管理する。コンピュータの 黎明期には、マイクロプロセッサの演算処理部分は独立した(しかもかなり大 きな)ユニットであった。中央演算処理装置(CPU)という言葉が作られた当時は そういうものであった。現代のマイクロプロセッサは、それらすべての部品を 組み合わせて集積回路を作り、非常に小さなシリコンチップにエッチング処理 されている。CPU, マイクロプロセッサ、プロセッサといった用語は、本書で はすべて同じ意味で使用される。 マイクロプロセッサは、バイナリデータを処理する。バイナリデータとは、1 と 0 から成るデータである。これらの 1 と 0 とは、電子スイッチのオンか オフかに対応している。単に 42 というと、十進数で " 10 が 4 つと 2 " を 意味するが、バイナリナンバー(二進数) では、一連の二進数値の並びとな り、それぞれが 2 のベキ数を表す。 ここでベキ数とは、ある数を自乗する回数のことである。10 の 1乗 は、10 である。 10 の 2 乗は、10x10 であり、10 の 3 乗は、10x10x10 になる。二 進数の 0001 は、十進数の 1 であり、二進数の 0010 は、十進数の 2 であ り、二進数の 0011 は、3 で、二進数の 0100 は、4 である。十進数 42 は、 二進数 101010 もしくは、 (2 + 8 + 32 もしくは、2 + 2x2x2 + 2x2x2x2x2 ) となる。 しかし、コンピュータプログラムの中で数を表現する場合には、二進数よりも むしろ、もう一つの基数である、十六進数のほうを使用するのが通常である。 16 進数では、それぞれの数が 16 のベキ数になっている。10 進数では 0 か ら 9 までしかないが、16 進数では 10 から 16 までが A, B, C, D, E, F と いう数で表現される。たとえば、16 進数の E は、10 進数では 14 であ り、16 進数 2A は、10 進数では 42 ( 16x2 + 10 ) である。C プログラム言 語の表記法では(本書ではこの表記法に従う)、16 進法は、先頭に " 0x " を 付ける。したがって、16 進数 2A は、0x2A と表記される。 マイクロプロセッサは、加算、乗算、除算などの算術演算や、「X は Y より 大きいか?」といった論理演算ができる。 プロセッサの処理は、外部クロックで管理されている。このクロックは、シス テムクロックであり、プロセッサに一定間隔でクロックパルスを送り、そのク ロックパルスごとにプロセッサが何らかの処理をする。たとえば、プロセッサ はクロックパルスごとに命令を実行したりする。プロセッサの速度は、システ ムクロックの発振速度で記述される。100MHz のプロセッサは、一秒間に 100,000,000 回のクロックパルスを受けているわけである。確かに、プロセッ サが違えばクロックごとに実行している処理の量も違うので、CPU のパワーを クロック速度で考えるのは誤解である。しかし、他のすべての条件が同じであ れば、クロック速度が速いほどプロセッサもパワフルなものになる。プロセッ サで処理される命令は単純である。たとえば、「メモリ上の X という場所に ある内容を読んでレジスタ Y に書き込め」といったものである。レジスタと は、マイクロプロセッサ内部の記憶装置で、データを蓄え、そのデータを使っ て処理を実行するために利用される。実行される処理によっては、実行中の処 理を停止して、メモリのどこか違う場所に存在する別の命令処理に移るようプ ロセッサに命ずるものもある。このように処理の対象を小さく分割したことに よって、現代のマイクロプロセッサは、毎秒数百万や数十億という命令の処理 ができる、無限ともいえる力を手に入れたのである。 命令は、実行時にメモリから取り出されなければならない。命令はそれ自体が メモリ内の参照データであり、それらのデータはメモリから取り出されなけれ ばならず、適切な時にメモリに保存されなければならない。 マイクロプロセッサ内のレジスタのサイズ、数、タイプは、完全にプロセッサ のタイプに依存している。Intel 4086 (訳注:80486か?)プロセッサは、Alpha AXP プロセッサとは違ったレジスタのセットを持っている。たとえば、Intel のものは 32 ビット幅だが、Alpha は、64 ビット幅であることからすでに異 なっている。しかし、一般的には、どのようなプロセッサでも、多くの汎用レ ジスタと、それよりも少ない数の特別なレジスタを持っている。大部分のプロ セッサには、次のような特定の目的に特化したレジスタがある。 Program Counter (PC) このレジスタは、次に実行される命令のアドレスを保持するものであ る。このカウンタ (PC) の内容は、命令がメモリから転送されるたび に、自動的にインクリメントされる。 Stack Pointer (SP) プロセッサは、メモリ(Random Access Memory, RAM)にアクセスできな ければならない。メモリは、(レジスタと比較した場合)大容量の、CPU 外部にある読み書き可能な記憶装置であり、一時(temporary)データの 保存を容易にするものである。スタックは、外部メモリに一時的に値を 保存したり取り出したりするための簡単な方法である。通常、プロセッ サは、特別な命令を使って、スタックに値を積み(push)、あとでそれを 取り出す(pop)処理をする。スタックは、後入れ先出し(Last In First Out, LIFO) の原則で動く。言い換えると、x と y のふたつの値を順に スタックに積んでそこからひとつの値を取り出すと、値 y が取り出さ れる。 あるプロセッサでは、スタックはメモリの上限に向かって積み上げら れ、他のプロセッサでは、メモリの下限あるいは最初に向かって積み上 げられる。あるいは、 ARM のように両方をサポートしているプロセッ サもある。 Processor Status (PS) 命令が実行されると結果が生じる。たとえば、「レジスタ X の内容 は、レジスタ Y の内容より大きいか比較せよ」という命令には、大小 という真か偽かの結果が生じる。 PS レジスタは、こうした結果を保持 するとともに、プロセッサの現在の状態についての情報も保持する。た とえば、大部分のプロセッサは、カーネルモード(あるいは、スーパバ イザーモード)とユーザモードという少なくともふたつのモードを持っ ている。 PS レジスタは、現在のモードを識別する情報も保持する。 2.2. メモリ すべてのシステムには、記憶階層(memory hierarchy)がある。そしてその階層 の位置によって、メモリのスピードやサイズが異なる。最速のメモリはキャッ シュメモリ(cache memory)と呼ばれ、その名前の通りメインメモリの内容を一 時的に保持あるいはキャッシュするために使用される。この種のメモリは非常 に高速だが高価なので、大部分のプロセッサではチップ上に少量しか組み込ま れず、マザーボード上にそれより少し多いキャッシュが置かれるだけである。 プロセッサによっては、命令とデータの両方をひとつのキャッシュに保持する ものがあるが、命令とデータを別々のキャッシュに保存するものもあ る。Alpha AXP プロセッサはふたつの内部キャッシュを持ち、ひとつはデータ 用(D-Cache)、もうひとつは命令用(I-Cache)として使用される。そして、外部 キャッシュ(B-Cache)は、両者を同時に保持する。最後にメインメモリがあ る。これは外部キャッシュに比べると非常に速度が遅い。CPU 上のキャッシュ と比較すると、メインメモリはイモ虫ほどの速度でしかない。 キャッシュとメインメモリとは足並みが揃っていなければならない(一貫性, coherent)。言い換えると、メインメモリ上のあるワード(word)がひとつ以上 のキャッシュに保持されている場合、(システムは)キャッシュとメモリの内容 を確実に一致させなければならない。キャッシュの一貫性を保つのは、主にハ ードウェアの仕事であり、オペレーティングシステムも部分的にそれに関与す る。このことは多くの重要なシステムタスクにも当てはまる。ハードウェアと ソフトウェアが緊密に協調することで目的を達成しなければならないタスクは 数多くある。 2.3. バス システムボード上の個別の部品は、バスと総称される多様な接続方法で互いに 結びつけられている。システムバスは、論理的な機能から次の 3 つに分類さ れる。アドレスバス、データバス、そしてコントロールバスである。アドレス バスは、データ転送のためにメモリ上の位置(アドレス)を指定するものであ り、データバスはデータを保持して転送するするためのものである。データバ スは双方向性で、CPU に読み込まれるデータと、CPU から書き込まれるデータ の転送を受け持つ。コントロールバスは多種の信号線を含んでいて、クロック パルスや制御シグナルなどを(バス上の)システム全体に配線するために使用さ れる。それら以外にも様々な形態のバスが存在する。たとえば ISA や PCI バ スは、システムに周辺機器を接続する際の一般的な方法である。 2.4. コントローラと周辺機器 周辺機器は、グラフィックカードやディスクといった実(real)デバイスであ り、それらは、システムボード上のチップやボードに差されるカード上のコン トローラによって制御される。IDE ディスクは、IDE コントローラチップで制 御され、SCSI ディスクは SCSI ディスクのコントローラチップで制御され る。それらのコントローラは CPU に接続されていて、多様なバスにより相互 にも接続されている。今日製造される大部分のシステムでは、PCI か ISA バ スによってメインシステムの部品に相互接続されている。コントローラ は、CPU 自体と同じプロセッサーであり、CPU を助ける情報処理装置と考える こともできる。CPU はシステム全体を統括している点でそれらと役割が異な る。 コントローラの仕様は相互に異なっているが、通常はレジスタを持ち、それに よってコントローラの制御がなされる。CPU 上で実行されるソフトウェアは、 そうしたコントローラの制御レジスタを読み書きできなければならない。ある レジスタはエラーを示すステータス情報を保持しているかもしれず、他のレジ スタは、コントローラのモード変更のための制御目的で使用されているかもし れない。バス上にある個々のコントローラは、CPU で個別にアドレス管理され ているので、それによって、ソフトウェアであるデバイスドライバは、コント ローラのレジスタに書き込みをして、それらを制御することができる。IDE ポ ート(IDE ribbon)がその典型であり、それを使用することで、バス上のデバイ スに対する個別のアクセスが可能になる。他の典型例は、PCI バスであり、こ れによっても個別のデバイス(たとえば、グラフィックカード)に独立してアク セスが可能となっている。 2.5. アドレス空間 システムバスは、CPU をメインメモリに接続するものであり、CPU をシステム の周辺機器に接続するバスとは区別されている。周辺機器が占めるメモリ空間 は、 I/O 空間(I/O space)と総称される。I/O 空間はそれ自体さらに細かく分 かれるのだが、現時点ではあまり気にしないこととする。CPU はシステム空間 メモリと I/O 空間メモリの両方にアクセス可能なのだが、コントローラ自体 はシステムメモリに間接的にアクセスできるだけであり、その際には CPU の 助けを借りなければならない。フロッピーディスクコントローラのようなデバ イスの側からこれを見ると、デバイスはデバイスのコントロールレジスタが占 めるアドレス空間しか見ることはできず (ISA 等の場合)、システムメモリを 見ることはできない。 CPU は、システムメモリにアクセスする場合と I/O 空 間にアクセスする場合とでは別々の命令を持っているのが一般的である。たと えば、「I/O 空間のアドレス 0x3f0 から 1 バイト読み出して、レジスタ X に入れよ」という命令があったとする。これは CPU がシステムの周辺機器を 制御する方法そのものであり、I/O 空間にある周辺機器コントローラのレジス タの読み書きがまさにそれにあたる。 PC アーキテクチャが今日のように発展 しても、I/O 空間のどこに、よく利用される周辺機器( IDE や シリアルポー トやフロッピーのコントローラ)のレジスタがあるかは、長年の慣習によって 決まっている。上記の I/O 空間のアドレス 0x3f0 は、シリアルポー ト(COM1)のコントロールレジスタのひとつがあるアドレスとなっている。 時には、コントローラが、システムメモリに対して直接大量のデータを読み書 きする必要が生じる場合がある。たとえば、ユーザデータがハードディスクに 書き込まれるような時である。その場合、DMA(Direct Memory Aceesss)コント ローラが利用され、周辺機器が直接システムメモリにアクセスできるように なっている。DMA によるシステムメモリへのアクセスは、CPU の厳しい管理・ 監督のもとで行われる。 2.6. タイマー 時間を知ることはすべてのオペレーティングシステムにとって必要なことなの で、現代の PC にはリアルタイムクロック(Real Time Clock, RTC) と呼ばれ る特別の周辺機器が組み込まれている。これが提供するのは、その日の正確な 時間と精密なタイミングインターバル(timing interval)のふたつである。RTC には内蔵バッテリーがあるので、PC に電源が入っていない時でも実行し続け ることができる。それゆえ PC はいつも正確な日付と時間とを知っているわけ である。インターバルタイマーによって、オペレーティングシステムは重要な 処理を正確にスケジューリングすることが可能となる。 3. ソフトウェアの基本 プログラムとは、特定のタスクを実行するコンピュータ命令のセットである。 プログラムは、低水準コンピュータ言語であるアセンブラでも記述でき、C の ようなマシンに依存しない高水準のプログラム言語でも記述できる。オペレー ティングシステムは、特別なプログラムであり、ユーザによるスプレッドシー トやワードプロセッサといったアプリケーションの実行を可能にするものであ る。この章では、基本的なプログラミングの原則を紹介し、オペレーティング システムの目的や機能の概観を述べる。 3.1. コンピュータ言語 3.1.1. アセンブリ言語 CPU がメモリから取り出して実行する命令は、人間が見ても全く意味不明であ る。それらの命令はマシン語であり、コンピュータに何を実行すべきかを正確 に伝達する。例えば、16 進数 0x89E5 は、Intel 80486 の場合、ESP レジス タの内容を EBP レジスタにコピーせよという命令に該当する。黎明期のコン ピュータのために最初に発明されたソフトウェアのひとつにアセンブラがあ る。それは、人間が読んで意味のわかるソースをマシン語に変換するプログラ ムである。アセンブリ言語は直接レジスタを操作し、そこにあるデータを処理 することができるが、その方法はマイクロプロセッサに固有のものとな る。Intel X86 プロセッサのアセンブリ言語は、Alpha AXP マイクロプロセッ サのアセンブリ言語とはかなり異なっている。次の Alpha AXP アセンブリコ ードは、プログラムが実行する処理の一種を示している。 ldr r16, (r15) ; Line 1 ldr r17, 4(r15) ; Line 2 beq r16,r17,100 ; Line 3 str r17, (r15) ; Line 4 100: ; Line 5 第一文(Line1)は、レジスタ 16 に対して、レジスタ 15 に保持されたアドレ スにある(メモリの)内容をロードする。次の命令は、レジスタ 17 に対して、 次のアドレス (レジスタ 15 + 4)のメモリ内容をロードする。Line 3 は、レ ジスタ 16 とレジスタ 17 の内容を比較し、同一なら Label 100 に分岐す る。両方のレジスタが同じ内容でない場合、プログラムは 4 行目に移行し、 レジスタ 17 の内容がメモリに保存される。同じ内容の場合は、データを保存 する必要はない。アセンブリレベルのプログラムは、冗長であり、トリッキー な書き方が必要で、エラーが生じやすい。 Linux カーネルはアセンブリ言語 で書かれた部分はほとんどなく、その部分も処理効率を上げる目的のためだけ に書かれたものであり、それらは特定のプロセッサに固有のものとなってい る。 3.1.2. C 言語とコンパイラ アセンブリ言語でプログラムを書くことは、困難で時間のかかる仕事である。 エラーを生じやすく、出来たプログラムは特定のプロセッサファミリーに束縛 されるので移植性がない。マシンに依存しない C のような言語を使う方が ずっとよい。 C を使えば、C で利用可能な論理アルゴリズムとデータ構造を 使ってプログラムを書くことができる。コンパイラと呼ばれる特別なプログラ ムは、C プログラムを読み込んで、アセンブリ言語に変換し、それによってマ シン語を生成する。よく出来たコンパイラは、優秀なアセンブリプログラマが 書いたのとほとんど同じくらい効率的なアセンブリ命令を生成することができ る。Linux カーネルの大部分は、C 言語で書かれている。次に C プログラム の一部を示す。 if (x != y) x = y ; これは、先ほどのアセンブリコードの例とまったく同じ処理をするものであ る。変数 x の内容が変数 y の内容と同一でない場合、y の内容が x にコピ ーされる。 C コードは、特定のタスクを処理するルーチンとしてまとめるこ とができる。ルーチンは C でサポートされているすべての値やデータ型を返 すことができる。Linux カーネルのような巨大なプログラムは、多数の独立し た C ソースモジュールから構成されていて、それぞれが独自のルーチンとデ ータ構造を持っている。そうした C のソースコードモジュールは、ファイル システム処理コードのグループのように、論理的な機能別にグループ化されて いる。 C では、種々の変数型がサポートされているが、変数とは、シンボル名で参照 可能なメモリ内の位置のことである。上記の C のプログラムでは、x と y は、メモリ内の位置を参照している。プログラマは、メモリ内に変数が置かれ た位置を知る必要はなく、それに配慮する必要があるのはリンカ(linker)(下 記参照)だけである。変数には、さまざまな種類のデータ、整数、浮動小数点 数を格納するためのものとポインタ (pointer)とがある。 ポインタは、アドレスを内容とする変数である。すなわち、他のデータのメモ リ内での位置を表す。x という変数があるとして、それがメモリ内のアドレス 0x80010000 に存在するとする。その場合、x を差すポインタ、たとえば px を作ることができる。ここで px は、アドレス 0x80010030 にあるとする。だ とすると、 px の値は、変数 x のアドレスである 0x80010000 を内容として 持つ。 C を使えば、関連のある変数をデータ構造体としてまとめることができる。た とえば、次のようなデータ構造体があるとする。 struct { int i ; char b ; } my_struct ; 上記の my_struct というデータ構造は、ふたつの要素を含んでいる。i とい う整数 (32 ビットのデータ記憶領域)と、b という文字(8 ビットのデータ)と いう要素である。 3.1.3. リンカ リンカとは、いくつかのオブジェクトモジュールやライブラリをリンクさせ、 ひとつの整合性のあるプログラムを作成するためのプログラムである。ここで オブジェクトモジュールとは、アセンブラやコンパイラが出力したマシンコー ドであり、それには、実行可能なマシンコードとデータが含まれると同時に、 リンカがモジュールを結合してひとつのプログラムを作るための情報も含まれ ている。たとえば、あるモジュールには、あるプログラムのデータベース関数 のすべてが含まれているかもしれず、ほかのモジュールにはコマンドライン引 数を処理するための関数が含まれているかもしれない。リンカはそうしたモ ジュール間での参照関係を解決するので、それによって、あるモジュールで参 照されているルーチンやデータ構造が、別のモジュールで実際に存在できるよ うになる。Linux カーネルという単一の巨大なプログラムは、多くの構成要素 であるオブジェクトモジュールを相互にリンクすることによって作られてい る。 3.2. オペレーティングシステムとは何か? ソフトウェアがなければ、コンピュータは単に熱を放出する電子部品の固まり にすぎない。ハードウェアがコンピュータの心臓だとすると、ソフトウェアは その魂である。オペレーティングシステムは、システムプログラムの集合体で あり、それがユーザによるアプリケーションプログラムの実行を可能にしてい る。オペレーティングシステムは、システム上の実(real)ハードウェアを抽象 化して、システムのユーザやアプリケーションに仮想マシンを提供する。オペ レーティングシステムは、本当の意味での「システム」を提供しているのであ る。大部分の PC では、複数のオペレーティングシステムの走行が可能であ り、それらはそれぞれに違った外観と操作性を持っている。Linux は、機能的 には別個の複数の部分から成り立っていて、それらがひとつになってオペレー ティングシステムを構成している。Linux の特徴的な部分のひとつが、カーネ ルである。しかし、それさえもライブラリやシェルがなければ役に立たない。 オペレーティングシステムがどういうものか理解するために、非常に単純なコ マンドを打ったときに何が起こるのかを考えてみよう。 $ ls Mail c images perl docs tcl $ $ は、ログインシェル(ここでは、bash)が出力するプロンプトである。プロン プトとは、ユーザのコマンド入力をシェルが待っているということを意味す る。ls と打ち込むと、キーボードドライバは、文字が打ち込まれたことを認 識し、そのコマンドをシェルに渡す。シェルは、それを処理するために、同じ 名前の実行イメージがあるかどうかを探す。シェルが /bin/ls にそのイメー ジがあることを発見すると、カーネルサービスを呼び出す。呼び出されたカー ネルサービスは ls の実行イメージを仮想メモリに置いて、その実行を始め る。実行された ls のイメージは、カーネルのファイルサブシステムを呼び出 して、どのようなファイルがあるのかを探し出すよう命じる。ファイルシステ ムは、キャッシュしておいたファイルシステムの情報を利用するか、ディスク デバイスドライバを使ってディスクからその情報を読み出させる。あるい は、(Network File System(NFS)経由でファイルシステムをリモートマウント している場合)ネットワークドライバを使用してリモートマシンと情報交換を し、ローカルシステムがアクセスしているリモートファイルの詳細について情 報を探し出させるかもしれない。その情報がいずれにある場合でも、ls はそ うした情報を出力し、ビデオドライバがそれをスクリーンに表示する。 上記の説明はやや複雑に思えるかもしれない。しかし、この非常に単純なコマ ンドの実行からでも明らかなように、オペレーティングシステムとは、実際、 協調して働く一連の関数のセットであり、それらが一体となることで、ユーザ に整合性のあるシステムの概観を提供しているのである。 3.2.1. メモリ管理 メモリ等のリソースが無限にあるなら、オペレーティングシステムが果たすべ き役割の多くは、余計なものとなるかもしれない。どんなオペレーティングシ ステムにもある基本的なトリックのひとつに、少ない物理メモリを実際以上に 見せる能力がある。この一見巨大なメモリは、仮想メモリと呼ばれる。このア イデアは、システム上で実行されているソフトウェアを騙して、大量のメモリ があるように思いこませるというものである。すなわち、システムはメモリを ページという扱いやすい単位に分割して、システム実行時に、そうしたページ をハードディスク上にスワップ(swap) する。ソフトウェアがそれに気付かな いのは、もうひとつのトリックであるマルチプロセス機能のせいである。 3.2.2. プロセス プロセスとは、実行中のプログラムであると考えることができる。個々のプロ セスは個別の実体であり、それぞれが特定のプログラムを実行してい る。Linux システム上のプロセスを見た場合、かなり多くのプロセスが存在す ることに気付くだろう。たとえば、ps とタイプすると、筆者のシステム上で は次のプロセスが表示される。 $ ps PID TTY STAT TIME COMMAND 158 pRe 1 0:00 -bash 174 pRe 1 0:00 sh /usr/X11R6/bin/startx 175 pRe 1 0:00 xinit /usr/X11R6/lib/X11/xinit/xinitrc -- 178 pRe 1 N 0:00 bowman 182 pRe 1 N 0:01 rxvt -geometry 120x35 -fg white -bg black 184 pRe 1 < 0:00 xclock -bg grey -geometry -1500-1500 -padding 0 185 pRe 1 < 0:00 xload -bg grey -geometry -0-0 -label xload 187 pp6 1 9:26 /bin/bash 202 pRe 1 N 0:00 rxvt -geometry 120x35 -fg white -bg black 203 ppc 2 0:00 /bin/bash 1796 pRe 1 N 0:00 rxvt -geometry 120x35 -fg white -bg black 1797 v06 1 0:00 /bin/bash 3056 pp6 3 < 0:02 emacs intro/introduction.tex 3270 pp6 3 0:00 ps $ 筆者のシステムに複数の CPU があれば、それぞれのプロセスを、(少なくとも 理論的には)違う CPU 上で実行することが可能である。しかし残念ながらひと つしかないため、オペレーティングシステムはここでもトリックを使って、短 い時間でプロセスを順番に実行する。その時間間隔はタイムスライ ス(time-slice)と呼ばれ、このトリックは、マルチプロセッシン グ(multi-processing)もしくはスケジューリング(scheduling)と呼ばれる。す なわちこれは、個々のプロセスを騙して、自分が唯一のプロセスであると思い 込ませるようにするものである。個々のプロセスは他のプロセスから保護され た状態にあるので、ひとつのプロセスがクラッシュしたりおかしな動きをして も、他のプロセスに影響を与えることはない。オペレーティングシステムはこ のことを実現するために、個々のプロセスに自分だけがアクセスできる個別の アドレス空間を付与している。 3.2.3. デバイスドライバ デバイスドライバは、Linux カーネルの重要な部分を形成している。オペレー ティングシステムの他の部分と同様に、デバイスドライバは非常に特権的な環 境で実行されるため、それらが上手く動かない場合は酷い状態になることがあ る。デバイスドライバは、デバイスを制御すると同時に、オペレーティングシ ステムとハードウェア間での相互作用も制御する。たとえば、ファイルシステ ムは、IDE ディスクにブロック単位の書き込みを行うとき、汎用のブロックデ バイスインターフェイスを利用する。ドライバはその IDE ディスクの詳細に 気を配り、そのディスク固有の処理が適切に実行されるようにする。デバイス ドライバは、操作対象であるコントローラチップに固有のものであるため、た とえば、システムで NCR810 SCSI コントローラを使用している場合、NCR810 SCSI ドライバーが必要になる。 3.2.4. ファイルシステム Linux では、Unix と同様に、システム上で個別のファイルシステムを使用す る場合、そのアクセス手段として、(ドライブ番号やドライブ名などの)デバイ ス識別子は利用しない。そのかわり、ファイルシステムは、単一の階層的なツ リー構造に組み込まれ、それによって単一の実体として表現される。Linux は、新しい個々のファイルシステムをその単一のファイルシステムに付け加え る際、たとえば /mnt/cdrom といったマウントディレクトリ上にマウントする という方法を取る。Linux の最も重要な特徴のひとつは、多くの異なるファイ ルシステムのサポートである。それによって、Linux は、非常に柔軟で、他の オペレーティングシステムとも上手く共存できるものになっている。Linux の 最も一般的なファイルシステムは、 EXT2 ファイルシステムであり、このファ イルシステムは、ほとんどの Linux ディストリビューションでサポートされ ている。 ファイルシステムは、システムのハードディスク上に保存されているファイル やディレクトリに関する分かり易い展望をユーザに与える。その際、ファイル システムのタイプや、基礎となる物理デバイスの性質の違いは捨象され る。Linux は多くの異なるファイルシステム(たとえば MS-DOS や EXT2)を透 過的にサポートし、マウントされたファイルやファイルシステムをひとつに統 合された仮想ファイルシステムとして提供する。したがって、一般的に、ユー ザやプロセスは、特定のファイルがどのような種類のファイルシステムに属し ているのかを知る必要なしに、それを使用できる。 ブロックデバイスドライバは、物理的なブロックデバイスのタイプ(たとえ ば、IDE や SCSI)の違いを隠すことが出来る。それゆえ、ファイルシステムに 関する限り、物理デバイスは、データブロックの線形的な集合にすぎない。ブ ロックサイズは、デバイスによって異なり、たとえば、フロッピーディスクは 512 バイトが一般的であり、IDE ハードディスクでは 1024 バイトが一般的で ある。しかし、これについても、システムのユーザからは隠されている。EXT2 ファイルシステムは、そのファイルシステムが置かれたデバイスがどのような ものであっても、同じ外観を持つ。 3.3. カーネルのデータ構造 オペレーティングシステムは、システムの現在の状態について多くの情報を保 持する必要がある。システム内で何かが起こったら、それに関するデータ構造 は現在の状態を反映するように書き換えられなければならない。たとえば、ユ ーザがシステムにログオンすると、新しいプロセスが生成される。カーネル は、新しいプロセスを表現するデータ構造体を作成して、それ以外のシステム 上の全プロセスを表現しているデータ構造体にそれをリンクさせなければなら ない。 このようなデータ構造体の大部分は、物理メモリ内に存在し、カーネルとその サブシステムからしかアクセスできないようになっている。データ構造体に は、データとポインタ、すなわち他のデータ構造のアドレスやルーチンのアド レスが含まれている。それらすべてを一度に見ようとした場合、Linux カーネ ルによって利用されているデータ構造体は、非常に紛らわしく分かりにくいも のに思えるかもしれない。しかし、すべてのデータ構造体には目的があり、数 種のカーネルサブシステムから利用される(複雑な)構造体もあるが、一般的に は、それらは最初に一見した時の印象よりもずっとシンプルである。 Linux カーネルの理解は、そのデータ構造と、Linux カーネル内の種々の関数 がそれを使用する方法とを理解できるかどうかにかかっている。本書は、 Linux カーネルの説明に際し、そのデータ構造の解説に重点を置く。個々のカ ーネルのサブシステムを説明するときは、そのアルゴリズム、処理方法と同時 に、それらが使用しているカーネルのデータ構造という観点を重視する。 3.3.1. 連結リスト Linux は、多くのソフトウェア工学上のテクニックを使って、データ構造を相 互にリンクしている。多くの場合、Linux では連結あるいは連鎖データ構造体 が使用されている。もし、個々のデータ構造体が、単一のプロセスやネットワ ークデバイスといった個別の現象や事物を記述するだけなら、カーネルにはそ れらの個別データをすべて探し出す能力が必要になる。連結リストでは、ルー トポインタがリスト上の最初のデータ構造体もしくは要素のアドレスを含んで いて、それぞれのデータ構造体がリスト上の次の要素へのポインタを含んでい る。最後の要素に含まれる次の要素へのポインタは(リストの終わりを示す意 味がある) 0 か NULL のどちらかである。二重連結リストでは、個々の要素 が、リスト上の次の要素へのポインタとひとつ前の要素へのポインタの両方を 含んでいる。二重連結リストを使えば、リストの真ん中で要素の追加や削除を することが容易になるが、ただし、メモリへのアクセス頻度は増加する。これ は、メモリアクセスと CPU サイクルのどちらを優先するかという、オペレー ティングシステムに典型的な二者択一の問題である。 3.3.2. ハッシュテーブル 連結リストは、データ構造体を結びつけるには手軽な方法だが、連結リストを 辿るのは非効率な場合がある。特定の要素を検索するなら、リスト全体を簡単 に眺めて、その上で必要な要素を見つけるようにすべきだろう。Linux は、 ハッシング(hashing) という別のテクニックを使って、連結リストの限界を克 服している。ハッシュテーブル(hash table)は、ポインタの配列(array)もし くはベクトル(vector) である。配列もしくはベクトルとは、単にメモリ内に 連続する一連の要素のことである。たとえば、本棚は、本の配列(array)であ ると言える。配列はインデックスでアクセスが可能である。インデックスとは 配列へのオフセット(offset)である。再度本棚の類推で考えると、それぞれの 本は、本棚上の位置で表すことができる。5 冊目 (offset)の本という言い方 でそれを指定できる。 ハッシュテーブルは、データ構造体に対するポインタの配列であり、そのイン デックスは、それらのデータ構造体内の情報から抽出される。ある村の人口を 表すデータ構造体があるとすると、その住人の年齢をインデックスとして使う ことができる。特定の住人のデータを検索する場合、年齢をインデックスとし て使用して、その村の人口についてのハッシュテーブルを検索し、該当するポ インタを辿れば、ポイント先の構造体にその住人の詳細が含まれている。残念 ながら、村には、同年齢の住人が大勢いる場合が多いので、ハッシュテーブル のポインタは、同年齢の住人を記述したデータ構造体の連鎖かリストに対する ポインタになるだろう。しかし、こうした短い連鎖を検索することは、データ 構造体すべてを検索するよりもずっと高速である。 ハッシュテーブルを使うと、よく利用されるデータ構造体へのアクセスを高速 化できるので、Linux は、しばしばハッシュテーブルを、キャッ シュ(cache)の実装のために使用している。キャッシュはすばやいアクセスが 必要な際の手軽な情報源であり、通常、手に入る情報のフルセットに対するサ ブセットとなっている。カーネルがあるデータ構造体に対し頻繁にアクセスす る場合、そのデータ構造体はキャッシュに入れられて保存される。キャッシュ の欠点は、単純な連結リストやハッシュテーブルよりも使用と管理が複雑にな ることである。もしデータ構造体がキャッシュ内で見つかれば(これは、 キャッシュヒット(cache hit)と言われる)、問題のない良い結果が得られる。 しかし、もし見つからない場合、関連するすべてのデータ構造体が検索され、 該当するデータ構造体が存在した際には、それをキャッシュに追加しなければ ならない。データ構造体をキャッシュに追加すると、古いキャッシュエントリ を破棄する必要が生じる。Linux はどれを破棄するか決定しなければならない のだが、問題は、破棄されたデータ構造体は、Linux が次に必要とするデータ そのものである場合があることである。 3.3.3. 抽象インターフェイス Linux はしばしばそのインターフェイスを抽象化する。インターフェイスと は、特定の方法で操作されるルーチンとデータ構造体の集合である。たとえ ば、すべてのネットワークデバイスドライバは、一定のルーチンを提供しなけ ればならない。そして、そのドライバのルーチンが、デバイス固有のデータ構 造体を処理する。この方法を使うと、汎用コードから成るレイヤーの存在が可 能になる。すなわち、汎用レイヤーのコードは、下層レイヤーにあるデバイス 固有のコードが提供するサービス(あるいは、インターフェイス)を使用するわ けである。ネットワークレイヤーは汎用コードから成り、その汎用コードが、 標準インターフェイスに準拠した、デバイス固有のコードによってサポートさ れている。 しばしばそうした下層レイヤーは、起動時に上層レイヤーと伴に登録される。 この登録の際には、通常、データ構造体を連結リストに追加する処理がなされ る。たとえば、あるファイルシステムがカーネルに組み込まれている場合、そ の個々のファイルシステムは起動時にカーネルに登録される。あるいは、もし モジュールになっている場合は、ファイルシステムが最初に呼びだされたとき に、登録がなされる。/proc/filesystems というファイルを見れば、どのファ イルシステムが登録されているかが分かる。カーネルへの登録に使用されるデ ータ構造体には、しばしば、複数の関数へのポインタが含まれている。それら は、特定のタスクを実行する関数のアドレスである。再度、ファイルシステム の登録を例にとると、個々のファイルシステムが登録の際にカーネルに渡すデ ータ構造体には、そのファイルシステムに固有のルーチンのアドレスが含まれ ている。そして、そのルーチンのアドレスを使用して当該ルーチンを呼び出す ことで、当該ファイルシステムがマウントされる仕組みになっている。 4. メモリ管理 メモリ管理サブシステムは、オペレーティングシステムの最も重要な部分のひ とつである。コンピュータの黎明期以来、システム上にある物理メモリだけで は足りない状況がずっと続いてきた。この限界を克服するために様々な戦略が 立てられたが、それらのうちで最も成功したのが、仮想メモリ(virtual memory)である。仮想メモリとは、システムに実際以上のメモリがあるかのよ うに見せる仕組みであり、メモリ争奪関係にあるプロセス間で、必要に応じて それらを協調して使用することにより実現されている。 仮想メモリは、コンピュータのメモリを大きく見せること以外にも様々な機能 を提供している。メモリ管理サブシステムが提供する機能には、次のようなも のがある。 巨大なアドレス空間 オペレーティングシステムは、システム上に実際以上のメモリがあるか のように振る舞う。仮想メモリはシステム上の物理メモリより、何倍も 大容量にできる。 データ保護 システム上のプロセスは、各々が独自の仮想アドレス空間を持ってい る。これらの仮想アドレス空間は、お互いに完全に独立しているので、 あるアプリケーションを実行しているプロセスは、他のプロセスに影響 を与え得ない。さらに、メモリエリアはハードウェア上の仮想メモリ機 構によって、書き込みから保護されている。これによって、コードやデ ータを、出来の悪いアプリケーションによる上書きから保護している。 メモリマッピング メモリマッピング(memory mapping)は、イメージやデータファイルをプ ロセスのアドレス空間にマップ(map)するために使用される。メモリ マッピングにおいて、ファイルの内容は、プロセスの仮想アドレス空間 に直接リンクされる。 公正な物理メモリの割り当て メモリ管理サブシステムによって、システム上で実行されている個々の プロセスは、システムの物理メモリ上に公正な持ち分を所持することが できる。 共有仮想メモリ 仮想メモリはプロセスが個別の(仮想)アドレス空間を持つようにしてい るが、複数のプロセスでメモリを共有(share)しなければならない場合 もある。たとえば、bash のコマンドシェルを実行しているプロセスが システム上に複数存在することがある。その場合、物理メモリ内に複数 の bash のコピーが存在して、個々のプロセスが個別の仮想アドレス空 間を持つよりも、ひとつだけのコピーが存在し、bash を実行するすべ てのプロセスでその仮想アドレス空間を共有するほうが効率がよい。動 的ライブラリ(dynamic library)は、複数のプロセス間で実行コードを 共有するもうひとつの典型例である。 共有メモリ(shared memory)は、プロセス間通信(Inter Process Communication, IPC)のメカニズムでも使用される。それは、ふたつ以 上のプロセスが、それらすべてに共通のメモリを経由して、情報を交換 する仕組みである。Linux は、Unix System V の共有メモリ IPC をサ ポートしている。 4.1. 仮想メモリの抽象モデル プロセス Y プロセス Y +--------+ プロセス X ページテーブル +-| VPFN 7 | +--------+ ________ | |--------| | VPFN 7 |-+ プロセス X +-|_______|<-+ | | VPFN 6 | |--------| | ページテーブル | |_______| | | |--------| | VPFN 6 | | ________ +-+-|_______|<-+-+ | VPFN 5 | |--------| +->|_______|-+ +-------+ | | |_______| | |--------| | VPFN 5 | |_______| | +->| PFN 4 |<-+-+ |_______| | | VPFN 4 | |--------| +->|_______|-+-+ | | | | |_______| | |--------| | VPFN 4 | | |_______| | | | |-------+ | | | VPFN 3 | |--------| | |_______| | | | | PFN 3 | | | |--------| | VPFN 3 |-+->|_______|-+-+-+ | | | | | VPFN 2 | |--------| | | | |-------| | | |--------| | VPFN 2 | | | | | PFN 2 |<-+ +---| VPFN 1 | |--------| | | | | | |--------| | VPFN 1 | | | | |-------| | VPFN 0 | |--------| | | +--->| PFN 1 | +--------+ | VPFN 0 |-+ | | | +--------+ | |-------| +----->| PFN 0 | | | +-------+ 仮想メモリ 物理メモリ 仮想メモリ 図表(3.1) 仮想アドレスから物理アドレスへのマッピングに関する抽象モデル Linux が仮想メモリをサポートする方法を考える前に、煩瑣な詳細を省いた抽 象モデルを検討することは有益である。 プロセッサがプログラムを実行するとき、プロセッサはメモリから命令を読み 出して、デコード(decode)する。命令をデコードする際、プロセッサは、ある 地点のメモリの内容を読み出したり(fetch)、書き込んだり(store)することが 必要になる場合もある。そして、プロセッサはその命令を実行し、プログラム の次の命令に移る。このように、プロセッサはいつもメモリにアクセスしなが ら、命令を読み出したり、あるいは、データを読み書きしたりしている。 仮想メモリシステムでは、それらに使用されるアドレスはすべて仮想アドレス であり、物理アドレスではない。それらの仮想アドレスはプロセッサによって 物理アドレスに変換されるのだが、その変換は、オペレーティングシステムが 管理する変換テーブルに保持された情報を基にして行われる。 この変換を容易にするため、仮想メモリと物理メモリは、ページ(page)と呼ば れる扱い易い単位に分割されている。それらのページはすべて同じサイズであ る。ページのサイズは必ずしも同一である必要はないのだが、同一サイズでな い場合、システムによる管理が非常に困難なものになるためにそうされてい る。Alpha AXP システム上の Linux では、8 k バイトのページを使用 し、Intel x86 システムでは、4 k バイトのページが使用されている。ページ それぞれには、他と重複しない数値のページフレーム番号(Page Frame Number, PFN)が割り振られている。 このようにページ分割されたシステムの場合、仮想アドレスは、オフセッ ト(offset) と仮想ページフレーム番号(PFN)のふたつの部分から構成される。 ページサイズが 4 k バイトならば、仮想アドレスの bit 11 から 0 にオフ セットが含まれていて、 bit 12 以降が仮想ページフレーム番号となってい る。プロセッサは仮想アドレスに出会うたびに、オフセット番号と仮想ページ フレーム番号を抽出する。プロセッサは、仮想ページフレーム番号を物理ペー ジフレーム番号に変換し、その上で、該当する物理ページの正しいオフセット 番号の位置にアクセスする。その変換のために、プロセッサはページテーブ ル(page table)を使用する。 ``図表(3.1)''は、プロセス X とプロセス Y というふたつのプロセスの仮想 アドレス空間を示すもので、どちらもプロセス自身のページテーブルを持って いる。それらのページテーブルは、プロセスの仮想ページをメモリの物理ペー ジにマップするものである。この図表では、プロセス X の仮想ページフレー ム番号 0 が物理ページフレーム番号 1 にマップされ、プロセス Y の仮想ペ ージフレーム番号 1 が物理ページフレーム番号 4 にマップされている。理論 的には、ページテーブル内のそれぞれのエントリーには、次の情報が含まれて いる。 有効フラグ(valid flag) ページテーブルのエントリが有効かどうかを示す。 物理ページフレーム番号(physical page frame number) そのエントリが記述している物理ページフレーム番号。 アクセス制御情報(access control information) そのページが利用される方法を記述している。書き込み可能か、実行コ ードを含むかといったことに関係する情報。 ページテーブルにアクセスする場合、仮想ページフレーム番号が(ページテー ブルへの)オフセットとして使用される。仮想ページフレーム番号の 5 は、ペ ージテーブルの 6 番目の要素に該当する。(0 が最初の要素に該当するためで ある。) 仮想アドレスを物理アドレスに変換する場合、プロセッサはまず仮想アドレス ページフレーム番号とその仮想ページ内のオフセット値を探さなければならな い。ページサイズを 2 の n 乗とすることで、マスク操作(masking)とシフト 操作(shifting)によりそれは簡単に実行できる。もう一度``図表(3.1)''を見 てほしい。ページサイズが 0x2000 バイト(十進数なら 8192)で、プロセス Y の仮想アドレス空間でのアドレスが 0x2194 だとすると、プロセッサがそのア ドレスを変換した結果は、オフセット値 0x194 を持つ仮想ページ番号 1 とな る。 プロセッサは、仮想ページフレーム番号をプロセスのページテーブルへのイン デックスとして使用することで、そのページテーブルエントリを知る。もしそ のオフセット位置にあるページテーブルエントリが有効(valid)ならば、プロ セッサはそのエントリから物理ページフレーム番号を取得する。エントリが無 効なら、プロセスは物理メモリ上に存在しない仮想メモリ領域にアクセスした ことになる。その場合、プロセッサはアドレスを解決できないので、オペレー ティングシステムに制御を渡すことで、問題の解決を依頼する。 現在のプロセスが有効な変換先のない仮想アドレスにアクセスしようとしたこ とをプロセッサがオペレーティングシステムに伝える方法は、プロセッサの種 類によって異なる。しかしプロセッサの伝達方法がどのようなものであれ、そ の現象はページフォルト(page fault)と呼ばれていて、オペレーティングシス テムには、フォルトとなった仮想アドレスとフォルトが生じた原因とが通知さ れる。 ページテーブルのエントリが有効であった場合、プロセッサは、物理ページフ レーム番号を取得し、それにページサイズを掛け算して、物理メモリ内のペー ジのベースアドレス(base address)を取得する。最後に、プロセッサは、(そ のベースアドレスを起点に)取得すべき命令やデータまでのオフセットを、そ のベースアドレスに加える (ことで、完全な物理アドレスを得る)。 上記の例を再び取り上げると、プロセス Y の仮想ページフレーム番号 1 は、 物理ページフレーム番号 4 にマップされる。その物理ページフレームは、ア ドレス 0x8000(4 x 0x2000)を起点(ベースアドレス)としている。そのアドレ スに 0x194 バイトのオフセット値を加えると、最後に 0x8194 という物理ア ドレスが得られる。 仮想アドレスから物理アドレスへのマッピングにこうした方法を使用すること で、仮想メモリからシステムの物理メモリへのマッピングが、その番号と無関 係に可能となる。たとえば、``図表(3.1)''において、プロセス X の仮想ペー ジフレーム番号 0 は、物理ページフレーム番号 1 にマップされるが、仮想ペ ージフレーム番号 7 は、仮想ページフレーム 0 より大きな番号であるにも関 わらず、物理ページフレーム 0 にマップされる。このことは、仮想メモリ機 構が興味深い副産物を持つことを意味する。すなわち、仮想メモリのページ は、物理メモリ内で特定の順番通りに並んでいる必要がないということであ る。 4.1.1. デマンドページング 仮想メモリに比べて物理メモリは非常に量が少ないので、オペレーティングシ ステムは物理メモリを効率的に使うよう気を配らなければならない。物理メモ リを節約するひとつの方法は、実行中のプログラムが現在使用している仮想ペ ージだけをロードすることである。たとえば、データベースプログラムがデー タベースに問い合わせを実行したとする。その場合、データベース全体がメモ リにロードされる必要はなく、調べる必要のあるデータレコードだけがロード されればよい。データベースへの問い合わせが検索である場合、新規レコード 追加の処理をするデータベースプログラムのコードをロードしても意味がな い。アクセスされた仮想ページだけをメモリにロードするというこのテクニッ クは、デマンドページング(demand paging) と呼ばれる。 プロセスが物理メモリ上にない仮想アドレスにアクセスしようとした場合、プ ロセッサは、参照された仮想ページのページテーブルエントリを見つけること ができない。たとえば、``図表(3.1)'' では、プロセス X のページテーブル には、仮想フレーム番号 2 のエントリがないので、プロセス X が仮想ページ フレーム番号 2 の中にあるアドレスを読もうとしても、プロセッサは、その アドレスを物理メモリ上のアドレスに変換できない。この時点で、プロセッサ は、ページフォルトが発生したことをオペレーティングシステムに通知する。 フォルトとなった仮想アドレスが無効なものである場合、それはプロセスが存 在しない仮想アドレスにアクセスしようとしたことを意味する。おそらく、ア プリケーションに不具合が起こって、たとえばメモリ内のランダムなアドレス へ書き込みをしようとしたのかもしれない。この場合、オペレーティングシス テムはそのプロセスを終了させて、暴走したそのプロセスからシステム内の他 のプロセスを保護する。 しかし、フォルトとなった仮想アドレスが有効なものであり、参照されたペー ジはそのときメモリ内になかっただけである場合、オペレーティングシステム は、ディスク上のイメージから適切なページをメモリ内に持ってこなければな らない。ディスクアクセスは、相対的に時間の掛かる処理なので、プロセス は、ページが来るまでの間待っている必要がある。その際に実行可能なプロセ スがあれば、オペレーティングシステムはそのいくつかを選んで実行する。そ して、取ってきたページを空いた物理ページフレームに書き込んで、仮想ペー ジフレーム番号用のエントリーをプロセスのページテーブルに追加する。それ によって、プロセスはメモリフォルトが発生した命令があった場所から再び実 行される。今度は、仮想メモリへのアクセスに成功し、プロセッサも仮想アド レスから物理アドレスへの変換ができるので、プロセスの実行は継続する。 Linux は、デマンドページングを使用して、実行イメージをプロセスの仮想メ モリにロードする。コマンドが実行される際はいつも、そのコマンドを含む ファイルがオープンされ、ファイルの内容がプロセスの仮想メモリにマップさ れる。その処理は、そのプロセスのメモリマップを記述しているデータ構造体 を修正することにより行われるので、メモリマッピング(memory mapping)と呼 ばれる。しかし、実際に物理メモリに置かれるのはイメージの最初の部分だけ であり、残りはディスクに保存されたままとなる。したがって、イメージが実 行されるとページフォルトが起こり、Linux は、プロセスのメモリマップを利 用して、イメージのどの部分をメモリに持ってくれば実行が完遂できるのかを 判断する。 4.1.2. スワッピング あるプロセスが仮想ページを物理メモリに持ってこなければならないのだが、 利用可能な物理メモリの空きがない場合、オペレーティングシステムは、別の ページを物理メモリから取り除くことでそのページのための空間を確保しなけ ればならない。 物理メモリから取り除くべきページがイメージやデータファイルのコピーであ り、上書きされてはいない場合、そのページは保存する必要はない。破棄して おいて、プロセスが再度そのページを必要としたときに、該当するイメージや データファイルからメモリ内に戻せばそれでよい。 しかし、ページが変更されている場合には、オペレーティングシステムは、そ のページを維持して、後でそのページにアクセスできるようにしなければなら ない。この種のページは、ダーティページ(dirty page)と呼ばれ、メモリから 削除されるときにスワップファイルと呼ばれる特別なファイルに保存される。 スワップファイルへのアクセスには、プロセッサとメモリとの間のスピードと 比べて非常に長い時間が掛かるので、オペレーティングシステムは、ページを ディスクに書き込む必要がある場合でも、再使用に備えて出来るだけそれをメ モリに保持する必要がある。 破棄やスワップするページを決めるために使用されるアルゴリズム(スワップ アルゴリズム)の効率が悪い場合、スラッシング(thrashing)と呼ばれる状態に 陥る。その場合、ページは間断なくディスクに書き込まれたり読み出されたり するので、オペレーティングシステムはそれに時間を取られて本来の仕事が出 来なくなる。たとえば、もし物理ページフレーム番号 1 (``図表(3.1)'') が 定期的にアクセスされるなら、そのページはハードディスクにスワップされる べきではない。プロセスが現在使用しているページセットは、ワーキングセッ ト(working set)と呼ばれる。効率の良いスワップスキームとは、すべてのプ ロセスが自分のワーキングセットを物理メモリに持つよう手配するスキームで ある。 Linux は、最も使用頻度の少ない(最長時間未使用(Least Recently Used, LRU)) ページの寿命を縮める(エイジング(aging))というテクニックを使っ て、システムから削除すべきページを公平に選択している。このスキームで は、システム内のすべてのページが寿命(age)を持っていて、それがページア クセスされると、変更される仕組みになっている。ページへのアクセスが多け れば、そのページの寿命は長くなる。アクセスが少なければ寿命が縮み、必要 性が薄れる。寿命が尽きたページは、スワッピングの有力な候補となる。 4.1.3. 共有仮想メモリ 仮想メモリ機構は、複数のプロセスでのメモリの共有を実現しやすくする。メ モリへのアクセスはすべてページテーブル経由で行われ、プロセスはそれぞれ 自分の独立したページテーブルを持っている。ふたつのプロセスがメモリ内の 物理ページを共有するには、その物理ページのフレーム番号が、両者のページ テーブル内のエントリに存在する必要がある。 図表 3.1 では、ふたつのプロセスが物理ページフレーム番号 4 を共有してい る様子が示されている。プロセス X にとって、これは仮想ページフレーム番 号 4 であり、プロセス Y にとって、これは仮想ページ番号 6 である。これ はページ共有の興味深い点を示している。共有された物理ページは、それを共 有するプロセスにとって仮想メモリの同じ位置に存在しなくてもよいというこ とである。 4.1.4. 物理アドレスモードと仮想アドレスモード オペレーティングシステム自体が仮想メモリ内で実行されるというのはほとん どナンセンスである。オペレーティングシステムが自分のページテーブルを管 理しなければならないとしたら、悪夢のような状況になるだろう。大部分の汎 用プロセッサは、仮想アドレスモードと同時に物理アドレスモードという概念 をサポートしている。物理アドレスモードでは、ページテーブルは不要であ り、プロセッサはこのモードのときはアドレス変換をしようとしない。Linux カーネルは、この物理アドレスモードで実行されるようにリンクされている。 Alpha AXP プロセッサは、特に物理アドレスモードというのは持っていない。 そのかわり、メモリ空間をいくつかのエリアに分割していて、そのうちのふた つを物理的にマッピングされたアドレス空間として使用する。このカーネルア ドレス空間は、KSEG アドレス空間と呼ばれていて、0xfffffx0000000000 以上 のすべてのアドレスを包含している。KSEG 領域にリンクされているコード(カ ーネルコードと定義されるコード)を実行したり、その領域のデータにアクセ スしたりするためには、そのコードはカーネルモードで実行される必要があ る。Alpha 上の Linux カーネルは、アドレス 0xfffffc0000310000 から実行 されるようにリンクされている。 4.1.5. アクセス制御 ページテーブルのエントリには、アクセス制御情報も含まれている。プロセッ サは、プロセスの仮想アドレスを物理アドレスにマップするためにいつもペー ジテーブルを使用するので、そこに含まれたアクセス制御情報を利用して、プ ロセスが許されていない方法でメモリにアクセスしていないかどうかを簡単に チェックできる。 メモリの特定のエリアへのアクセスを制限すべき理由は数多くある。実行コー ドを含むようなメモリは、通常読み出し専用である。オペレーティングシステ ムは、プロセスがそのような実行コードの位置にデータを上書きするのを許す べきではない。反対に、データを含むページは上書きされてもよいわけであ り、そのメモリの内容を実行しようとする命令があった場合、失敗させなけれ ばならない。大部分のプロセッサには、実行に関してカーネルモードとユーザ モードという少なくともふたつのモードがある。カーネルコードをユーザが勝 手に実行できないようにするためであり、プロセッサがカーネルモードで動い ているとき以外は、カーネルのデータ構造へのアクセスを許さないようにする ためである。 15 13 11 31 14 12 10 9 8 7 6 5 4 3 2 1 0 +----------------------------------------------------------------+ | | | | | | | | | | | | | | | | | | | | | | | | | |U|K| |U|K| | G|A|F|F|F|V| | | | | | | |W|W| |R|R| | H|S|O|O|O| | | | | | | | |E|E| |E|E| | |M|E|W|R| | | | | | | | | | | | | | | | | | | | | +----------------------------------------------------------------+ ^ ^ | +------- _PAGE_DIRTY | +----------- _PAGE_ACCESSED 63 32 +----------------------------------------------------------------+ | | | | | P F N | | | | | +----------------------------------------------------------------+ 図表(3.2) Alpha AXP のページテーブルエントリ(PTE) アクセス制御情報は、PTE に保持されており、それはプロセッサに固有のもの である。図表(3.2)は、Alpha AXP の PTE を表している。そのビットフィール ドは、次のような意味を持つ。 V 「有効(Valid)」。これが設定されると、PTE が有効となる。 FOE 「実行フォールト(Fault On Execute)」。このページ内で命令が実行されようとし た際は、プロセッサがページフォルトを報告し、制御をオペレーティングシステム に渡す。 FOW 「書き込みフォールト(Fault on Write)」。上記と同様だが、このページへ書き込 もうとした場合のページフォルトである。 FOR 「読み込みフォールト(Fault On Read)」。上記と同様だが、このページの読み込も うとした場合のページフォルトである。 ASM 「アドレス空間合致(Address Space Match)」。これが使用されるのは、オペレー ティングシステムが変換テープルから数個のエントリだけを削除したいときである。 KRE カーネルモードで実行されているコードは、このページを読むことができる。 URE ユーザモードで実行されているコードは、このページを読むことができる。 GH Granularity hint(粒度ヒント). ひとつのブロック全体を分割せずに単一の変換 バッファにマッピングするときに利用される。 KWE カーネルモードで実行されているコードはこのページに書き込み可能。 UWE ユーザモードで実行されているコードは、このページに書き込み可能。 PFN(page frame number) V ビットが設定された PTE では、このフィールドには、当該 PTE への物理フレー ム番号(ページフレーム番号)が書かれている。V ビットが設定されていおらず、こ のフィールドがゼロでない場合、スワップファイル内でのページの場所に関する情 報が書かれている。 次のふたつのビットは、Linux で定義され、使用されているものである。 _PAGE_DIRTY これが設定された場合、このページはスワップに書き出される必要がある。 _PAGE_ACCESSED ページがアクセスされたことを記すために使用される。 4.2. キャッシュ 上記の理論モデルを使ってシステムを実装した場合、確かに機能はするであろ うが、それほど効率のよいものにはならない。オペレーティングシステムとプ ロセッサの設計者はどちらもシステムからよりよいパフォーマンスを引き出そ うと懸命に努力しているからである。より速いプロセッサやメモリを製造する といったことを別にすると、処理速度を上げる最良の方法は、頻繁に使用する 情報やデータのキャッシュを維持管理することである。Linux では、キャッ シュに関するいくつかのメモリ管理機構が利用されている。 バッファキャッシュ バッファキャッシュ(buffer cache)には、ブロックデバイスドライバが 使用するデータバッファが含まれている。 [see: fs/buffer.c] これらのバッファは固定サイズ(たとえば、512 バイト)で、ブロックデ バイスから読み出されたか、そこに書き込まれた情報のブロックが入っ ている。ブロックデバイスとは、データアクセスの際に、固定サイズの ブロック単位でのみ読み書きできるデバイスを指す。すべてのハード ディスクはブロックデバイスである。 バッファキャッシュは、デバイス識別子と必要なブロック番号とでイン デックス付けされていて、データブロックをすばやく見つけだすために 使用されるものである。ブロックデバイスは、バッファキャッシュを経 由しなければアクセスできない。データがバッファキャッシュに見つか れば、たとえばハードディスクのような物理ブロックデバイスから読み 出す必要がなくなるので、アクセスがずっと高速になる。 ページキャッシュ ページキャッシュ(page cache)は、ディスク上のイメージやデータへの アクセスを高速化するために使用される。 [see: mm/filemap.c] これは、ページ単位でファイルの論理的な内容をキャッシュし、ファイ ル名とそのファイル内のオフセットを使ってアクセスされる。ページが ディスクからメモリに読み出されると、それらはページキャッシュに キャッシュされる。 スワップキャッシュ 変更された(あるいは、dirty な)ページだけが、スワップファイルに保 存される。 [see: swap.h, mm/swap_state.c, mm/swapfile.c] 当該ページがスワップファイルに書き込まれてから変更されていない場 合は、再度スワップアウトされたとしても、同じページがすでにスワッ プファイル内にあるわけだから、スワップファイルに書き込む必要はな い。そのページは単に破棄される。システムがスワップを頻繁に使用す る場合、これによって時間の掛かる不必要なディスク操作を大幅に省略 できる。 ハードウェアキャッシュ ハードウェアキャッシュ(hardware cache)の実装として一般的なのが、 プロセッサ内のハードウェアキャッシュである、ページテーブルエント リのキャッシュである。この場合、プロセッサはいつも直接ページテー ブルを読み出すのではなく、変換が必要であったときにそのページ変換 をキャッシュしておく。プロセッサ内には、アドレス変換バッ ファ(Translation Look-aside Buffer, TLB)があり、そこには、システ ム上の複数のプロセスに関するページテーブルエントリのコピーが キャッシュされている。 仮想アドレスへの参照が行われるとき、プロセッサは合致する TLB エ ントリを探そうとする。それが見つかれば、仮想アドレスを直接物理ア ドレスに変換して、データに対して適切な処理を実行する。プロセッサ が合致する TLB エントリを見つけ出せない場合は、オペレーティング システムに助けを求める。すなわち、プロセッサは、オペレーティング システムに対して、TLB 失敗(TLB miss)が起こったことをシグナルで伝 える。システム固有のメカニズムを使用して、その例外(exception)を オペレーティングシステムの例外処理コードに渡す。オペレーティング システムは、そのシグナルを受けて、アドレスマッピングのための新し い TLB エントリを生成する。例外がクリアされると、プロセッサは再 度仮想アドレスの変換を試みる。今回は、 TLB 内に必要なアドレスに 関する有効なエントリがあるので、問題なく変換される。 ハードウェアキャッシュやその他のキャッシュを使う場合の欠点は、手順を簡 略化するために、これまで以上の時間とメモリ空間を使ってそうしたキャッ シュを維持しなければならないということであり、もしキャッシュが壊れる と、システムがクラッシュするということである。 4.3. Linux のページテーブル 仮想アドレス +-------+--------------+-------------+-----------+----------------+ | | | | | | | | Level 1 | Level 2 | Level 3 |Byte within page| | | | | | | | | | | +-------+------+-------+-----+-------+-----+-----+---------+------+ | | | | | | | | Level 1 | Level 2 | Level 3 | | Page Table | Page Table | Page Table | Physical Page | +-------+ | +-------+ | +-------+ | +---------+ | | | | | | | | | | | | | | | | | | | | | | | | | |---+---| | |---+---| | |---+---+ | |---------| | |PFN| |<-+ |PFN| |<-+ |PFN| |<-+ | |<-+ |-+-+---| |-+-+---| |-+-+---| |---------| | | | | | | | | | | | PGD | | | | | | | | | | | --->+-+-----+ +->+--+----+ +->+-+-----+ +->+---------+ | | | | | | +--------+ +-------+ +--------+ 図表(3.3) 3 つのレベルのページテーブル Linux では、3 つのレベルのページテーブルの存在が前提になっている。アク セスされる個々のページテーブルには、次の段階のページテーブルにおけるペ ージフレーム番号が含まれている。図表(3.3)では、仮想アドレスがいくつか のフィールドに分割されている様子が示されている。図中の仮想アドレスの個 々のフィールドが提供するのは、特定のページテーブルへのオフセットであ る。プロセッサは、仮想アドレスを物理アドレスに変換するため、まず個々の レベルのフィールドの内容を取得して、その内容を当該レベルのページテーブ ルを含む物理ページ上でのオフセットへと変換し、それによって次のレベルの ページテーブルのページフレーム番号を読み取る。この処理を三度繰り返す と、その仮想アドレスを含んだ物理ページのページフレーム番号が分かる。仮 想アドレスの最後のフィールドは、バイトオフセット(byte offset)となって いて、それを使用して当該物理ページ内で必要なデータを見つけ出す。 Linux を実行するプラットフォームが提供しなければならないのが、変換マク ロである。これは、カーネルが特定のプロセスのためにページテーブルを走査 することを可能にするものである。そうすることで、カーネルはページテーブ ルエントリのフォーマットや、その仕組みを知る必要がなくなる。 [see: include/asm/pgtable.h] この方法は非常に成功しているので、Linux は、ページテーブル操作のコード として Alpha プロセッサにも Intel x86 プロセッサにも同じものを使用して いる。ただ、 Alpha プロセッサは、3 つのレベルのページテーブルを持つ が、Intel x86 は、 2 つのレベルのページテーブルを持つという違いがあ る。 4.4. ページの割り当てとページの解放 システム内の物理メモリに対する需要は大きい。たとえば、イメージがメモリ にロードされるとき、オペレーティングシステムは、それに物理ページを割り 当てる。イメージが実行を終了し取り除かれたら、そのページは解放される。 物理メモリは他にも、ページテーブル自体のようなカーネル固有のデータ構造 を保持する用途に使用される。ページ割り当てと解放に使用されるメカニズム とデータ構造は、仮想メモリサブシステムの効率を維持する上でおそらく最も 重要なものである。 システム内のすべての物理ページは ``mem_map'' というデータ構造によって 記述されている。それは、``mem_map_t'' 構造体(``脚注 1 '')のリストであ り、それらは起動時に初期化される。 [see: include/linux/mm.h] 個々の mem_map_t は、システム内の単一の物理ページを記述する。(メモリ管 理に関する限り)その構造体の重要なフィールドには、次のようなものがあ る。 count これは、そのページのユーザ数のカウンターである。ページが複数のプロセ スで共有されている時、このカウントは 1 より多くなる。 age このフィールドは、ページの 寿命(age)を記述するもので、そのページが破棄 やスワップの候補であるかを判断するのに使われる。 map_nr これは、mem_map_t が記述する物理ページのフレーム番号である。 ``free_area'' という配列も、ページ割り当てのコードにより、ページの検索 と解放のために使用される。すべてのバッファ管理スキームはこのメカニズム によってサポートされており、そのコードに関する限り、プロセッサの物理ペ ージングメカニズムとページサイズとの間には、関連性がない。 ``free_area'' の個々の要素は、ブロック単位のページに関する情報を含んで いる。すなわち、配列の最初の要素はひとつのページ、次が 2 つのページか ら成るブロック、さらに次が 4 つのページから成るブロックというふうに 2 の n 乗でブロックのページ数が増える。構造体の要素 ``list'' はキューの 先頭として使用され、``mem_map'' 配列内の ``page'' データ構造体へのポイ ンタとなっている。 (訳注: list は、v2.0.12 以降は、next と prev による 二重連結リストになっています。) 空のページブロックはこのキューに並べら れる。``map'' は個々のサイズのページグループの割り当て状態を管理してい るビットマップ(bitmap)に対するポインタである。bitmap の N ビット目は、 ページの N 番目のブロックが空である場合にセットされる。 ``図表(3.4)''では、free_area 構造体が示されている。要素 0 はひとつの空 ページ(ページフレーム番号 0 )を持っており、要素 2 は 4 つのページから 成る 2 つの空ブロックを持っている。最初の空ブロックは、ページフレーム 番号 4 から始まり、次の空ブロックはページフレーム番号 56 から始まって いる。 4.4.1. ページの割り当て Linux は、相棒アルゴリズム (``Buddy algorithm'') ``(脚注 2)'' を使用す ることで、ページブロックを効率的に割り当てたり解放したりしている。 [see: __get_free_page(), in mm/linux/page_alloc.c] ページ割り当てのコードは、ひとつもしくは複数のページから成るブロックを 割り当てようとする。ページの割り当ては、大きさとして 2 の n 乗のブロッ ク単位となっている。すなわち、ひとつのブロックに 1 つのページ、2 つの ページ、4 つのページ等が割り当てられる。システム内に要求(nr_free_pages > min_free_pages)に応えられるだけの空き空間があれば、割り当てコード は、要求されたサイズのページ数でブロックを作成するために ``free_area'' を調べる。 free_area のそれぞれの要素は、該当するサイズのブロックに関 するマップを持っていて、割り当て済みもしくは空いているページブロックが 参照できるようになっている。たとえば、配列の要素 2 が持つメモリマップ では、4 つのページから成るそれぞれのブロックのどれが割り当て済みであ り、どれが空なのかが記録されている。 割り当てアルゴリズムは、まず要求されたサイズのページブロックを探す。そ れは、 ``free_area'' データ構造体のキューにある、 ``list'' 要素上か ら、空のページの連続を調べる。もし要求されたサイズのページブロックでは 空きがない場合、次のサイズ(これは要求されたサイズの 2 倍である)のブ ロックがないか調査する。この過程は、free_area のすべてが調べられるか、 ページブロックが見つかるかするまで続けられる。見つかったページブロック が当初の要求よりも大きい場合、適切なサイズになるまで分割される。ブロッ クは 2 の n 乗のページから成り立つので、この分割プロセスは半分に割るだ けの簡単なものである。空ブロックは適当なキュー上に並べられ、割り当てら れるページブロックが、呼び出しをしたプロセスに返される。 free_area 物理メモリ +-----+ +-----------+ | | | | | 5 | | | |-----| |-----------| | | | |8 | 4 | map | | |-----| +------+ |-----------| | |--->+------+ | / / / / |7 mem_map_t mem_map_t | 3 | map | / / / / | +----+ +----+ |-----| +------+ |-----------| | |<----| |<----| |--->| | | / / / / |6 | |---->| |---->| 2 | +------+ | / / / / | | 56 | | 4 | |-----| map |-----------| +----+ +----+ | |--->+------+ | / / / / |5 mem_map_t | 1 | | | | / / / / | +----+ |-----| | | |-----------| | |<----| | +------+ | / / / / |4 | |---->| 0 |-+ map | / / / / | | 0 | +-----+ | +------+ |-----------| +----+ +->| | | |3 | | | | | | |-----------| | | | |2 | | | | +------+ |-----------| * | |1 /| | | | |-----------| | / / / / | / / / / |0 PFN / / / / Free PFN | / / / / | +-----------+ 図表(3.4) free_area のデータ構造 たとえば、図表(3.4)では、2 つのページから成るブロックが要求された場 合、 (ページフレーム番号 4 から始まっている) 4 つのページから成る最初 のブロックが、2 つのページから成るふたつのブロックに分割される。ページ フレーム番号 4 から始まる最初のブロックは、割り当てられたページとして 呼び出したプロセスに戻され、ページフレーム番号 6 から始まる二番目のブ ロックは、``free_area'' 配列の要素 1 上にある 2 つのページから成る空ブ ロックとしてキュー上に置かれる。 4.4.2. ページの解放 ページブロックの割り付けは、大きな空ページを小さく分割するので、断片化 したメモリ(framgment memory)を生じやすい。 [see: free_pages(), in mm/page_alloc.c] ページを解放するコード(page deallocation code)は、可能なときはいつで も、空のページをより大きなブロックに連結する。実際、ブロックをまとめて より大きなブロックを簡単に作れることを考えると、この(2 の n 乗とい う)ページブロックサイズには重要な意味がある。 ページブロックが解放されると、同じサイズの近接ブロックもしくは相 棒(buddy) ブロックが空かどうかチェックされる。もしそうなら、新しく解放 されたページブロックと結合して、一段階大きいサイズの新しい空ブロックを 作成する。ふたつのページブロックが結合されてより大きな空のページブロッ クが形成されるたびに、ページ解放コードは、そのブロックをさらに大きなも のにしようとする。このようにして、空のページブロックは、メモリの使用方 法として許される限り大きなブロックになっていく。 たとえば、``図表(3.4)''では、ページフレーム番号 1 が解放されると、すで に解放されていたページフレーム番号 0 と結合される。そして、 2 つのペー ジから成る空ブロックとして ``free_area'' の要素 1 上のキューに置かれ る。 4.5. メモリマッピング イメージが実行されるとき、その実行イメージの内容は、プロセスの仮想アド レス空間に置かれなければならない。このことは、実行イメージで使用するた めにリンクされた共有ライブラリの場合でも同じである。実行ファイルは実際 に物理メモリに置かれるわけではなく、プロセスの仮想メモリにリンクされる だけである。そして、実行中のアプリケーションからそのプログラムの一部が 参照されると、実行イメージ内のその部分のイメージがメモリの中に置かれ る。あるイメージを、プロセスの仮想アドレス空間にリンクさせるこの仕組み は、メモリマッピング (memory mapping)と呼ばれる。 +--------------+ | | | | | | +------>|--------------| | | | | | | | | | | | Virtual Area | vm_area_struct | | | +------------+ | | | | vm_end |------------------------+ | | |------------| | | | vm_start |------------------------------->|--------------| |------------| | | | vm_flags | | | |---------- | | | | vm_inode | | | |------------| 仮想メモリ | | | vm_ops |----------> 操作ルーチン | | |------------| | | | | open() | | | | close() | | |------------| unmap() | | | vm_next | protect() | | +------------+ advise() | | nopage() | | wppage() | | swapout() | | swapin() | | +--------------+ 図表3.5 仮想メモリのエリア すべてのプロセス仮想メモリは、``mm_struct''データ構造体で表現される。 これには、現在実行中のイメージ(たとえば、 bash)に関する情報が含まれる と同時に、いくつかの ``vm_area_struct'' データ構造体へのポインタも含ま れる。 vm_area_struct データ構造体には、その仮想メモリ領域の始点と終 点、そのメモリへのプロセスのアクセス権、およびそのメモリに対する一連の 操作ルーチンが含まれている。これらの操作ルーチンは、Linux がこの仮想メ モリの領域を操作する際に使わなければならない一連のルーチンである。たと えば、仮想メモリ操作のひとつに訂正処理があり、それはプロセスが仮想メモ リにアクセスしようとしたが、 (ページフォルトによって)その仮想メモリが 実際には物理メモリ上にないことが分かった時になされる。この操作 は、``nopage'' 操作である。 nopage 操作が利用されるのは、Linux のデマ ンドページングにより実行イメージのページが物理メモリ内にページを割り当 てられるときである。 実行イメージがプロセス仮想アドレスにマップされると き、``vm_area_struct'' データ構造が一組生成される。 vm_area_struct デ ータ構造体はそれぞれ実行イメージの一部を表している。実行コード、初期化 されたデータ(変数)、初期化されないデータ等につき、ひとつづつの構造体が 生成される。Linux はいつくかの標準的な仮想メモリ操作をサポートしている ので、vm_area_struct データ構造体が生成されると、仮想メモリ操作の適切 なセットがそれらと結びつけられる。 4.6. デマンドページング 実行イメージがいったんメモリにマップされてプロセス仮想メモリに入ると、 イメージの実行開始が可能となる。しかし、そのイメージの最初の部分だけし か物理メモリに入っていないので、すぐに物理メモリ内にない仮想メモリ領域 へとアクセスがある。プロセスが、有効なページテーブルエントリを持たない 仮想アドレスにアクセスしたとき、プロセッサは Linux にページフォルトの 発生を報告する。 [see: handle_mm_fault, in mm/memory.c] ページフォルトは、そのページフォルトが発生した仮想アドレスと、発生の原 因となったメモリアクセスのタイプとの情報を含んでいる。 Linux は、ページフォルトが起こったメモリ領域を示している ``vm_area_struct'' 構造体を探す。 vm_area_struct データ構造での検索は ページフォルト処理の効率を決定する上で非常に重要なので、それら は、AVL(Adelson-Velskii and Landis)木構造にリンクされている。もし、 フォルトが起こった仮想アドレスに関する vm_area_struct データ構造体が存 在しない場合、このプロセスは、無効な仮想アドレスにアクセスしたことにな る。Linux は、そのプロセスに SIGSEGV シグナルを送り、もしそのプロセス が当該シグナルを処理しない場合は、そのプロセスを終了させる。 次に、Linux は、その仮想メモリ領域のアクセス権限に違反して起こったペー ジフォルトのタイプをチェックする。プロセスが、たとえば読み出ししか許さ れない領域に書き込みをしようとした場合のように、無効な方法でメモリにア クセスしている場合、そのプロセスにはメモリエラー(memory error)のシグナ ルも送られる。 Linux がページフォルトが正当なものと判断した場合、ページフォルトに対処 しなければならない。 [see: do_no_page(), in mm/memory.c] Linux は、スワップファイル内のページとディスクの他の場所にある実行イメ ージの一部とを区別しなければならない。それをするためには、フォルトが起 こった仮想アドレスのページテーブルエントリが使用される。 フォルトページのページテーブルエントリが無効だが空でない場合、そのペー ジフォルトは、現在スワップファイルに保存されているページのものであ る。Alpha AXP のページテーブルエントリの場合、有効ビットは設定されてい ないが ``PFN'' フィールドに 0 でない値が設定されているエントリがある。 その場合、PFN フィールドには、スワップのどこに(そしてどのスワップファ イルに)当該ページが保存されているのかに関する情報が記されている。ス ワップファイル内のページを操作する方法は、この章の後の部分で紹介され る。 ``vm_area_struct'' データ構造体のすべてが、仮想メモリ操作のセットを 持っているわけではなく、nopage 操作すらもっていない場合もある。これ は、デフォルトでは Linux が、アクセスの問題を解決するために、新しい物 理ページを割り付け、そのための有効なページテーブルエントリを作成するよ うになっているからである。(vm_area_struct に)この仮想メモリ領域に対す る nopage 操作が存在する場合、Linux はそのルーチンを使用する。 汎用的な Linux の nopege 操作ルーチンは、メモリにマップされた実行イメ ージに対して使用されるものなので、その nopage 操作ルーチンは、ページ キャッシュを利用して、必要なイメージページを物理メモリに持ってくる。 [see: filemap_nopage(), in mm/filemap.c] 方法はどうあれ、必要なページが物理メモリに置かれた場合は、プロセスペー ジテーブルはアップデートされる。それらのエントリをアップデートするため にはハードウェア固有の処理が必要になるかもしれない。特に、プロセッサが アドレス操作バッファ(Translation Look-aside Buffer, TLB)を利用している 場合はそうである。これでようやくページフォルトは処理されたので、フォル トは解除され、プロセスは、仮想メモリアクセスでフォルトを起こした命令の 時点から再スタートされる。 4.7. Linux のページキャッシュ page_hash_table +--------------+ | | mem_map_t mem_map_t |--------------| +-----------+ +-----------+ | |------>| inode |12 +-->| inode |12 |--------------| |-----------| | |-----------| | | | offset |0x8000 | | offset |0x2000 |--------------| |-----------| | |-----------| | | | | | | | |--------------| | | | | | | | | | | | | |--------------| |-----------| | |-----------| | | | next_hash |--------+ | next_hash | | | |-----------| |-----------| | | | prev_hash | | prev_hash | | | |-----------| |-----------| | | | | | | | | +-----------+ +-----------+ | | |--------------| | | |--------------| | | +--------------+ 図表3.6 Linux のページキャッシュ Linux のページキャッシュの役割は、ディスク上のファイルへのアクセス速度 を上げることである。メモリにマップされたファイルはページ単位で読み出さ れ、それらのページはページキャッシュに保存される。図表(3.6)では、ペー ジキャッシュが ``page_hash_table'' から構成されており、それが ``mem_map_t'' データ構造へのポインタの配列となっていることが示されてい る。 [see: include/linux/pagemap.h] Linux 内のファイルはそれぞれ VFS inode データ構造で識別される(詳細は、 ``「ファイルシステム」''の章で述べる)。VFS inode はシステム上ユニーク であり、特定の単一のファイルに関する完全な識別子となる。ページテーブル へのインデックスは、ファイルの VFS inode とそのファイルへのオフセット から導き出される。 メモリにマップされたファイルからページが読み出されるとき、たとえばデマ ンドページングでページがメモリに戻されるときなどには、ページはページ キャッシュから読み出される。ページがキャッシュ内にある場合は、その キャッシュ内のページを表す ``mem_map_t'' データ構造体へのポインタが、 ページフォルト処理コードに返される。そうでない場合は、ページは、そのイ メージを保存しているファイルシステムからメモリ内へと取り出さる必要があ る。その場合、Linux は物理ページを割り付けて、ディスク上のファイルから そのページを読み出す。 要求がなくても、Linux の側でファイル内の次のページを読み出しておくこと も可能である。この単一ページの先読みは、もしプロセスがファイル内のペー ジに連続してアクセスしている場合は、そのプロセスのために次のページをメ モリ内に待機させることを意味する。 イメージが読み出されて実行されていくと、ページキャッシュはだんだんと大 きくなる。必要のなくなったページ、たとえばどのプロセスからもアクセスさ れなくなったイメージは、キャッシュから削除される。メモリの使用量が増え ると、物理ページが不足してくることがある。その場合、 Linux はページ キャッシュのサイズを小さくする。 4.8. スワップアウトとページの破棄 物理メモリが少なくなると、Linux のメモリ管理サブシステムは、物理ページ を解放するよう努力しなければならない。このタスクは、カーネルスワップデ ーモン (kernel swap daemon)(kswapd)の仕事である。カーネルスワップデー モンは、カーネルスレッド(kernel thread)という特別なプロセスである。カ ーネルスレッドは仮想メモリを持たず、そのかわり物理アドレス空間内におい てカーネルモードで実行される。このカーネルスワップデーモンは、その役割 が単にページをシステムのスワップファイルに書き出すだけでないことからす ると、やや命名に難があるといえる。その役割は、システム内で充分な空ペー ジを確保して、メモリ管理システムの操作効率を維持することである。 カーネルスワップデーモン(kswapd)は、起動時にカーネル初期化プロセスによ り起動され、カーネルスワップタイマーが定期的に時間切れになるのを待って いる。 [see: kswapd(), in mm/vmscan.c] タイマーが時間切れになると、スワップデーモンはシステムの空ページの数が 減りすぎていないかどうかを確認する。kswapdは、``free_pages_high'' と free_pages_low のふたつの変数を使ってページを解放するかどうかを判断す る。システム内の空ページの数が free_pages_high よりも多い限り、カーネ ルスワップデーモンは何もしない。タイマーが切れるまで再び休憩する。この チェックに際して、カーネルスワップデーモンは、スワップファイルに現在書 き出されているページ数を考慮に入れる。カーネルスワップデーモンは、その 数を、nr_async_pages に保持している。この数は、スワップファイルに書き 出されるためにページがキューに入れられたときに増加し、スワップデバイス への書き込みが完了したときに減少する。free_pages_high と free_page_low とは、システム起動時にセットされ、システム内の物理ページの数と関連付け られる。システム内の空ページの数が free_page_high 以下になる か、free_pages_low 以下にまで落ち込んでしまった場合には、カーネルス ワップデーモンは、3 つの方法を使ってシステムで使用されている物理ページ の数を減らそうとする。 o バッファとページキャッシュのサイズを減らす。 o System V 共有メモリページをスワップアウトさせる。 o ページをスワップアウトさせて、破棄する。 システムの空ページの数が free_pages_low 以下になっている場合、カーネル スワップデーモンは、次の実行時までに 6 つのページを解放する。でなけれ ば、3 つのページを解放する。上記の方法は、充分なページが解放されるまで 順番に繰り返される。カーネルスワップデーモンは、物理メモリを解放すると き最後に使った方法を覚えている。実行の際はいつも、前回の最後に成功した 方法を使用してページを解放しようとし始める。 充分な空ページが出来たら、スワップデーモンは、タイマーが切れるまで再び 休憩に入る。カーネルスワップデーモンがページを解放した理由が、システム 内の空ページの数が free_pages_low を下回ったからであった場合は、その休 憩時間は、通常の半分に短縮される。空ページの数が free_pages_low の数を 超えてしまえば、カーネルスワップデーモンは通常のタイマー間隔での休憩に 戻る。 4.8.1. ページキャッシュとバッファキャッシュのサイズの縮小 ページキャッシュとバッファキャッシュに保存されているページは、解放して ``free_area'' 配列に入れるべき有力な候補者である。ページキャッシュに は、メモリにマップされたファイルのページが含まれているので、メモリを満 杯にしている不要なページが含まれている場合がある。同様に、バッファ キャッシュには、物理デバイスに対する読み書きの結果としてのバッファが含 まれているので、ここにも必要のないバッファが含まれている場合がある。シ ステム内の物理ページが不足し始めると、これらのキャッシュからページを削 除することは相対的に容易になる。ページをメモリからスワップアウトさせる 場合と異なり、それらは、物理デバイスへの書き込みを必要としないからであ る。キャッシュから不要なページをいくつか破棄したとしても、物理デバイス やメモリにマップされたファイルへのアクセスが遅くなる以外には、目立った 有害な副作用はない。しかし、もしそれらのキャッシュからページをすべて破 棄したとしたら、全プロセスが等しく悪影響を受ける。 カーネルスワップデーモンは、これらのキャッシュを縮小しようとするたび に、 page の配列 ``mem_map'' 内のページブロックを調べて、物理メモリか ら削除できるものがないかどうかを確認する。 [see: shrink_mmap(), in mm/filemap.c] カーネルスワップデーモンが熱心にスワップ処理をしている場合、すなわちシ ステムの空ページの数が危険なほど低下している場合には、より大きなサイズ のページブロックが調査される。ページブロックの調査は循環的な方法でなさ れる。すなわち、メモリマップを縮小しようとするたびごとに違うサイズのペ ージブロックが調査される。この方法は、時計の分針に似ていることからク ロックアルゴリズム (clock algorithm)と呼ばれ、page の配列 ``mem_map'' 全体が、一度に数ページ単位で調査される。 個々のページを調査する際は、そのページがページキャッシュかバッファ キャッシュにキャッシュされているかどうか確認される。注意すべきなのは、 この時点では共有ページは破棄されないこと、および同時に両方のキャッシュ に入っているページは存在しないことである。当該ページがどちらのキャッ シュにも入っていない場合、page の配列 mem_map の次のページが調査され る。 ページがバッファキャッシュにキャッシュされるのは(あるいは、バッファが ページキャッシュにキャッシュされるのは)、バッファの割り付けと解放とを より効率的に行うためである。メモリ縮小コードは調査が終わったページの バッファから、解放しようとする。 [see: try_to_free_buffer, in fs/buffer.c] それらすべてのバッファが解放 されると、それらを含むページも解放される。調査されたページが Linux の ページキャッシュに入っている場合、それはページキャッシュから削除され解 放される。 この作業によって充分なページが解放されたら、カーネルスワップデーモン は、次の定期的呼び出しまで休憩する。解放されたページはどのプロセスの仮 想メモリの一部にもなっていなかったので(それらはキャッシュされたページ であったので)、どのページテーブルもアップデートされる必要がない。 キャッシュされたページが充分に解放されない場合、スワップデーモンは共有 ページのいくつかをスワップアウトしようとする。 4.8.2. System V 共有メモリページのスワップアウト System V 共有メモリとは、プロセス間通信のメカニズムであり、ふたつ以上 のプロセスがお互いに情報を交換するために仮想メモリを共有することを許す 仕組みである。この方法でどのようにプロセスがメモリを共有するかについて は、 ``「プロセス間通信の仕組み」''の章で詳細に解説する。今のとこ ろ、System V 共有メモリのそれぞれのエリアは、``shmid_ds'' データ構造体 により記述されているとだけ述べておく。これには、``vm_area_struct'' デ ータ構造体のリストへのポインタが含まれている。その個々の vm_area_struct データ構造体は、仮想メモリの当該領域を共有するプロセス のものであり、それぞれのプロセスの仮想メモリのどこに System V 共有メモ リの領域があるのかを記述するものである。System V 共有メモリのための vm_area_struct データ構造体はそれぞれ、``vm_next_shared'' と vm_prev_shared ポインタを使ってお互いにリンクされている。個々の shmid_ds データ構造体には、ページテーブルエントリのリストが含まれてい て、それぞれ共有仮想ページがマップされている物理ページを記述している。 カーネルスワップデーモンは、System V 共有メモリページをスワップアウト する時にもクロックアルゴリズムを使用する。それが実行される際はいつも、 どの共有仮想メモリエリアのどのページを最後にスワップアウトしたか、カー ネルスワップデーモンは覚えている。それを実現するために、スワップデーモ ンはふたつのインデックスを使用する。ひとつは、``shmid_ds'' データ構造 体のセットへのインデックスであり、もうひとつは、System V 共有メモリの 該当エリアに関するページテーブルエントリのリストへのインデックスであ る。それによって、 System V 共有メモリに対して公正な負担が課されるよう になっている。 System V共有メモリの仮想ページに対する物理ページフレーム番号は、該当す る仮想メモリエリアを共有するすべてのプロセスのページテーブル内に含まれ ているので、カーネルスワップデーモンはこれらすべてのページテーブルを書 き換えて、該当ページはもはやメモリ内にはなく、スワップファイルに保存さ れているということを示さなければならない。共有ページはひとつずつスワッ プアウトされるので、カーネルスワップデーモンは、個々の共有プロセスのペ ージテーブル内のページテーブルエントリを探す。(これは、個々の ``vm_area_struct'' データ構造体のポインタを追跡することにより実行され る。) もし、その System V 共有メモリエリアに対するプロセスのページエン トリーテーブルが有効ならば、スワップデーモンは、それを無効だがスワップ アウトされたページテーブルエントリである旨に書き換えて、その(共有)ペー ジのユーザ数カウントをひとつ減少させる。スワップアウトされた System V 共有ページのテーブルエントリのフォーマットには、``shmid_ds'' データ構 造体のセットへのインデックスと その System V 共有メモリエリアに対する ページテーブルエントリへのインデックスとが含まれる。 共有プロセスのページテーブルが完全に書き換えられてページカウントがゼロ になると、共有ページをスワップファイルに書き出すことが可能になる。その System V 共有メモリエリアに対するページテーブルエントリのなか で、shmid_ds データ構造体によって指示されたリスト内に存在するものは、 スワップアウトされたページテーブルのエントリ(swapped out page table entry)によって置き換えられる。スワップアウトされたページテーブルのエン トリは無効だが、スワップファイルをオープンするためのセットへのインデッ クスと、そのファイル内でスワップアウトされたページを探すためのオフセッ トとが含まれている。この情報が使用されるのは、そのページが物理メモリ内 に再度戻されるときである。 4.8.3. スワップアウトされたか破棄されたページ スワップデーモンはシステム内のプロセスを順番に調べて、スワップすべき有 力候補がいないかどうかを探す。 [see: swap_out(), in mm/vmscan.c] 候補者とは、スワップ可能で(不可能なプロセスもある)、メモリ内からスワッ プか破棄することが可能なひとつ以上のページを持っているプロセスである。 ページが物理メモリからスワップアウトされてシステムのスワップファイルに 入れられるのは、ページ内のデータがスワップ以外の方法では元に戻せない場 合だけである。 実行イメージの内容の多くはそのイメージのファイルからメモリ内に置かれて いるので、その内容の再読み出しは簡単に実行できる。たとえば、あるイメー ジの実行命令は、そのイメージによって修正されることはないので、それがス ワップファイルに書き込まれることは決してない。それらのページは単に破棄 されるだけである。プロセスから再度参照される時は、実行イメージからメモ リ内に戻せばいいからである。 スワップすべきプロセスが見つかったら、スワップデーモンはそのプロセスの 仮想メモリ領域を走査して共有されたりロック(lock)されたりしていないエリ アを探す。 Linux は、選択したプロセスのスワップ可能なすべてのページを スワップするわけではない。むしろ、ごく少数のページだけを削除する。メモ リ内でロックされている場合、ページはスワップも破棄もできない。 (原注: この操作は、当該プロセスの ``mm_struct'' 上でキューイングされた vm_area_struct 構造体のリストにある mv_next ポインタを順に検査すること で実行される。) Linux のスワップアルゴリズムは、ページエイジング(page aging)を使ってい る。 [see: swap_out_vma(), in mm/vmscan.c] ページはそれぞれ(``mem_map_t'' 構造体の中に) カウンタを持っていて、そ れによってカーネルスワップデーモンにページがスワップされるべきかどうか を伝えている。ページが使用されなかったりアクセスによって若返らない場合 に、ページは歳を取る。スワップデーモンは高齢のページだけをスワップアウ トさせる。ページを最初に割り付けるときのデフォルトの操作では、最初に 3 の寿命を与える。アクセスがあるたびに、年齢は 3 から最大 20 まで上が る。カーネルスワップデーモンが実行されると、そのたびにページを加齢し、 その年限をひとつずつ引いていく。こうしたデフォルトの操作は変更可能であ り、それゆえ、設定値(と、その他のスワップに関係した情 報)は、``swap_control'' データ構造体に保存されている。 ページの寿命が尽きたら(age=0)、スワップデーモンはさらにその処理を進め る。修正されているページ(dirty page)は、スワップアウトすることができ る。Linux は、 PTE 内のアーキテクチャに固有のビットを使ってそれを区別 する。 (``図表(3.2)'' 参照) しかし、dirty page がすべてスワップファイ ルに書き出される必要はない。あらゆるプロセスの仮想メモリ領域は、それ自 身に必要なスワップ操作が指定されているかもしれず(それ は、``vm_area_struct'' 内の vm_ops ポインタで示される)、そこでの方法が 利用されるからである。そうでない場合、スワップデーモンは、ページをス ワップファイルに割り付け、スワップデバイスに書き出す。 スワップアウトされたページのページテーブルエントリは、無効とマークされ たエントリによって置き換えられるが、そのエントリには、ページのスワップ ファイル内の位置に関する情報が含まれている。その情報は、スワップファイ ル内のどこにページが保存されているかを表すオフセット値と、どのスワップ ファイルが使用されたかを示す指示子から成る。どのようなスワップメソッド が使用された場合でも、もとの物理ページは解放され、 ``free_area'' の中 に入れられる。内容の変更されていない(あるいは、修正されていない(not dirty))ページは、破棄されて、再利用のために free_area に入れられる。 スワップ可能なプロセスのページが、充分な数だけスワップアウトされるか破 棄された場合、スワップデーモンは再度スリープする。次に起きるときは、シ ステム内の次のプロセスを対象として考慮する。この方法によって、スワップ デーモンはシステムがバランスを取り戻すまで、プロセスの物理ページを少し ずつ解放していく。この方法は、プロセス全体をスワップアウトするよりも ずっと公平である。 4.9. スワップキャッシュ ページをスワップファイルに移す際、Linux は必要がなければページを書き込 まない。あるページがスワップファイルと物理メモリの両方に存在するときが ある。そのようなことが起こるのは、スワップされてメモリから削除されてい たページがプロセスに再度アクセスされたことでメモリに戻った場合である。 メモリにあるページが書き込みを受けていない限り、スワップファイルにある ページのコピーは有効なままである。 Linux はそのようなページを監視するためにスワップキャッシュを使う。ス ワップキャッシュはページテーブルエントリのリストであり、エントリはシス テム上の物理ページごとに存在する。これは、スワップアウトしたページのペ ージテーブルエントリであり、そのページがどのスワップファイルに保存され ているかということと、そのスワップファイル内での位置とが記されているも のである。スワップキャッシュエントリがゼロでない場合、それは、そのペー ジがスワップファイル内にあり、しかもそれが変更されていないということを 表している。ページの中味がその後で (書き込みをされて)変更された場合、 そのエントリはスワップキャッシュから削除される。 Linux が物理ページをスワップファイルにスワップアウトする必要がある場 合、スワップキャッシュをまず調べる。スワップキャッシュにそのページ用の 有効なエントリがあるなら、そのページをスワップファイルに書き出す必要は ない。なぜなら、メモリ内のそのページは、スワップファイルから最後に読み 出されて以来変更されていないからである。 スワップキャッシュのエントリはスワップアウトされたページに関するページ テーブルのエントリである。それらは無効とマークされているが、どのスワッ プファイルのどこでそのページが見つかるかを Linux に知らせる情報を含ん でいる。 4.10. スワップのページイン 書き込みをされてスワップファイルに保存されたページが再度必要となること がある。たとえば、アプリケーションが仮想メモリのある領域に書き込みをし たいのだが、その内容が物理ページからスワップアウトされていた場合などで ある。物理メモリ上にない仮想メモリのページにアクセスがあった場合、ペー ジフォルトが起こる。ページフォルトとは、プロセッサがオペレーティングシ ステムにシグナルを送って、仮想アドレスを物理アドレスに変換できないこと を伝えることである。この場合、原因は、仮想メモリの該当ページの情報を記 しているページテーブルのエントリが、ページがスワップアウトされる際に無 効とマークされるからである。プロセッサはその仮想アドレスを物理アドレス に変換できないので、制御をオペレーティングシステムに手渡して、仮想アド レスがフォルトを起こしたこととそのフォルトの理由とを伝える。この情報の フォーマットとプロセッサがオペレーティングシステムに制御を渡す方法と は、プロセッサ固有のものである。 [see: do_page_fault(), in arch/i386/mm/fault.c] プロセッサ固有のページフォルト処理コードは、まず必要な ``vm_area_struct'' データ構造を探さなければならない。 vm_area_structは、仮想メモリのエリアを記述したものであり、そのフォルト が起きた仮想メモリアドレスを含んでいるからである。フォルト処理コード は、それらの構造体を検索し、フォルトを起こした仮想アドレスを含むものを 見つけだす。これらは、決して長時間掛かってはいけない(time critical) コ ードや処理であるので、vm_area_struct 構造体は、検索時間が最短になるよ うに並べられている。 プロセッサ固有の動作を適切に処理し、フォルトが起きた仮想アドレスが有効 な仮想メモリ領域にあることが分かった場合、その後のページフォルト処理 は、Linux が稼働するどのプロセッサにも汎用的で適用可能なものとなる。 [see: do_no_page(), in mm/memory.c] 汎用のページフォルト処理コードは、フォルトを起こした仮想アドレスのペー ジテーブルエントリを探す。探し出したページテーブルエントリがスワップア ウトされたページのものであった場合、Linux はページを物理メモリに戻さな ければならない。スワップアウトされたページに対するページテーブルのエン トリはプロセッサ固有のものだが、すべてのプロセッサはそれらのページを無 効とマークしてあり、そのページテーブルエントリにはスワップファイル内で 該当ページを見つける際に必要な情報が書き込まれている。Linux はこの情報 を使って、そのページを物理メモリに呼び戻す。 [see: do_swap_page(), in mm/memory.c] この時点で、Linux は、フォルトが起こった仮想アドレスを知っており、該当 ページがどこにスワップアウトされているかに関する情報を含むページテーブ ルエントリを持っている。``vm_area_struct'' 構造体にはある``ルーチ ン''へのポインタが含まれている場合があり、そのルーチンは、その vm_area_struct 構造体で記述している仮想メモリ領域の任意のページを物理 メモリへと戻す処理をする。これは、スワップイン(swapin)操作と呼ばれる。 [see: shm_swap_in(), in ipc/shm.c] その仮想メモリエリアにスワップイン処理ルーチンが存在する場合、Linux は そのルーチンを使用する。これは実際、スワップアウトされた System V 共有 メモリページが処理される方法でもある。スワップアウトされた System V 共 有ページのフォーマットは通常のスワップアウトされたページのフォーマット とは少し異なるので、それには特別な処理方法が必要とされるからである。し かし、そうしたスワップイン処理ルーチンが存在しない場合もあるので、その 際には、Linux は、そのページが特別な処理を必要としない通常のページであ ると推定する。 [see: swap_in(), in mm/page_alloc.c] そして、空の物理ページを割り当て、スワップファイルからスワップアウトさ れたページを読み出す。ページがスワップファイルのどこに(およびどのス ワップファイルに)あるかを示す情報は、無効とマークされたそのページテー ブルエントリから取得される。 ページフォルトを発生させたアクセスが書き込みアクセスでない場合、ページ はスワップキャッシュにそのまま残され、そのページテーブルエントリも書き 込み可能とはマークされない。その後、そのページへの書き込みアクセスが発 生すると、その時点で別なページフォルトが生じ、該当するページ dirty と マークされ、そのエントリはスワップキャッシュから削除される。そのページ が書き込みされずに、再度スワップアウトされる必要が生じた場合、そのペー ジは既にスワップファイルに存在するので、Linux はページをあらためてス ワップファイルに書き込みせずに済ませることができる。 ページがスワップファイルから取ってこられるきっかけを作ったアクセスが書 き込み操作であった場合、そのページはスワップキャッシュから削除され、そ のページテーブルエントリは、dirty かつ書き込み可能であるとマークされ る。 (-- (脚注1)紛らわしいことに、この構造体は、ページ構造体とも呼ばれてい る。 (脚注2)ここに参考文献を置くこと。--) 5. プロセス この章ではプロセスとは何かを説明し、Linux がシステム上でプロセスをどの ように生成、管理、削除しているのかについて解説する。 プロセスはオペレーティングシステム内でタスクを実行する。プログラムと は、実行イメージとしてディスクに保存されたマシン語の命令とデータとの セットである。したがって、プログラムとは受動的な実体である。反対に、プ ロセスとは実行中のコンピュータプログラムであると考えることができる。プ ロセスはダイナミックな実体であり、マシン語の命令がプロセッサにより実行 されるにつれて常に変化してゆく。プログラムという概念が命令とデータを含 むものであるように、プロセスも、プログラムカウンタ(program counter)な ど CPU の全レジスタ情報やプロセススタックを含む概念であり、それらが保 持する一時データであるルーチンパラメータや戻りアドレス、保存された変数 などを包含する概念である。現在実行中のプログラムもしくはカレントプロセ ス(current process)とは、マイクロプロセッサでの現在の活動すべてを含む 概念である。 Linux はマルチプロセスのオペレーティングシステムである。プロセスとは個 別のタスクであり、それぞれが独自の権限と責任を持つ。ひとつのプロセスが クラッシュした場合でも、それが他のプロセスをクラッシュさせることはな い。個々の独立したプロセスが独自の仮想アドレス空間で実行されていて、カ ーネルが管理する安全な仕組みを使う場合を除いては、他のプロセスと相互に 影響を与えあうことはない。 プロセスは、そのライフサイクルの中で、多くのシステムリソース(system resources)を使用する。命令を実行するときにはシステムの CPU を使用し、 自分自身とそのデータを保持するにはシステムの物理メモリを使用する。ファ イルシステム内でファイルをオープンして使用し、システムの物理デバイスを 直接・間接に利用する場合もある。Linux は、プロセス自体とそれが持つシス テムリソースを常に監視することで、システム内の当該プロセスとその他のプ ロセスを公平に管理しなければならない。ひとつのプロセスがシステムの物理 メモリや CPU を独占したとしたら、それはシステムの他のプロセスに対して 公平とは言えない。 システムの最も貴重なリソースは、CPU である。それは通常ひとつしかない。 Linux はマルチプロセス(multiprocessing)のオペレーティングシステムであ り、その目標は、ひとつのプロセスをシステムの個別の CPU 上で常時走らせ て、CPU の使用効率を最大限に上げることである。もしプロセスの数が CPU の数を上回る場合(通常はそうである)、残りのプロセスは、プロセスの実行が 終わって CPU が空くまで待たなければならない。マルチプロセッシングのア イデアは単純である。プロセスが実行されると、通常何らかのシステムリソー スのために待ち状態となり、リソースが得られると実行を再開する。 DOS の ような単一プロセス(uniprocessing)のシステムでは、CPU はその間、単に 待っているだけで、待ち時間は無駄になる。マルチプロセッシングシステムで は、多くのプロセスが同時にメモリに保持されている。プロセスに待ち時間が 生じたとき、オペレーティングシステムはそのプロセスから CPU を取り上げ て、より優先すべき他のプロセスにそれを与える。次に実行するプロセスとし てどれが最も適切かを決定するのがスケジューラ(scheduler)であり、Linux は、公平を期すためにいくつかのスケジューリングの戦略を使っている。 Linux は、多くの実行ファイルフォーマットをサポートしている。ELF はその ひとつであり、Java もそうである。プロセスがシステムの共有ライブラリを 透過的に扱えるようにしなければならないのと同様に、それらのファイルフォ ーマットも透過的に扱われるよう管理されなければならない。 5.1. Linux のプロセス Linux がシステム上のプロセスを管理できるようにするため、個々のプロセス は、 ``task_struct'' というデータ構造体で表現される。(task と process という用語は、Linux では同義として使われている。) [see: include/linux/sched.h] その ``task'' 配列は、システム上のすべての task_struct データ構造に対 するポインタの配列である。このことが意味するのは、システム上のプロセス の最大数がその task 配列のサイズによって制限されるということである。デ フォルトでは、それは 512 のエントリを持つ。プロセスが生成されると、新 しい task_struct がシステムメモリに割り当てられ、task 配列に追加され る。発見を容易にするために、現在実行中のプロセスは、``current'' ポイン タによってポイントされている。 通常タイプのプロセスだけでなく、Linux ではリアルタイムプロセス(real time process)もサポートしている。そうしたプロセスは外界イベントに即座 に反応しなければならない(したがって、「リアルタイム」という用語が使わ れる)ので、スケジューラからは通常のユーザプロセスとは異なる扱いを受け る。 ``task_struct'' のデータ構造体は極めて巨大で複雑だが、そのフィー ルドはいくつかの機能別エリアに分けることができる。 状態(State) プロセスは、実行中に、環境に応じて状態を変化させる。Linux のプロ セスは次のような状態(state)を取る。(``脚注1'') 実行中(Running) この状態のプロセスは、走行中(システム上で現在走行中のプロセ ス)か、直ぐに走行できる(システム上の CPU の割り当てを待ってい る状態)かのいずれかである。 待機中(Waiting) この状態のプロセスは、イベントかリソースを待っている状態であ る。Linux は、待機状態のプロセスを割り込み可 能(interruptible)か割り込み不可 (uninterruptible)かどうかで区 別する。割り込み可能な待機中のプロセスとはシグナルによって割 り込みを掛け得るものであり、割り込み不可な待機中のプロセスと は、直接ハードウェアの状態に依存するかたちで待機しているの で、どのような状況でも割り込みを掛けられないものをいう。 停止中(Stopped) この状態のプロセスは、通常はシグナルを受信したことで停止状態 にある。デバッグ中のプロセスは、停止状態に置くことが可能であ る。 ゾンビ(Zombie) これは終了したプロセスだが、何らかの理由で、``task'' 配列内に ``task_struct'' 構造体をまだ持っているものである。これは、そ の名の通り、既に死んでいるプロセスである。 スケジューリング情報(Scheduling Information) スケジューラ(scheduler)は、どのプロセスをシステム内で最も優先的 に実行すべきか公平に判断するために、この情報を必要とする。 識別子(Identifiers) システム内のすべてのプロセスはプロセス識別子を持つ。プロセス識別 子は、task 配列へのインデックスではなく、単なる番号である。また 個々のプロセスはユーザ ID およびグループ ID も持ち、それらの識別 子はシステムのファイルやデバイスに対するこのプロセスのアクセス制 御のために使用される。 プロセス間通信(Inter-Process Communication, IPC) Linux は、シグナル、パイプ、セマフォという古典的な Unix IPC メカ ニズムをサポートすると同時に、共有メモリ、セマフォ、メッセージ キューといった System V IPC メカニズムもサポートしている。Linux でサポートされている IPC のメカニズムについては、``「プロセス間 通信の仕組み」''の章で解説する。 リンク(Links) Linux システムでは、他のプロセスから完全に独立したプロセスは存在 しない。システム上のすべてのプロセスは、初期化プロセスを除いて、 親プロセス(parent process)を持つ。新しいプロセスは創造されるので はなく、コピーされるのであり、むしろ以前のプロセスのクローンとし て生まれるのである。プロセスを表現する ``task_struct'' 構造体は すべて、自分の子プロセス (child process)へのポインタを保持すると 同時に、その親プロセスと兄弟プロセス (siblings)(同じ親プロセスを 持つプロセス)へのポインタを保持している。 pstree コマンドを使え ば、Linux システム上で実行中のプロセス間の家族関係を見ることがで きる。 init(1)-+-crond(98) |-emacs(387) |-gpm(146) |-inetd(110) |-kerneld(18) |-kflushd(2) |-klogd(87) |-kswapd(3) |-login(160)---bash(192)---emacs(225) |-lpd(121) |-mingetty(161) |-mingetty(162) |-mingetty(163) |-mingetty(164) |-login(403)---bash(404)---pstree(594) |-sendmail(134) |-syslogd(78) `-update(166) さらに、システム内のあらゆるプロセスは、二重連結リストの中に保持さ れていて、そのリストのルートは、init プロセスの task_struct 構造体 となっている。このリストがあることで、Linux カーネルはシステム上の すべてのプロセスを見ることができる。ps や kill といったコマンドをサ ポートするために、カーネルにはこの機能が必要である。 時間とタイマー カーネルは、プロセスが生成された時間と、プロセスの生存時間内での CPU 使用時間とを監視している。クロックティック(``clock tick'') ごとに、カーネルは、走行中のプロセスのシステムモードおよびユーザ モードでの経過時間を変数 ``jiffies'' の値を使って更新する。ま た、Linux はプロセス固有のインターバルタイマー(interval timer)も サポートしているので、プロセスはシステムコールを利用してタイマー をセットし、所定時間が経過すると自分自身にシグナルを送るように設 定することもできる。そうしたタイマーは、一回だけ使うことも定期的 に使用することもできる。 ファイルシステム (File system) プロセスは、好きなときにファイルのオープンやクローズができる。プ ロセスの ``task_struct'' 構造体には、オープンされたそれぞれの ファイルのディスクリプタに対するポインタと、ふたつの VFS inode に対するポインタが含まれている。個々の VFS inode はファイルシス テム内のファイルかディレクトリをユニークに記述するもので、基盤と なるファイルシステムに対する統一的なインターフェイスを提供するも のである。 Linux 上でファイルシステムがどのようにサポートされて いるかは、 ``「ファイルシステム」''の章で述べられている。最初の VFS inode はプロセスのルート(そのホームディレクトリ)に対するもの であり、二番目の VFS inode はカレント(もしくは pwd ) ディレクト リに対するものである。ここで pwd という呼び方は、Unix のコマンド pwd (print working directory)に由来するものである。これらふたつ の VFS inode は、それぞれの ``count''フィールドを増加させて、ひ とつ以上のプロセスがそのディレクトリを参照中であることを示す。そ れゆえ、あるプロセスが pwd ディレクトリとしてセットしているディ レクトリは削除できず、同様にそのサブディレクトリも削除できないと いうことが起こる。 仮想メモリ (Virtual memory) 大部分のプロセスは、何らかの仮想メモリを持っている。(カーネルス レッド (kernel threads)とデーモン(daemons)はそうではない。) そし て、Linux カーネルは、そうした仮想メモリがどのようにシステムの物 理メモリにマップされているのかを常に監視しなければならない。 プロセッサ固有のコンテキスト (Processor Specific Context) プロセスとは、現在のシステム状態(system state)の総体であると考え ることができる。プロセスの走行中には、プロセッサのレジスタやス タック等が使用されている。これはプロセスのコンテキス ト(context)であり、プロセスがサスペンドされたときには、その CPU 固有の全コンテキストが、 プロセスの ``task_struct'' 構造体に保存 される。プロセスがスケジューラにより再始動されたときは、そのコン テキストが task_struct データ構造体から回復される。 5.2. 識別子(identifiers) Linux は、他のすべての Unix と同様に、ユーザ ID とグループ ID を使用し てシステム上のファイルやイメージへのアクセス権をチェックする。Linux シ ステム上のすべてのファイルは、所有者とパーミッションとを持っていて、そ のパーミッションによってシステム上のユーザが該当するファイルやディレク トリに対してどのようなアクセス権を持つのかを記述している。基本的なパー ミッションには、読み出し (read)、書き込み(write)、実行(execute)があ り、それらがユーザの 3 つのクラス、すなわちファイル所有者、特定のグル ープの属するプロセス、およびシステム上のすべてのプロセスというクラスご とに割り当てられている。それぞれのユーザのクラスが異なるパーミッション を持つということが可能である。たとえば、所有者には書き込みと読み出しが 可能なファイルでも、そのファイルのグループには読み出しのみが可能であ り、システム上の他のすべてのプロセスではまったくアクセスできないという 設定が可能である。 REVIEW NOTE: この部分を詳しくして、ビット割り当てについて説明するこ と。(777) 個人ユーザやシステム上のすべてのプロセスに対して、ファイルやディレクト リへのアクセス権権を与えるのではなく、ユーザグループ単位でそれらの権限 を割り当てるのが Linux の流儀である。たとえば、あるソフトウェアプロ ジェクトに参加したユーザ全員のグループを作成し、そのプロジェクトのソー スコードに対してそのグループだけが読み書きできるように権限を割り当てる ことができる。ひとつのプロセスが複数のグループに属することも可能であ り(デフォルトで最大 32 グループ )、それらの情報は、プロセスごとの ``task_struct'' 構造体の groups 配列に保持される。あるプロセスが複数の グループに属していて、そのうちひとつのグループがファイルへのアクセス権 をもっている場合、そのプロセスは当該ファイルへの適切なグループアクセス 権を持つことになる。 プロセスの ``task_struct'' 構造体には、プロセスとグループの識別子に関 する 4 組の情報がある。 uid と gid 実行中のプロセスを代表するユーザ識別子(User ID)とグループ識別 子(Group ID)。 実効(effective) uid と gid プログラムのなかには、実行プロセスの uid と gid をプログラム自体 の uid と gid に変更するものがある。(実行イメージ自体の uid と gid は、VFS inode の属性値として保持されている。) そうしたプログ ラムは、setuid プログラムと呼ばれていて、アクセス制限、特に個別 のユーザを持たずに実行されるネットワークデーモンのようなサービス へのアクセスを制限するひとつの方法として有益なものである。実効 uid と gid とは、setuid プログラムの uid と gid が使用され、 実(real) uid と gid も元のまま残される。カーネルが権限のチェック をするときはいつも、実効 uid と gid をチェックする。 ファイルシステムの uid と gid これらは通常、実効 uid や gid と同じであり、ファイルシステムへの アクセス権限をチェックする際に使用される。これらは、NFS でマウン トされたファイルシステムにとって必要なものである。ユーザモードの NFS サーバは、あたかもそれ自身がある特定のプロセスであるかのよう にファイルにアクセスする必要があるからである。その場合、(実効 uid と gid ではなく)ファイルシステムの uid と gid だけが変更され る。これは、悪意のあるユーザが NFS サーバに kill シグナルを送る ことができるという状態を避けるためである。それゆえ、kill シグナ ルは、特定の実効 uid および gid を持つプロセスに配信される。 保存された uid と gid これらは、POSIX 規格で義務付けられており、システムコール経由でプ ロセスの uid と gid を変更するプログラムによって利用される。それ らは、実 uid と gid が変更されている間、そのオリジナルな uid と gid を保存するために使用される。 5.3. スケジューリング すべてのプロセスは、ある時はユーザモードで、ある時はシステムモードで走 行する。基礎になるハードウェアがそれらのモードをサポートする方法はハー ドウェアによって異なるが、一般的には、ユーザモードからシステムモードへ の移行およびその逆に移行する場合の安全なメカニズムというものが存在す る。ユーザモードはシステムモードよりもずっと限られた特権しか持たない。 プロセスはシステムコールを実行するたびに、ユーザモードからシステムモー ドに移行して実行を継続する。このとき、カーネルはそのプロセスを代表して 実行を行う。 Linux では、プロセスは、現在走行中のプロセスを横取 り(preempt)しない。自分が実行されるために走行中のプロセスを止めること はできない。プロセスは、自分が何らかのシステムイベントを待たなければな らないとき、使用中の CPU を放棄するかどうかを決める。たとえば、あるプ ロセスは、ファイルから一文字読み出されるまで待たなければならないかもし れない。この待機状態は、システムモードにおけるシステムコールによって起 こる。すなわち、プロセスは、ファイルの open や read のためにライブラリ 関数を使用し、次にそのライブラリ関数がシステムコールを発して open され たファイルからバイト列を read する。この場合、待機中のプロセスはサスペ ンドされ、その間に他のより優先度の高いプロセスが選ばれて実行される。 プロセスは終始システムコールを実行するので、しばしば待機する必要が生じ る。その場合でも、もしプロセスが待機状態になるまで実行され続けるとする と、そのプロセスはやはり不公平な CPU 時間を消費していることになり、し たがって Linux は横取り可能(pre-emptive)なスケジューリングを使わざるを 得なくなるだろう。実際の仕組みでは、個々のプロセスは 200ms の短い時間 走行を許され、その時間が経過すると次のプロセスが選ばれて実行され、もと のプロセスは再度実行が可能になるまでしばらく待たされる。この短い時間間 隔は、タイムスライス(time-slice)と呼ばれる。 システム上の実行可能なすべてのプロセスの中から最も優先するプロセスを選 んで実行させる役割を担うのがスケジューラ(scheduler)である。 [see: shedule(), in kernel/sched.c] 実行可能なプロセスとは、CPU が空きさえすれば直ぐに走行可能なプロセスで ある。 Linux は、優先度(priority)ベースの適度にシンプルなスケジューリ ングアルゴリズムを使ってシステム上の走行プロセスを選択している。新しい プロセスを選んで実行するとき、Linux は、まず現在実行中のプロセスの状 態(state)、すなわちプロセッサ固有のレジスタ内容、およびプロセスの ``task_struct'' データ構造体に保持されたその他のコンテキストを保存す る。そして、新しいプロセスの状態(これもプロセッサ固有のものである)を復 元して実行し、システムの制御をそのプロセスに渡す。スケジューラがシステ ム上の実行可能なプロセスに対して公平に CPU 時間を割り当てるため に、Linux は個々のプロセスの task_struct 構造体内の情報を保持してい る。その情報とは次のようなものである。 ポリシー (policy) これは、該当プロセスに適用されるスケジューリングポリシーであ る。Linux のプロセスには、ノーマルとリアルタイムのふたつのタイプ がある。リアルタイムプロセスは、他のどのプロセスよりも高い優先度 を持つ。実行可能なリアルタイムプロセスがある場合、まずそれが常に 実行される。リアルタイムプロセスは、ラウンドロビン (round robin)と先入れ先出し(first in first out)というふたつのタイプのポ リシーを持ち得る。ラウンドロビンスケジューリングでは、個々の実行 可能なリアルタイムプロセスは順番に実行されるが、先入れ先出しスケ ジューリングでは、個々のプロセスは実行キューに置かれた順序で実行 され、その順番は絶対に変更されない。 優先度 (priority) これは、該当するプロセスに対してスケジューラが与える優先度であ る。これは、実行を許された時に、そのプロセスが走行可能な (``jiffies'' 値で表された)時間間隔でもある。システムコールや renice コマンドを使用すれば、プロセスの優先度を変更することがで きる。 リアルタイムプロセスの優先順位 (rt_priority) Linux はリアルタイムプロセスをサポートしており、それらはシステム 上の他のどの非リアルタイムプロセスよりも高い優先順位でスケジュー リングされる。このフィールドは、スケジューラによる個々のリアルタ イムプロセスに対する相対的な優先順位の付与を可能にするものであ る。リアルタイムプロセスの優先順位は、システムコールを使用すれば 変更できる。 カウンタ (counter) これは、当該プロセスが実行を許可される (``jiffies'' 値で表され た)時間間隔である。これはプロセスが初めて実行される時に priority に対して設定され、クロックティックごとに減少していく。 スケジューラは、カーネル内のいろいろな場所から実行される。それは現在走 行中のプロセスを待ち行列に入れたあとに実行され、またシステムコールが終 了してプロセスがシステムモードからプロセスモードに戻る直前にも実行され るときがある。スケジューラの実行が必要とされる理由は、システムタイマー がカレントプロセスのカウンター(counter)をその時にゼロにセットするから である。 [see: schedule(), in kernle/sched.c] スケジューラが実行されると、それは次のようなことをする。 カーネルワーク(kernel work) スケジューラは、ボトムハーフハンドラ(bottom half handlers)を実行 し、スケジューラのタスクキューを処理する。これらのライトウェイト カーネルスレッド (lightweight kernel threads)の詳細は、``「カー ネルメカニズム」''の章で解説する。 カレントプロセス (Current process) 次のプロセスを選んで実行する前に、現在走行中のプロセス(current process)が処理されなければならない。 走行中のプロセスのスケジューリングポリシーがラウンドロビンなら、 そのプロセスは実行キューの最後に置かれる。 タスクが割り込み可能(INTERRUPTIBLE)で、最後にスケジューリングさ れてからプロセスがシグナルを受け取っている場合、そのプロセスの ``task_struct'' の stateが、走行中 (RUNNING)となる。 現在走行中のプロセスが時間切れになった場合、その state は走行中 (RUNNING)となる。 現在走行中のプロセスの state が走行中(RUNNING)の場合、その state がそのまま維持される。 走行中(RUNNING)でも割り込み可能(INTERRUPTIBLE)でもないプロセスは 実行キューから削除される。すなわち、スケジューラが実行すべき最も 優先度の高いプロセスを探しているとき、それらは実行の対象とは見な されないということを意味する。 プロセス選択 (process selection) スケジューラは、実行キュー上のプロセスを見渡して、実行に最も値す るプロセスを探す。もしリアルタイムプロセスがあれば(それらはリア ルタイムスケジューリングポリシーを持つ)、それらは通常のプロセス よりも重視される。通常のプロセスの重要性はその counter で決まる が、リアルタイムプロセスに関してはその counter 値に 1000 が付加 される。すなわち、システム内に実行可能なリアルタイムプロセスがあ る場合、通常の実行可能などんなプロセスよりも必ず先に実行されると いうことである。現在走行中のプロセスは、タイムスライスの一部を消 費してしまっている(プロセスの counter 値が減少している)ので、シ ステム上の同じ優先度を持つプロセスよりも不利な立場に立つ。それは 当然そうなるべきである。多くのプロセスが同一の優先順位を持つ場 合、実行キューの先頭に最も近いものが選択される。現在のプロセスは 実行キューの最後に戻される。同一の優先順位にある多数のプロセス間 でシステムのバランスを保つため、個々のプロセスは順番に実行され る。これはラウンドロビンスケジューリングと呼ばれる。しかし、プロ セスはリソースの取得を待つ場合があるので、実行の順番は前後する傾 向がある。 スワッププロセス (swap process) 最優先で実行されるべきプロセスが現在のプロセスとは異なる場合、現 在のプロセスは停止状態にされ、新しいプロセスが実行される。プロセ スは実行中に CPU やシステムのレジスタや物理メモリを使用してい る。ルーチンをコールするたびに、プロセスはレジスタ内の引数をルー チンに渡しているし、呼び出したルーチンから戻されるアドレス等の保 存すべき値をスタックに積む場合もある。それゆえ、スケジューラが実 行されている際、スケジューラはカレントプロセスのコンテキス ト(``context'')で実行される。スケジューラは特権モードであるカー ネルモードで動作しているのだが、それでも実行されているのは、カレ ントプロセスである。そのプロセスが停止されるべきときは、プログラ ムカウンタやすべてのプロセッサのレジスタを含むそのマシンの状 態(machine state)全体が、プロセスの ``task_struct'' データ構造体 に保存されなければならない。その後で、新しいプロセスのマシンの状 態すべてがロードされる。これはシステムに依存する操作であり、まっ たく同じ方法で動作する CPU はないのだが、この処理に対しては通常 なんらかのハードウェアによる援助がなされる。 この(新旧両プロセス間での)プロセスコンテキストの入れ替 え(swapping)は、スケジューラの処理の一番最後で実行される。直前の プロセスのために保存されたコンテキストは、スケジューラの終わりの 時点でのそのプロセスが所持した、システムのハードウェアコンテキス トに関するスナップショットである。同様に、新しいプロセスのコンテ キストがロードされる時、それもまたスケジューラの最後の時点におけ るそのプロセスのスナップショットであり、それには、プロセスのプロ グラムカウンタやレジスタの内容などが含まれている。 直前のプロセスや新しいプロセスが仮想メモリを使用する場合、システ ムのページテーブルエントリはアップデートされる必要がある。これも またアーキテクチャ固有の処理である。Alpha AXP のようなプロセッサ の場合、アドレス変換バッファかキャッシュされたページテーブルエン トリを使用するので、直前のプロセスに属するそれらのキャッシュテー ブルのエントリを無効にする必要がある。 5.3.1. マルチプロセッサシステムでのスケジューリング 複数の CPU を持つシステムは Linux の世界ではそれほど多くはない が、Linux を SMP(Symmetric Multi-Processing)対応のオペレーティングシス テムにするための取り組みは既に数多くなされている。すなわち、システム内 の CPU 間の仕事量を均等に分担させる仕組みなどである。スケジューラほど この作業分担処理が顕著に現れるところはない。 マルチプロセッサのシステムでは、すべてのプロセッサが常時プロセスの実行 にあたっていることが望ましい。個々の CPU 上の現在のプロセスがタイムス ライスを使い切ったり、システムリソースを待たなければならなくなったと き、CPU は独立してスケジューラを実行する。SMP システムに関してまず注意 するべきことは、システム内のアイドル状態のプロセスはひとつではないとい うことである。シングルプロセッサのシステムなら、アイドルプロセスは ``task'' 配列の最初のタスクであるが、SMP システムでは CPU ごとにアイド ルプロセスが存在する。加えて、カレントプロセスも CPU ごとに存在するの で、SMP システムはカレントプロセスとアイドルプロセスとを CPU ごとに監 視しなければならない。 SMP システムでは、個々のプロセスの ``task_struct''_ 構造体は、現在プロ セスが走行中のプロセッサの番号 (``processor'')、およびそのプロセスが前 回実行されたプロセッサの番号(``last_processor'') という情報を含む。プ ロセスが選ばれるたびに違うプロセッサ上で実行されることがいけないという 理由はないが、Linux は、``processor_mask'' を使用して、プロセスがシス テム上の特定のプロセッサしか使わないよう制限することができる。ビット N がセットされると、そのプロセスはプロセッサ N 上で実行が可能になる。ス ケジューラが、実行すべき新しいプロセスを選んでいるとき、割り当てを行お うとするプロセッサ番号に適した番号が processor_mask にセットされていな いプロセスは候補から除外する。また、前回実行されたプロセッサとは異なる プロセッサ上でプロセスを実行するときは、しばしばパフォーマンス上のオー バーヘッドが生じるので、あるプロセスが、これから割り付けを行うプロセッ サを前回も使っていた場合は、スケジューラはそのプロセスに対し若干のアド バンテージを与える。 5.4. ファイル fs_struct inode +----------+ +------+ +-->| count | +----->| | task_struct | |----------| | | | | | unmask |0x022 | | | | | | |----------| | | | | | | | *next |------+ +------+ |-----------+ | |----------| | fs |-+ | *prev |------+ |-----------+ +----------+ | inode | files |-+ | +------+ |-----------+ | +----->| | | | | | | | | | files_struct | | | +---------------+ | | +-->| count | +------+ |---------------| | close_on_exec | |---------------| | open_fs | |---------------| inode | fd[0] | file +------+ |---------------| +--------------+ +-->| | | fd[1] |-->| f_mode | | | | |---------------| |--------------| | | | | | | f_ps | | | | | | |--------------| | +------+ | | | f_flags | | | | |--------------| | | | | f_count | | | | |--------------| | |---------------| | f_owner | | | fd[255] | |--------------| | +---------------+ | f_inode |-+ |--------------| | f_op |--->ファイル |--------------| 操作 | f_version | ルーチン +--------------+ 図表(4.1) プロセスのファイル 図表 4.1 では、ふたつのデータ構造があり、システム上の個々のプロセスに 関するファイルシステム固有の情報を記述している仕組みが示されている。 [see: include/linux/sched.h] ひとつめの、``fs_struct'' は当該プロセスの VFS ``inode'' とその umask に対するポインタを含んでいる。 umask とは新しいファイルが作成される際 のデフォルトモードであり、そのモードはシステムコールを介して変更可能で ある。 ふたつめのデータ構造である ``files_struct'' には、そのプロセスが現在使 用しているすべてのファイルに関する情報が含まれる。プログラムは標準入 力(standard input)から読み込みをし、標準出力(standard output)に書き出 す。すべてのエラーメッセージは標準エラー(standard error)に送られる。そ れらはファイルであったり、ターミナルへの入出力であったり、実デバイスで あったりするが、そのプログラムに関する限り、それらはすべてファイルとし て扱われる。すべてのファイルは自分自身の記述子(descriptor)を持ってお り、files_struct は、256 までの ``file'' データ構造体に対するポインタ を含むことができ、それらの各々がそのプロセスで使用されているファイルを 指し示している。``file'' 構造体の ``f_mode'' フィールドはそのファイル がどのモードで作成されているのかを示す。読み込み専用、読み書き可能、書 き込み専用などである。 ``f_pos'' はファイル内の位置情報を保持してい て、次の読み書き処理がファイル内のどこでなされるかを示している。 ``f_inode'' は、そのファイルを記述する VFS ``inode'' をポイントしてお り、f_ops (訳注: ``f_op''?) は、ルーチンのアドレスの配列に対するポイ ンタであり、個々のルーチンは、たとえば、データ書き込みのための関数など の、そのファイルに対する操作の際に呼び出される関数である。このインター フェイスの抽象化は非常にパワフルであり、それによって Linux は幅広い ファイルタイプをサポートすることができる。Linux では、このメカニズムを 利用したパイプが実装されているが、それについては後ほど述べる。 ファイルがオープンされるたびに、``file_struct'' 内にある使用されていな い ``file'' へのポインタが新しい file 構造体に対するポインタとして使用 される。 Linux のプロセスは、実行される際に 3 つのファイルディスクリプ タがあることを前提にしている。それらは、標準入力、標準出力、標準エラー と呼ばれ、通常生成に関与した親プロセスからそれらを受け継ぐ。ファイルへ のアクセスはすべて標準システムコール経由で行われ、それらのシステムコー ルとの間でファイルディスクリプタのやり取りがある。これらのファイルディ スクリプタは、プロセスの ``fd'' 配列へのインデックスとなっていて、標準 入力、標準出力、標準エラーはそれぞれ 0, 1, 2 のファイルディスクリプタ を持つ。ファイルへのアクセスのたびに file データ構造のファイル操作ルー チンと、VFS inode とを使用し、目的とする必要な処理が行われる。 5.5. 仮想メモリ プロセスの仮想メモリに含まれる実行コードとデータは、様々なソースから成 り立っている。第一に、ロードされるべきプログラムイメージがある。たとえ ば、ls のようなコマンドは、他のすべての実行イメージ同様、実行コードと データから構成されている。そのイメージファイルには、実行コードとプログ ラムの関連データとをプロセスの仮想メモリにロードするのに必要なすべての 情報も含まれている。第二に、プロセス自体が、新たに仮想メモリを割り付け る場合がある。これは、読み出し中のファイルの内容の保持するときなどに、 そのプロセスの処理中に使用されるものである。この新しく割り付けられた仮 想メモリを使えるようにするには、プロセスの持つ既存の仮想メモリにリンク しなければならない。第三に、Linux のプロセスは、たとえばファイルハンド リングのルーチンなど、一般的によく利用されるコードであるライブラリを使 用する。個々のプロセスが自分自身のライブラリのコピーを持つのでは意味が ないので、Linux はいくつかの実行中のプロセスから同時に使用することが可 能な共有ライブラリを使用している。そうした共有ライブラリのコードやデー タも、そのプロセスの仮想アドレス空間にリンクされなければならない。ま た、それらは、ライブラリを共有している他のプロセスの仮想アドレス空間に もリンクされなければならない。 ある限られた時間の単位で眺めれば、プロセスは仮想メモリ内に含まれるコー ドやデータのすべてを使っているわけではない。特定の状況でしか利用されな いコード、たとえば初期化や特別なイベントを処理するときだけしか使用され ないコードも仮想メモリに含まれている。共有ライブラリのルーチンも僅かし か使用していないかもしれない。そうしたコードやデータのすべてを物理メモ リにロードして、使わずに放っておくのは資源の無駄である。システム上のプ ロセスの数だけこの無駄があるとすると、システムを実行する上で非常に効率 が悪い。これに対処するために、Linux は、デマンドページングというテク ニックを使用し、プロセスがその仮想メモリを使用するときだけその仮想メモ リを物理メモリに戻すようにしている。したがって、コードやデータを物理メ モリに直接ロードするかわりに、Linux カーネルはプロセスのページテーブル を変更し、仮想エリアを存在はするが(訳注: valid だが)物理メモリ内には存 在しない状態にしている。プロセスがコードやデータにアクセスしようとする と、システムのハードウェアがページフォルトを起こし、制御を Linux カー ネルに渡して、カーネルが問題を解決するようになっている。それゆえ、そう したページフォルトに対処するために、プロセスのアドレス空間にある仮想メ モリのすべての領域に関して、Linux は、仮想メモリがどういったソースから 成り立つもので、どうすれば物理メモリ内に取ってこれるのかを知っている必 要がある。 プロセス仮想メモリ +----------+ task_struct | | | | | | | | | | | | mm_struct vm_area_struct +->|----------| |--------| +----------+ +----------+ | | | | mm |->| count | +->| vm_end |--+ | | |--------| |----------| | |----------| | | | | | pgd | | | vm_start |--+ | Data | | | |----------| | |----------| | | | | | | | | | vm_flag | | | | | | | | | |----------| | | | | | | | | vm_inode | | | | |----------| | |----------| +->|----------|0x8059BB8 | mmap |-+ | vm_ops | | | |----------| |----------| | | | mmap_avi | | | | | |----------| | | | | | mmap_sem | |----------| +->|----------| +----------+ | vm_next |-+| | | +----------+ || | | +---------------+| | | | vm_area_struct | | | | +----------+ | | | +->| vm_end |--+ | | |----------| | | | vm_start |---->|----------|0x8048000 |----------| | | | vm_flag | | | |----------| | | | vm_inode | | | |----------| | | | vm_ops | | | |----------| | | | | | | | | | | |----------| | | | vm_next | | | +----------+ | | +----------+ 0x0000000 図表(4.2) プロセスの仮想メモリ Linux カーネルはそうした仮想メモリの領域全体を管理する必要があり、個々 のプロセスの仮想メモリの内容は、そのプロセスの``task_struct'' からポイ ントされた ``mm_struct'' データ構造体によって記述されている。プロセス の mm_struct データ構造体は、ロードされた実行イメージに関する情報とプ ロセスのページテーブルへのポインタも含んでいる。また、mm_struct に は、``vm_area_struct'' データ構造体のリストに対するポインタが含まれて いて、個々の ``vm_area_struct'' 構造体は、プロセス内の仮想メモリの領域 を表現している。 この ``vm_area_struct'' の連結リストは、仮想メモリアドレスの昇順に並べ られている。``図表(4.2)''では、単純なプロセスの仮想メモリ内のレイアウ トが、それを管理するカーネルのデータ構造と一緒に示されている。仮想メモ リの領域はいくつものソースから成立しているので、Linux はそのインター フェイスを抽象化するために、vm_area_struct を使って(``vm_ops''経由 で)、仮想メモリ操作の一連のルーチンをポイントしている。この方法によ り、プロセスの仮想メモリはすべて一貫した方法で操作できるようになってお り、その操作方法は、仮想メモリを管理する基礎的なサービスがどのように異 なっていても変わらない。たとえば、プロセスが仮想メモリにアクセスしよう としたがそれが物理メモリ上に存在しなかったときに呼び出されるルーチンが ある。ページフォルトを処理する場合に使用されるのが、そのルーチンであ る。 プロセスが持つ一連の ``vm_area_struct'' データ構造体は、Linux カーネル から繰り返しアクセスされる。たとえば、Linux が、そのプロセスのために新 しい仮想メモリのエリアを作成するときや、システムの物理メモリ内にない仮 想メモリが参照された場合にそれを解決するときなどである。それゆえ、正し い vm_area_struct を発見するのにかかる時間は、システムのパフォーマンス に深刻な影響を与える。このアクセス時間を短縮するために、Linux は、AVL (Adelson-Velskii and Landis) ツリーのなかに vm_area_struct データ構造 体を配置している。このツリーは、個々の vm_area_struct (あるいは、ノー ド) が左右両隣の vm_area_struct データ構造体へのポインタを持つように配 置されている。左のポインタは、それより低い仮想アドレスを持つノードをポ イントしており、右のポインタはそれより高い仮想アドレスを持つノードをポ イントしている。正しいノードを発見する際、 Linux はツリーの根(root)の 部分から入って、個々のノードの左右のポインタを辿り、正しい vm_area_struct を発見する仕組みになっている。もちろん、この方法もいい ことずくめではなく、このツリーに新しい vm_area_struct を追加する場 合、(通常より)多くのプロセス時間が消費される。 プロセスが仮想メモリの割り付けを行うとき、Linux は実際にそのプロセスの ための物理メモリを確保するわけではない。そのかわり、Linux は、新しく ``vm_area_struct'' データ構造体を作成することでその仮想メモリを記述す る。この構造体はプロセスの仮想メモリのリストにリンクされる。プロセスが その新しい仮想メモリの領域内にある仮想メモリに書き込みを行おうとすると き、システムはページフォルトを起こす。プロセッサは仮想アドレスを(物理 アドレスに)デコードしようとするが、その仮想メモリに関するページテーブ ルエントリ(PTE)が存在しないので、諦めてページフォルト例外(page fault exception)を発行し、Linux カーネルに後の処理を任せる。Linux は、参照さ れた仮想アドレスが現在のプロセスの仮想アドレス空間にあるかどうかを確認 する。もしあるなら、Linux は適切な PTE を作成し、このプロセスにメモリ の物理ページを割り付ける。ファイルシステムやスワップディスクから、コー ドやデータをその物理ページへと持って来なければならない場合もある。それ によって、プロセスはページフォルトの原因となった命令の時点から再始動さ れ、今回は物理メモリが存在するので、実行が継続する。 5.6. プロセスの生成 システムは、起動時に、カーネルモードで実行されていて、その際は、ある意 味で、たったひとつのプロセスである初期化プロセスしか存在しない。初期化 プロセスも、他のすべてのプロセスと同様に、スタックやレジスタなどで表現 されるマシン状態(machine state)を持っている。他のプロセスが生成されて 実行される時、それらの情報は、初期化プロセスの ``task_struct'' データ 構造体に保存される。システムの初期化が終了した時点で、初期化プロセス は、(init と呼ばれる)カーネルスレッド(kernel thread)を起動し、アイドル ループ(idle loop)状態に入って、何もしなくなる。他に全く何もすることが なければ、スケジューラは、このアイドルプロセスを実行する。このアイドル プロセスの task_struct は、唯一、動的に割り付けをされないプロセスであ り、それはカーネルがビルドされた時に静的に定義されているプロセスであ り、やや紛らわしい名前だが、init_task と呼ばれている。 init というカーネルスレッドもしくはプロセスは、システムで最初の実プロ セスなので、プロセス識別子 1 を持っている。それは、システムの初期化設 定のいくつか(システムコンソールのオープンやルートファイルシステムのマ ウントなど)を実行し、その後でシステムの初期化プログラムを実行する。そ のプログラムは、システムに依存するので、/etc/init, /bin/init, もしくは /sbin/init のいずれかとなっている。init プログラムは、スクリプトファイ ルとして /etc/inittab を使用して、システム上で新しいプロセスを生成す る。それらの新しいプロセスはそれ自体でさらに新しいプロセスを生成する場 合もある。たとえば、getty プロセスは、ユーザがログインしようとする 時、login プロセスを生成する。システム内のすべてのプロセスは、その init カーネルスレッドの子孫である。 新しいプロセスは、古いプロセスをクローニングするか、もしくは現在のプロ セスをクローニングすることで生成される。 [see: do_fork(), in kernel/fork.c] 新しいタスクはシステムコール(fork もしくは clone )によって生成され、ク ローニングはカーネル内でカーネルモードにおいて実行される。そのシステム コールが終了すると、新しいプロセスが誕生し、スケジューラが選択したなら すぐに実行できる状態で待機している。新しい ``task_struct'' データ構造 体には、クローニングされたプロセスの(ユーザおよびカーネル)スタックのた めに、いくつかの物理ページが、システムの物理メモリから割り付けられる。 新しいプロセスの識別子が作成されるが、その識別子は、システム内にある一 連のプロセス識別子のなかでユニークなものである。しかし、クローニングさ れたプロセスがその親プロセスの識別子を保持するという仕組みには、特別な 合理的理由がある。新しい task_struct が task 配列に追加され、古い方 の(現在の) プロセスの task_struct の内容がクローニングされたプロセスの 新しい task_struct にコピーされる。 クローニングの処理の際、Linux は、ふたつのプロセスが個別のリソースを持 つのでなく、同じリソースを共有することが出来るようにしている。このこと は、プロセスのファイルやシグナルハンドラー、仮想メモリにもついても当て はまる。リソースが互いに共有されるべき時は、リソースの count フィール ドがインクリメントされ、Linux は、両方のプロセスがそれを使うのを止める までそれらのリソースを解放しないようにする。したがって、たとえば、クロ ーニングされたプロセスが仮想メモリを共有しようとする場合、そのプロセス の ``task_struct'' には、元の(親)プロセスの ``mm_struct'' へのポインタ が含められ、親プロセスの mm_struct の ``count'' フィールドが加算され て、それを共有するプロセスの数が示される。 プロセスの仮想メモリのクローニングはややトリッキーであ る。``vm_area_struct'' データ構造体の新しいセットが、それらを所有する ``mm_struct'' データ構造体とクローニングされたプロセスのページテーブル とともに生成される。プロセスの仮想メモリはこの段階ではまだ複製されてい ない。それは面倒で時間の掛かるタスクである。というのも、親プロセスの仮 想メモリのうち、ある部分は物理メモリにあるかもしれないが、プロセスが現 在実行している実行イメージの中にあるものもあり、おそらくスワップファイ ルの中にあるものもあるからである。それゆえ、Linux は、「コピーオンライ ト(copy on write)」と呼ばれるテクニックを使用する。その意味は、ふたつ のプロセスのうちのひとつがそれに書き込みをしようとするときだけその仮想 メモリがコピーされるというものである。書き込みが可能な場合でも、(書き 込み要求がないために)書き込まれていないそうした仮想メモリは、何の問題 もなくふたつのプロセス間で共有される。たとえば実行コードのような、読み 込み専用のメモリは常に共有される。「コピーオンライト」方式を機能させる 場合、書き込み可能なエリアは、そのページテーブルエントリを読み込み専用 とマークし、それらのエリアを記述する vm_area_struct データ構造体を「コ ピーオンライト」とマークする。共有プロセスのひとつがこの仮想メモリに書 き込みをしようとすると、ページフォルトが起こる。この時点で、Linux はメ モリのコピーを作成し、ふたつのプロセスのページテーブルと仮想メモリのデ ータ構造を訂正する。 5.7. 時間とタイマー カーネルは、プロセスが生成される時間を監視すると同時に、それがライフサ イクルにおいて消費する CPU 時間も監視している。クロックティッ ク(``clock tick'')ごとに、カーネルは、現在のプロセスがシステムモードお よびユーザモードで消費した時間の総量を jiffies 値を使って計測・更新し ている。 そうした時間記録用のタイマーに加えて、Linux はプロセス固有のインターバ ルタイマー(interval timer)もサポートしている。 [see: kernel/itimer.c] プロセスはそのタイマーを使用して、タイマーの所定時間が経過するごとに自 分自身に対して様々なシグナルを送っている。サポートされている三種類のイ ンターバルタイマーには、次のようなものがある。 real 実時間で計測されるタイマチック(``timer tick'')。そのタイマーの所 定時間が経過すると、プロセスに SIGALRM シグナルが送られる。 virtual プロセスの実行中だけ計測されるタイマチック。所定時間が経過する と、プロセスに SIGVTALRM シグナルを送る。 profile プロセスの実行時と、プロセスを代理してシステムが実行されている時 の両方で計測されるタイマチック。所定時間経過により SIGPROF シグ ナルを送る。 ひとつもしくはすべてのインターバルタイマーを実行することが可能 で、Linux はそれらすべての必要な情報をプロセスの ``task_struct'' デー タ構造体に保存する。これらのインターバルタイマーの設定、始動、停止およ び現在の値の読みとりは、システムコールにより実行される。 [see: do_it_virtual(), in kernel/sched.c] [see: do_it_prof(), in kernel/sched.c] virtual および profile タイマーは、同じ方法で操作される。クロック ティックごとに走行中のプロセスのインターバルタイマーの値は減少し、所定 時間経過により、適切なシグナルが送られる。 リアルタイム(real time)インターバルタイマーはそれとは少し異なり、Linux は、そのために``「カーネルの仕組み」''の章で解説するタイマーメカニズム を使用する。 [see: it_real_fn(), in kernel/itimer.c] 個々のプロセスは、自分の ``timer_list'' データ構造体を持ち、リア ル(real)インターバルタイマーが実行されているとき、その構造体がシステム タイマーリストのキューに入る。所定時間が経過すると、タイマーのボトムハ ーフハンドラー(bottom half handler)がそれをキューから削除し、インター バルタイマーハンドラー(interval timer handler)を呼び出す。そしてそれが SIGALRM シグナルを生成し、インターバルタイマーを再始動させ、当該構造体 を再度システムタイマーのキューに戻す。 5.8. プログラムの実行 Linux では、他の Unix と同様に、プログラムやコマンドは、通常コマンドイ ンタープリタによって実行される。コマンドインタープリタは、他のプロセス 同様ユーザプロセスであり、シェル(``脚注 2'')と呼ばれる。Linux 上には多 くのシェルがあり、最も有名なものとして、sh, bash そして tcsh があ る。cd や pwd といったいくつかの組み込みコマンドを除けば、コマンドとは 実行可能なバイナリファイルのことである。コマンドが入力されると、シェル は、環境変数 PATH に設定されたプロセスの検索パス内のディレクトリを探 し、同じ名前の実行イメージを見つけようとする。そのファイルが見つかれ ば、それはロードされ実行される。シェルは、fork という仕組みを使って、 先ほど説明したように自分自身をクローニングする。そして、次に新しい子プ ロセスは、親プロセスが実行していたバイナリイメージ、すなわちシェルを、 見つかったばかりの実行イメージの内容と置き換える。通常、シェルは、その コマンドの完了、言い換えると子プロセスの終了を待つ。シェルを再度走らせ たいときは、control-Z とタイプして、子プロセスをバックグラウンドに押し やればよい。これは、子プロセスに SIGSTOP シグナルを送って、それを停止 させる操作である。次に、シェルコマンド bg を使って子プロセスをバックグ ラウンドに押しやることもできる。これは、シェルから子プロセスに対して SIGCONT シグナルを送って再始動させるもので、子プロセスは実行が終了する か、ターミナルでの入出力が必要になるまでそこに留まる。 実行ファイルは多種のフォーマットを取ることができる。それは、スクリプト ファイルであってもよい。スクリプトファイルは解釈可能なものでなければな らず、適切なインタープリタを実行してそれを処理しなければならない。たと えば、/bin/sh は、シェルスクリプトを解釈するインタープリタである。実行 可能なオブジェクトファイルには、実行コードとデータ、およびオペレーティ ングシステムがそれをメモリにロードして実行することができるだけの充分な 情報が含まれている。Linux 上で最も一般的に利用されているオブジェクト ファイルのフォーマットは、ELF であるが、理論上は、Linux は、ほとんどす べてのオブジェクトファイルのフォーマットを扱えるだけの柔軟性をもってい る。 formats linux_binfmt linux_binfmt linux_binfmt +----------------+ +----------------+ +----------------+ ------->| next |--->| next |--->| next | |----------------| |----------------| |----------------| | use_count | | use_count | | use_count | |----------------| |----------------| |----------------| | *load_binary() | | *load_binary() | | *load_binary() | |----------------| |----------------| |----------------| | *load_shlib() | | *load_shlib() | | *load_shlib() | |----------------| |----------------| |----------------| | *core_dump() | | *core_dump() | | *core_dump() | +----------------+ +----------------+ +----------------+ 図表(4.3) 登録されたバイナリフォーマット ファイルシステムの場合と同様に、Linux でサポートされているバイナリフォ ーマットは、カーネルをビルドする際にカーネルに組み込まれるか、ローダブ ルモジュールとして利用可能かのいずれかである。カーネルはサポートするバ イナリフォーマットのリストを保持しており(図表(4.3)参照)、ファイルが実 行されるときは、その実行に成功するまで、個々のバイナリフォーマットが順 番に試される。 [see: do_execve(), in fs/exec.c] 広くサポートされている Linux のバイナリフォーマットは、a.out と ELF で ある。実行ファイルは、完全にメモリに読み込まれる必要はなく、デマンドロ ーディングと呼ばれるテクニックが使用される。実行イメージの一部がプロセ スによって使用されるたびに、その部分だけがメモリに取り込まれる。そのイ メージの使用されない部分は、メモリから破棄されてもよい。 5.8.1. ELF ELF (Executable and Linkable Format) というオブジェクトファイルのフォ ーマットは、Unix System 研究所で設計されたもので、今日では Linux 上で 最も広く利用されているフォーマットとしてその地位が確立している。ECOFF や a.out のような他のオブジェクトファイルのフォーマットと比較するとパ フォーマンス上ややオーバーヘッドがあるのだが、ELF はむしろそれら以上に 柔軟である点が評価されている。 ELF の実行ファイルには、時にはテキス ト(text)とも呼ばれる実行コード、およびデータが含まれる。実行ファイル内 のテーブルでは、そのプログラムがプロセッサの仮想メモリに置かれる際の方 法が示されている。静的にリンクされたイメージは、リンカ(ld)、もしくはリ ンクエディタによって、そのイメージの実行に必要なすべてのコードとデータ を含む単一のイメージにビルドされる。そのイメージは、自分自身のメモリ内 でのレイアウトや、イメージ内の最初の実行コードのアドレスについても指定 している。 ELF 実行イメージ +---------------+ | e_ident |'E' 'L' 'F' | e_entry | 0x8048090 | e_phoff | 52 | e_phentsize | 32 | e_phnum | 2 |---------------| | | |---------------| | p_type | PT_LOAD | p_offset | 0 物理ヘッダ | p_vaddr | 0x8048000 | p_filesz | 68532 | p_memsz | 68532 | p_flags | PF_R, PF_X |---------------| | p_type | PT_LOAD | p_offset | 68536 | p_vaddr | 0x8059BB8 物理ヘッダ | p_filesz | 2200 | p_memsz | 4248 | p_flags | PF_R, PF_W |---------------| ~ ~ ~ ~ | | | Code | | | |---------------| | | | Data | | | | | +---------------+ 図表(4.4) ELF 実行ファイルフォーマット 図表(4.4) は、静的にリンクされた ELF 実行イメージのレイアウトを示して いる。 [see: include/linux/elf.h] これは、簡単な C プログラムであり、"hello world" を表示して、終了する ものである。ヘッダは、ファイルが ELF イメージであること示すのに、2 つ の物理ヘッダを使っていて(e_phnum は 2 である)、それらはイメージファイ ルの先頭から 52 バイト目(e_phoff)から始まっている。最初の物理ヘッダ は、イメージ内の実行コードについて記述している。実行コードは、仮想アド レスの 0x8048000 に置かれ、その容量が 65532 バイトであることを示してい る。この容量は、それが静的にリンクされたイメージだからであり、"hello world" を出力するための printf() 呼び出しのライブラリコードのすべてを 含むからである。イメージのエントリーポイント、すなわちプログラムの最初 の命令がある場所は、イメージの先頭ではなく、仮想アドレス 0x8048090 (e_entry) である。このコードは、二つ目の物理ヘッダのすぐ後ろから始まっ ている。二つ目の物理ヘッダは、プログラムのデータを記述していて、仮想メ モリ内のアドレス 0x8059BB8 にロードされるようになっている。このデータ は読み書き可能である。ファイルのデータサイズが 2200 バイト (p_filesz) となっており、メモリ内でのそのサイズは 4248 バイトであることに気付いた と思う。これは、最初の 2200 バイトにはあらかじめ初期化されたデータが含 まれていて、次の 2048 バイトには実行コードによって初期化されるデータが 含まれているからである。 Linux が ELF 実行イメージをプロセスの仮想アドレス空間にロードすると き、 Linux は実際にそのイメージをロードするわけではない。 [see: do_load_elf_binary(), in fs/binfmt_elf.c] 仮想メモリのデータ構造とプロセスの ``vm_area_struct'' ツリー、およびそ のページテーブルを設定するだけである。プログラムが実行されると、ページ フォルトによりプログラムのコードとデータが物理メモリに取り出される。プ ログラムの使用されていない部分は決してメモリにロードされない。 ELF バ イナリフォーマットローダ(loader)は、ロードすべきイメージが有効な ELF 実行イメージであることを確認すると、そのプロセスの現在の実行イメージを その仮想メモリから消去する。このプロセスはクローニングされたイメージな ので (すべてのプロセスがそうである)、この古いイメージは、親プロセスが 実行しているプログラム、たとえばコマンドインタープリタシェルである bash 等のプログラムにすぎないからである。古い実行イメージをこのように 消去することで、古い仮想メモリのデータ構造が破棄され、プロセスのページ テーブルがリセットされる。ローダはまた、設定されたすべてのシグナルハン ドラをクリアし、オープンされていたすべてのファイルを閉じる。それらの消 去が終わると、子プロセスは新しい実行イメージを受け入れ可能になる。実行 イメージのフォーマットが何であれ、同じ情報がプロセスの ``mm_struct'' に設定される。mm_struct 構造体にはイメージのコードとデータの始点と終点 についてのポインタが存在する。それらの値が見いだされるのは、ELF 実行イ メージの物理ヘッダが読み込まれて、それらのヘッダが記述するプログラムの セクションがプロセスの仮想アドレス空間にマップされたときである。それ は、同時に vm_area_struct データ構造体が設定され、プロセスのページテー ブルが変更されるときでもある。mm_struct データ構造体には、そのプログラ ムに渡されるパラメータとプロセスの環境変数に対するポインタも含まれてい る。 5.8.1.1. ELF 共有ライブラリ 反対に、動的にリンクされたイメージは、実行に必要なすべてのコードとデー タを含むわけではない。その幾分かは、共有ライブラリに保持されており、実 行時にイメージにリンクされる。ELF 共有ライブラリのテーブルは、共有ライ ブラリが実行時にイメージにリンクされるときに、ダイナミックリン カ(dynamic linker)によっても使用される。 Linux は、ld.so.1, libc.so.1, ld-linux.so.1 などいくつかのダイナミックリンカを使用しており、それらは /lib に置かれている。ライブラリには、言語のサブルーチンなどのよく使用 されるコードが含まれている。ダイナミックリンカがないと、すべてのプログ ラムはこれらのライブラリのコピーを自分で所有しなければならず、今よりも かなり多くのディスク容量と仮想メモリを要することになってしまう。ダイナ ミックリンクが使用される場合、参照されるすべてのライブラリルーチンに関 する情報が ELF のイメージテーブルに含まれている。その情報は、ダイナ ミックリンカに対して、ライブラリルーチンを探すべき場所とプログラムのア ドレス空間にそれをリンクする方法と指示するものである。 REVIEW NOTE: 実行時の動作などを、もっと詳しく説明すべきだろうか? 5.8.2. スクリプトファイル スクリプトファイルは、その実行にインタープリタを必要とする実行ファイル である。Linux 上で入手可能なインタープリタは非常に多種類ある。たとえ ば、wish, perl, それに bash のようなコマンドシェルである。Linux では、 標準的な Unix の慣習に従って、スクリプトファイルの最初の一行にインター プリタの名前を書くようになっている。典型的なスクリプトファイルは次によ うに始まる。 #!/usr/bin/wish スクリプトのバイナリーローダは、そのスクリプトのインタープリタを探そう とする。 [see: do_load_script(), in fs/binfmt_script.c] そのためにローダは、スクリプトの最初の行で指名されている実行ファイルを オープンしてみる。オープンできたなら、そのインタープリタは、オープンし たファイルの VFS inode に対するポインタを所持し、さらに先を読み込ん で、スクリプトファイルを解釈する。スクリプトファイルの名前は、引数 0 (最初の引数)となり、他のすべての引数の番号がひとつずつ上がる(すなわ ち、もともと最初にあった引数は新たに第二引数となり、以下それに続く)。 インタープリタのロードは、 Linux が他の実行ファイルをロードする方法と 同じである。Linux は、実行に成功するまで、個々のバイナリフォーマットを 順番に試す。つまり、理論的には、いくつかのインタープリタとバイナリフォ ーマットを用意しておいて、Linux のバイナリフォーマットハンドラを非常に 柔軟なソフトウェアにすることが可能なのである。 (-- (脚注1)REVIEW NOTE: スワッピング(SWAPPING)状態はわざと省いた。使わ れているとは思えないからだ。 (脚注2)木の実に例えると、カーネルは中心の食べられる部分であり、シェル がその周りを囲んで、インターフェイスを提供している。--) 6. プロセス間通信の仕組み プロセスは、他のプロセスやカーネルと相互に通信することで、自らの作業の 調整を図っている。Linux では、いくつかのプロセス間通信(Inter-Process Communication, IPC)のメカニズムがサポートされている。シグナルとパイプ はその典型であるが、Linux は System V IPC メカニズムもサポートしてい る。System V IPC という名称は、Unix の当該リリースで初めて登場したこと からその名が付けられている。 6.1. シグナル シグナルは、Unix システムで使用される最も古いプロセス間通信の方法であ る。シグナルは、ひとつ以上のプロセスに対して非同期イベン ト(asynchronous events)を伝達するために使用される。シグナルが生成され るのは、キーボード割り込みがあったり、プロセスが仮想メモリ内に存在しな い場所にアクセスしようとしてエラーが起きたときなどである。シグナルは、 シェルが子プロセスに対してジョブ制御の信号を伝達するときにも利用され る。 一連の定義済みシグナルがあり、それらはカーネルによって生成されるが、正 しい権限を与えられているなら、システム上のカーネル以外のプロセスでも生 成が可能である。kill コマンド (kill -l) を使用すれば、システム上で利用 可能なシグナルのリストを得ることができる。わたしの Intel の Linux ボッ クスでは次のような結果が表示された。 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGIOT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR シグナルの番号は、Alpha AXP の Linux ボックスでは異なったものになる。 プロセスは、生成されたシグナルの大部分を無視することもできるが、ふたつ だけ有名な例外がある。プロセスの実行を停止させる SIGSTOP シグナルと、 プロセスを終了させる SIGKILL シグナルであり、これらは無視できない。そ れ以外については、プロセスが様々なシグナルをどう処理するかを自分で選択 できる。プロセスはシグナルをブロックすることができるが、ブロックしない 場合は、自分自身で処理するか、あるいはカーネルに処理してもらうこともで きる。カーネルがシグナルを処理する場合、そのシグナルが要求するデフォル トの行為が実行される。たとえば、プロセスが SIGFPE (floatin point exception)シグナルを受け取った際のデフォルトの行為は、core dump を行っ て終了することである。シグナルには、内在的な相対的優先順位といったもの はない。ひとつのプロセスに対してふたつのシグナルが同時に生成された場 合、それらがプロセスに渡される順番も、プロセスが処理すべき順番も決まっ ていない。また、同じ種類の重複したシグナルを処理するメカニズムというの も存在しない。プロセスは、 SIGCOUNT をひとつだけ受け取ったのか、それと もそれを 42 個受け取ったのかということを伝えるべき方法を持たない。 Linux のシグナルの実装では、プロセスの ``task_struct'' 構造体に保存さ れた情報が利用される。サポートされるシグナルの数は、プロセッサのワー ド(``word'')サイズに制限されている。 32 ビットのワードサイズを持つプロ セッサは、32 個のシグナルを持つことが出来るが、Alpha AXP のような 64 ビットプロセッサでは 64 個までである。まだ処理の済んでいないシグナル は、task_struct の ``signal'' フィールドに保存され、``blockd'' フィー ルドにブロックされたシグナルのシグナルマスク(``signal mask'')が保持さ れる。 SIGSTOP と SIGKILL を除いて、すべてのシグナルはブロックすること ができる。ブロックされたシグナルが生成されると、それはブロックを解除さ れるまで未処理状態に留まる。また、Linux は、個々のプロセスが受け取るか もしれないあらゆるシグナルをどう処理するのかに関する情報を保持している のだが、その情報は ``sigaction'' データ構造体の配列に保存され、個々の プロセスの task_struct データ構造体によってポイントされている。それ以 外にも、task_struct には、シグナルを処理するルーチンのアドレスか、もし くはフラグのいずれかが含まれている。フラグを使う場合、プロセスは、当該 シグナルを無視するか、そのシグナルの処理をカーネルに代行させるかをその フラグによって Linux に伝える。プロセスは、システムコールを使ってシグ ナルに対するデフォルト処理を変更することができ、そのシステムコールに よって適切なシグナルに関する sigaction 構造体を変更したり、blockd マス クを変化させたりする。 システム上の全プロセスが他のどのプロセスにもシグナルを送れるというわけ ではないが、カーネルとスーパーユーザにはそれが可能である。通常のプロセ スは、同じ uid や gid を持つプロセスか、同じプロセスグループに属するプ ロセスにのみシグナルを送れる。``(脚注1)'' シグナルの生成 は、``task_struct'' の signal フィールドにある適切なビットをセットする ことにより為される。プロセスがシグナルをブロックしておらず、待機中だが 割り込み可能な(INTERRUPTIBLE な)状態にある場合、そのプロセスは、目を覚 まして実行中(RUNNING)状態に置かれ、実行キューへ登録されて、その登録を 確認される。それによって、スケジューラは、システムの次のスケジュールに おいて、そのプロセスを実行の候補者と見なす。デフォルトの処理が必要な場 合、 Linux はそのシグナルの処理を最適化できる。たとえば、シグナル SIGWINCH (X Window のフォーカス変更)が生成され、それに対してデフォルト のハンドラが使用される場合は、そのハンドラによって適切な処理がなされる ので、それ以外の特別な処理はなされない。 シグナルは生成されてすぐにプロセスに渡されるわけではない。それらはプロ セスが再び実行されるまで待たなければならない。プロセスがシステムコール によって終了するときはいつでも、``signal'' と ``blocked'' フィールドが チェックされ、もしプロセスが予めブロックしていないシグナルが存在する場 合は、その時点でようやくプロセスにシグナルを渡すことができる。これは非 常に信頼性の低い方法に思われるかもしれないが、システム内のすべてのプロ セスは、始終システムコールを発して、たとえば、端末に文字を書き込んだり しているので、こうした仕組みはやむを得ないものである。しかし、プロセス は、自分が望む場合は、自らシグナル待ちの状態になることもできる。その場 合、シグナルが送られるまで、割り込み可能なサスペンド状態となる。その 際、Linux のシグナル処理のコードは、現在ブロックされていないシグナルを 受け取るたびに、``sigaction'' 構造体を調べる。 プロセスのシグナルハンドラがデフォルトにセットされている場合、カーネル がそのシグナルを処理する。SIGSTOP シグナルのデフォルトのハンドラは、現 在のプロセスの状態を停止状態(STOPPED)とし、スケジューラを起動して新し いプロセスを実行する。SIGFPE シグナルのデフォルトの処理は、そのプロセ スに core dump させ、それを終了させる。反対に、プロセスが自分自身のシ グナルハンドラを指定することもできる。これは、そのシグナルが生成される と必ず呼び出されるルーチンとなり、``sigaction'' 構造体がそのルーチンの アドレスを保持する。その場合、カーネルはプロセスのシグナル処理ルーチン を呼び出さなければならず、その呼び出し方法はプロセッサに固有のものであ るが、すべての CPU が対処しなければならない事実として、カーネルモード で実行されているカレントプロセスは、カーネルやシステムルーチンを呼び出 した時のユーザモードのプロセスへと戻ろうとしているということである。こ の問題を解決するために、プロセスのスタックとレジスタが操作される。プロ セスのプログラムカウンタには、そのシグナル処理ルーチンのアドレスが設定 され、そのルーチンへのパラメータがコールフレームに付加されるか、レジス タに渡される。プロセスが再び実行を開始するとき、シグナル処理ルーチンは 通常通り呼び出されたかのように見える。 Linux は POSIX に準拠しているので、プロセスは、特定のシグナル処理ルー チンが呼び出されたとき、そのシグナルをブロックするかどうか指定ができ る。これは、プロセスのシグナル処理ルーチンの呼び出しがなされている間に ブロックマスクを変更することを意味する。シグナル処理ルーチンが終了した ら、そのブロックマスクの値は元に戻されなければならない。したがっ て、Linux は、片づけ用のルーチン(tidy up routine)へのコールを追加し て、そのルーチンがシグナルを受け取ったプロセスのコールスタック上のブ ロックマスクを元に戻す。また、Linux は、複数のシグナル処理ルーチンの呼 び出しが必要な場合、それらをスタックに積んでおいて、ひとつの処理ルーチ ンが終了したら次のルーチンを呼び出す操作を繰り返して、最後に片づけルー チンを呼び出すという方法でそうした処理を最適化している。 6.2. パイプ 一般的な Linux シェルは、すべてリダイレクション(redirection)をサポート している。たとえば、次のようなリダイレクションがある。 $ ls | pr | lpr 上記の例では、ディレクトリ内のファイルを表示する ls コマンドの出力をパ イプ処理して、pr コマンドの標準入力に振り向けて、pr にページ処理をさせ る。そうして、そのコマンド出力をパイプ処理して、lpr コマンドの標準入力 に振り向けて、デフォルトプリンタ上でその結果をプリントさせている。すな わち、パイプとは、あるプロセスの標準出力を別のプロセスの標準入力へと繋 ぐ、片方向性をもったバイトストリームである。その場合、どちらのプロセス もリダイレクションを意識せず、通常通りに振る舞う。プロセス間にそれらの 一時的なパイプを設定するのはシェルの役目である。 プロセス 1 プロセス 2 file file +-----------+ +-----------+ | f_mode | | f_mode | |-----------| |-----------| | f_pos | | f_pos | |-----------| |-----------| | f_flags | | f_flags | |-----------| |-----------| | f_count | | f_count | |-----------| |-----------| | f_owner | +---------------+ | f_owner | |-----------| | | |-----------| | f_inode |---+ | inode | | f_inode |-----+ |-----------| | | +---------+ | |-----------| | | f_op |-+ | +->| | | | f_op |--+ | |-----------| | +---->| | | |-----------| | | | f_version | | | | | | f_version | | | +-----------+ | | | | +-----------+ | | | | | | | | |/ +---------+ +-----------------+--+ * | | パイプ | データページ | 書き込み | +-----------+ | 操作ルーチン | | | |/ +------>| | * | | パイプ | | 読み出し | | 操作ルーチン | | | | | | +-----------+ 図表(5.1) パイプ Linux において、パイプの実装には、ふたつの ``file'' データ構造体が使用 される。メモリ内のある物理ページをポイントする VFS inode を一時的に作 成して、それをふたつの構造体で同時にポイントするのである。図表(5.1)で は、それぞれの file データ構造体が、ファイル操作ルーチンの配列の異なる 要素をポイントしている様子が示されている。ひとつは、パイプへの書き込み であり、もうひとつは、パイプからの読み出しである。 [see: include/linux/inode_fs_i.h](ママ) (訳注: pipe_fs_i.h ?) この方法では、通常ファイルへの書き込み処理と読み出し処理をする汎用シス テムコール間にある基礎的な違いは隠蔽される。書き込みプロセスがパイプに 書き込むとき、バイト列は共有データページにコピーされ、読み出しプロセス がパイプから読み出すときは、バイト列がその共有データページからコピーさ れる。Linux は、そのパイプへのアクセスを同期させなければならな い。Linux は、パイプからの読み出しと、そこへの書き込みとが確実に歩調を 合わせるようにしなければならず、そのために Linux は、ロッ ク(``lock'')や待ち行列(``wait queue'')やシグナル(signal)といった仕組み を利用している。 書き込みプロセスがパイプに書き込もうとするときは、標準の書き込みライブ ラリ関数を使用する。それらの間でやり取りされるファイルディスクリプタは すべて、書き込みプロセスが持つ一連の ``file'' データ構造体へのインデッ クスとなっていて、それら個々の構造体がオープンされたファイル、この場合 にはオープンされたパイプを表している。 Linux のシステムコールが使用す る書き込みルーチンは、そのパイプを記述する file データ構造体によってポ イントされたルーチンである。書き込みルーチンが書き込み要求を処理する際 に利用する情報は、そのパイプを表す VFS ``inode'' に保持された情報であ る。 [see: pipe_write(), in fs/pipe.c] パイプへ書き込もうとする全バイト量を保持するだけの空きが共有ページ上に 存在し、パイプが読み出しプロセスによってロックされていない場合、Linux は書き込みプロセスのためにそのパイプをロックして、書き込まれるべきバイ ト列を書き込みプロセスのアドレス空間から共有データページにコピーする。 パイプが読み出しプロセスによってロックされているか、データを保持する充 分な空きスペースが存在しない場合は、現在のプロセスがパイプの inode の 待ち行列上でスリープ状態にされ、スケジューラが呼び出されて、他のプロセ スが実行される。書き込みプロセスは割り込み可能であるので、シグナルの受 信が可能である。書き込みプロセスは、データ書き込みのための充分なスペー スが確保されるかパイプのロックが解除されたときに、読み込みプロセスに よってスリープ状態から起こされる。データが書き込まれるとき、パイプの VFS inode のロックは解除され、その inode の待ち行列上でスリープ状態に なっている読み出しプロセスがあれば、読み出しプロセスもそれによって目を 覚ます。 パイプからのデータの読み出しは、書き込みプロセスと非常によく似ている。 [see: pipe_read(), in fs/pipe.c] プロセスは、ブロッキングしない読み出し(non-blocking read)をすることも 許される。(それは、プロセスがファイルやパイプをオープンするときのモー ドに依存する。)そして、その場合、読み出すべきデータがないか、パイプが ロックされている場合は、エラーが返される。これは、そのプロセスが実行を 継続できることを意味する。それ以外の方法として、パイプの inode の待ち 行列上で休止して、書き込みプロセスの終了を待つことも可能である。両方の プロセスがパイプ処理を終了すると、パイプの inode とその共有データペー ジとは破棄される。 Linux は、名前付きパイプもサポートしている。パイプは先入れ先出し(First In, First Out)の原理で処理を行うので、名前付きパイプは FIFO とも呼ばれ る。パイプに書き込まれた最初のデータは、パイプから読み出される最初のデ ータとなる。通常のパイプとは異なり、FIFO は一時的なオブジェクトではな く、ファイルシステム内の実体であり、mkfifo コマンドによって作成が可能 である。プロセスは、それに対する適切なアクセス権限を持つ限り、自由に FIFO を利用することができる。FIFO をオープンする方法は、通常のパイプの それとは少し異なる。通常のパイプ(ふたつの file データ構造体、VFS inode とその共有データページのセット)は必要に応じて一時的に作成されるだけだ が、FIFO はすでに存在する実体であり、ユーザによってオープンされ、クロ ーズされる。Linux は、書き込みプロセスが FIFO をオープンする前に読み出 しプロセスにそれをオープンさせ、書き込みプロセスがそれに書き込んでしま う前に読み出しプロセスがそれを読める状態にするよう、それらを操作しなけ ればならない。しかし、そうしたことを除くと、FIFO の処理は、通常のパイ プの処理方法とほとんど全く同一であり、両者は同じデータ構造と処理メカニ ズムを使っている。 6.3. ソケット REVIEW NOTE: Add when networking chapter written. 6.3.1. System V IPC のメカニズム Linux は、Unix System V (1983) で初めて登場したプロセス間通信(IPC)の仕 組みを 3 種類サポートしている。それらは、メッセージキュー(message queue)、セマフォ (semaphore)、そして共有メモリ(shared memory)である。 これらの System V IPC の仕組みはすべて共通の認証方法を利用している。プ ロセスが共有リソースにアクセスできるのは、システムコール経由でカーネル に対してユニークな参照識別子 (reference identifier)を渡した場合だけで ある。これらの System V IPC オブジェクトへのアクセスをチェックする場合 には、アクセスパーミッションが使用されるが、それはファイルへのアクセス チェックに類似したものである。System V IPC オブジェクトへのアクセス権 限は、そのオブジェクトの作成者によってシステムコール経由で設定される。 個々の IPC メカニズムは、オブジェクトの参照識別子を、リソーステーブル へのインデックスとして利用している。しかし、それは単純なインデックスで はなく、そのインデックス作成には多少の操作を必要とする。 Linux システム上で System V IPC オブジェクトを表すすべてのデータ構造に は、 ``ipc_perm'' 構造体が含まれている。そして、その構造体には、そのオ ブジェクトの所有プロセスおよび作成プロセスのユーザ ID とグループ ID と が含まれる。 [see: include/linux/ipc.h] また、ipc_perm には、そのオブジェクトへのアクセスモード(所有者、グルー プ、その他)と IPC オブジェクトキー(``key'')も含まれている。キー は、System V IPC オブジェクトの参照識別子を探す手段として使用される。 サポートされているキーには、プライベート(private)とパブリッ ク(public)の二種類がある。キーがパブリックの場合、どのようなプロセスで も、アクセス権をチェックされた後、その System V IPC オブジェクトの参照 識別子を見つけることができる。 System V IPC オブジェクトは、キーでは参 照できないようになっていて、その参照識別子でのみ参照が可能となってい る。 6.3.2. メッセージキュー メッセージキュー(message queue)とは、ひとつ以上のプロセスがメッセージ を書き込んで、それをひとつ以上の読み出しプロセスによって読み出すことを 可能にする仕組みである。Linux は、メッセージキューのリストを ``msque'' 配列によって管理している。この配列の個々の要素は、 ``msqid_ds'' データ 構造体をポイントしていて、その構造体が個々のメッセージキューを完全に記 述している。メッセージキューが作成されるとき、新しい msqid_ds データ構 造体がシステムメモリから割り当てられ、配列のなかに挿入される。 [see: include/linux/msg.h] msqid_ds +--------------+ | | | ipc | | | |--------------| | | |--------------| | *msg_last | msg msg |--------------| +------------+ +-----------+ | *msg_first |----------->| *msg_next |----------->| *msg_next | |--------------| |------------| |-----------| | | | msg_type | | msg_type | | times | |------------| |-----------| | | | *msg_spot | | *msg_spot | |--------------| |------------| |-----------| | *wwait | | msg_stime | | msg_stime | |--------------| |------------| |-----------| | *rwait | | msg_ts | | msg_ts | |--------------| * |------------| * |-----------| | | /| | | /| | | |--------------| | | | | | | | msg_qnum | | | | | | | |--------------| msg_ts | message | msg_ts | message | | | | | | | | | +--------------+ | | | | | | |/ | | |/ | | * +------------+ * +-----------+ <---------------- msg_qnum -----------> 図表(5.2)System V IPC メッセージキュー 個々の ``msqid_ds'' 構造体には、``ipc_perm'' データ構造体とそのキュー に入れられたメッセージへのポインタが含まれている。さらに、Linux は、 キューの変更時間を記録しており、たとえばそのキューに書き込みがなされた 最後の時間といった情報を保持している。msqid_ds には、ふたつの待ち行列 が含まれていて、それぞれメッセージキューへの書き込みプロセスに対するも のと読み出しプロセスに対するものとなっている。 プロセスが書き込みキューにメッセージを書き込もうとするときはいつも、そ の実効ユーザ ID と実効グループ ID とが、そのキューの ipc_perm データ構 造体にあるモードと比較される。プロセスがそのキューに書き込み可能な場 合、メッセージは、そのプロセスのアドレス空間から ``msg'' データ構造体 へとコピーされ、そのメッセージキューの最後に置かれる。個々のメッセージ には、協調するプロセス間で合意されたアプリケーション固有のタイ プ(type)がタグ付けされる。しかし、Linux は、書き込み可能なメッセージの 数と長さを制限しているので、メッセージの書き込みの際に容量が足りない場 合が生じ得る。その場合、そのプロセスが当該メッセージキューの書き込み キューに加えられ、スケジューラが呼び出されて、新しいプロセスが実行され る。そして、いくつかのメッセージがそのメッセージキューから読み出された ときに、先ほどのプロセスが起こされる。 キューからの読み出しも同様の過程を辿る。再度、書き込みキューに対するプ ロセスのアクセス権がチェックされる。読み出しプロセスは、メッセージのタ イプに関係なくキューの最初のメッセージを取得するか、特定のタイプのメッ セージを選択するかのいずれかを選択することができる。選択基準に合致する メッセージがない場合、読み出しプロセスはメッセージキューの読み出し待ち 行列に加えられて、スケジューラが実行される。新しいメッセージがキューに 書き込まれると、そのプロセスは起こされて再び実行される。 6.3.3. セマフォ 最も単純な形式で考えると、セマフォとは、複数のプロセスにより、ある値を テスト (test)したりセット(set)したりすることが可能な、メモリ上の場所で ある。テストとセットの操作は、個々のプロセスに関する限り、割り込みがで きない、もしくは他のプロセスから干渉を受けない(atomic)操作である。すな わち、いったんプロセスが操作を始めると他のプロセスがそれを止めることは できない。テストとセットの操作の結果は、そのセマフォの現在の値とセット した値との和となる。セットされる値は、正の場合も負の場合もある。テスト とセットの操作の結果によっては、あるプロセスが、他のプロセスによってセ マフォの値が変更されるまで、休止しなければならない場合が起こる。セマ フォは、非常に重要なメモリ領域を作成したいときに利用することができ、一 度に単一のプロセスだけが実行すべき非常に重要なコードの領域を作り出すこ とができる。 たとえば、協調して働く多くのプロセスがあり、それらは単一のファイルを読 み込んだり、それに書き込んだりしているとする。そして、そのファイルへの アクセスを厳しく調整したいと思っているとする。その場合、セマフォを使 い、初期値を 1 に設定し、ファイル操作コードの中に、ふたつのセマフォ操 作コードを加える。ひとつは、セマフォの値をテストして、その値から 1 を 引くもの、もうひとつは、セマフォ値をテストして値に 1 を足すものであ る。ファイルにアクセスする最初のプロセスは、セマフォの値から 1 を引こ うとし、それに成功すればセマフォの値は 0 になる。このプロセスは処理を 続けて、データファイルを使用するが、もし次のプロセスがそれを使いたいと 思ってセマフォの値から 1 を引こうとしても、その結果が -1 になってしま うので失敗する。そのプロセスは、最初のプロセスがデータファイルの使用を 終えるまで、停止状態になる。最初のプロセスがデータファイルの使用を終え たら、セマフォの値に 1 を足して、1 に戻す。この時点で、待機していたプ ロセスが起こされて、今回はセマフォ値に 1 を足す(訳注: -1 か?)ことに成 功する。 +-------+ +------->| | セマフォ配列 | |-------| semid_ds | | | +------------------+ | |-------| | | | | | | ipc | | |-------| | | | | | |------------------| | |-------| | | | | | | times | | |-------| | | | | | |------------------| | +-------+ | sem_base |----+ sem_queue |------------------| +---------+ | sem_pending |-------------------------->| next | |------------------| |---------| | sem_pending_last | sem_undo | prev | |------------------| +-----------+ |---------| | undo |->| proc_next | | sleeper | |------------------| |-----------| |---------| | sem_nsems | | id_next | | undo | +------------------+ |-----------| |---------| | semid | | pid | |-----------| +------+ |---------| | semadj |->| | | status | +-----------+ | | |---------| | | | sma | | | |---------| +------+ | | | sops |->| | | | |---------| | | | | | nsops | | | +------+ +---------+ | | | | | | | | +------+ 図表(5.3)System V IPC セマフォ System V IPC セマフォオブジェクトは、それぞれがセマフォ配列を記述して おり、 Linux は、``semid_ds'' データ構造体を使用してこれを表現する。 [see include/linux/sem.h] システム上のすべての semid_ds データ構造体は、 ``semary'' というポイン タの配列によってポイントされている。個々のセマフォ配列に は、``sem_nsems'' 個の要素があり、各々のセマフォ配列の要素は、``sem'' データ構造体により記述されていて、sem 構造体は ``sem_base'' によってポ イントされている。 System V IPC セマフォオブジェクトから成るセマフォ配 列の操作を許されているすべてのプロセスは、システムコールを生成して、そ れらを操作する。システムコールは多くの操作を指定することができるが、個 々の操作は、セマフォインデックス(semaphore index)、オペレーション 値(operation value)、および一連のフラグ(a set of flag)という 3 つの入 力項目によって記述される。セマフォインデックスとは、セマフォ配列へのイ ンデックスであり、オペレーション値とは、セマフォの現在の値に加えるべき 数値である。 Linux はまず、すべての操作が成功したかどうかをテストす る。操作が成功するのは、セマフォの現在の値にオペレーション値を加えた値 がゼロより大きいか、もしくはオペレーション値とセマフォの現在の値の両方 がゼロの場合のいずれかである。セマフォ操作がひとつでも失敗した場合で、 他のプロセスをブロックしない (non-blocking)システムコールをオペレー ションフラグが要求しない場合、Linux は、プロセスをサスペンドする。プロ セスがサスペンド状態になると、Linux は実行されるべきセマフォ操作の状態 (state)を保存して、現在のプロセスを待ち行列(wait queue)上に置かなけれ ばならない。その処理は、スタックに ``sem_queue'' データ構造体を作成し て、必要な情報を書き込むことによってなされる。新しい sem_queue データ 構造体は、そのセマフォオブジェクトの待ち行列の最後に置かれる。(その際 は、``sem_pending'' と ``sem_pending_last'' ポインタが使用される。) 現 在のプロセスは、sem_queue データ構造体の待ち行列上に(休止状態で)置か れ、スケジューラが呼び出されて、次のプロセスが実行される。 もしセマフォ操作がすべて成功し、現在のプロセスを停止させる必要がなけれ ば、 Linux は処理を継続し、セマフォ配列の適切なメンバーにその操作を適 用する。この時点で、Linux は、待ち状態や停止状態にあるプロセスがそれぞ れのセマフォ操作を適用できるかどうかをチェックする。Linux は、操作ルー チンのペンディングキュー(pending queue)(``sem_pending'')のメンバーそれ ぞれを順番に見て、今回はそのセマフォ操作が成功するかどうかをテストす る。成功した場合、Linux は、ペンディングリストからその sem_queue デー タ構造体を削除して、セマフォ配列にそのセマフォ操作を適用する。 Linux は、休眠中のプロセスを起こして、次にスケジューラが実行されるときに再始 動されるようにする。Linux は、ペンディングリストを最初から順に調べて いって、適用できるセマフォ操作が無くなり、それ以上プロセスを起こせなく なるまでそれを続ける。 セマフォには、デッドロックという問題がある。それが起こるのは、あるプロ セスが、重要なメモリ領域に入ったときのに、クラッシュするか kill される かしてそこから出られなくなり、セマフォ値が変更されたままになる場合であ る。Linux はこれを防止するために、セマフォ配列に対する調 整(adjustment)リストを管理している。そのアイデアは、セマフォ配列に調整 が適用されると、セマフォが、問題を起こしたプロセスの操作が適用される前 の状態(state)に戻されるということである。これらの調整は、``sem_undo'' データ構造体に保存されていて、それは ``semid_ds'' データ構造体上のキュ ーとそれらのセマフォ配列を使うプロセスの ``task_struct'' データ構造体 上のキューとに置かれる。 個々のセマフォ操作は、調整のためのデータ管理がなされるされることを個別 に要求する場合がある。Linux は、個々のセマフォ配列について、プロセスあ たり最大ひとつの ``sem_undo'' データ構造体しか管理しない。調整を要求す るプロセスがそれをひとつも持っていない場合は、必要なときにひとつのデー タ構造体が作成される。新しい ``sem_undo'' データ構造体は、そのプロセス の ``task_struct'' データ構造体上のキューと、セマフォ配列の ``semid_ds'' データ構造体上のキューとに入れられる。セマフォ操作がセマ フォ配列のセマフォに対して適用されるとき、オペレーション値を正負逆にし た値が、そのプロセスの sem_undo データ構造の調整 (adjustment)配列の中 にあるセマフォエントリーに加えられる。それゆえ、オペレーション値が 2 の場合、-2 がそのセマフォの調整エントリに加えられる。 プロセスが終了して削除されるとき、Linux は、そのプロセスの ``sem_undo'' データ構造体のセットを調査して、セマフォ配列に対する調整 を適用する。セマフォセットが削除された場合、sem_undo データ構造体は、 プロセスの ``task_struct'' のキューに残ったままになるが、セマフォ配列 の識別子は無効になる。その場合、セマフォを片づける(clean up)コードが、 単にその sem_undo 構造体を破棄する。 6.3.4. 共有メモリ 共有メモリ(shared memory)は、プロセスの仮想アドレス空間の任意の場所を 経由して、複数のプロセスが通信することを可能にする仕組みである。共有さ れる仮想メモリのページは、個々の共有プロセスのページテーブル内にあるペ ージテーブルエントリによって参照される。共有されるページは、すべてのプ ロセスの仮想メモリ内の同一アドレスになくてもよい。System V IPC のオブ ジェクトを使う場合はすべて、共有メモリエリアへのアクセスは、キーとアク セス権限のチェックによって制御されている。(正当なアクセスにより)いった んメモリが共有されたなら、プロセスがそれを使用する方法はチェックされな い。メモリへのアクセスの同期を取るといったことは、他の仕組み、たとえ ば、 System V のセマフォなどの仕組みに依存しなければならない。 shmid_ds +------------+ | | | ipc | | | |------------| | shm_segsz | +------+ |------------| +->| pte | | | | |------+ | | | | pte | | times | | |------| | | | | | | | | | | |------------| | | | | shm_npages | | |------| |------------| | | pte | | shm_pages |-+ +------+ vm_area_struct vm_area_struct |------------| +----------------+ +----------------+ | attaches |------------>| | +-->| | +------------+ | | | | | | | | | | |----------------| | |----------------| | vm_next_shared |-+ | vm_next_shared | |----------------| |----------------| | | | | | | | | | | | | +----------------+ +----------------+ 図表(5.4) System V IPC 共有メモリ 新しく作成された個々の共有メモリエリアは、``shmid_ds'' データ構造体に よって表される。それらは、``shm_segs'' 配列に保存される。 [see: include/linux/sem.h](訳注: include/linux/shm.h?) shmid_ds データ構造体は、その共有メモリのエリアの大きさ、それを使用す るプロセスの数、およびその共有メモリがプロセスのアドレス空間にマップさ れている様子に関する情報を記述している。そのメモリへのアクセス権限や、 キーがパブリックキーかプライベートキーかを管理するのは、その共有メモリ の作成者である。プロセスが適切なアクセス権を持っているなら、共有メモリ を物理メモリに ``ロック''することもできる。 メモリを共有しようとする個々のプロセスが、自己の仮想メモリに共有領域を 張り付ける(attach)場合は、システムコールを利用しなければならない。そし て、そのシステムコールによって ``vm_area_struct'' データ構造体が作成さ れ、それがプロセスの共有メモリを記述する。プロセスは、共有メモリが自分 の仮想アドレス空間内のどこに置かれるかを選択することができるが、充分な 広さの空領域の確保を Linux に任せることもできる。新しい vm_area_struct 構造体は、vm_area_struct のリストに挿入され、``shmid_ds'' によってポイ ントされる。 ``vm_next_shared'' と ``vm_prev_shared'' ポインタが、それ らをリンクするために使用される。共有メモリ領域は、仮想メモリに張り付け られた(attach)だけで実際に物理メモリ上に作成されるわけではない。プロセ スが最初にそこにアクセスしようとしたとき、初めて作成される。 初めてプロセスが共有メモリのページのひとつにアクセスしたときは、ページ フォルトが発生する。Linux がそのページフォルトを解消したとき、プロセス は、共有メモリを記述する ``vm_area_struct'' データ構造体を発見する。そ の構造体には、その共有メモリのタイプに合わせた処理ルーチンへのポインタ が含まれている。共有メモリページフォルト処理コードは、その ``shmid_ds'' に対するページテーブルエントリのリストを探して、共有仮想 メモリの該当ページに関するエントリがあるかどうかを確認する。もし存在し ないなら、物理ページを割り当てて、そのためのページテーブルエントリを作 成する。カレントプロセスのページテーブルにそのエントリが付け加わると同 時に、shmid_ds にもそれが保存される。すなわち、そうしておけば、このメ モリにアクセスしようとする次のプロセスがページフォルトを起こしたとき、 共有メモリ処理コードはこの新しく作成された物理ページをそのプロセスのた めに使用できるわけである。それゆえ、共有メモリにアクセスした最初のプロ セスが物理メモリ上にそれを割り当てたことで、他のプロセスによるそれ以後 のアクセスは、その物理ページをそれらのプロセスの仮想アドレス空間に付加 するだけで済むようになる。 プロセスがもはや仮想メモリの共有を必要としなくなったとき、プロセスは自 己の仮想メモリからその領域を外す(detach)。他のプロセスがまだそのメモリ を使用している限り、外す(detach)操作はカレントプロセスだけにしか影響し ない。その ``vm_area_struct'' は、``shmid_ds'' データ構造体から削除さ れ、解放される。カレントプロセスのページテーブルがアップデートされ、共 有されていた仮想メモリのエリアは無効にされる。メモリを共有していた最後 のプロセスが共有メモリ領域を外す(detach)とき、物理メモリにある共有メモ リのページは解放され、その共有メモリに関する shmid_ds データ構造体も解 放される。 共有仮想メモリが物理メモリにロックされないときは、さらに複雑なことが起 こる。その場合、共有メモリのページは、物理メモリの消費が激しい間は、シ ステムのスワップディスクにスワップアウトされる。共有メモリが物理メモリ からスワップアウトやスワップインされる方法は、``「メモリ管理」''の章で 解説している。 (-- (脚注1)プロセスグループについて、説明すること。(訳注: 原文のままで す。)--) 7. PCI PCI (Peripheral Component Interconnect)とは、その名前から分かるよう に、周辺機器(peripheral)とシステムの接続方法について、構造と制御方法を 解説した規格である。この規格(``[3, PCI Local Bus Specification]'') で は、システム上の機器を電気的に接続する方法と、それらが振る舞うべき方法 とが述べられている。この章では、Linux カーネルがシステムの PCI バスと デバイスを初期化する方法について説明する。 +-------------+ | | | CPU | | | +------+------+ | PCI Bus 0 ---------+----------------+--------+--------+----------------------- | | | | | | * +-----+-----+ +----+----+ +-----+-----+ /| Upstream | | | | | | | | PCI-ISA | | | | PCI-PCI | | | Bridge | | | | Bridge | | | +---------+ | | | Downstream +-----+-----+ Video +-----+-----+ | | | |/ | | * | ISA Bus | PCI Bus 1 ------+-+------------ ----------+--------+---------+------------- | | | | | | +----+----+ +----+----+ +----+----+ | | | | | | | | | | | | | | | | | | | | | | | | +---------+ +---------+ +---------+ Super I/O Controller SCSI Ethernet 図表(6.1) PCI バスシステムの例 図表(6.1) では、PCI ベースのシステム例を論理的なダイアグラムで示してい る。 PCI バスと PCI-PCI ブリッジとは、システム機器を相互に接続する仕組 み(glue)である。 CPU は、ビデオデバイスと一緒にプライマリ PCI バスであ る PCI Bus 0 に接続されている。特別な PCI デバイスである PCI-PCI ブ リッジが、プライマリバスをセカンダリ PCI バスである PCI Bus 1 に接続し ている。PCI 規格の専門用語では、 PCI bus 1 は、PCI-PCI ブリッジの下 流(downstream)であり、PCI Bus 0 は PCI-PCI ブリッジの上流(up-stream)で ある。セカンダリ PCI バスには、システムの SCSI とイーサネットデバイス が接続されている。物理的には、ブリッジとセカンダリ PCI バスとふたつの デバイスは、これらが組み合わされ、同じ PCI カード上にすべて含まれてい る。システムの PCI-ISA ブリッジは、旧弊な遺物たる ISA デバイスをサポー トするものである。またこのダイアグラムでは、Super I/O チップコントロー ラが示されているが、これはキーボードやマウス、フロッピーを制御するコン トローラである。 ``(脚注1)'' 7.1. PCI アドレス空間 CPU と PCI デバイスとは、両者で共有されたメモリにアクセスする必要があ る。そのメモリは、デバイスドライバが PCI デバイスを制御したり、それら の間で情報をやり取りするために使用される。通常その共有メモリには、デバ イスのコントロールレジスタやステータスレジスタの情報が含まれる。それら のレジスタ情報は、デバイスを制御し、その状態を読みとるために利用され る。たとえば、PCI 上の SCSI デバイス (コントローラ) 用のデバイスドライ バは、そのステータスレジスタを読んで、SCSI デバイスが SCSI ディスクに 情報ブロックを書き込む準備が出来ているかを調べる。あるいは、デバイスに 電源が入った後、デバイスドライバは、デバイスを起動するためにコントロー ルレジスタに書き込みをしたりする。 CPU のシステムメモリをこの共有メモリの代わりに使用することもできるが、 そうしたことがなされると、PCI デバイスがメモリにアクセスするたび に、CPU は一時停止し、PCI デバイスがメモリの使用を終えるまで待たなけれ ばならなくなる。メモリへのアクセスも、通常、一度にひとつのシステム機器 だけに限られる。したがって、その場合、システムの処理速度が遅くなる。ま た、適切な管理方法のないままシステムの周辺機器にメインメモリへのアクセ スを許すことは、よい考えとはいえない。それは非常に危険な場合がある。悪 質なデバイスがシステムを不安定にするからである。 周辺機器は独自のメモリ空間を持っていて、CPU はそれらの空間へアクセスで きる。しかし、デバイス側からのシステムメモリへのアクセスは、DMA(Direct Memory Access) チャンネルを使用して厳しく制御される。ISA デバイス は、ISA I/O(Input/Output)空間と ISA メモリ空間のふたつのアドレス空間へ アクセスできる。PCI には三つあり、PCI I/O 空間と PCI メモリ空間、PCI コンフィグレーション空間(configuration space) へアクセスできる。これら すべてのアドレス空間は CPU からもアクセス可能である。その際、CPU は、 デバイスドライバを使って PCI I/O 空間と PCI メモリ空間へアクセス し、Linux カーネル内の PCI 初期化コードを使って PCI コンフィグレーショ ン空間へアクセスする。 Alpha AXP プロセッサは、本来、システムアドレス空間以外のアドレス空間へ はアクセスできない。それゆえ、Alpha AXP プロセッサは、サポートチップを 使用して、 PCI コンフィグレーション空間等の他のアドレス空間にアクセス している。サポートチップは、スパースアドレスマッピングスキー ム(``sparse address mapping scheme'')を使用して、巨大な仮想アドレス空 間の一部を使い、それを PCI アドレス空間にマップしている。 7.2. PCI コンフィグレーションヘッダ 31 16 15 0 +---------------+--------------+ | デバイス ID | ベンダ ID |00h |---------------+--------------| | ステータス | コマンド |04h |---------------+------+-------| | クラスコード | |08h |-------+-------+------+-------| | | | | |10h |-------+-------+------+-------| | | | | | | | ベースアドレスレジスタ | | | | | | |24h |------------------------------| | | | | | | | | | | | | | | | | | | | | | | |---------------+------+-------| | | Line | Pin |3Ch +---------------+------+-------+ 図表(6.2) PCI コンフィグレーションヘッダ PCI-PCI ブリッジを含むシステム上のすべての PCI デバイスは、PCI コン フィグレーションアドレス空間内に設定用のデータ構造を保持している。シス テムは、PCI コンフィグレーションヘッダ(PCI configuration header)を使用 することで、デバイスを識別し制御している。 PCI コンフィグレーションア ドレス空間内におけるヘッダの正確な位置は、そのデバイスが PCI トポロジ ーのどこに位置するかに依存する。たとえば、PC のマザーボード上のある PCI スロットに差された PCI ビデオカードは、そのスロット番号の位置にコ ンフィグレーションヘッダを持つことになり、違うスロットに差されていれ ば、そのヘッダは、PCI コンフィグレーションアドレス空間の違うメモリ位置 にあることになる。しかし、そうした位置は問題ではない。というのも、PCI デバイスやブリッジがどこにある場合でも、システムは、それらのコンフィグ レーションヘッダ内のステータスレジスタとコンフィグレーションレジス タ(configuration register)を使用して、デバイスを検出し、設定するからで ある。 一般的に言って、システムは、すべての PCI スロットが、ボード上のスロッ トの場所に関連したオフセット位置に PCI コンフィグレーションヘッダを持 つように設計されている。したがって、たとえば、ボードの最初のスロットは PCI コンフィグレーションアドレス空間をオフセット 0 の位置に持ち、二つ 目のスロットはオフセット 256 の位置に持つ。(ヘッダは、256 バイトのコン フィグレーション空間のなかで、先頭 64 バイトを占めている。) そしてこれ 以降のスロットについても同様にそれぞれの場所が決められている。システム 固有のハードウェアのメカニズムを設計する場合、PCI 設定コードが、すべて の PCI バス上にあるすべての PCI コンフィグレーションヘッダを調べること ができ、その際に、ヘッダのフィールド(通常はベンダ識別フィールド)を読み 込むことでエラーが取得されるかどうかによって、デバイスがそのバス上にあ るのかどうかを確認できるように設計されなければならない。規格書(``参考 文献 3'')では、空の PCI スロットのベンダ ID とデバイス ID を読み取ろう としたときは、0xFFFFFFFF というエラーメッセージだけしか返してはいけな いことが記述されている。 ``図表(6.2)''では、256 バイトある PCI コンフィグレーションヘッダのレイ アウトが示されている。それには、次のようなフィールドが含まれる。 [see: include/linux/pic.h] ベンダ ID (Vendor Identification) その PCI デバイスの製造会社を示す固有の数字である。DEC の PCI ベ ンダ ID は、 0x1011 であり、Intel は、0x8086 である。 デバイス ID (device Identification) デバイスそのものを示す固有の数字。たとえば、DEC の 21141 ファス トイーサネットデバイスは、デバイス ID として 0x0009 を持ってい る。 ステータス (Status) このフィールドは、規格上定められた意味を持つビットによって、デバ イスの状態 (status)を表すものである。(``参考文献 3'') コマンド (Command) このフィールドに書き込むことで、システムはデバイスを制御する。た とえば、デバイスに PCI I/O メモリへのアクセスを許すなど。 クラスコード (Class Code) これは、そのデバイスのタイプを特定するものである。ビデオ、SCSI など、すべての種類のデバイスには、規格上のクラスがある。SCSI の クラスコードは 0x0100 である。 ベースアドレスレジスタ (Base Address Register) このレジスタは、デバイスが使用できる PC I/O 空間と PCI メモリ空 間のタイプと容量と位置とを決定し割り当てるために使用される。 割り込みピン (Interrupt Pin) PCI カード上の 4 つの物理ピンが、カードから PCI バスへの割り込み 信号を伝えている。規格では、それらを A, B, C, D とラベル付けして いる。割り込みピンフィールドは、PCI デバイスがそれらのうちどのピ ンを使うかを記述している。一般的には、それは特定のデバイスごとに ハードウェア的に固定されている。すなわち、システム起動のたびに、 デバイスは同じ割り込みピンを使用するわけである。この情報により、 割り込み処理サブシステムはそのデバイスからの割り込みを管理でき る。 割り込みライン (Interrupt Line) デバイスの PCI コンフィグレーションヘッダ内の割り込みラインの フィールドは、 PCI 初期化コードやデバイスドライバ、そして Linux の割り込み処理サブシステムの間で割り込みハンドルをやり取りするた めに使用される。このフィールドに書かれた番号は、デバイスドライバ にとっては無意味である。しかし、Linux オペレーティングシステム上 で、割り込み処理ルーチンが、 PCI デバイスから発せられた割り込み シグナルを正しいデバイスドライバの割り込み処理コードへと手渡すた めに必要である。 Linux の割り込み処理の方法についての詳細 は、``「割り込みと割り込み処理」''の章を参考にしてほしい。 7.3. PCI I/O と PCI メモリアドレス PCI I/O 空間と PCI メモリ空間のふたつのアドレス空間は、デバイスが、 CPU 上で実行されている Linux カーネル内のデバイスドライバと通信をする ために使用される。たとえば、DEC チップ 21141 ファーストイーサネットデ バイスは、内部レジスタ情報を PCI I/O 空間にマップする。デバイスドライ バは、それらのレジスタを読み書きしながら、そのデバイスを制御する。ビデ オドライバは、一般に巨大な PCI メモリ空間を使用して、ビデオ情報を保持 する。 PCI システムの設定が行われ、さらに PCI コンフィグレーションヘッダ内の コマンドフィールドを使用して、デバイスの PCI I/O と PCI メモリアドレス 空間へのアクセスが有効と設定されて、初めてそれらのアドレス空間へのアク セスが可能になる。注意すべきなのは、PCI 設定コードだけが PCI コンフィ グレーションアドレス空間に対する読み書きができるということであ る。Linux デバイスドライバは、PCI I/O と PCI メモリアドレス空間に対し てだけしか読み書きできない。 7.4. PCI-ISA ブリッジ PCI-ISA ブリッジは、PCI I/O 空間と PCI メモリ空間へのアクセスを ISA I/O 空間と ISA メモリ空間へのアクセスに変換することで、古い ISA デバイ スをサポートしている。現在販売されている多くのシステムには ISA スロッ トと PCI スロットの両方がいくつか付属している。しばらくすれば、この下 位互換性の必要は減少して、PCI のみのシステムが販売されるだろう。システ ムの ISA デバイスが ISA アドレス空間(I/O 空間とメモリ空間)内でレジスタ を保持している場所は、今では遠い昔となっている初期の Intel 8088 ベース の PC 時代からずっと固定されたままである。5000 ドルの Alpha AXP ベース のコンピュータシステムでさえ、ISA フロッピーコントローラが置かれる ISA I/O 空間内の位置は、初期の IBM PC と同じである。PCI の仕様では、それに 対処するために、PCI I/O と PCI メモリアドレス空間の下位アドレス領域を システムの ISA 周辺機器用に予約し、単一の PCI-ISA ブリッジを使用してそ れらの領域にある PCI メモリ空間へのアクセスを ISA メモリ空間へのアクセ スに変換している。 7.5. PCI-PCI ブリッジ PCI-PCI ブリッジは、システム上にある複数の PCI バスを結合するための特 別な PCI デバイスである。シンプルなシステムは、単一の PCI バスしか持た ないが、単一の PCI バスでサポートできる PCI デバイスの数には電気的な制 限がある。PCI-PCI ブリッジを使用して多くの PCI バスを加えれば、システ ムは、より多くの PCI デバイスをサポートできる。これは、高いパフォーマ ンスを要求されるサーバの場合特に重要である。もちろん、Linux は、PCI- PCI ブリッジを完全にサポートしている。 7.5.1. PCI-PCI ブリッジ: PCI I/O と PCI メモリウィンドウ PCI-PCI ブリッジは、PCI I/O 空間と PCI メモリ空間に対する読み書きのリ クエストのサブセットを下流領域(downstream)のバスに渡すだけである。たと えば、``図表(6.1)''では、 PCI-PCI ブリッジは、読み出しと書き込みのアド レスが、SCSI かイーサネットが所有する PCI I/O アドレス空間か PCI メモ リアドレス空間へのものであるならば、そのアドレスを PCI バス 0 か ら、PCI バス 1 に渡すだけである。それ以外の PCI I/O アドレス空間と PCI メモリアドレス空間に関するアドレスはすべて無視される。このフィルタリン グによって、アドレスが不必要にシステム全体に流れるのを防いでいる。これ を実現するためには、PCI-PCI ブリッジは、プライマリバスからセカンダリバ スに渡す必要のある PCI I/O 空間と PCI メモリ空間のベースアドレスとその 上限のアドレスとをプログラムされていなければならない。システム上の PCI-PCI ブリッジの設定が完了すると、Linux デバイスドライバが PCI I/O 空間と PCI メモリ空間に上記の範囲内でアクセスする限り、デバイスドライ バからは、PCI-PCI ブリッジが見えなくなる。これは、Linux の PCI デバイ スドライバの作者にとっては、ドライバの作成が少し楽になるという点で非常 に重要な機能である。しかし、これは、Linux からすると、後に述べるように PCI-PCI ブリッジの設定をややトリッキーにする機能でもある。 7.5.2. PCI-PCI ブリッジ: PCI コンフィグレーションサイクルと PCI バス の番号付け 31 11 10 8 7 2 1 0 +---------------------------------------+-------+----------+---+---+ | | | | | | | Device Select | Func | Register | 0 | 0 | | | | | | | +---------------------------------------+-------+----------+---+---+ 図表(6.3) Type 0 PCI コンフィグレーションサイクル 31 24 23 16 15 11 10 8 7 2 1 0 +--------------+-----------+------------+-------+----------+---+---+ | | | | | | | | | Reserved | Bus | Device | Func | Register | 0 | 1 | | | | | | | | | +--------------+-----------+------------+-------+----------+---+---+ 図表(6.4) Type 1 PCI コンフィグレーションサイクル CPU の PCI 初期化コードがメイン PCI バス上にないデバイスを初期化できる ようにするには、コンフィグレーションサイクル(Configuration cycle)を PCI のプライマリインターフェイスからセカンダリインターフェイスへと渡す か否かをブリッジが決定できるような仕組みが存在しなければならない。コン フィグレーションサイクルは、PCI バス上では単なるアドレスとして表現され る。 PCI の規格書では、PCI コンフィグレーションアドレス空間設定のため に、Type 0 と Type 1 のふたつのフォーマットが定義されていて、それらは 図表(6.3)と図表(6.4)とにそれぞれ示されている。 Type 0 の PCI コンフィ グレーションサイクルにはバス番号が含まれない。そして、 Type 0 の PCI コンフィグレーションサイクルは、すべてのデバイスによって、その PCI バ ス上の PCI コンフィグレーションアドレスであると解釈される。 Type 0 の コンフィグレーションサイクルのビット 31 から 11 までは、デバイス選択 フィールドとして扱われる。システムを設計する際のひとつの方法として、個 々のビットが違うデバイスを選択するようにする方法がある。その場合、ビッ ト 11 は、スロット 0 にある PCI デバイスを選択し、ビット 12 は、スロッ ト 1 にある PCI デバイスを選択する等ということになる。もうひとつの方法 は、デバイスのスロット番号を直接ビット 31 から 11 の領域に書き込むこと である。どちらの方法が利用されるかは、システムの PCI メモリ空間のコン トローラに依存する。 Type 1 の PCI コンフィグレーションサイクルには、PCI バス番号が含まれ る。そして、Type 1 のコンフィグレーションサイクルは、PCI-PCI ブリッジ 以外のすべての PCI デバイスから無視される。 Type 1 のコンフィグレー ションサイクルを見たすべての PCI-PCI ブリッジは、それらを、自分の下 流(downstream)側に渡すかどうかを選択する。PCI-PCI ブリッジが Type 1 コ ンフィグレーションサイクルを無視するか、それとも下流の PCI バスに渡す かは、PCI-PCI ブリッジの設定次第である。 PCI-PCI ブリッジはすべて、プ ライマリバスインターフェイス番号とセカンダリバスインターフェイス番号と を持っている。個々の PCI-PCI ブリッジにとって、プライマリバスインター フェイスは、CPU に最も近い側のインターフェイスであり、セカンダリバスイ ンターフェイスは CPU に最も遠い側になるインターフェイスである。また、 それぞれの PCI-PCI ブリッジは、サブオーディネイトバス番号(subordinate bus number)も持っている。これは、そのセカンダリバスインターフェイスを 越えてブリッジされているすべての PCI バスの中で最大のバス番号に該当す る。言い換えるとサブオーディネイトバス番号は、その PCI-PCI ブリッジの 下流に位置する PCI バス番号のなかで最も大きいバス番号である。PCI-PCI ブリッジが Type 1 の PCI コンフィグレーションサイクルを見るとき、PCI- PCI ブリッジは次のような処理をする。 o 指定されたバス番号がブリッジのセカンダリバス番号とサブオーディネイ トバス番号の間にない場合(両方の番号を含む)、Type 1 のコンフィグレー ションサイクルを無視する。 o 指定されたバス番号がそのブリッジのセカンダリバス番号と一致する場 合、 Type 1 コンフィグレーションサイクルを Type 0 の設定コマンドに 変換する。 o 指定されたバス番号がそのブリッジのセカンダリバス番号よりも大きく、 かつサブオーティネイトバス番号以下である場合、それをそのままセカン ダリバスに渡す。 したがって、もし``図表(6.9)''のトポロジーの Bus 3 上のデバイス 1 (D1)と通信したい場合は、Type 1 コンフィグレーションコマンドを CPU から 送り出さなければならない。ブリッジ 1 は、これを変更せずに Bus 1 に渡 す。ブリッジ 2 は、これを無視するが、ブリッジ 3 は、これを Type 0 コン フィグレーションコマンドに変換し、バス 3 上に送信するので、そこにある デバイス 1 がこれに応答する。 PCI の初期化の過程でバス番号を割り当てることは個々のオペレーティングシ ステムの責任であるが、どのような番号割り当て方法が取られた場合でも、次 の説明はシステムのすべての PCI-PCI ブリッジに当てはまらなければならな い。 「PCI-PCI ブリッジの下流にあるすべての PCI バスは、セカンダ リバスのバス番号とサブオーティネイトバス番号との間(その番号 を含む)のバス番号を持たなければならない。」 このルールが破られた場合、PCI-PCI ブリッジは、Type 1 コンフィグレー ションサイクルを正しく渡したり変換したりできないので、システムはシステ ム上の PCI デバイスを見つけて初期化することに失敗することになる。この 番号割り当て方法を実現するために、 Linux は、それらの特殊なデバイスで ある PCI-PCI ブリッジをある決まった順番で設定する。``「PCI バス番号の 割り当て」'' のセクションでは、Linux の PCI ブリッジとバス番号当ての方 法について、その実際の例も交えて詳解している。 7.6. Linux の PCI 初期化方法 Linux の PCI 初期化コードは、論理的に 3 つの部分に分かれる。 PCI デバイスドライバ この仮想デバイスドライバは、Bus 0 から PCI システムの検索を始め て、システム内のすべての PCI デバイスとブリッジの所在を検出す る。そのドライバは、システムのトポロジーを示すデータ構造体の連結 リストを作成する。さらに、発見したブリッジすべてに番号付けをす る。 [see: drivers/pci/pci.c, include/linux/pci.h] PCI BIOS このソフトウェア層(layer)は、``「PCI BIOS の機能」''で述べるサー ビスを提供する。Alpha AXP は BIOS サービスを持たないが、同様の機 能を提供するコードが Linux カーネルの中にある。 [see: arch/*/kernel/bios32.c](i386, alpha) PCI フィックスアップ(fixup) システム固有のフィックスアップコード(fixup code)は、PCI の初期化 におけるシステム固有の設定の隙間を埋める役割を果たす。 [see: arch/*/kernel/bios32.c](i386, alpha) 7.6.1. Linux カーネルの PCI データ構造 pci_bus pci_root +----------+ ------------->| parent | | children |--------+ | next | | | self | | | devices |--+ | | bus = 0 | | | +----------+ | | +-------------------+-----+ | | | +--------------+ | | | | pci_dev pci_dev pci_dev | | +---------+ +---------+ +---------+ | +-->| bus | +-->| bus | +--->| bus | | | sibling | | | sibling | | +->| sibling | | | next |--+ | next |--+ | | next | | | | | | | | | | | | | | | | | | +---------+ +---------+ | +---------+ | PCI-ISA Bridge Video | PCI-PCI Bridge | | | pci_bus | | +----------+ | +---->| parent | | | children | | | next | | | self |-----------------------+ | devices |--+ | bus = 1 | | pci_dev pci_dev +----------+ | +---------+ +---------+ +---->| bus | +--->| bus | | sibling | | | sibling | | next |--+ | next | | | | | | | | | +---------+ +---------+ SCSI Ethernet 図表(6.5) Linux カーネルの PCI データ構造 Linux カーネルが PCI システムを初期化する際、カーネルは、システムの実 際上の PCI トポロジーを忠実に反映するため PCI データ構造体を作成する。 上記図表(6.5) は、``図表(6.1)''の PCI システムの例に対して作成されるデ ータ構造体の相互関係を表したものである。 個々の PCI デバイス(PCI-PCI ブリッジも含む)は、``pci_dev'' データ構造 体によって記述される。個々の PCI バスは、``pci_bus'' データ構造体で記 述される。その結果、PCI バスのツリー構造が生成され、個々の PCI バスに いくつかの子 PCI デバイスが繋がる構造ができる。PCI バスには、 (プライ マリ PCI バスである Bus 0 を除いて) PCI-PCI ブリッジを使ってのみ到達が 可能なので、個々の pci_bus 構造体には、そのバスへアクセスするための PCI デバイス(すなわち PCI-PCI ブリッジ)へのポインタが含まれている。 (pci_bus 構造体では)PCI ブリッジは、もとのバスの親に当たる PCI バスか ら見て子に該当する。 ``図表(6.5)''では表示されていないが、システム上のすべての PCI デバイス に対するポインタとして ``pci_devices'' がある。システム上のすべての PCI デバイスは、そのキューの上に pci_dev データ構造体を登録する。この キューは、Linux カーネルがシステム上のすべての PCI デバイスをすばやく 探すために使用される。 7.6.2. PCI デバイスドライバ PCI デバイスドライバは、実際にはデバイスドライバではなく、システムの初 期化の際にコールされるオペレーティングシステムの関数である。PCI 初期化 コードは、システム上のあらゆる PCI バスをスキャンして、すべての PCI デ バイスを捜す(これには、PCI-PCI ブリッジデバイスも含まれる)。 [see: Scan_bus(), in drivers/pci/pci/c] PCI 初期化コードは、PCI BIOS のコードを使用して、現在スキャン中の PCI バス上に存在するスロットが占有されているかどうかをすべて調べる。PCI ス ロットが占有されている場合、PCI 初期化コードは、``pci_dev'' 構造体を作 成してそのデバイスを記述し、その構造体を(``pci_devices'' によってポイ ントされた)既知の PCI デバイスのリストにリンクする。 PCI 初期化コードは、PCI バスの Bus 0 をスキャンすることから始める。そ れは、存在する全 PCI スロットの全 PCI デバイスに対して、そのベンダ ID とデバイス ID を読み出そうとする。占有されたスロットを見つけた ら、``pci_dev'' データ構造体を作成し、デバイス情報を書き込む。PCI 初期 化コードによって作成された pci_dev データ構造はすべて(すべての PCI-PCI ブリッジも含めて)、単純な連結リストである pci_devices にリンクされる。 発見された PCI デバイスが PCI-PCI ブリッジであった場合は、``pci_bus'' データ構造体が作成され、pci_bus と ``pci_dev'' データ構造体から成るツ リーにリンクされる。そのツリーは、 ``pci_root'' によって参照され る。PCI-PCI ブリッジは、クラスコード``(class code)'' 0x060400 を持って いるので、PCI 初期化コードは、PCI デバイスが PCI-PCI ブリッジであるか どうかを判別できる。次に、Linux カーネルは、発見された PCI-PCI ブリッ ジの反対側(下流側)にある PCI バスを設定する。そのバス上でさらに PCI- PCI ブリッジが発見された場合、それらにも設定がなされる。このプロセス は、デプスワイズアルゴリズム(depthwise algorithm)と呼ばれる。システム の PCI トポロジーは、まずひとつの階層が一番奥までマップされてから、並 列する部分の検出が行われる。``図表(6.1)''では、Linux は、まず PCI Bus 1 にあるイーサネットと SCSI デバイスを設定してから、PCI Bus 0 にあるビ デオデバイスを設定する。 Linux が下流の PCI バスを検出する際は、介在する PCI-PCI ブリッジのセカ ンダリバス番号とサブオーディネイトバス番号も設定しなければならない。こ れについては、以下の``「PCI-PCI ブリッジの設定: PCI バス番号の割り当 て」''で詳しく説明する。 7.6.2.1. PCI-PCI ブリッジの設定 - PCI バス番号の割り当て +-----------+ | | | | +-------+ | CPU | +-------+ | | | | | | | D 1 | | | | D 2 | | | | | | | +---+---+ +-----+-----+ +---+---+ Bus 0 | | | -----------+------------------+------------------+------------------ | | +-------+ +-------+ +----+----+ | | | | | |Primary Bus = 0 | D 1 | | D 2 | | Bridge |Secondary Bus = 1 | | | | | 1 |Subordinate = 0xFF +---+---+ +---+---+ +----+----+ | | | Bus 1 ----+---------+-+--------------+-------------+--------------------- | | | | +-------+ +-----+---+ +----+----+ | | | | | | | D 1 | | Bridge | | Bridge | | | | 3 | | 2 | +---+---+ +----+----+ +----+----+ | | Bus ? | Bus ? ----+-------+---+------------ ------------+---------------------- | | +----+----+ +-------+ +-------+ | | | | | | | Bridge | | D 1 | | D 2 | | 4 | | | | | +----+----+ +---+---+ +---+---+ | | | Bus ? -----------+----------------------------+----------+---------------- 図表(6.6) PCI システムの設定: Part 1 PCI-PCI ブリッジを介して PCI I/O アドレス空間、PCI メモリアドレス空 間、または PCI コンフィグレーションアドレス空間への読み書きを行うため には、各 PCI-PCI ブリッジについて以下の情報が必要である。 プライマリバス番号 PCI-PCI ブリッジの直上のバス番号 セカンダリバス番号 PCI-PCI ブリッジの直下のバス番号 サブオーディネイトバス番号(subordinate bus number) ブリッジの下流に位置する到達可能なバスの中で、最も大きなバス番号 PCI I/O と PCI メモリウィンドウ PCI-PCI ブリッジの下流にある、PCI I/O アドレス空間と PCI メモリ アドレス空間のすべてのアドレスに対するウィンドウのベースアドレス とそのサイズ 問題は、ある PCI-PCI ブリッジを設定しようとするとき、そのブリッジに関 するサブオーディネイトバス番号をその時点では知り得ないということであ る。さらに下流の PCI-PCI ブリッジがあるかどうか分からず、分かったとし てもそれらに割り当てる番号は分からない。解決策は、デプスワイズ再帰的ア ルゴリズム(depthwise recursive algorithm)を使用して PCI-PCI ブリッジの 個々のバスをスキャンして、発見された時点で番号を当てていくことである。 個々の PCI-PCI ブリッジが発見されて、そのセカンダリバス番号が割り当て られたら、それに一時的なサブオーディネイトバス番号として 0xFF を割り当 てておいて、その下流にあるすべての PCI-PCI ブリッジをスキャンして番号 を割り当てる。こうした処理全体は複雑に思えるかもしれないが、下記の実際 の例を見れば、そのプロセスが分かりやすくなるだろう。 PCI-PCI ブリッジ番号割り当て: Step 1 ``図表(6.6)''のトポロジーを使うと、スキャンによって、最初のブ リッジとして Bridge 1 が発見される。Bridge 1 の下流に位置する PCI バスは、バス 1 と番号付けられ、Bridge 1 は、セカンダリバス番 号として 1 が、一時的なサブオーディネイトバス番号として OxFF が 割り当てられる。これは、PCI バス番号として 1 かそれ以上を指定す るType 1 の PCI コンフィグレーションアドレスが、Bridge 1 を通っ て PCI バス 1 に渡されることを意味する。それらは、バス番号 1 を 持つ場合は Type 0 のコンフィグレーションサイクルに変換されるが、 それ以外の番号の場合は変換されない。このことは、PCI バス 1 に降 りてスキャンするために、Linux PCI 初期化コードがしなければならな いことそのものである。 +-----------+ | | | | +-------+ | CPU | +-------+ | | | | | | | D 1 | | | | D 2 | | | | | | | +---+---+ +-----+-----+ +---+---+ Bus 0 | | | -----------+------------------+------------------+------------------ | | +-------+ +-------+ +----+----+ | | | | | |Primary Bus = 0 | D 1 | | D 2 | | Bridge |Secondary Bus = 1 | | | | | 1 |Subordinate = 0xFF +---+---+ +---+---+ +----+----+ | | | Bus 1 ----+---------+-+--------------+-------------+--------------------- | | | | +-------+ +-----+---+ +----+----+ | | | | | |Primary Bus = 1 | D 1 | | Bridge | | Bridge |Secondary Bus = 2 | | | 3 | | 2 |Subordinate = 2 +---+---+ +----+----+ +----+----+ | | Bus ? | Bus 2 ----+-------+---+------------ ------------+---------------------- | | +----+----+ +-------+ +-------+ | | | | | | | Bridge | | D 1 | | D 2 | | 4 | | | | | +----+----+ +---+---+ +---+---+ | | | Bus ? -----------+----------------------------+----------+---------------- 図表(6.7) PCI システムの設定: Part 2 PCI-PCI ブリッジの番号割り当て: Part 2 Linux は、デプスワイズアルゴリズム(depthwise algorithm)を使うの で、初期化コードは PCI バス 1 へと進んでスキャンする。ここで、初 期化コードは、PCI-PCI Bridge 2 を発見する。PCI-PCI ブリッジ 2 以 下には PCI-PCI ブリッジは存在しないので、それにはサブオーディネ イトバス番号 2 が割り当てられ、それはそのセカンダリインターフェ イスに割り当てられた番号と合致する。 ``図表(6.7)''では、バスと PCI-PCI ブリッジとがこの時点で番号付けされたことを示している。 +-----------+ | | | | +-------+ | CPU | +-------+ | | | | | | | D 1 | | | | D 2 | | | | | | | +---+---+ +-----+-----+ +---+---+ Bus 0 | | | -----------+------------------+------------------+------------------ | | +-------+ +-------+ +----+----+ | | | | | |Primary Bus = 0 | D 1 | | D 2 | | Bridge |Secondary Bus = 1 | | | | | 1 |Subordinate = 0xFF +---+---+ +---+---+ +----+----+ | | | Bus 1 ----+---------+-+--------------+-------------+--------------------- | | | | +-------+ +-----+---+ +----+----+ | | | |Primary Bus = 1 | |Primary Bus = 1 | D 1 | | Bridge |Secondary Bus = 3 | Bridge |Secondary Bus = 2 | | | 3 |Subordinate = 0xFF | 2 |Subordinate = 2 +---+---+ +----+----+ +----+----+ | | Bus 3 | Bus 2 ----+-------+---+------------ ------------+---------------------- | | +----+----+ +-------+ +-------+ | | | | | | | Bridge | | D 1 | | D 2 | | 4 | | | | | +----+----+ +---+---+ +---+---+ | | | Bus ? -----------+----------------------------+----------+---------------- 図表(6.8) PCI システム設定: Part 3 PCI-PCI ブリッジの番号割り当て: Step 3 PCI 初期化コードは、PCI バス 1 のスキャニングに戻り、もうひとつ の PCI-PCI ブリッジである Bridge 3 を発見する。それは、そのプラ イマリバスインターフェイスとして 1 を割り当てられ、セカンダリイ ンターフェイスの番号として 3 を、サブオーディネイトバス番号とし て 0xFF を割り当てられる。 ``図表(6.8)''では、システムが現在どの ように設定されたのかを示している。バス番号 1, 2, 3 についての Type 1 の PCI コンフィグレーションサイクルは、適切な PCI バスに 対して正しく伝達される。 +-----------+ | | | | +-------+ | CPU | +-------+ | | | | | | | D 1 | | | | D 2 | | | | | | | +---+---+ +-----+-----+ +---+---+ Bus 0 | | | -----------+------------------+------------------+------------------ | | +-------+ +-------+ +----+----+ | | | | | |Primary Bus = 0 | D 1 | | D 2 | | Bridge |Secondary Bus = 1 | | | | | 1 |Subordinate = 4 +---+---+ +---+---+ +----+----+ | | | Bus 1 ----+---------+-+--------------+-------------+--------------------- | | | | +-------+ +-----+---+ +----+----+ | | | |Primary Bus = 1 | |Primary Bus = 1 | D 1 | | Bridge |Secondary Bus = 3 | Bridge |Secondary Bus = 2 | | | 3 |Subordinate = 4 | 2 |Subordinate = 2 +---+---+ +----+----+ +----+----+ | | Bus 3 | Bus 2 ----+-------+---+------------ ------------+---------------------- | | +----+----+ +-------+ +-------+ | |Primary Bus = 3 | | | | | Bridge |Secondary Bus = 4 | D 1 | | D 2 | | 4 |Subordinate = 4 | | | | +----+----+ +---+---+ +---+---+ | | | Bus 4 -----------+----------------------------+----------+---------------- 図表(6.9) PCI システムの設定: Part4 PCI-PCI ブリッジの番号当て: Step 4 Linux は、PCI-PCI ブリッジ Bridge 3 の下流にある PCI バス 3 のス キャニングを始める。PCI バス 3 は、次の PCI-PCI ブリッジ(Bridge 4)をバス上に持っているので、それは、プライマリバス番号として 3 を、セカンダリバス番号として 4 を割り当てられる。これはそのバス 上の最後のブリッジなので、ブリッジにはサブオーディネイトバスイン ターフェイス番号 4 が割り当てられる。初期化コードは、PCI-PCI Bridge 3 に戻り、それにサブオーディネイトバス番号 4 を割り当て る。最後に、PCI 初期化コードは、PCI-PCI Bridge 1 にサブオーディ ネイトバス番号 4 を割り当てることができる。``図表(6.9)''では、最 後のバス番号が示されている。 7.6.3. PCI BIOS 関数 PCI BIOS 関数は、全プラットフォームに共通な、一連の標準ルーチンであ る。たとえば、それらは、Intel や Alpah AXP ベースのシステムの両方で同 一である。 PCI BIOS 関数は、PCI アドレス空間への CPU で制御されたアク セスを可能にする。 [see: arch/*/kernel/bios32.c]( i386, alpha ) それらを使用できるのは、Linux カーネルコードとデバイスドライバだけであ る。 7.6.4. PCI フィックスアップ(fixup) Alpah AXP の PCI フィックスアップ(fixup)コードは、Intel のもの(Intel 版のコードは基本的に何もしない)以上の役割を果たす。 [see: arch/*/kernel/bios32.c]( i386, alpha ) Intel ベースのシステムでは、ブート時に実行されるシステム BIOS が、PCI システムを完全に設定する。そのために、Linux は、そこでの設定をマップす る以外はほとんどすることがない。Intel ベースでないシステムの場合、次の ようなさらなる設定作業がなされる必要がある。 o 個々のデバイスに PCI I/O と PCI メモリ空間を割り当てること。 o システムの PCI-PCI ブリッジそれぞれに、PCI I/O アドレスと PCI メモ リアドレスのウィンドウを設定すること。 o デバイスの割り込み処理を制御する、割り込みライン値を生成すること。 次のサブセクションでは、それらのコードの働きを説明する。 7.6.4.1. デバイスが必要とする PCI I/O と PCI メモリ空間の容量を調べる 検出された PCI デバイスは、その PCI I/O と PCI メモリがアドレス空間を どの程度必要とするのか調べるために、検査される。その検査のために、個々 のベースアドレスレジスタには、すべて 1 が書き込まれ、その後で読み出さ れる。デバイスは、使用しないアドレスのビットに関しては 0 を返すので、 必要とされるアドレス空間を効率的に確定することができる。 31 4 3 2 1 0 +--------------------------------------------------+--+-----+--+ | | | | | | Base Address | | | 0| | | | | | +--------------------------------------------------+--+-----+--+ | | prefetchable-----+ Type PCI メモリ空間のベースアドレス 31 2 1 0 +---------------------------------------------------------+--+--+ | | | | | Base Address | | | | | | | +---------------------------------------------------------+--+--+ | Reserved PCI I/O 空間のベースアドレス 図表(6.10) PCI コンフィグレーションヘッダ: ベースアドレスレジスタ ベースアドレスレジスタには、基本的にふたつのタイプがある。ひとつは、デ バイスレジスタが、PCI I/O もしくは PCI メモリ空間のどの範囲になければ ならないかを示す。これは、レジスタのビット 0 によって示される。 ``図 表(6.10)''では、PCI メモリと PCI I/O についての二種類のフォームのベー スアドレスレジスタが示されている。 そのベースアドレスレジスタがどの程度のアドレス空間を要求しているのかを 正確に知るために、そのレジスタにすべて 1 を書き込んで、そのあとでそれ を読み出す。デバイスは、使用しないアドレス空間ではビット値 0 を返すの で、効果的に必要なアドレス空間を確定できる。こうした設計方法によって、 利用されるアドレス空間全体は 2 の n 乗となり、自然な境界に整合する位置 に置かれることになる。 たとえば、DEC チップ 21142 PCI ファーストイーサネットデバイスを初期化 するとき、デバイスは、PCI I/O か PCI メモリのいずれかのスペースとして 0x100 バイトが必要であると告げるとする。初期化コードは、デバイスにその 空間を割り当てる。空間を割り当てた瞬間、21142 のコントロールレジスタと ステータスレジスタが、それらのアドレス上に存在するのがわかるようにな る。 7.6.4.2. PCI-PCI ブリッジとデバイスに対して PCI I/O と PCI メモリを割 り当てる すべてのメモリと同様に、PCI I/O と PCI メモリ空間は有限であり、ときに やや不足することもある。Intel 以外のシステムにおける PCI フィックス アップコード (fixup code)(および、Intel システムにおける BIOS コー ド)は、個々のデバイスに対して、それが要求する量のメモリを効率的に割り 当てなければならない。一方、PCI I/O と PCI メモリ空間のアドレスは、自 然な境界に整合するような方法で各デバイスに割り当てられなければならな い。たとえば、デバイスが PCI I/O 空間に 0xB0 バイトの容量を要求する場 合、それは 0xB0 の倍数となるアドレス上に割り当てられる必要がある。これ に加えて、各ブリッジに関する PCI I/O と PCI メモリのベースアドレスは、 それぞれ 4 k と 1 M バイトの境界に整合しなければならない。下流に位置す るデバイスのアドレス空間は、上流にあるすべてのブリッジが下流デバイス用 として確保するメモリ領域内になければならない。したがって、空間を効率的 に割り当てるのは、やや難しい問題である。 Linux が使っているアルゴリズムでは、PCI デバイスドライバによって作成さ れたバスとデバイスとのツリー構造によって記述される個々のデバイスに対し て、PCI I/O 空間および PCI メモリ空間にそれぞれが使うアドレスを昇順に 割り当てるという方法でこの問題に対処している。この場合も、再帰的アルゴ リズムが使用され、PCI 初期化コードによって作成された ``pci_bus'' と ``pci_dev'' データ構造体が順番に調べられる。ルート PCI バス(これ は、``pci_root'' によってポイントされている)から調査を始めて、BIOS 関 数やフィックスアップコードは次のようなことをする。 o グローバルな PCI I/O と PCI メモリのカレント(current)ベースアドレス を 4 k と 1 M バイトの境界に合わせてそれぞれ設定する。 o カレントバス上のすべてのデバイスに対して、(要求された PCI I/O およ び PCI メモリ空間上のアドレスについては昇順で)、次のことをする。 o デバイスに、PCI I/O と PCI メモリの両方か一方の空間を割り当て る。 o 適切な容量分だけ、グローバルな PCI I/O と PCI メモリのベースアド レスを移動させる。 o デバイスが PCI I/O と PCI メモリを使用できるようにする。 o カレントバスの下流にあるすべてのバスに対して、再帰的に空間を割り当 てる。これには、グローバル PCI I/O と PCI メモリのベースアドレスの 変更が伴うことに注意。 o グローバルな PCI I/O と PCI メモリのカレントベースアドレスをそれぞ れ 4 k と 1 M バイトの境界に合わせて設定し、その際に、現在の PCI- PCI ブリッジが必要とする PCI I/O と PCI メモリのウィンドウのサイズ とベースアドレスとを調べる。 o そのバスに繋がっている PCI-PCI ブリッジを、その PCI I/O と PCI メモ リのベースアドレスと上限アドレスとを使って、プログラムする。 o PCI-PCI ブリッジにプログラムされた PCI I/O と PCI メモリへのアクセ スがある場合にそれを通過させる機能を、有効にする。このことは、サイ ズとベースアドレスによって指定されたそのブリッジのアドレス空間ウィ ンドウ内にある PCI I/O または PCI メモリアドレスがブリッジのプライ マリバスに現れた場合、それをセカンダリ PCI バスに通過させる (bridge する) ことを意味する。 ``図表(6.1)''にある PCI システムを例に取ると、 PCI フィックスアップコ ードは、次のような方法でシステムを設定することになる。 PCI ベースアドレスの初期設定 ベースアドレスについて、PCI I/O を 0x4000 とし、PCI メモリを 0x100000 とする。これによって、PCI-ISA ブリッジは、これより下位 のアドレスを ISA アドレスサイクルに変換できるようになる。 ビデオデバイス このデバイスは、PCI メモリの容量として 0x200000 を要求している。 したがって、要求されたサイズのメモリを連続的に割り当てる必要があ るので、その容量を現在の PCI メモリのベースアドレスである 0x200000 から始まる領域に割り当てる。これによって、PCI メモリの ベースアドレスは 0x400000 に移動し、PCI I/O のベースアドレスは 0x4000 のままである。 PCI-PCI ブリッジ この時点で、PCI-PCI ブリッジを通過して、その下流にあるデバイスに PCI メモリを割り当てる。注意: ベースアドレスは既に正しく設定され ており、ここで再設定する必要はない。 イーサネットデバイス このデバイスは、PCI I/O と PCI メモリ空間両方に 0xB0 バイトを 要求している。したがって、0x4000 に PCI I/O が、0x400000 に PCI メモリが割り当てられる。 PCI メモリのベースアドレス は、0x4000B0 に移動し、PCI I/O ベースアドレスは 0x40B0 に移動 する。 SCSI デバイス このデバイスは、PCI メモリに 0x1000 を要求しているので、それ が自然な境界に沿って配置されるように、0x401000 に必要な空間を 割り当てられる。 PCI I/O ベースアドレスは、そのまま 0x40B0 で あり、PCI メモリのベースアドレスは、0x402000 に移動する。 PCI-PCI ブリッジの PCI I/O と PCI メモリのウィンドウ ここで、ブリッジに戻って、PCI I/O ウィンドウを 0x4000 と 0x40B0 の間に設定して、その PCI メモリウィンドウを 0x400000 と 0x402000 との間に設定する。これは、PCI-PCI ブリッジがビデオデバイスのため の PCI メモリアクセスを無視して、それらがイーサネットか SCSI デ バイスのためのものなら通すということを意味する。 (-- (脚註1)たとえば?--) 8. 割り込みと割り込み処理 この章では、割り込みが Linux によってどのように処理されるかを見る。カ ーネルは、割り込み処理のための汎用的な仕組みとインターフェイスを持って いるが、割り込み処理の詳細の大部分はアーキテクチャに固有のものである。 +---------------+ | * / | | | / | | |/ | |. + .| +-----------------+ Real Time Clock| | | |<-------------------------------| | | | +---------+ | . | | |<-------| |0 +---------------+ | C P U | | |1<-------------キーボード | | +-->| P |2 | | | | I | | | | | C |4<----------シリアルポート +-----------------+ | | |5<----------サウンド | | 1 |6<----------フロッピードライブ | | |7 | +---------+ | | +---------+ | | |0 +---| | | P | | I |3<-------------SCSI | C | | | | 2 |6<-------------ide0 | |7<-------------ide1 +---------+ 図表(7.1) 割り込みのルーティングに関する論理的なダイアグラム Linux は、多種多様なハードウェアを使用して多くの異なるタスクを実行して いる。ビデオデバイスはモニタをドライブし、IDE デバイスはディスクをドラ イブする等である。それらの複数のデバイスを同期させて使用することも可能 であり、その際は、ある操作についてのリクエストを送り(たとえば、あるメ モリブロックをディスクに書き出す等)、処理の完了を待つことになる。この 方法は、たしかに実行はされるのだが、非常に効率が悪く、オペレーティング システムは個々の操作が完了するまで待ち状態になるため、「何もしないビジ ー」状態で多くの時間を消費することになる。もっと効率のよい方法は、リク エストを送った後、より有益な他の仕事をしながら、リクエストが完了した時 点でデバイスから割り込みを受けるという方法である。この方法ならば、デバ イスに対する多くのリクエストを未処理の状態で同時にシステム上に併存させ ることが可能になる。 CPU がその時にどのような処理を行っていようともデバイスが割り込みをかけ ることができるためには、なんらかのハードウェアによるサポートが必要であ る。すべてではないとしても、Alpha AXP のような大部分の汎用プロセッサ は、そのために類似した方法を使っている。 CPU の物理ピンのいくつかは、 そのピンに電圧の変化(たとえば、+5V から -5V への変化)が生じると、CPU の現在の処理が中断され、割り込みを処理をする特別なコードである割り込み 処理コード(interrupt handling code)の実行が開始されるように設計されて いる。それらのピンのひとつはインターバルタイマーに接続され、千分の一秒 ごとに割り込みを受けられるようになっていて、それ以外のピンが SCSI コン トローラのようなシステム上の他のデバイスに接続されている。 しばしばシステムは、割り込みコントローラ(interrupt controller)を使用し て、デバイスの複数の割り込みをグループ化し、その上で CPU 上の単一の割 り込みピンにシグナルを送っている。これは CPU の割り込みピンの数を節約 し、システムをデザインする際に柔軟性をももたらす。割り込みコントローラ は、マスクレジスタ(mask register)とステータスレジスタ(status register)を持つことで割り込みを制御している。マスクレジスタは、ビット の設定によって割り込みの可不可を決定するものであり、ステータスレジスタ はシステム上の現在アクティブな割り込みを示すものである。 システム上の割り込みのいくつかは、物理的に配線が固定されている場合があ る。たとえば、リアルタイムクロックのインターバルタイマーは、割り込みコ ントローラのピン 3 に固定的に接続されていることがある。しかし、ピンが 何に接続されているかは、特定の PCI か ISA スロットにどのようなコントロ ーラカードが差されているかによって決まる場合もある。たとえば、割り込み コントローラのピン 4 は、PCI スロットの 0 番に接続されているかもしれ ず、そのスロットには、あるときはイーサネットが差されていて、別のときは SCSI コントローラが差されているかもしれない。結局、基本的に、割り込み を伝達する仕組みはシステムごとに異なるので、オペレーティングシステムに はそれに対処するだけの柔軟性がなければならない。 現在の汎用マイクロプロセッサの大部分は、同じ方法で割り込みを処理してい る。ハードウェア割り込みが発生すると、CPU は、現在実行中の命令の実行を 停止し、割り込み処理コードそのものか、あるいは割り込み処理コードへと分 岐する命令のどちらかを含んだメモリ内のある場所へとジャンプする。通常こ のコードは、割り込みモードと言われる CPU の特別なモードで実行されるの で、一般に、このモードにあるときは、他の割り込みは起こらない。ただし例 外があり、ある CPU では割り込みに優先順位を付けているので優先順位の高 い割り込みは起こる場合がある。すなわち、最高の優先度を持つ割り込み処理 コードは非常に入念に書かれていなければならず、しばしば独自のスタックを 備えているため、その割り込み処理コードは、起動されて割り込みを処理する 前に、そのスタックを使って CPU の実行状態(CPU の通常のレジスタとコンテ キストのすべて)を保存する。 CPU のなかには、割り込みモード専用の特別な レジスタセットを持っているものがあるため、割り込み処理コードは、それら のレジスタを使って必要なコンテキスト保存操作の大部分を実行する。 割り込み処理が終了すると、CPU の状態は元に戻され、割り込みは解除され る。 CPU は、割り込みが起こる前に実行していた処理を継続する。重要なの は、割り込み処理コードは出来る限り効率の良いものであることであり、オペ レーティングシステムが頻繁に、あるいは長時間、他の割り込みをブロックす ることがないようにすることである。 8.1. プログラム可能な割り込みコントローラ システム設計者は、自分の好きな割り込みの仕組みを自由に使うことができる が、 IBM PC では、Intel 82C59A-2 という CMOS プログラム可能な割り込み コントローラ (Programable Interrupt Controller)か、その派生コントロー ラが使用されている。このコントローラは、PC の黎明期からあるもので、ISA アドレス空間の決まった場所 (well known location)に存在するレジスタを 使ってプログラムすることができる。最新の補助ロジックチップでさえ、同様 のレジスタを ISA メモリの同じ場所に置いている。Alpha AXP のような非 Intel ベースの PC では、そうしたハードウェア上の制約から解放されてお り、たいていの場合、それとは異なる割り込みコントローラを使用している。 ``図表(7.1)''では、ふたつの 8 ビットのプログラム可能な割り込みコントロ ーラ(Programable Interrupt Controller, PIC)が鎖状に繋がれていて、PIC 1 と PIC 2 の個々のコントローラが、それぞれマスクレジスタ(mask register)と割り込みステータスレジスタ(interrupt status register)を持っ ている様子が示されている。マスクレジスタは、アドレス 0x21 と 0xA1 にあ り、ステータスレジスタは、アドレス 0x20 と 0xA0 にある。マスクレジスタ の特定のビットに 1 を書き込むと割り込みが可能になり、0 を書き込むと不 可になる。そして、ビット 3 に 1 を書き込むと、割り込み 3 が使用可能に なり、 0 を書き込むと不可となる。残念なことに(そして、困ったことに)、 割り込みマスクレジスタは書き込み専用であり、書き込んだ値を読み出すこと はできない。すなわち、 Linux は、マスクレジスタに設定した値のコピーを 自分で保存しなければならないことを意味する。Linux はいつも、割り込みの 可不可を設定するルーチンに保存した、それらのマスク値を修正してから、レ ジスタにマスク値全部を上書きしている。 割り込みが発生したとき、割り込み処理コードは、ふたつの割り込みステータ スレジスタ(Interrupt status register, ISR)の値を読み出す。割り込み処理 コードは、16 ビットある割り込みステータスレジスタのうち、0x20 にある ISR を下位 8 ビットとして扱い、0xA0 にある ISR を上位 8 ビットとして扱 う。それゆえ、0xA0 にある ISR のビット 1 は、システム割り込み 9 として 扱われる。プログラム可能な割り込みコントローラ PIC 1 のビット 2 は利用 できないが、それは、PIC 2 からの割り込みを繋げるために使用されているか らであり、PIC 2 上の割り込みは、PIC 1 のビット 2 としてセットされる。 8.2. 割り込み処理のデータ構造の初期化 カーネルの割り込み処理のためのデータ構造体は、デバイスドライバがシステ ムの割り込みの制御を要求する際に、デバイスドライバによって設定される。 その場合、デバイスドライバは、Linux カーネルの一連のサービスを使用し て、それによって、割り込みを要求したり、あるいは割り込みの可不可を設定 したりする。 [see: request_irq(), enable_irq(), disable_irq(), in arch/*/kernel/irq.c] (i386, alpha) 個別のデバイスドライバは、そうしたルーチンを呼び出して、それぞれの割り 込み処理ルーチンのアドレスを登録する。 割り込みのなかには、PC アーキテクチャの慣習によって固定されているもの があるので、その場合、ドライバは、初期化される際、単にその割り込みを要 求するだけでよい。これは、フロッピーディスクドライバの動作そのものであ り、フロッピーディスクドライバは、いつも IRQ 6 を要求する。デバイスド ライバは、そのデバイスがどの割り込みを使うかを知らない場合もある。 PCI デバイスドライバの場合は、割り込み番号は常に明らかなので、そうした問題 は起きない。残念ながら、ISA デバイスドライバが割り込み番号を知ろうとす る場合は、簡単な方法というのはない。Linux は、この問題を解決するため に、デバイスドライバに割り込みを検出させる。 まず、デバイスドライバは、何らかの形でデバイスに働きかけて、デバイスに 割り込みを発生させる。次に、システム上でまだ割り当てられていないすべて の割り込みを有効にする。これによって、そのデバイスが伝達できずに保留し ていた割り込みが、プログラム可能な割り込みコントローラ経由で伝達され る。 Linux は、割り込みステータスレジスタを読み出し、デバイスドライバ にその内容を返す。0 でない値が返った場合、それは、ひとつ、もしくはそれ 以上の割り込みが、割り込み検出中に発生したことを意味する。この時点で、 ドライバは、検出を終了し、割り当てられていない割り込みは、すべて無効に される。 [see: irq_problem_*(), in arch/*/kernel/irq.c](i386, alpha) ISA デバイスドライバが、IRQ 番号の検出に成功した場合、ドライバは、通常 通りその IRQ 番号の制御を要求する。 PCI ベースのシステムでは、ISA ベースのシステムよりももっと動的である。 ISA デバイスが使用する割り込みピン(IRQ)は、しばしばハードウェアデバイ ス上のジャンパを使って設定されるので、デバイスドライバ内で固定されてい る。反対に、PCI デバイスは、システムのブート時に PCI が初期化される 際、 PCI BIOS か PCI サブシステムによって割り込みを割り当てられる。個 々の PCI デバイスは、A, B, C, D の 4 つの割り込みのうち、ひとつを使用 する。 (訳注: 規格上は、ひとつから 4 つまで使用できるそうです。) これ はデバイスの作成時に固定されており、大部分のデバイスは標準的なピン A を割り込みに使用する。個々の PCI スロット上の A, B, C, D の割り込みラ イン(interrupt line)は、割り込みコントローラに配線されている。それゆ え、PCI スロット 4 のピン A は、割り込みコントローラのピン 6 に配線さ れていて、PCI スロット 4 のピン B は、割り込みコントローラのピン 7 に 配線されている等々となっている。 割り込みがどのように伝達されるかは、完全にシステム固有のものであるの で、そのシステムの PCI 割り込みを伝達するトポロジーを理解しているなん らかの設定コードが存在する必要がある。 Intel ベースの PC では、これ は、起動時に実行されるシステム BIOS であるが、 BIOS のないシステム(た とえば、Alpha AXP ベースのシステム)では、Linux カーネルがそのセット アップを行う。 [see: arch/alpha/kernel/bios32.c] PCI 設定コードは、割り込みコントローラのピン番号をデバイスごとの ``PCI コンフィグレーションヘッダ''に書き込む。 PCI 設定コードは、、PCI の割 り込み伝達トポロジーやデバイスの PCI スロット番号、およびそれが現在ど の PCI 割り込みピンを使っているかという知識を利用して、割り込みピン(も しくは IRQ)を決定する。デバイスが使用する割り込みピンは固定されてい て、そのデバイスの PCI コンフィグレーションヘッダのフィールド内に保存 されている。 PCI 設定コードは、割り込みライン(Interrupt Line)フィール ドに、その情報を書き込む。割り込みラインフィールドはこのために予約され たフィールドである。デバイスドライバの実行時には、ドライバはこの情報を 読み出し、それを使って Linux カーネルに割り込み制御を渡すよう要求す る。 たとえば、PCI-PCI ブリッジが使用されているシステム上などでは、PCI 割り 込みを必要とするソースがたくさん存在する場合がある。割り込みソースの数 が、システムのプログラム可能な割り込みコントローラ上にあるピンの数を超 える場合もある。その場合、PCI デバイスは割り込みを共有し、割り込みコン トローラ上のひとつのピンで、複数の PCI デバイスからの割り込みを受ける ことがある。 Linux がこれをサポートする場合、割り込みソースの中でその 割り込みピンを最初に要求したデバイスが、それを共有するかどうか明示でき るようにするという方法を取っている。割り込みが共有されるときは、いくつ かの ``irqaction'' データ構造体が作成され、それらのデータ構造体が、 ``irq_action'' 配列の配列内にある各エントリによってポイントされる。共 有割り込みが起こると、Linux は、そのソースに対するすべての割り込みハン ドラを呼び出す。割り込みの共有が可能なすべてのデバイスドライバ(これら はすべて PCI デバイスドライバでなければならない)は、割り込みがサービス されていないときは、そのドライバの割り込みハンドラーがいつ呼び出されて もいいように準備していなければならない。 8.3. 割り込み処理 irq_action +----------------+ irqaction | | +---------+ |----------------| +---->| handler |-----> このデバイスの | | | | flags | 割り込み処理 |----------------| | | name | ルーチン | |--------+ | next | |----------------| | | | | +---------+ |----------------| | | |----------------| | | |----------------| irqaction irqaction 3 | | +---------+ +---------+ |----------------| +---->| handler |---->| handler | 2 | | | | flags | | flags | |----------------| | | name | | name | 1 | |---------+ | next | | next | |----------------| | | | | 0 | | +---------+ +---------+ +----------------+ 図表(7.2) Linux 割り込み処理のデータ構造 Linux の割り込み処理サブシステムの主要なタスクのひとつが、割り込み要求 を正しい割り込み処理コードへと伝えることである。このコードは、システム の割り込みトポロジーを理解していなければならない。たとえば、フロッピー コントローラが割り込みコントローラのピン 6 上で割り込む場合、それは、 その割り込みがフロッピーからのものであることを認識して、フロッピーデバ イスの割り込み処理コードにそれを伝えなければならない。Linux は、一群の ポインタを使って、システムの割り込み処理ルーチンのアドレスを持つ各デー タ構造体を参照する。これらのルーチンは、システム上のデバイス用のデバイ スドライバに属しており、ドライバが初期化されるとき、必要な割り込みを要 求するのは個々のデバイスドライバの責任である。図表(7.2)で は、``irq_action'' は、 ``irqaction'' データ構造体へのポインタの配列で あることが示されている。個々の irqaction データ構造体は、割り込み処理 ルーチンのアドレスが入っていると同時に、その割り込みに対する処理ルーチ ンに関する情報が含まれている。割り込みの数とその処理方法は、アーキテク チャや、時にはシステムによって異なるので、Linux の割り込み処理コード は、アーキテクチャ固有のものである。これは、irq_action 配列の配列が、 存在する割り込みソースの数によって異なることを意味している。 割り込みが起こると、Linux は、まず、システムのプログラム可能な割り込み コントローラの割り込みステータスレジスタを読み出して、その割り込みソー スを判断する。そして、そのソースを ``irq_action'' 配列の配列に対するオ フセット値に変換する。それゆえ、たとえば、フロッピーコントローラからの 割り込みコントローラのピン 6 上の割り込みは、割り込み処理ルーチンの配 列内での 7 番目のポインタへと変換される。発生した割り込みに対する割り 込み処理ハンドラがない場合、Linux カーネルはエラーをログに記録するか、 あるいは、その割り込みソースの全 ``irqaction'' データ構造体に対する(デ フォルトの) 割り込み処理ルーチンを呼び出す。 デバイスドライバの割り込み処理ルーチンが Linux カーネルによって呼び出 されたとき、ルーチンは、割り込みが発生してそれに対処することになった理 由を効率的に調べなければならない。割り込みの原因を見つけるため、デバイ スドライバは、割り込みの発生したデバイスのステータスレジスタを読み出 す。デバイスは、エラーを報告しているかもしれないし、要求された処理が完 了したことを報告しているかもしれない。たとえば、フロッピーコントローラ が、フロッピーディスクの正しいセクタの上への読みとりヘッドの位置移動を 完了したことを報告しているのかもしれない。割り込みの原因が判明すると、 デバイスドライバは、さらに次の仕事をする必要が生じる場合がある。その場 合、Linux カーネルは、その仕事を後回しにすることができる仕組みを持って いる。すなわち、それによって、CPU が、割り込みモードに長時間入ったまま になるのを防止する。それについての詳細は、``「デバイスドライバ」''の章 を見てほしい。 REVIEW NOTE: 高速割り込みと低速割り込みというのは、Intel 系での実装な のか? (-- (脚注 1): 実際、フロッピーディスクコントローラは、慣習上、PC シス テムにおいて決まった割り込みを使うデバイスのひとつである。フロッピー ディスクコントローラは、いつも割り込み 6 に接続されている。--) 9. デバイスドライバ オペレーティングシステムの目的のひとつは、ユーザからシステムのハード ウェアの特殊性を隠すことである。たとえば仮想ファイルシステムは、土台と なる物理デバイスとは無関係に、マウントされたファイルシステムに統一的な 視野を提供する。この章では、Linux カーネルがシステム上の物理デバイスを どのように管理しているのかを解説する。 CPU だけがシステムで唯一の情報処理デバイスではなく、すべての物理デバイ スが独自のハードウェア制御装置を持っている。キーボード、マウス、シリア ルポートは Super I/O チップで制御されており、IDE ディスクは IDE コント ローラで、SCSI ディスクは SCSI コントローラで制御されている。個々のハ ードウェアコントローラは独自のコントロールレジスタ(control registers)とステータスレジスタ(status registers)(あわせて CSRs)を持っ ている。それらレジスタの仕様はデバイスごとに異なっており、Adaptec 2940 SCSI コントローラの CSRs は NCR 810 SCSI コントローラのそれとは別物で ある。CRSs はデバイスの始動と停止、初期化、そして問題診断 (diagnose)の ために利用される。システム上のハードウェアコントローラを管理するコード は、個々のアプリケーションに実装されるのではなく、Linux カーネルの中に 保持されている。ハードウェアコントローラを操作あるいは管理するソフト ウェアはデバイスドライバ (device driver)と呼ばれている。Linux カーネル のデバイスドライバは、本質的に特権を与えられ、メモリに常駐する、低レベ ルハードウェアの処理ルーチンから成る共有ライブラリである。担当するデバ イスの特殊性に対処するのが、Linux のデバイスドライバの役割である。 Un*x の基本的特徴のひとつは、デバイスの操作を抽象化していることであ る。すべてのハードウェアデバイスは、通常ファイルのようのように見える。 それらは、ファイル操作で使用されるのと同じ標準的なシステムコールを利用 して、オープン、クローズ、書き込み、読み出しが可能である。システム上の あらゆるデバイスは、デバイススペシャルファイルによって表現されていて、 たとえば、システム上の最初の IDE ディスクは /dev/hda と表される。 ブロック(ディスク)デバイスとキャラクタデバイスに関しては、それらのデバ イススペシャルファイルは mknod コマンドによって作成され、デバイスを記 述するためにメジャー番号(``major number'')とマイナー番号(minor number)とのデバイス番号が使用される。ネットワークデバイスもデバイスス ペシャルファイルによって表現されるが、それらは Linux がシステム上の ネットワークコントローラを見つけて初期化するときに Linux によって作成 される。同一のデバイスドライバによって制御されるすべてのデバイスコント ローラは、共通のメジャー番号を持つ。マイナーデバイス番号が使用されるの は、デバイスドライバが異なるデバイスやそのデバイスのコントローラを区別 するためである。たとえば、プライマリ IDE ディスク上の個々のパーティ ションは異なるマイナー番号を持つ。それゆえ、プライマリ IDE ディスクの 第二パーティション /dev/hda2 は、メジャー番号 3 とマイナー番号 2 を持 つ。 Linux は、システムコールで(たとえばブロックデバイス上のファイルシ ステムをマウントするなどして)渡されたデバイススペシャルファイルをその デバイスのデバイスドライバにマップする。その際は、デバイスのメジャー番 号といくつかのシステムテーブル、たとえば ``chrdevs'' というキャラクタ デバイステーブルなどを使用する。 [see: fs/devices.c] Linux は 3 種類のハードウェアデバイスをサポートしている。キャラクタデ バイス (character device)、ブロックデバイス(block device)、ネットワー クデバイス( network device)である。キャラクタデバイスは、バッファなし で直接読み書きするデバイスであり、たとえばシステムのシリアルポート /dev/cua0 や /dev/cua1 などがそうである。ブロックデバイスとは、ブロッ クサイズの倍数、典型的には 512 か 1024 バイトでのみ読み書きするデバイ スである。ブロックデバイスはバッファキャッシュ経由でアクセスされ、ラン ダムなアクセス、すなわちデバイス上のどこにあるブロックでも読み書きでき るアクセス方法が利用されることが多い。ブロックデバイスへのアクセスは、 それぞれに対応したデバイススペシャルファイルを経由しても可能であるが、 ファイルシステム経由でアクセスするほうがより一般的である。マウントされ るファイルシステムをサポートできるのはブロックデバイスだけである。ネッ トワークデバイスへのアクセスは、 ``「ネットワーク」''の章で解説する BSD ソケットインターフェイスまたはネットワークサブシステム経由で行な う。 Linux カーネル内には多種多様のデバイスドライバがある(これは、Linux の 長所のひとつである)が、それらすべてが以下の一般的な属性を共有してい る。 カーネルコード (kernel code) デバイスドライバはカーネルの一部なので、カーネル内の他のコードと 同様に、もし上手く動かないとシステムに深刻な影響を与える。出来の 悪いドライバはシステムをクラッシュさせる場合すらあり、その結果 ファイルシステムが破壊されデータが喪失することもあり得る。 カーネルインターフェイス (kernel interfaces) デバイスドライバは、Linux カーネルやそれが所属するサブシステムに 対して標準インターフェイスを提供しなければならない。たとえば端末 ドライバは Linux カーネルにファイルへの I/O インターフェイスを提 供し、SCSI デバイスドライバは SCSI サブシステムに対してインター フェイスを提供し、そのサブシステムが次に Linux カーネルに対して ファイル I/O とバッファキャッシュインターフェイスを提供する。 カーネルメカニズムとサービス デバイスドライバは、メモリ割り当てや割り込みの伝達、待ち行列など の標準的なカーネルサービスを利用する。 動的ロードが可能(loadable) Linux のデバイスドライバの大部分は必要なときにオンデマンドでカー ネルモジュールとしてロードが可能であり、必要が無くなればアンロー ドもできる。これによって、カーネルの適用能力が高くなり、システム リソースの利用効率も上がる。 設定の柔軟性 Linux のデバイスドライバはカーネル内に組み込むことも可能である。 どのデバイスを組み込むかは、カーネルのコンパイル時に設定できる。 動的な設定(dynamic) システムが起動して個々のデバイスドライバが初期化されると、ドライ バは制御すべきハードウェアデバイスを捜す。特定のデバイスドライバ により制御されるはずのデバイスが存在しなかったとしても問題はな い。その場合、デバイスドライバは、単に余分なだけであり、少量のシ ステムメモリが占有されることを除いて如何なる悪影響も及ぼさない。 9.1. ポーリングと割り込み たとえば「読み取りヘッダをフロッピーディスクのセクタ 42 へ動かせ」と いったコマンドをデバイスが受け取ると、デバイスドライバは次のいずれかの 方法、つまりデバイスをポーリング(polling)するか、あるいは割り込 み(interrupt)を使うことによって、そのコマンドが完了したかどうかを確認 する。 通常、デバイスのポーリングとは、デバイスのステータスレジスタが変化し て、リクエストの実行を完了したことが示されるまで、デバイスのステータス レジスタを頻繁に読み出す操作を意味している。デバイスドライバはカーネル の一部であるので、もし、ドライバがポーリングし続けるとすると、システム に悪影響を与える。というのも、その場合、デバイスがリクエストを完了する まで、カーネル内では何も実行されなくなるからである。しかし、実際にポー リングを行うデバイスドライバは、システムタイマーを使用して、一定時間経 過後に、カーネルにデバイスドライバ内のルーチンを呼び出してもらうように している。このシステムタイマーのルーチンは、コマンドのステータスを チェックする。Linux のフロッピードライバは、まさにこの仕組みを使ってい る。だが、タイマーを使ったポーリングでは、精々大ざっぱな制御しかできな い。もっと効率の良い方法は、割り込み(interrupt)を使うことである。 割り込みによって駆動されるデバイスドライバとは、ハードウェアデバイスが サービスを要求するとき、ハードウェアデバイスがハードウェア割り込みをか けることで、制御されているものである。たとえばイーサネットドライバは、 イーサネットがネットワークからパケットを受信したときに割り込みをかけ る。Linux カーネルは、ハードウェアデバイスから正しいデバイスドライバへ とその割り込みを伝達できなければならない。これを実現するために、デバイ スドライバは、割り込みの処理方法をカーネルに登録する。デバイスドライバ が登録するのは、割り込み処理ルーチンのアドレスと占有したい割り込み番号 である。どの割り込みがデバイスドライバに使用されているか、割り込みのタ イプ(``type of interrupts'')がそれぞれいくつあるかを確認するに は、/proc/interrupts を見ればよい。 0: 727432 timer 1: 20534 keyboard 2: 0 cascade 3: 79691 + serial 4: 28258 + serial 5: 1 sound blaster 11: 20868 + aic7xxx 13: 1 math error 14: 247 + ide0 15: 170 + ide1 割り込みリソースに関する要求は、ドライバの初期化の際に処理される。シス テム上の割り込みのいくつかは固定されているが、それは IBM PC アーキテク チャの遺産である。したがって、たとえばフロッピーディスクコントローラは 常に割り込み 6 を使用する。それ以外の割り込み、たとえば PCI デバイスか らの割り込みは、起動時に動的に割り当てられる。その場合、デバイスドライ バは、まず、制御しているデバイスの割り込み番号(IRQ)を検出した上で、そ の番号を占有する旨要求しなければならない。PCI デバイスの割り込みに関し て、Linux は、標準的な ``PCI BIOS'' のコールバックをサポートすること で、システム上のデバイスに関する、IRQ 番号等の情報を判断している。 割り込みが CPU 自体に伝達される方法はアーキテクチャによって異なるが、 大部分のアーキテクチャでは、割り込みの伝達は、システム上で他の割り込み の発生を停止させる特別なモードで行われる。したがって、デバイスドライバ は、割り込み処理ルーチン内でできる限り簡潔な処理をすべきである。そうす れば、Linux カーネルは割り込み処理をすぐに終了して、割り込みが起こる前 に実行していたことに戻ることができる。割り込みを受信した結果多くの処理 をしなければならなくなった場合、デバイスドライバは、カーネルのボトムハ ーフハンドラ(``bottom half handler'')かタスクキュー(``task queue'') を 使用してルーチンをキューイングし、後で呼び出すという方法を取ることがで きる。 9.2. ダイレクトメモリアクセス(Direct Memory Access, DMA) 割り込み駆動型のデバイスドライバを使ってハードウェアデバイスと情報をや り取りすることが有効なのは、データ量が比較的少ないときである。たとえば 9600 baud のモデムはミリ秒(1/1000 秒)あたりおよそ一文字を送信可能であ る。割り込み遅延 (interrupt latency)、すなわちハードウェアデバイスが割 り込みを発信してからデバイスドライバの割り込み処理ルーチンが呼び出され るまでの時間が短い場合 (たとえば 2 ミリ秒)、データ転送に要するシステム 全体へのインパクトは非常に小さい。9600 baud のモデムのデータ転送 は、0.002% の CPU 処理時間しかかからない。しかし、ハードディスクコント ローラやイーサネットのような高速なデバイスの場合、データ転送レートは ずっと高くなる。SCSI デバイスは、最大で毎秒 40 M バイトの情報を転送で きる。 ダイレクトメモリアクセス(``DMA'')はこの問題を解決するために発明され た。DMA コントローラによって、デバイスはプロセッサの介在なしでシステム メモリとのデータのやり取りが可能になる。ISA の DMA コントローラは 8 個 の DMA チャンネルを持っていて、そのうち 7 個はデバイスドライバによって 使用することができる。個々の DMA チャンネルは、16 ビットのアドレスレジ スタ(address register)と 16 ビットのカウントレジスタ(count register)を 利用して、デバイスとメモリ間での転送を行っている。データ転送を始める 際、デバイスドライバは DMA チャンネルのアドレスレジスタとカウントレジ スタを設定し、同時に読み出しか書き込みかのデータ転送の方向を設定する。 そしてデバイスドライバは、デバイスに対して、デバイス側の準備が整い次第 DMA データ転送を始めることを告げる。転送が完了したとき、デバイスは PC に割り込みをかける。転送が行われている間、CPU は自由の他のことが出来 る。 デバイスドライバは DMA を使用する際に注意しなければならないことがあ る。何よりも先ず、DMA コントローラは仮想メモリについては何も知らず、シ ステムの物理メモリだけにしかアクセスできないことである。したがっ て、DMA 転送で書き込まれたり読み出されたりするメモリは、物理メモリ上の 連続したブロックでなければならない。これは、DMA を使ってプロセスの仮想 アドレス空間へ直接アクセスできないことを意味する。しかし、メモリ上のプ ロセスの物理ページをロックして、DMA 処理の間にそれらがスワップデバイス にスワップアウトされないようにすることができる。次に、DMA コントローラ は物理メモリの全域にアクセスできるわけではないということである。DMA チャンネルのアドレスレジスタは、DMA アドレスの最初の 16 ビットを表し、 次の 8 ビットは DMA のページレジスタ(page register)から来ている。これ は、DMA リクエストは、メモリの下位 16 M バイトに制限されていることを意 味する。 DMA チャンネルは 7 本しかない希少資源であり、デバイスドライバ間で共有 ができない。割り込みの場合のように、デバイスドライバは、どの DMA チャ ンネルを使うか決定できなければならない。割り込みの場合と同様、デバイス のなかには固定された DMA チャンネルを持つものがある。たとえばフロッピ ーディスクは常に DMA チャンネル 2 を使用する。デバイスによっては、 DMA チャンネルをジャンパによって設定することができる。多くのイーサネットデ バイスはこのテクニックを使用する。より柔軟なデバイスでは、(CSRs 経由 で)どの DMA チャンネルを使うかを伝えられるようになっていて、その場合、 デバイスドライバは単に空の DMA チャンネルを選んで使用することができ る。 Linux は DMA チャンネルの利用状況を監視するために、DMA チャンネルごと にひとつある ``dma_chan'' データ構造体の、配列を使用する。 dma_chan デ ータ構造体にはフィールドがふたつだけあり、DMA チャンネルを使用している デバイスを記述した文字列へのポインタと、その DMA チャンネルがすでに割 り当てされているかどうかを表すフラグが含まれている。 /proc/dma を cat した際にプリントされるのは、この dma_chan データ構造体の配列である。 9.3. メモリ デバイスドライバは注意深くメモリを使用しなければならない。デバイスドラ イバは Linux カーネルの一部であるので、仮想メモリは使用できない。割り 込みの受信やボトムハーフハンドラやタスクキューハンドラの実行によって、 デバイスドライバが起動されるたびに、カレントプロセスが変化する。デバイ スドライバは、実行中であったプロセスに代わって処理をする場合であって も、特定の実行中のプロセスに依存できない。カーネルの他の部分と同様に、 デバイスドライバはデータ構造体を使用して、現在制御しているデバイスを監 視する。それらのデータ構造体はデバイスドライバの一部として静的に割り付 けられることも可能だが、それはカーネルを必要以上に大きくするだけでリソ ースの無駄使いになる。大部分のデバイスドライバは、ページ単位に分割され ていないカーネルメモリを割り当てて、データを保持している。 Linux はカーネルメモリの割り当てと解放のためのルーチンを提供しているの で、デバイスドライバはそのルーチンを使用する。カーネルメモリは 2 の n 乗ごとの範囲で割り当てられる。たとえば、デバイスドライバが少量のメモリ しか要求しない場合でも 128 や 512 バイトを割り当てる。デバイスドライバ が要求するバイト数は、次のブロックサイズの値にまで切り上げられる。これ によって、小さな空きメモリのブロックをより大きなブロックに再結合しやす くなるので、カーネルメモリの解放と統合が容易になる。 カーネルメモリがリクエストされた際、Linux カーネルは非常に多くの余計な 仕事をしなければならない場合がある。空きメモリの量が不足する場合、物理 ページは破棄されるかスワップデバイスに書き込まれる必要がある。通 常、Linux は、要求したプロセスを待ち行列に入れて、充分なメモリが確保さ れるまでそれをサスペンド状態にする。しかし、デバイスドライバ(あるいは Linux カーネルコード)のなかにはサスペンドできないプロセスもあるので、 そうした場合、すぐにメモリを確保できないときは、カーネルメモリ割り当て ルーチンは、リクエストの処理に失敗することもある。デバイスドライバが DMA を使用して割り当てられたメモリに対して書き込みや読み出しをしたい場 合、ドライバは、そのカーネルメモリを DMA 転送が可能であると指定するこ ともできる。この場合、システム上で DMA 転送が可能なメモリの構成がどう なっているのか理解しなければならないのは、デバイスドライバではな く、Linux カーネルである。 9.4. デバイスドライバとカーネルとのインターフェイス Linux カーネルとデバイスドライバーとの相互通信は、標準的な方法で行われ なければならない。キャラクタデバイス、ブロックデバイス、ネットワークデ バイスなど個々のデバイスドライバのクラス(class)は、クラス共通のインタ ーフェイスを提供していて、カーネルがそれらにサービスをリクエストする際 は個々のクラスのインターフェイスを使用する。各クラスのデバイスやデバイ スドライバはしばしばかなり性質の異なることがあるが、共通のインターフェ イスがあることで、それらを全く同じものとして扱うことができる。たとえ ば、SCSI と IDE ディスクとは非常に異なった動作をするが、Linux カーネル はそれら両方に対して同じインターフェイスを使用することができる。 Linux は動的な設定にも柔軟に対応する。Linux は、起動のたびに異なった物 理デバイスを検出するかもしれないので、そのために様々なデバイスドライバ を必要とする。 Linux では、カーネルビルドの際の設定によって、デバイス ドライバをカーネルに組み込むことができる。しかし、起動時に初期化される 際、組み込まれたデバイスドライバは、制御すべきハードウェアを見つけられ ないことがある。また、カーネルに組み込まれない場合でも、ドライバが必要 なときは、カーネルモジュールとしてそれをロードすることもできる。このよ うな動的なデバイスドライバの性質に対処するために、デバイスドライバは初 期化される際にカーネルに対して自分の情報を登録する。Linux は、それらへ のインターフェイスの一部として、登録されたデバイスドライバのテーブルを 管理している。それらのテーブルには、そのクラスのデバイスへのインター フェイスをサポートするルーチンおよび情報へのポインタが含まれている。 9.4.1. キャラクタデバイス chrdevs +---------------+ | name | | fops |----------> file operations |---------------| | | lseek | | read | | write | | readdir | | select | | ioctl | | mmap | | open | | release | | fsync | | fasync | | check_media_change +---------------+ revalidate 図表(8.1) キャラクタデバイス キャラクタデバイス(character device)は、Linux の中で最もシンプルなデバ イスである。それらはファイルとしてアクセスできるので、まるでそのデバイ スがファイルと全く同じであるかのように、アプリケーションは、標準システ ムコールを使用してそれらをオープンし、読み出しや書き込みをし、クローズ する。これは、デバイスとしてモデムを使い、PPP デーモンで Linux システ ムをネットワークに接続する場合でも同じである。キャラクタデバイスが初期 化されるとき、そのデバイスドライバは、自分自身を Linux カーネルに登録 するのだが、その際は、``device_struct'' データ構造体による配列 ``chrdevs'' にエントリを追加する。デバイスのメジャー番号(たとえば、4 は tty デバイス)は、この配列へのインデックスとして使用される。デバイス の``メジャー番号''は、予め決められている。 [see: include/linux/major.h] ``chrdevs'' 配列の個々のエントリである ``device_struct'' データ構造体 には、ふたつの要素がある。登録されたデバイスドライバの名前へのポインタ と、ファイル操作ルーチン群へのポインタである。このファイル操作ルーチン 群は、それ自体が当該デバイスのキャラクタデバイスドライバ内にあるルーチ ンのアドレスであり、その個々のルーチンが、open, read, write, close な どファイルへの特定の操作を処理する。キャラクタデバイスに関する /proc/devices の内容は、chrdevs 配列から取得される。 キャラクタデバイスを表すキャラクタ型スペシャルファイル(たとえば /dev/cua0)がオープンされるとき、カーネルは適切な設定を行い、正しいキャ ラクタデバイスドライバのファイル操作ルーチンが呼び出されるようにしなけ ればならない。通常のファイルやディレクトリの場合と同様に、個々のデバイ ススペシャルファイルは VFS ``inode'' によって表される。キャラクタ型ス ペシャルファイルの VFS inode には、すべてのスペシャルファイルと同様 に、デバイスに関するメジャー番号とマイナー番号が含まれている。この VFS inode は、デバイススペシャルファイルの名前が問い合わせされる際に、実 ファイルシステム内の情報を元にして、EXT2 などの基本ファイルシステムに よって作成される。 [see: ext2_read_inode(), in fs/ext2/inode.c] 個々の VFS inode は一連のファイル操作ルーチンと関連付けられるので、そ れら VFS inode の性質は、その inode が表現しているファイルシステムオブ ジェクトの種類によって異なったものとなる。キャラクタ型スペシャルファイ ルを表す VFS inode が作成される際はいつも、デフォルトのキャラクタデバ イス操作ルーチンがそのファイルの操作ルーチンとして設定される。 [see: def_chr_fops, in fs/devices.c] [see: chrdev_open(), in fs/devices.c] このデフォルトのルーチンは、ファイルのオープンというひとつだけのファイ ル操作を含むものである。アプリケーションによってキャラクタファイルがオ ープンされたとき、その汎用のファイルオープン操作ルーチンは、デバイスの メジャー番号を ``chrdevs'' 配列へのインデックスとして使用して、その特 定のデバイス用のファイル操作ルーチン群を取ってくる。またそれは、当該 キャラクタスペシャルファイルを記述する ``file'' データ構造体を設定し、 そのファイル操作ルーチンへのポインタが当該デバイスドライバのファイル操 作関数群を差すようにする。こうして、アプリケーションのファイル操作ルー チンはすべてマップされ、そのキャラクタデバイスに対する一連のファイル操 作ルーチンの呼び出しに備える。 9.4.2. ブロックデバイス ブロックデバイスの場合も、ファイルにアクセスするときのようにアクセスで きる仕組みがサポートされている。オープンされたブロック型スペシャルファ イルを操作するために適切な一連のファイル操作ルーチンを提供する際、その ために使用されるメカニズムは、キャラクタデバイスの場合と全く同じであ る。Linux は、``blkdevs'' 配列によって、登録された一連のブロックデバイ スを管理している。 [see: fs/devices.c] それは、``chrdevs'' 配列と同様に、デバイスのメジャー番号を使って索引付 けされている。そのエントリも ``device_struct'' データ構造体である。 キャラクタデバイスと違う点は、ブロックデバイスにはクラスがあることであ る。 SCSI デバイスがそれであり、IDE デバイスもそうである。Linux に自分 自身を登録するのも、カーネルにファイル操作ルーチンを提供するのも、そう したクラスである。ブロックデバイスのクラスに対するデバイスドライバは、 そのクラスにクラス固有のインターフェイスを提供する。それゆえ、たとえば SCSI デバイスドライバは、SCSI サブシステムに対してクラス固有のインター フェイスを提供しなければならず、 SCSI サブシステムはそれを使ってそのデ バイスに対するファイル操作をカーネルに提供する。 すべてのブロックデバイスドライバは、通常のファイル操作インターフェイス に加えて、バッファキャッシュへのインターフェイスを提供しなければならな い。個々のブロックデバイスドライバは、``blk_dev_struct'' データ構造体 の配列である ``blk_dev'' 内に自分のエントリを作成する。 [see: drivers/block/ll_rw_blk.c] [see: include/linux/blkdev.h] この配列へのインデックスもまたデバイスのメジャー番号であ る。blk_dev_struct データ構造体は、リクエストルーチンのアドレスと ``request'' データ構造体のリストへのポインタによって構成される。個々の request 構造体は、デバイスドライバにデータブロックを読み書きさせようと する、バッファキャッシュからのリクエストを表現している。 blk_dev +---------------+ | | | | request request |---------------| +---------+ +---------+ |request_fn() |-->|rq_status| +->|rq_status| |current_request| |---------| | |---------| | | | rq_dev | | | rq_dev | |---------------| |---------| | |---------| | | | mcd | | | mcd | | : | |---------| | |---------| | | | | | | | | : | | | | | | | | |---------| | |---------| | | | sem | | | sem | buffer_head | | |---------| | |---------| +---------+ | | | bh | | | bh |-->| b_dev |0x0301 |---------------| |---------| | |---------| |---------| | | | tail | | | tail | |b_blocknr|39 | | |---------| | |---------| |---------| |---------------| | next |-+ | next | | b_state | | | +---------+ +---------+ |---------| | | | b_count | +---------------+ |---------| | b_size |1024 blk_dev_struct 配列 |---------| | | | | |---------| | b_next | |---------| | b_prev | |---------| | | | | |---------| | b_data | +---------+ 図表(8.2) バッファキャッシュブロックデバイスリクエスト バッファキャッシュが登録済みデバイスに対してデータブロックを読み書きし ようとする度に、バッファキャッシュはその ``blk_dev_struct'' に対して ``request'' データ構造体を付け加える。図表(8.2)では、個々のリクエスト がひとつの以上の ``buffer_head'' データ構造体へのポインタを持ってい て、それぞれがデータブロックの読み書きのリクエストとなっていることが示 されている。 buffer_head データ構造体は、(バッファキャッシュによっ て)ロックされるので、そのバッファへのブロック操作の完了を待っているプ ロセスが存在することがある。個々の request 構造体は、``all_requests'' リストというスタティックなリストから割り当てられる。リクエストが空のリ クエストリストに付け加えられた場合、ドライバのリクエスト処理関数が呼び 出され、リクエストキューの処理を始める。そうでない場合、ドライバは単に リクエストリストにあるすべての request を処理する。 デバイスドライバがあるリクエストの処理を完了すると、ドライバ は、``request'' 構造体から個々の ``buffer_head'' 構造体を削除し、それ らが更新されたことをマークして、ロックを解除しなければならな い。buffer_head のロック解除はブロック操作の完了を待っているすべてのプ ロセスを目覚めさせる。たとえば、ファイル名を解決するために、EXT2 ファ イルシステムは、そのファイルシステムを保持するブロックデバイスから次の EXT2 ディレクトリエントリーを含んだデータブロックを読み出さなければな らないとする。そのプロセスは、読み出そうとするディレクトリエントリを含 む buffer_head 上でスリープ状態にあるが、デバイスドライバがそのプロセ スを目覚めさせる。(操作が完了すると) request データ構造体は利用可能と マークされるので、次のブロックリクエストの際に使用される。 9.5. ハードディスク ディスクドライブは、データを回転ディスクプラッタ(platter)上に保持する ことで、メモリよりもデータを長期的に保存する仕組みを提供する。データの 書き込みには小さなヘッドがプラッタの表面にある微細な粒子を磁化する。デ ータはヘッドによって読み出され、そのヘッドが特定の微細な粒子の磁化の有 無を検出している。 ディスクドライブは一枚以上のプラッタから構成される。個々のプラッタは丹 念に磨かれたガラスかセラミック合金から成り、酸化鉄の非常に薄い層でコー トされている。プラッタはセンタースピンドル(central spindle)に取り付け られ、モデルによって違いがあるが、3000 から 10,000 の間の一定の速度で 回転する。これと比較した場合、フロッピーは 360 RPM でしか回転しない。 ディスクの読み書きヘッドはデータの読み書きを担当し、プラッタ一枚に一 組、その両面に装着されている。読み書きヘッドはプラッタ表面に物理的に接 触することなく、非常に薄い(1/1千万 インチ)空気の層をクッションに浮いて いる。読み書きヘッドはアクチュエータによってプラッタの表面を移動する。 すべての読み書きヘッドは一体となっていて、プラッタ表面上をすべてが同時 に動く。 (訳注: RPM, Revolutions Per Minute, 分あたりの回転数) プラッタの個々の表面は、トラック(track)とよばれる細い同心円に分割され ている。トラック 0 が一番外側のトラックであり、最も大きな番号のトラッ クがセンタースピンドルに最も近い位置にある。シリンダー(cylinder)とは、 同一番号のトラックをすべて一組にした概念である。それゆえ、ディスク内の すべてのプラッタの両面にある 5 番トラック全体が、シリンダー 5 と呼ばれ る。シリンダ数はトラック数と同じなので、ディスクジオメトリは、シリンダ の方を使って記述される。個々のトラックはセクタ(sector)に分割される。セ クタはハードディスクで読み書き可能な最も小さなデータ単位であり、ディス クブロックサイズでもある。一般的なセクタサイズは 512 バイトであり、セ クタサイズはディスクのフォーマット時、通常はディスクが製造された時に設 定される。 ディスクは通常、シリンダ(cylinder)、ヘッド(head)、セクタ(sector)という ジオメトリによって記述される。たとえば起動時に、Linux は IDE ディスク を次のように記述する。 hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63 上記は、ディスクが 1050 シリンダ(トラック)、16 ヘッド(8 プラッタ)、そ してトラックごとに 63 セクタを持つことを意味する。セクタもしくはブロッ クサイズが 512 バイトの場合、ディスクは 529200 キロバイト (1 キロバイ ト = 1024 バイトとして) の記憶容量を持つことになる。これはディスク情報 として表示されるサイズである 516 メガバイトと一見異なっているように見 えるが、529200 / 1024 は約 516.8 となり、実は合っている。 (訳注: ちな みに 540MB というのは 1MB = 1000 x 1000 バイトで計算した場合の数値であ る。) ディスクのなかには、自動的に不良セクタを検出して、そのセクタの周 辺にあるセクタを適切に利用するために、ディスクのインデックスを付け直す ものもある。 ハードディスクはさらにパーティションに分割できる。パーティションは特別 な目的のために割り当てられたセクタの大規模なグループである。ディスクを パーティションに分割することで、そのディスクを様々なオペレーティングシ ステムや様々な目的に利用することが可能になる。多くの Linux システム は、単一ディスク上に 3 つのパーティションを持つ。ひとつは DOS ファイル システムを持ち、ふたつ目が EXT2 ファイルシステム、3 つ目がスワップパー ティションを持つ。ハードディスクのパーティションは、パーティションテー ブルによって記述されている。その個々のエントリは、パーティションの最初 と最後の位置をヘッド、セクタ、シリンダ番号によって記述している。fdisk でフォーマットされた、DOS フォーマットのディスクの場合、4 つのプライマ リディスクパーティションが存在するが、4 つ全部のエントリがパーティショ ンテーブルで使用される必要はない。fdisk によってサポートされているパー ティションタイプには、プライマリ、拡張(extended)、論理(logical)の 3 種 類がある。拡張パーティションは実際のパーティションではなく、それらは任 意の数の論理パーティションを含む。拡張パーティションと論理パーティショ ンとは、4 つのプライマリパーティションという限界を回避するために発明さ れた。次に示すのは、ふたつのプライマリパーティションを含むディスクに対 する fdisk の出力である。 Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders Units = cylinders of 2048 * 512 bytes Device Boot Begin Start End Blocks Id System /dev/sda1 1 1 478 489456 83 Linux native /dev/sda2 479 479 510 32768 82 Linux swap Expert command (m for help): p Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID 1 00 1 1 0 63 32 477 32 978912 83 2 00 0 1 478 63 32 509 978944 65536 82 3 00 0 0 0 0 0 0 0 0 00 4 00 0 0 0 0 0 0 0 0 00 上記では、第一パーティションは、シリンダもしくはトラック 0 、ヘッド 1 、そしてセクタ 1 から始まり、シリンダ 477 、セクタ 32 、そしてヘッド 63 までの領域になっている。トラックあたり 32 セクタで、 64 ヘッドある ので、このパーティションは、シリンダ数に合致した容量を持つ。fdisk は、 デフォルトでシリンダの境界線上で区切って、パーティションを割り当てる。 fdiskは、最外周のシリンダ(0)から出発して、内側方向に広げていき、回転軸 に向かって 478 シリンダから構成される領域を割り当てている。第二パー ティションはスワップパーティションであり、次のシリンダ(478)からスター トしてディスクの最内周のシリンダに及んでいる。 gendisk_head gendisk gendisk +-----------+ +-----------+ ----------->| major |8 +->| major |3 |-----------| | |-----------| |major_name |"sd" | |major_name |"ide0" |-----------| | |-----------| |minor_shift| | |minor_shift| |-----------| | |-----------| | max_p | | | max_p | |-----------| | |-----------| | max_nr | | | max_nr | |-----------| | |-----------| | init() | | | init() | hd_struct[] |-----------| | |-----------| +----------+ * | part | | | part |-->|start_sect| /| |-----------| | |-----------| | nr_sects | | | sizes | | | sizes | |----------| | |-----------| | |-----------| | | | | nr_real | | | nr_real | | : | | |-----------| | |-----------| | | |real_device| | |real_device| | : | max_p |-----------| | |-----------| | | | next |-----+ | next | | : | | +-----------+ +-----------+ | | | |----------| | |start_sect| | | nr_sects | |/ +----------+ * 図表(8.3) ディスクの連結リスト 初期化の過程で Linux はシステム上のハードディスクのトポロジーをマップ する。まず、ハードディスクの数と、そのタイプを調べる。さらに、Linux は 個別のディスクがどのようにパーティション分割されているかを発見する。こ れらはすべて、``gendisk'' データ構造体のリストによって表現され、それが ``gendisk_head'' リストポインタによってポイントされる。個々のディスク サブシステム、たとえば IDE ディスクサブシステムは、初期化される 際、gendisk データ構造体を生成して、検出したディスクを記述する。それと 同時に、ディスクサブシステムは、そのファイル操作ルーチンを登録し、その エントリを ``blk_dev'' データ構造に付け加える。個々の gendisk データ構 造体は、ユニークなデバイスメジャー番号を持ち、その番号は、ブロックスペ シャルデバイスのメジャー番号と一致する。たとえば、SCSI ディスクサブシ ステムは、単一の gendisk エントリ("sd")を作成し、それに SCSI ディスク デバイス全体のメジャー番号である、メジャー番号 8 を付ける。図表(8.3)で は、ふたつの gendisk エントリがあり、最初はすべての SCSI サブシステム のものであり、ふたつめが IDE ディスクコントローラのものとなっている。 後者は、プライマリ IDE コントローラである ide0 である。 ディスクサブシステムは初期化の過程で ``gendisk'' エントリを作成する が、それらは Linux によってパーティションチェックの際に使用されるだけ である。それとは別に、個々のディスクサブシステムは、それぞれ独自のデー タ構造体を管理することで、デバイスのメジャー番号とマイナー番号を物理 ディスク内のパーティションにマップしている。ブロックデバイスがバッファ キャッシュやファイル操作ルーチン経由で読み書きされるときはいつも、カー ネルは、ブロックスペシャルデバイスファイル(たとえば /dev/sda2)に書かれ たメジャー番号を使って、その操作を適切なデバイスへと導く。そして、デバ イスのマイナー番号を実(real)物理デバイスにマップしているのが、個別のデ バイスドライバやデバイスサブシステムである。 9.5.1. IDE ディスク 今日 Linux システム上で利用される最も一般的なディスクは、IDE (Integrated Disk Electronics)ディスクである。IDE とは、SCSI のような I/O というよりも、むしろディスクインターフェイスである。個々の IDE コ ントローラはふたつまでのディスクをサポートできる。一方がマスターディス ク(master disk)であり、他方がスレイブディスク(slave disk)である。マス ターとスレイブの機能は通常、ディスクのジャンパピンで設定される。システ ム上の最初の IDE コントローラはプライマリコントローラと呼ばれ、次のコ ントローラはセカンダリコントローラ等と呼ばれる。IDE はディスクとの間で 毎秒 3.3 M バイトのデータ転送能力を持っており、IDE の最大ディスクサイ ズは 528 M バイトである。拡張 IDE 、もしくは EIDE(Extended IDE) は、最 大ディスクサイズを 8.4 G バイトに拡大し、データ転送能力も毎秒 16.6 M バイトに上げたものである。IDE と EIDE ディスクは SCSI ディスクよりも安 価であるので、現在の PC にはひとつ以上のオンボード IDE コントローラが 含まれている。 Linux はコントローラを見つけた順番に IDE ディスクに名前を付ける。プラ イマリコントローラのマスターディスクは /dev/hda であり、スレイブディス クは /dev/hdb である。/dev/hdc はセカンダリ IDE コントローラのマスター ディスクとなる。IDE サブシステムは Linux カーネルに IDE コントローラを 登録し、ディスクを登録しない。プライマリ IDE コントローラのメジャー番 号は、 3 であり、セカンダリ IDE コントローラのメジャー番号は 22 であ る。これは、システムにふたつの IDE コントローラがある場 合、``blk_devs'' と ``blkdevs'' 配列には 3 と 22 のインデックスがつい た IDE サブシステムのエントリが存在することを意味する。IDE ディスクの ブロックスペシャルファイルはこの番号の割り当てを反映しているので、プラ イマリ IDE コントローラに接続されたディスクである /dev/hda と /dev/hdb は、どちらもメジャー番号 3 を持つ。カーネルはメジャー番号をインデック スとして使用しているため、それらのブロックスペシャルファイル上の IDE サブシステム操作ルーチンを使ったファイル操作やバッファキャッシュ操作 は、まず、IDE サブシステムへと導かれる。そうしたリクエストが為されたと き、リクエストがどの IDE ディスクに対するものなのかを判別するのは、IDE サブシステムの役目である。そのために、IDE サブシステムは、デバイスの特 別な識別子としてマイナー番号を使用する。その識別子には、リクエストを正 しいディスクの正しいパーティションに送るための情報が含まれている。プラ イマリ IDE コントローラ上のスレイブ IDE ディスクである /dev/hdb のデバ イス識別子は、``(3,64)''(メジャー番号 3 、マイナー番号 64)である。その ディスクの最初のパーティション(/dev/hdb1)のデバイス識別子は、 (3,65) である。 9.5.2. IDE サブシステムの初期化 IDE ディスクは、IBM PC の歴史と深く関わっている。それらの時代を通じて IDE デバイスに対するインターフェイスは何度も変更された。それによって、 IDE サブシステムの初期化は、それが登場した当初よりも複雑なものになって いる。 Linux がサポートできる IDE コントローラの最大数は 4 つである。個々のコ ントローラは、``ide_hwifs'' 配列にある ``ide_hwif_t'' データ構造体に よって表される。個々の ide_hwif_t データ構造体はふたつの ``ide_drive_t'' 構造体を含んでおり、各々がマスターとスレイブの IDE ド ライブのサポートに備えたものとなっている。IDE サブシステムの初期化の過 程で、 Linux はまず、システムの CMOS メモリにディスクに関する情報があ るかどうかを確認する。これは、PC の電源が切られたときもバッテリ駆動の バックアップメモリによってその内容が失われないようになっている。この CMOS メモリは実際にはシステムのリアルタイムクロックの中にあり、そのク ロックは PC の電源が入っているかどうかに関わらず、実行されている。その CMOS メモリの場所は BIOS を使って設定が可能であり、それが Linux に対し てどのような IDE コントローラやドライブが検出されたかを告げる。Linux は検出されたディスクのジオメトリを BIOS から取ってきて、その情報を利用 して当該デバイスの ide_hwif_t データ構造体を設定する。より現代的な PC では、PCI (E)IDE コントローラに含まれる Intel の 82430 VX のような PCI チップセットを使用する。そして、システム上にあるそうしたチップセット用 の PCI 固有の検出ルーチンをコールする。 個々の IDE インターフェイスやコントローラが発見されたら、その ``ide_hvif_t'' 構造体が、そのコントローラやそれに接続されたディスクの 状態を反映するために設定される。設定の過程で、IDE ドライバは、I/O メモ リ空間に存在する IDE コマンドレジスタにコマンドを書き込む。プライマリ IDE コントローラ上のコントロールレジスタとステータスレジスタのデフォル トの I/O アドレスは、0x1F0 から 0x1F7 である。これらのアドレスの設定 は、初期の IBM PC の慣習によって決まっている。 IDE ドライバは、個々の コントローラを Linux のブロックバッファキャッシュと VFS に登録し、それ を ``blk_dev'' と ``blkdevs'' 配列にそれぞれ付け加える。IDE ドライブは 適切な割り込み制御も要求する。ここでもまた、その割り込み番号は慣習的に 決まっており、プライマリ IDE コントローラが 14 で、セカンダリコントロ ーラが 15 である。しかし、それらは、他のすべての IDE の詳細と同様に、 カーネルへのコマンドラインオプションによって上書き変更が可能である。ま た IDE ドライバは、起動中に発見された個々の IDE コントローラについ て、``gendisk'' のリストに gendisk のエントリを付け加える。このリスト は後で、起動時に見つかったすべてのハードディスクのパーティションテーブ ルを発見するために使用される。 9.5.3. SCSI ディスク SCSI (Small Computer System Interface)バスは、効率的な一対一(peer-to- peer) のデータバスであり、ひとつ以上のホストを含む 8 個のデバイスをひ とつのバスでサポートする。個々のデバイスは固有の識別子を持つ必要があ り、通常それはディスク上のジャンパピンで設定される。バス上にある任意の ふたつのデバイス間で同期あるいは非同期のデータ転送が可能であり、32 ビット幅のデータ転送により最大毎秒 40 M バイトの転送速度が得られ る。SCSI バスは、デバイス間でデータとステート (状態)の情報の両方を転送 する。イニシエータ(initiater)とターゲット(target)間の一回の転送中に最 大 8 つの異なるフェイズ(phase)を取ることがある。現在の SCSI バスのフェ イズは 5 本の制御線から、確認できる。8 つのフェイズとは次のようなもの である。 BUS FREE バスを制御しているデバイスは存在せず、トランザクションは現在行わ れていない。 ARBITRATION SCSI デバイスが SCSI バスを制御しようとしていて、SCSI 識別子に対 応したピンを有効にしている。SCSI 識別子の最高値が優先される。 SELECTION ARBITRATION を通じてデバイスが SCSI バスの制御に成功したとき、そ のデバイスは SCSI のターゲットに対して、コマンドを送る旨のリクエ ストを SCSI バスの制御線で伝えなければならない。それをするため に、ターゲットの SCSI 識別子に対応したピンを有効にする。 RESELECTION SCSI デバイスは、リクエスト処理中に接続を中止するかもしれない。 その場合、ターゲットは、イニシエータに再接続できる。すべての SCSI デバイスがこのフェーズをサポートしているわけではない。 COMMAND 6,10, もしくは 12 バイトの命令を、イニシエータからターゲットに送 ることができる。 DATA IN, DATA OUT これらのフェーズにおいて、データはイニシエータとターゲット間で転 送される。 STATUS このフェーズに入るのは、コマンドがすべて実行され、ターゲットがイ ニシエータに対して成功か失敗を示すステータスバイトを送ることがで きる場合である。 MESSAGE IN, MESSAGE OUT イニシエータとターゲット間での追加情報の転送。 Linux SCSI サブシステムは、ふたつの基本要素から成り、それぞれがデータ 構造体によって表される。 host SCSI ホスト(host)とは物理的なハードウェアである SCSI コントロー ラを指す。 NCR810 PCI SCSI コントローラは SCSI ホストの典型であ る。Linux システムが同種の SCSI コントローラを複数持つ場合、個々 のコントローラは、個別の SCSI ホストとして表現される。これ は、SCSI デバイスドライバは同種のコントローラを複数制御できるこ とを意味する。SCSI ホストは、ほとんどいつも SCSI コマンドのイニ シエータである。 Device SCSI デバイスの中で最も一般的なのが SCSI ディスクであるが、SCSI 規格はもっと多種類のデバイスをサポートしている。テープ、CD-ROM それに汎用 SCSI デバイスなどである。SCSI デバイスはほとんどいつ も SCSI コマンドのターゲットである。それらのデバイスは異なった取 り扱いが必要であり、たとえば CD-ROM やテープなどのリムーバルメ ディアの場合、Linux はそのメディアが取り出されたかどうか確認でき なければならない。異なるディスクタイプには異なるメジャー番号が与 えられ、それによって Linux はブロックデバイスへのリクエストを適 切な SCSI タイプに伝達することができる。 9.5.4. SCSI サブシステムの初期化 SCSI サブシステムの初期化は、SCSI バスやデバイスの動的な性質を反映し て、非常に複雑なものになっている。Linux は SCSI サブシステムをブート時 に初期化する。システム上の SCSI コントローラ(SCSI ホストと呼ばれる)を 検出し、コントローラごとの SCSI バスを検査して、バス上のデバイスをすべ て検出する。その上で、それらのデバイスを初期化して、Linux カーネルの残 りの部分が、通常ファイルの操作ルーチンやバッファキャッシュブロックデバ イスの操作ルーチンを経由して、デバイスを利用することが出来るようにす る。この初期化には 4 つのフェイズがある。 まず Linux は、カーネル構築時にカーネルに組み込まれた SCSI ホストアダ プタもしくはコントローラのうち、制御すべきハードウェアを持つのはどれな のかを見つけだす。カーネルに組み込まれた個々のホスト は、``builtin_scsi_host'' 配列内に ``Scsi_Host_Template'' エントリを持 つ。 Scsi_Host_Template データ構造体にはルーチンへのポインタが含まれて いて、それによって SCSI ホストに接続された SCSI デバイスの検出といった その SCSI ホスト固有の処理が実行される。それらのルーチンは、SCSI サブ システムによっても呼び出されて、その設定を行ったりもする。それらは、そ のホストタイプをサポートする SCSI デバイスドライバの一部である。検出さ れた個々の SCSI ホストのうち、実際に SCSI デバイスが接続されていたもの は、自分の Scsi_Host_Template データ構造体を、アクティブな SCSI ホスト のリストである ``scsi_hosts'' リストに付け加える。検出されたホストタイ プの個々の SCSI コントローラは、``scsi_hostlist'' リスト内にある ``Scsi_Host'' データ構造体によって表現される。たとえば、NCR810 PCI SCSI コントローラを持つシステムは、リスト内に、コントローラごとにひと つ、合計ふたつの Scsi_Host エントリを持つ。個々の Scsi_Host 構造体は、 そのデバイスドライバを表している Scsi_Host_Template をポイントしてい る。 Scsi_Host_Template scsi_hosts +--------------+ ---------------------------------->| next | |--------------| +--------->| name | | |--------------| | | | | | Device | | | Driver | | | Routines | | | | | +--------------+ +--------------------------------+ | scsi_hostlist Scsi_Host | ----------------------------------->+-------------+ | +---------->| next | | | |-------------| | | | | | | |-------------| | | | this_id | | | |-------------| | | | max_id | | | |-------------| | | | | | | |-------------| | | | host |<-----+ | |-------------| | | | | +-------------+ +--------------------------------+ | Scsi_Device Scsi_Device | scsi_device +-------------+ +-------------+ | -------------->| next |------->| next | | |-------------| |-------------| | | | | | | |-------------| |-------------| | | id | | id | | |-------------| |-------------| | | type | | type | | |-------------| |-------------| | | | | | | |-------------| |-------------| | | host | | host |<---+ |-------------| |-------------| | | | | | | | | +-------------+ +-------------+ 図表(8.4) SCSI データ構造 SCSI ホストがすべて見つかったら、次に SCSI サブシステムは、個々の SCSI ホストにどういった SCSI デバイスが繋がれているのか見つけ出さなければな らない。SCSI デバイスには 0 から 7 までの番号が振られ、個々のデバイス 番号もしくは SCSI 識別子はそれが繋がれた SCSI バス上においてユニークで なければならない。通常、SCSI 識別子はデバイス上のジャンパピンで設定さ れる。SCSI 初期化コードは、 SCSI バス上の個々の SCSI デバイスを検出す るとき、それらに TEST_UNIT_READY コマンドを送信する。デバイスが応答す ると、ENQUIRY コマンドが送信されて、そのデバイスの識別子が読み出され る。これによって Linux は、そのベンダ名、デバイスモデル、リビジョン名 を取得する。 SCSI コマンドは ``Scsi_Cmnd'' データ構造体によって表現さ れ、それらがこの SCSI ホストに対するデバイスドライバに渡されるときは、 その ``Scsi_Host_Template'' データ構造体内にあるデバイスドライバルーチ ンが呼び出される。発見されたすべての SCSI デバイスは ``Scsi_Device'' データ構造体によって表現され、その個々の構造体がその親である ``Scsi_Host'' をポイントしている。すべての Scsi_Device データ構造体は ``scsi_devices'' リストに付け加えられる。図表(8.4) では、主要なデータ 構造体がどのような相互関係にあるのかが示されている。 SCSI デバイスには、ディスク、テープ、CD 、汎用の 4 種類のタイプがあ る。それらの SCSI タイプの各々が異なるメジャーブロックデバイスタイプと してカーネルに独立して登録される。しかし、それらは、その種の SCSI デバ イスタイプがひとつ以上見つかった場合のみ登録がなされる。SCSI ディスク のような個々の SCSI タイプは独自のデバイステーブルを管理している。そし てそれらのテーブルを利用してカーネルの(ファイルやバッファキャッシュに 対する)ブロック操作ルーチンを正しいデバイスドライバや SCSI ホストに導 く。個々の SCSI タイプは ``Scsi_Device_Template'' データ構造体によって 表現される。これには、その SCSI デバイスのタイプと様々なタスクを実行す るルーチンのアドレスに関する情報が含まれる。SCSI サブシステムはそれら のテンプレートを使用して個々の SCSI タイプのデバイスに対する SCSI タイ プルーチンを呼び出す。言い換えると、もし SCSI サブシステムが SCSI ディ スクデバイスを追加したい場合、その SCSI ディスクタイプの追加用ルーチン が呼び出される。ひとつ以上のそのタイプの SCSI デバイスが見つかった場 合、Scsi_Type_Template データ構造体(訳注: ``Scsi_Device_Template''か?)が ``scsi_devicelist'' リストに付け加えら れる。 SCSI サブシステムの初期化における最後のフェーズは、登録された ``Scsi_Device_Template'' の個々に対する終了用関数を呼び出すことであ る。SCSI ディスクタイプごとに、その関数が、検出された SCSI ディスクす べてをまとめて、それらのディスクジオメトリを記録する。また、それは、す べての SCSI ディスクを表現している ``gendisk'' データ構造体を``図 表(8.3)''で示したディスクの連結リストに付け加える。 9.5.5. ブロックデバイスのリクエストを伝達する Linux が SCSI サブシステムの初期化を完了すると、SCSI デバイスが利用可 能になる。個々のアクティブな SCSI デバイスのタイプは、Linux がブロック デバイスリクエストをそれに導けるように、カーネルに登録される。そうした リクエストには ``blk_dev'' を経由したバッファキャッシュリクエストや ``blkdevs'' を経由したファイル操作リクエストなどがある。では、ひとつ以 上の数の EXT2 ファイルシステムのパーティションを持つ SCSI ディスクを例 に取ると、その EXT2 パーティションがマウントされているとき、カーネルの バッファリクエストはどのようにして適切な SCSI ディスクに渡されるのだろ うか? SCSI ディスクパーティションに対するデータブロックの読み書きリクエスト が為されると、``request'' 構造体が ``blk_dev'' 配列内にあるその SCSI ディスクの ``current_request'' リストに付け加えられる。 request リスト が処理されている場合、バッファキャッシュは何もする必要がないのだが、そ うでない場合は、 SCSI サブシステムを促してそのリクエストキューを処理さ せなければならない。システム上の個々の SCSI ディスクは ``Scsi_Disk'' データ構造体によって表現されている。その構造体は ``rscsi_disks'' 配列 に保存されていて、その配列は SCSI ディスクパーティションのマイナー番号 の一部を使用してインデックス付けされている。たとえば、/dev/sdb1 はメ ジャー番号 8 とマイナー番号 17 を持っているが、これはインデックス 1 を 生成する。個々の Scsi_Disk データ構造体には、このデバイスを表現する ``Scsi_Device'' データ構造体へのポインタが含まれている。その構造体が次 に ``Scsi_Host'' 構造体をポイントしており、それがそのデバイスの「オー ナー」である。バッファキャッシュからの request データ構造体は、SCSI デ バイスに送信されるべき SCSI コマンドを記述している ``Scsi_Cmnd'' 構造 体に変換され、これがそのデバイスを表す Scsi_Host データ構造体上のキュ ーに置かれる。いったん適切なデータブロックが書き込まれたり読み出された りすると、そうしたことが個別の SCSI デバイスドライバによって処理され る。 9.6. ネットワークデバイス Linux のネットワークサブシステムに関する限り、ネットワークデバイスと は、データパケットを送受信する実体のことである。通常これはイーサネット カードなどの物理デバイスである。しかし、データを自分に送信するために使 用されるループバックデバイス(loopback device)といったソフトウェアの ネットワークデバイスもある。個々のネットワークデバイスは ``device'' デ ータ構造体によって表現される。 [see: include/linux/netdevice.h] ネットワークデバイスコントローラは、カーネル起動時のネットワーク初期化 過程で、 Linux に制御しようとするデバイスを登録する。device データ構造 体には、デバイスに関する情報と、サポートされる多様なプロトコルがデバイ スのサービスを利用する際に使う関数のアドレスとが含まれる。これらの関数 は大部分がネットワークデバイスを使用したデータ転送に関するものである。 デバイスは、受信したデータを適切なプロトコル層に渡すために標準的なネッ トワークサポート機能を使用する。送受信されたすべてのデータ(パケッ ト)は、``sk_buff'' データ構造体によって表現されるが、それは柔軟な構造 体なので、ネットワークプロトコルのヘッダの簡単な追加や削除を可能にす る。ネットワークプロトコル層がネットワークデバイスを使用する方法、それ らが sk_buff データ構造体を使用してデータを受け渡しする方法について は、 ``「ネットワーク」''の章で詳しく解説する。本章では、 device デー タ構造体とネットワークデバイスが検出され初期化される方法とに集中する。 ``device'' データ構造体にはネットワークデバイスに関する次のような情報 が含まれている。 名前 (name) mknod コマンドを使ってデバイススペシャルファイルを作成するブロッ クデバイスやキャラクタデバイスとは異なり、ネットワークデバイスの スペシャルファイルは、システムのネットワークデバイスが検出され初 期化される際に自動的に出現する。その名前は標準的なもので、個々の 名前はそのデバイスのタイプを表している。同タイプのデバイスが複数 ある場合は、0 から順に番号付けされる。したがって、複数のイーサ ネットデバイスは、/dev/eht0, /dev/eth1, /dev/eth2 等となる。一般 的なネットワークデバイスをいくつか示す。 /dev/ethN イーサネットデバイス /dev/slN SLIP デバイス /dev/pppN PPP デバイス /dev/lo ループバックデバイス バス情報 (bus information) これは、デバイスドライバがデバイスを制御するために必要な情報であ る。``irq'' 番号はそのデバイスが使用する割り込み番号である。 ``base_addr'' は I/O メモリ内にあるコントロールレジスタとステー タスレジスタのアドレスである。 ``dma'' はそのネットワークデバイ スが使用する DMA チャンネルの番号である。これらすべての情報はブ ート時のデバイスが初期化される際に設定される。 インターフェイスフラグ(interface flag) これらは、ネットワークデバイスの特徴と能力とを示している。 IFF_UP インターフェイスが起動され実行中である。 IFF_BROADCAST デバイスのブロードキャストアドレスが有効である。 IFF_DEBUG デバイスのデバッギングが有効である。 IFF_LOOPBACK これは、ループバックデバイスである。 IFF_POINTTOPOINT これは、一対一(point to point)のリンクである。(SLIP と PPP) IFF_NOTRAILER ネットワークトレイラー(network trailer)なし。(訳注: 用語集) IFF_RUNNING リソースが割り当てられた。 IFF_NOARP ARP プロトコルをサポートしない。 IFF_PROMISC プロミスク受信モードのデバイスは、パケットの送信先アドレス とは無関係にすべてのパケットを受信する。 IFF_ALLMULTI IP マルチキャストフレームを受信する。 IFF_MULTICAST IP マルチキャストフレームの受信が可能。 プロトコル情報 個々のデバイスは、ネットワークプロトコル層によってどのように利用 されるのかを示している。 最大転送単位 (mtu) そのネットワークで送信可能な最大パケットのサイズ。ただし、そ れに付加されるリンク層のヘッダ部分は含まれない。この最大値 は、IP などのプロトコル層が送信パケットの適正サイズを選択する 際に利用される。 ファミリー (family) ファミリーとは、そのデバイスがサポートできるプロトコルファミ リーを指す。すべての Linux ネットワークデバイスでサポートされ るファミリーは、AF_INET、すなわちインターネットアドレスファミ リーである。 タイプ (type) ハードウェアインターフェイスタイプとは、ネットワークデバイス が取り付けられたメディアを表している。Linux のネットワークデ バイスがサポートするメディアのタイプは多種に渡る。それには、 イーサネット、X.25、トークンリング、SLIP、PPP、アップルローカ ルトークなどが含まれる。 アドレス (Address) device データ構造体は、IP アドレスを含めて、そのネットワーク デバイスに関係するいくつかのアドレスを保持する。 パケットキュー (Packet Queue) これは、ネットワークデバイス上で転送を待っている ``sk_buff'' パ ケットのキューである。 サポート関数 (Support Functions) 個々のデバイスは、そのデバイスのリンク層に対するインターフェイス の一部として、プロトコル層がコールできるルーチンの標準セットを提 供する。それには、設定ルーチンやフレーム送信ルーチン以外にも、標 準フレームヘッダ付加や統計情報収集のルーチンも含まれる。それらの 統計情報は ifconfig コマンドを使って見ることができる。 9.6.1. ネットワークデバイスの初期化 ネットワークデバイスドライバは、他の Linux デバイスドライバ同様、Linux カーネルに組み込むことができる。組み込まれた個々のネットワークデバイス は、 ``device'' データ構造体によって表され、その構造体は、ポインタのリ ストである ``dev_base'' によってポイントされたネットワークデバイスのリ スト内に置かれる。ネットワーク層は、デバイス固有の仕事が必要な場合、 ネットワークデバイスに関する多数のサービスルーチンのひとつを呼び出す が、そうしたルーチンのアドレスは device データ構造体内に保持されてい る。しかし、適切な初期化が済むまでは、 device データ構造体は、初期化も しくは検出ルーチンのアドレスしか保持していない。 ネットワークデバイスドライバに関して解決すべき問題はふたつある。まず、 Linux カーネルに組み込まれたネットワークデバイスドライバのすべてが制御 すべきデバイスを持っているわけではないことである。もうひとつは、システ ム上のイーサネットデバイスは、それらの基礎になるデバイスドライバとは無 関係に、 /dev/eth0、/dev/eth1 等と呼ばれることである。ネットワークデバ イスが存在しないという問題は、簡単に解決できる。個々のネットワークデバ イスの初期化ルーチンが呼び出された際に、それらのルーチンは、駆動すべき 実際のコントローラを特定できたかどうかを報告するステータス情報を返す。 ドライバがデバイスを見つけられなかった場合、``dev_base'' によってポイ ントされる ``device'' 構造体のリスト内のデバイスのエントリは削除され る。ドライバがデバイスを検出できた場合、device データ構造体の未設定部 分に、そのデバイス情報とネットワークデバイスドライバ内のサポート関数の アドレスとを書き込む。 もうひとつの問題である、イーサネットデバイスに標準的な /dev/ethN デバ イススペシャルファイルを動的に割り当ててしまうという点については、もう 少し手際よく解決される。デバイスリストには 8 個の標準エントリがあり、 順に eth0, eth1 等から eth7 まで並んでいる。初期化ルーチンは全部に共通 であり、カーネルに組み込まれたイーサネットドライバを、合致するデバイス が見つかるまで順番に試していく。ドライバが該当するイーサネットデバイス を見つけたとき、ドライバが所有することになった ethN ``device'' データ 構造体に情報を書き込む。そして、この時同時に、ネットワークデバイスドラ イバは、制御する物理デバイスを初期化し、使用する IRQ や(もし必要なら) DMA チャンネル等を決定する。ドライバは、制御すべき複数のネットワークデ バイスを検出することがあり、その場合は、複数の /dev/ethN device データ 構造体を取得することになる。8 個すべての /dev/ethN devicd が割り当てら れると、それ以上のイーサネットデバイスの検出は行われない。 10. ファイルシステム この章では、Linux カーネルがサポートするファイルシステム内でのファイル 管理の方法や、仮想ファイルシステムの仕組みについて解説する。ま た、Linux カーネルの実(real)ファイルシステムがどのようにサポートされて いるのかについても説明する。 Linux の最も重要な特徴のひとつに、サポートするファイルシステムの豊富さ がある。このことが、Linux を非常に柔軟なものにし、他の多くのオペレー ティングシステムとの共存を可能にしている。書き込みに関して、Linux は 15 のファイルシステムをサポートしている。それらは、ext, ext2, xia, minix, umsdos, msdos, vfat, proc, smb, ncp, iso9660, sysv, hpfs, affs, それに ufs であり、おそらく今後もっと多くのファイルシステムが追加され るだろう。 Linux では、Unix の場合と同様に、システム上で使用される個別のファイル システムにアクセスする際、(ドライブ番号やドライブ名といった)デバイス識 別子は使われない。それらのファイルシステムは、単一の階層的なツリー構造 に統合され、それによってファイルシステムは全体でひとつの実体として表現 される。新しいファイルシステムをマウントする場合、Linux は個々のファイ ルシステムをこの単一のツリー構造に付け加える。すべてのファイルシステム は、そのタイプに関わらず、任意のディレクトリにマウントされ、マウントさ れたファイルシステムのファイルがそのディレクトリに現存する内容を包み隠 す。そうしたディレクトリは、マウントディレクトリもしくはマウントポイン トと呼ばれる。ファイルシステムがマウントを解除されると、そのマウント ディレクトリに存在したファイルが再度出現する。 ディスクが(たとえば、fdisk を使用して)初期化されるとき、ディスク上にパ ーティション構造が設定され、物理ディスクがいくつかの論理パーティション に分割される。個々のパーティションは、EXT2 ファイルシステムといった単 一のファイルシステムを保持する。ファイルシステムはファイルを組織し、物 理デバイスのブロック上に保持されたディレクトリやソフトリンク(シンボ リックリンク)などによる論理的な階層構造を形成する。ファイルシステムを 内包し得るデバイスはブロックデバイスと呼ばれる。 IDE ディスクのパー ティション /dev/hda1 は、システム上の最初の IDE ディスクの最初のパー ティションであるが、これはブロックデバイスである。Linux ファイルシステ ムは、ブロックデバイスをブロックが単に線形に並んだものと見看しており、 下位にある物理ディスクのジオメトリに関しては知らないかもしくは関心を持 たない。デバイスの特定ブロックの読み出しリクエストを、そのデバイスに とって意味のある言葉に置き換えこと、すなわち、ブロックが保存されている そのハードディスク上の特定のトラック、セクタ、シリンダなどに置き換える のは、個々のブロックデバイスドライバの仕事である。ファイルシステムは、 それを保持しているのがどのようなデバイスであっても、同じ方法でそれを見 て、認識し、操作をする。さらに、Linux のファイルシステムを使用すれば、 そうした異なるファイルシステムが異なるハードウェアコントローラに制御さ れた異なる物理メディア上にあるということは、(少なくともシステムのユー ザにとっては)問題ではなくなる。ファイルシステムはローカルシステム上に ないことすら可能であり、ネットワークリンク越しに遠隔操作でマウントされ たディスクであってもよい。Linux システムが SCSI ディスク上にルートファ イルシステムを持っている次の例で考えてほしい。 A E boot etc lib opt tmp usr C F cdrom fd proc root var sbin D bin dev home mnt lost+found ユーザはもちろん、ファイル自体に操作を加えるプログラムでさえ、上記 /C が実はマウントされた VFAT ファイルシステムであり、システムの第一の IDE ディスク上にあることを知る必要はない。上記の例では(これは実際にわたし が自宅で使用している Linux システムのものなのだが)、/E は第二の IDE コ ントローラ上のマスター IDE ディスクである。プライマリ IDE コントローラ が PCI コントローラで、第二のコントローラが IDE CD-ROM を制御する ISA コントローラであるといったことは(ファイルシステム上では)どちらも問題で はない。また、モデムとそのモデムによる PPP ネットワークプロトコルを使 用して、電話で職場のネットワークに接続し、この場合、そこにあるわたしの Alpha AXP Linux システム上のファイルシステムを /mnt/remote に遠隔操作 でマウントすることもできる。 ファイルシステム上のファイルはデータの集合体である。この章の文章(訳注: 原文のこと)が入った元のソースファイルは filesystems.tex というファイル 名の ASCII ファイルである。ファイルシステムは、そのファイルシステム上 のファイル内に含まれるデータを保持するだけでなく、ファイルシステムの構 造をも保持している。Linux のユーザやプロセスが、ファイル、ディレクト リ、ソフトリンク(シンボリックリンク)、ファイル保護情報等々として見てい る情報はすべて、ファイルシステムによって保持されている。さらに、ファイ ルシステムはそうした情報を安全、確実に保持しなければならない。オペレー ティングシステムの基本的な統一性は、ファイルシステムに依存しているから である。データやファイルの喪失が突発的に起こるオペレーティングシステム を使おうとする人はいないだろう(``脚注1'')。 Linux が最初に採用していた minix ファイルシステムは、やや制限が多く、 パフォーマンスにも欠けていた。ファイル名は 14 文字以上になってはなら ず(これでも、8 文字 + 3文字 というファイル名よりはましである)、最大 ファイルサイズは 64M バイトであった。64 M バイトというと一見充分なサイ ズのように思えるが、ちょっとしたデータベースを作ろうとすると大きなファ イルサイズは必要である。Linux 専用にデザインされた最初のファイルシステ ムである Extended File System (EXT) は、1992 年 4 月に発表され、多くの 問題が改善されたが、それでもまだパフォーマンスの欠如が感じられた。それ ゆえ、1993 年、Second Extended File System (EXT2) が登場した。この章で 後ほど詳細に述べるのは、このファイルシステムについてである。 EXT ファイルシステムが加わった際、重要な発展があった。実(real)ファイル システムがオペレーティングシステムやシステムのサービスから分離され、そ のインターフェイス層として仮想ファイルシステム(Virtual File System, VFS) が付加されたことである。VFS によって、Linux は、多くの、しばしば 非常に性質の異なるファイルシステムのサポートが可能になった。そうした個 々のファイルシステムは VFS に対し共通のソフトウェアインターフェイスを 提供する。Linux のファイルシステムの詳細のすべてがソフトウェア上で変換 されるので、Linux カーネルの残りの部分やシステム上で実行されているプロ グラムにとって、あらゆるファイルシステムは全く同じに見える。Linux の仮 想ファイルシステムによって、同時に多くの異なるファイルシステムを透過的 にマウントすることが可能になっている。 Linux の仮想ファイルシステムは、ファイルへのアクセスを出来るだけ速く効 率的なものにするために実装されている。それはファイルやデータの正確な保 存をも確実に行わなければならない。これらふたつの要求は両立し難いもので ある。ファイルシステムがマウントされて利用される際、Linux の VFS はそ れら個々の情報をメモリにキャッシュする。ファイルやディレクトリに対する 作成や書き込み、削除がなされてそのキャッシュ内のデータが変更された場 合、ファイルシステムを正しく更新するには細かな配慮が必要になる。実行中 のカーネル内にあるファイルシステムのデータ構造を見ることができるとすれ ば、ファイルシステムによってデータブロックの読み書きがなされる様子を観 察できるだろう。アクセスされているファイルやディレクトリを記述するデー タ構造体の作成や破棄がなされ、また終始デバイスドライバが動作していて、 データの取り込みや保存を行っている。これらのキャッシュで最も重要なの が、バッファキャッシュ(buffer cache)であり、それは、個別のファイルシス テムがその基礎であるブロックデバイスにアクセスする仕組みのなかに組み込 まれている。ブロックがアクセスされると、それはバッファキャッシュに入れ られ、それらの状態に応じて様々なキュー上に保持される。バッファキャッ シュは、データバッファをキャッシュするだけではなく、非同期インターフェ イスを管理することで、ブロックデバイスドライバの手助けもしている。 10.1. Second Extended File System (EXT2) +---------------------------------------------+ | | Block | | Block | Block | | | Group 0 | | Group N-1 | Group N | +---------------------------------------------+ | | +-----+ +-----+ | | <--------------------------+ +--------------> +------------------------------------------------------------------+ | Super | Group | Block | Inode | Inode | Data | | Block | Descriptors | Bitmap | Bitmap | Table | Block | | | | | | | | +------------------------------------------------------------------+ 図表(9.1) EXT2 ファイルシステムの物理的レイアウト EXT2 ファイルシステム(Second Extended File System) は、Remy Card に よって考案された Linux 用の拡張性に富むパワフルなファイルシステムであ る。それは Linux コミュニティーにおいてこれまでで最も成功したファイル システムであり、現在出荷されているすべての Linux ディストリビューショ ンの基盤でもある。 [see: fs/ext2/*] EXT2 ファイルシステムは、多くのファイルシステム同様、ファイルに保持さ れたデータはデータブロックに保存されるという前提のもとに構築されてい る。これらのデータブロックはすべて同じサイズであり、そのサイズは異なる EXT2 ファイルシステム間で変化することがあるが、特定の EXT2 ファイルシ ステムにおけるブロックサイズは(mke2fs コマンドを使用した)ファイルシス テム作成時に設定される。ファイルサイズはすべて、ブロックサイズの整数倍 に切り上げられる。ブロックサイズが 1024 バイトの場合、1025 バイトの ファイルは 1024 バイトのブロックふたつを占有する。残念なことに、これは 平均するとファイルごとに半ブロックのディスクスペースが浪費されることを 意味する。( CPU 対記憶装置という二者択一の原理からすると)コンピュータ では通常、CPU 効率を犠牲にしてメモリとディスクスペースを有効利用する。 しかしこの場合、Linux は、大部分のオペレーティングシステムと同様、CPU の負荷を減らすために、比較的効率の悪いディスクの使い方をする。 ファイルシステム上のすべてのブロックがデータを保持しているのではない。 ブロックの一部は、ファイルシステムの構造を記述した情報を保持するために 使用されている。 EXT2 はファイルシステムのトポロジーを定義するために、 システム上の個々のファイルを ``inode'' データ構造体で記述している。 inode が記述する事柄には、ファイル内のデータが保持されたブロックの位置 や、ファイルへのアクセス権、ファイルの変更時間、ファイルタイプなどがあ る。 EXT2 ファイルシステム上のすべてのファイルは、単一の inode で記述 され、個々の inode は識別のための単一でユニークな番号を持つ。ファイル システム上の inode は、inode テーブル内に保存される。 EXT2 のディレク トリとは(それ自体も inode によって記述されている)特殊なファイルにすぎ ず、そのファイルに含まれるのは、そのディレクトリ(が保持する) エントリ の inode へのポインタである。 ``図表(9.1)''は、EXT2 ファイルシステムのレイアウトを示すものであるが、 それらは、ブロック構造を持ったデバイス上で連続したブロックを占有してい る。個々のファイルシステムからすると、ブロックデバイスとは読み書き可能 な一連のブロックにすぎない。ファイルシステムは物理メディア上のどこにブ ロックを置くべきかを気にする必要がない。それはデバイスドライバの仕事で ある。ファイルシステムがブロックデバイスの持つ情報やデータを読み込む必 要があるときは、そのデバイスをサポートするデバイスドライバに対して、あ る整数倍の数のブロックを読み込むようリクエストをする。 EXT2 ファイルシ ステムは、ディスク上の論理パーティションを、複数のブロックグルー プ(Block Group)に分割する。個々のブロックグループは、実(real)ファイル や実ディレクトリの情報やデータをブロックとして保持すると同時に、ファイ ルシステムの整合性(integrity)を保つために必須の情報を二重化して保持す る。二重化が必要なのは、不具合が生じてファイルシステムを復旧しなければ ならない場合があるからである。ブロックグループそれぞれの内容について は、サブセクションでさらに詳しく説明する。 10.1.1. EXT2 Inode ext2_inode +-----------------+ +-------+ | Mode | +---------->| Data | |-----------------| | +-------+ | Owner info | | |-----------------| | +-------+ | Size | | +------>| Data | |-----------------| | | +-------+ | Timestamps | | | +-------+ |-----------------| | | | Data | | |----------------------+ | +-------+ | Direct Blocks | | | |--------------------------+ +-------+ | | +->| Data | | | +---+ | +-------+ | | +------>| |---------+ +-------+ |-----------------| | | |----------->| Data | | Indirect blocks |---------+ | | +-------+ |-----------------| +---+ +---+ +---+ +-------+ | Double Indirect |---------->| |------->| |---->| Data | |-----------------| | |-+ | | +-------+ | Triple Indirect | | | | | |-+ +-------+ +-----------------+ +---+ | +---+ +-->| Data | | +---+ +-------+ +----->| | +-------+ | |---->| Data | | | +-------+ +---+ 図表(9.2) EXT2 Inode EXT2 ファイルシステムでは、``inode'' が基本的な建築材料(building block)である。ファイルシステム上のすべてのファイルとディレクトリは、そ れらと一対一の関係に立つ inode によって記述される。ブロックグループご との EXT2 inode は、inode テーブルに保存され、同時に、システムが割り当 てや解放を行った inode の状態を監視するためのビットマップ(bitmap)もそ のテーブルに保存される。図表(9.2)では、EXT2 inode のフォーマットを示し ていて、その種々の情報の中には、次のようなフィールドが含まれている。 [see: include/linux/ext2_fs_i.h] モード (mode) このフィールドは 2 種類の情報を保持している。その inode が何を記 述しているかということと、ユーザがそれに対して持つパーミッション である。 EXT2 の場合、inode が記述できるのは、ファイル、ディレク トリ、シンボリックリンク、ブロックデバイス、キャラクタデバイ ス、FIFO のうちのひとつである。 所有者情報 そのファイルやディレクトリの所有者(owner)に関するユーザ識別子と グループ識別子。これによって、ファイルシステムは正しいアクセス権 限を適切に判断できる。 サイズ バイト単位で表されるファイルサイズ タイムスタンプ(timestamps) ``inode'' が作成された時間とそれが最後に修正された時間 データブロック ``inode'' が記述するデータを含んだブロックに対するポインタ。最初 の 12 個は、その inode が記述するデータを含んだ物理ブロックへの ポインタであり、最後の 3 つはそれよりも 2 段階間接的なポインタと なっている。たとえば、この二重間接ブロックポインタ(double indirect block pointer)は、物理データブロックへのポインタ群に対 するポインタ群をポイントしている。これは、12 個のデータブロック 以下のサイズしかないファイルは、それより大きいファイルよりも高速 にアクセスされることを意味する。 注意すべきなのは、EXT2 inode はスペシャルファイルを表すことができるこ とである。これらは実(real)ファイルではないが、プログラムがそれを使って デバイスにアクセスできるようにするためのものである。/dev ディレクトリ にすべてのデバイスファイルが存在することによって、プログラムは Linux 上のデバイスにアクセスできる。たとえば、mount プログラムは、マウントし ようとするデバイスファイル名を引数に取る。 10.1.2. EXT2 スーパーブロック(Superblock) スーパーブロック(superblock)には、そのファイルシステムの基本的なサイズ と構造(shape)とを記述した情報が含まれる。そこにある情報によって、ファ イルシステムマネージャは、ファイルシステムの使用と管理が可能となる。通 常、ファイルシステムがマウントされる時、ブロックグループ 0 上のスーパ ーブロックだけが読み込まれるのだが、個々のブロックグループにはシステム の破壊に備えた複製が含まれている。様々な情報のなかには次のようなものが ある。 [see: include/linux/ext2_fs_sb.h] [訳注:ext2_super_block, in include/linux/ext2_fs.h] マジックナンバー(``magic number'') これは、マウント操作を行うソフトウェアが、そのスーパーブロックが 本当に EXT2 ファイルシステムのものであるかチェックすることを可能 にするためにある。EXT2 の現行バージョンでは、この番号は 0xEF53 である。 リビジョンレベル(revision level) メジャーとマイナーのリビジョンレベルによって、マウント操作を行う コードは、そのファイルシステムの特定のリビジョンでしか利用できな い機能(feature)をサポートすべきかどうかを判断する。また、機能互 換性フィールド(feature compatibility field)というのが存在し、そ の情報は、マウント操作コードが、そのファイルシステム上で新しい機 能を安全に使用することができるかどうかを判断する際の手助けをす る。 マウントカウントと最大マウントカウント(mount count and maximum mount count)" これらふたつは、ファイルシステムの整合性を完全にチェック すべきかどうかをシステムが判断できるようにするために存在する。マ ウントカウントは、ファイルシステムがマウントされるたびに加算さ れ、最大マウントカウントに達すると、「最大マウントカウントに達し たので、e2fsck を実行することを薦めます」という警告メッセージが 表示される。 ブロックグループ番号(block group number) このスーパーブロックのコピーを保持するブロックグループ番号。 ブロックサイズ(block size) バイト数で表されるそのファイルシステムのブロックサイズ。たとえ ば、1024 バイト等と表示される。 グループごとのブロック数 ひとつのグループのブロック数。これは、ブロックサイズ同様、ファイ ルシステム作成時に確定される。 フリーブロック ファイルシステム上のフリーブロックの数。 フリー inode ファイルシステム上のフリー inode の数。 最初の inode (first inode) ファイルシステム上の最初の inode の inode 番号。EXT2 ルートファ イルシステム上での最初の inode は、"/" ディレクトリのディレクト リエントリである。 10.1.3. EXT2 グループディスクリプタ 個々のブロックグループは、それを記述するデータ構造体を持っている。スー パーブロックと同様に、全ブロックグループに対するグループディスクリプタ はすべて、ファイルシステムの障害に備えて、個々のブロックグループ内で二 重化されている。 [see: ext2_group_desc, in include/linux/ext2_fs.h] 個々のグループディスクリプタには、次のような情報が含まれている。 ブロックビットマップ(Block Bitmap) そのブロックグループに関するブロック割り当てビットマップ(block allocation bitmap)のブロック番号。これは、ブロックの割り当てと解 放の際に使用される。 inode ビットマップ (Inode Bitmap) そのブロックグループに対する inode 割り当てビットマップのブロッ ク番号。これは、inode の割り当てと解放の際に使用される。 inode テーブル(Inode Table) そのブロックグループの対する inode テーブルの開始ブロックのブ ロック番号。個々の inode は、下記で述べる EXT2 inode データ構造 体によって表されている。 Free block count, Free Inodes count, Used directory count (訳注:これらの項目の説明は原文から省略されています。) グループディスクリプタは連続的に配置されているので、全体でグループディ スクリプタテーブルを形成している。個々のブロックグループには、そのスー パーブロックのコピーの後に、グループディスクリプタのテーブル全体が含ま れている。 EXT2 ファイルシステムによって実際に使用されるのは、(ブロッ クグループ 0 にある) 最初のコピーだけである。その他のコピーは、スパー ブロックのコピーの場合と同様に、メインコピーが壊れた場合に備えてのもの である。 10.1.4. EXT2 ディレクトリ 0 15 55 +---+---+---+------+---+---+---+--------------+--------------------+ | il| 15| 5| file| i2| 40| 14|very_long_name| | +---+---+---+------+---+---+---+--------------+--------------------+ | | * | | * | | /| | | /| | | | | | | | +-----------+ | +-------------------+ | | | | inode table | | +--------------------+ | | | | | | |--------------------| | | | | | | |--------------------| | +---->| | | |--------------------| | | | | |--------------------| +----------------------->| | +--------------------+ 図表(9.3) EXT2 ディレクトリ EXT2 ファイルシステムにおいて、ディレクトリとは、ファイルシステム上の ファイルに対するアクセスパス(access path)の作成と保持のために使用され るスペシャルファイルである。図表(9.3)では、メモリ上でのディレクトリエ ントリのレイアウトが示されている。ディレクトリファイルは、ディレクトリ エントリのリストであり、個々のエントリには次のような情報が含まれてい る。 [see: ext2_dir_entry, in include/linux/ext2_fs.h] inode そのディレクトリエントリに対する inode 。これは、ブロックグルー プの inode テーブルに保持されている inode 配列へのインデックスに なっている。図表(9.3) では、file という名前のファイルに対する ディレクトリエントリが inode 番号 i1 を参照している。 名前のサイズ (name length) バイト数で示されるそのディレクトリエントリのサイズ 名前 (name) そのディレクトリエントリの名前 すべてのディレクトリにおける最初のふたつのエントリはいつも、標準となっ ている "." と ".." エントリであり、それらはそれぞれ "現在のディレクト リ" と "親ディレクトリ(parent directory)" を意味している。 10.1.5. EXT2 ファイルシステム上のファイルの検索 Linux のファイル名は、Unix 上のファイル名と同じフォーマットになってい る。それは、スラッシュ("/")で区切られた一連のディレクトリ名とその最後 にあるファイルの名前から成る。ファイル名の一例 は、/home/rusling/.cshrcであり、ここでの /home と /rusling はディレク トリ名であり、ファイル名は .cshrc である。他のすべての Unix システムと 同様に、 Linux はファイル名そのもののフォーマットには特別な様式を要求 しない。それは任意の長さで、表示可能な任意の文字であってよい。EXT2 ファイルシステム上の上記ファイルを表す inode を発見するため、システム は、そのファイル自体の名前に到達するまでファイル名をディレクトリ単位で 分解しなければならない。 必要とされる最初の inode は、ファイルシステムのルートディレクトリの inode であるので、ファイルシステム上のスーパーブロック内でその番号を見 つける。EXT2 inode を読むためには、適切なブロックグループの inode テー ブルでそれを探さなければならない。たとえば、ルートの inode 番号が 42 である場合、ブロックグループ 0 の inode テーブルにある 42 番目の inode を必要とする。ルート inode は EXT2 ディレクトリのものであり、言い換え ると、ルート inode のモード(mode)にはディレクトリと記述されているの で、そのデータブロックには EXT2 ディレクトリのエントリが含まれている。 home は多くのディレクトリエントリのひとつにすぎず、このディレクトリエ ントリからは、/home ディレクトリが記述する inode の番号が分かる。 rusling のエントリを見つけるにはこのディレクトリを読み込まなければなら ない。(まず、最初にその inode を読み込んで、次にその inode によって記 述されたデータブロックからディレクトリエントリを読み込む。)そして、そ の中から rusling エントリが見つかったら、そこから /home/rusling ディレ クトリを記述する inode の番号が分かる。最後に、/home/rusling ディレク トリを記述する inode によってポイントされたディレクトリエントリを読ん で、.cshrc ファイルの inode 番号を発見し、その番号によってそのファイル の情報を含んでいるデータブロックを取得する。 10.1.6. EXT2 ファイルシステム上のファイルサイズの変更 ファイルシステムに共通の問題のひとつに、ファイルの断片化(fragment)傾向 がある。ファイルのデータを保持するブロックはファイルシステム全体に分散 してしまい、そのため、データブロック間の距離が遠くなるにつれて、ファイ ルのデータブロックに対する連続したアクセスが困難になりアクセス効率が落 ちてしまう。 EXT2 ファイルシステムはこの問題を克服するために、ファイル に新しいデータブロックを割り当てる際、それが現在存在するデータブロック に対し物理的に近接する位置に割り当てるか、少なくとも元のデータブロック と同じブロックグループ内に割り当てようとする。そしてそれが出来ない場合 のみ、データブロックを別のブロックグループに割り当てる。 プロセスがデータをファイルに書き込もうとするとき、Linux ファイルシステ ムは、(データ書き込みの途中で)そのファイルに対して最後に割り当てられた ブロックの終端を越えたかどうかをチェックする。終端を越えた場合、ファイ ルシステムはそのファイルに新しいデータブロックを割り当てる処理をする。 割り当てが終わるまで、プロセスは実行を中断する。ファイルシステムが新し いデータブロックを割り当てて、それに残りのデータを書き込むまで待ってか ら、プロセスは実行を再開しなければならない。 EXT2 ブロック割り当てルー チンが最初に実行することは、そのファイルシステムに対する EXT2 スーパー ブロックをロック(lock)することである。割り当てと解放はスーパーブロック 内のフィールドの値を変更するので、Linux ファイルシステムは同時に複数の プロセスがそれをすることを許さない。他のプロセスがさらにデータブロック を割り当てなければならない場合は、実行中のプロセスが終了するまで待つ必 要がある。スーバーブロックのロック解除を待つプロセスはサスペンド状態に なり、スーパーブロックの制御が現在のユーザによって開放されるまでは実行 されない。スーパーブロックへのアクセスは早い者勝ちの仕組みで許可される ので、プロセスがいったんスーパーブロックの制御を得た場合、処理の終了ま でその制御を保持する。スーパーブロックをロックした後、プロセスはファイ ルシステム上に充分な空きブロックがあるかどうかをチェックする。充分な空 きブロックがない場合、それ以上割り当てようとしても失敗するので、プロセ スは、そのファイルシステムのスーパーブロックの制御を解放する。 ファイルシステム上に充分な空きブロックがある場合、プロセスはそれを割り 当てようとする。 [see:ext2_new_block(), in fs/ext2/balloc.c] EXT2 ファイルシステムがデータブロックを先割り当て(preallocate)するよう ビルドされている場合、それらの中から割り当てを行うことができる。この先 割り当てされたブロックは実際には存在せず、割り当てられたブロックビット マップ(``block bitmap'')内で予約されているだけである。ファイルに新しい データブロックを割り当てようとしている場合、そのファイルを表す VFS ``inode'' には、EXT2 に特有の ``prealloc_block'' と ``prealloc_count'' のふたつのフィールドがあり、それらはそれぞれ、先割り当てされたデータブ ロックの最初のブロック番号と、存在するブロック数を表す。先割り当てされ たブロックが存在しないかブロックの先割り当てが無効な場合、EXT2 ファイ ルシステムは新しいブロックを割り当てなければならない。まず EXT2 ファイ ルシステムはファイル内の最後のデータブロックの直後にあるデータブロック が空かどうかを確認する。理論的には、このブロックは、連続的なアクセスを より高速にできるので、割り当てるのに最も効率的なブロックである。このブ ロックが空でない場合、検索範囲が広げられ、先ほどの理想的なブロックから 64 ブロック以内にあるブロックを探す。このブロックは最適ではないにして も、少なくとも非常に近くにあり、そのファイルに属する他のデータブロック と同じブロックグループ内にある。 [see: ext2_new_block(), in fs/ext2/balloc.c] そのブロックが空でない場合であっても、プロセスは他のブロックグループを 順番に探して、何らかの空ブロックを見つける。ブロック割り当てコードは、 ひとつのブロックグループ内のどこかに 8 個の空データブロックのクラスタ があるか調べる。 8 個を一緒に見つけられない場合、それ以下で処理を行 う。ブロックの先割り当てが必要かつ有効とされている場合、当該コードは prealloc_block と prealloc_count を適宜更新する。 空ブロックを見つけた場所に関わりなく、ブロック割り当てコードはブロック グループのブロックビットマップを更新し、バッファキャッシュにデータバッ ファを割り当てる。そのデータバッファは、ファイルシステムがサポートする デバイス識別子と割り当てられたブロックのブロック番号とによってユニーク に識別される。バッファ内のデータは 0 とされ、バッファの内容が物理ディ スクに書き込まれていないことを示すためにそのバッファは「ダーティ ー(dirty)」とマークされる。最後に、スーパーブロック自身が、変更されて ロックを解除されたことを示すために「ダーティー」とマークされる。スーパ ーブロックの空きを待つプロセスがある場合、キューの最初にあるプロセスが 再度実行され、ファイル操作のためにスーパーブロックの排他的な実行制御権 を獲得する。プロセスのデータが新しいデータブロックに書き込まれ、もしそ のデータブロックが満杯になると、そのプロセス全体が繰り返されて次のデー タブロックが割り当てられる。 10.2. 仮想ファイルシステム (Virtual File System, VFS) +---------+ +------------+ | |<------------------>| inode | | VFS |<----------+ | キャッシュ | | | | | | +--*---*--+ | +------------+ /| /| | +-------+ +------+ | |/ |/ | +----*----+ +----*----+ | | | | | | | MINIX | | EXT2 | | +------------+ | | | | +------->|ディレクトリ| +----*----+ +----*----+ | キャッシュ | /| /| | | *-------+ +------+ +------------+ |/ |/ +--*---*---+ | バッファ | |キャッシュ| | | +----*-----+ /| |/ +----*-----+ | ディスク | | ドライブ | | | +----------+ 図表(9.4) 仮想ファイルシステムの論理ダイアグラム 図表(9.4)では、Linux の仮想ファイルシステムと実(real)ファイルシステム との関係が示されている。仮想ファイルシステムは任意の時間にマウントされ た異なるファイルシステムすべてを管理しなければならない。そのために、仮 想ファイルシステムは、(仮想)ファイルシステム全体、およびマウントされた 実(real)ファイルシステムを記述するデータ構造体を管理している。 [see: fs/*] やや混乱するかもしれないが、VFS は、EXT2 ファイルシステムがスーパーブ ロックや inode を利用する方法と非常に似通った方法で、システムのファイ ルをスーパーブロックと inode を使って記述している。 EXT2 ``inode'' の 場合と同様に、VFS ``inode'' も、システム上のファイルやディレクトリを記 述している。すなわち、仮想ファイルシステムの内容やトポロジーを VFS inode を使って記述しているわけである。ここからは、混乱を避けるため に、(仮想ファイルシステムの inode やスーパーブロックに関しては) VFS inode や VFS スーパーブロックと書くことで、EXT2 の inode やスーパーブ ロックと区別する。 個々のファイルシステムが初期化されると、それは VFS に登録される。これ は、システムのブート時、オペレーティングシステムが初期化される際に行わ れる。実(real)ファイルシステムは、カーネルに組み込まれているか、ローダ ブルモジュールとしてビルドされているかのいずれかである。ファイルシステ ムモジュールは、システムがそれを必要とするときにロードされるので、たと えば、VFAT ファイルシステムがカーネルモジュールとして実装されている場 合、それは VFAT ファイルシステムがマウントされたときにのみロードされ る。ブロックデバイスベースのファイルシステムがマウントされ、ルートファ イルシステムに組み込まれると、VFS はそのファイルシステムが持つスーパー ブロックのデータ構造体を読み込まなければならない。ファイルシステムタイ プごとのスーパーブロック読み込みルーチンは、そのファイルシステムのトポ ロジーを読み解いて、その情報を VFS スーパーブロックのデータ構造体に マップする。 VFS は、VFS スーパーブロックを保持すると同時に、システム 上のマウントされたファイルシステムに関するリストを保持しなければならな い。個々の VFS スーパーブロックには、特定の機能を担当するルーチンの情 報と、そのルーチンへのポインタが含まれている。それゆえ、たとえば、マウ ントされた EXT2 ファイルシステムを表すスーパーブロックには、EXT2 固有 の inode 読み込みルーチンへのポインタが含まれている。この EXT2 inode 読み込みルーチンは、ファイルシステム固有の inode 読み込みルーチンすべ てに共通することでもあるが、VFS inode のフィールドに必要な情報を記入す る。個々の VFS スーパーブロックには、そのファイルシステム上の最初の VFS inode へのポインタが含まれている。ルートファイルシステムの場合、そ れは "/" ディレクトリを表す inode である。こうした情報のマッピングは EXT2 ファイルシステムの場合は非常に効率的に行われるが、他のファイルシ ステムに対してはやや効率が落ちる。 システム上のプロセスがディレクトリやファイルにアクセスする際、システム のルーチンはシステム上の VFS ``inode'' を経由して呼び出される。 [see: fs/inode.c] たとえば、ディレクトリ上で ls とタイプするか、ファイルに対して cat と タイプした場合、仮想ファイルシステムは、そのファイルシステムを表す VFS inode 群を検索する。システム上の全ファイルとディレクトリは VFS inode によって表現されているので、いくつかの inode が繰り返しアクセスされ る。それらの inode は inode キャッシュに保存されて、それらに対するアク セス速度の向上が図られる。inode キャッシュに inode がない場合、適切な inode を読み込むためにファイルシステム固有のルーチンが呼び出され る。inode を読み込む処理によって、inode は inode キャッシュに入れら れ、その inode への更なるアクセスによってキャッシュに保存されたままと なる。ほとんど使用されない VFS inode はキャッシュから削除される。 すべての Linux ファイルシステムは、共通のバッファキャッシュを利用し て、基礎となるデバイスのデータバッファをキャッシュする。これは、ファイ ルシステムが、そのファイルシステムを保持する物理デバイスにアクセスする 際、アクセス速度の向上に役立つ。 [see: fs/buffer.c] このバッファキャッシュは、ファイルシステムから独立している。そして、そ れは、 Linux カーネルがデータバッファの割り当て、読み出し、書き込みの ために使用するメカニズムの中に組み込まれている。これによって Linux の ファイルシステムは、基礎となるメディアやそれをサポートするデバイスドラ イバから独立できるので、その仕組みは Linux の明らかな長所となってい る。ブロック構造を持つデバイスはすべて、Linux カーネルに登録され、ブ ロックベースの、一般に非同期な、統一的インターフェイスを提供する。SCSI のような比較的複雑なデバイスでも同様である。実(real)ファイルシステムが 基礎となる物理デバイスからデータを読み込む場合、実際にはブロックデバイ スドライバにリクエストを送り、ドライバが、その制御するデバイスから物理 ブロックを読み込む。このブロックデバイスのインターフェイスに組み込まれ ているのが、バッファキャッシュである。ファイルシステムによってブロック が読み込まれると、それらのブロックは、すべてのファイルシステムと Linux カーネルによって共有される包括的なバッファに保存される。バッファ内の個 々のブロックは、ブロック番号とそれを読み込んだデバイス固有の識別子とに よって識別される。したがって、同じデータがしばしば必要とされる場合、そ れは、時間のかかるディスクからの読み出しを利用するのではなく、バッファ キャッシュから取り出される。デバイスの中には先読みをサポートするものが あるが、それはデータブロックが必要とされる場合に備えて、予め予測される データブロックを読み込んでおく機能である。 VFS は、頻繁に使用されるディレクトリをすばやく見つけるために、検索され たディレクトリのキャッシュをも保持している。 [see : fs/dcache.c] 実験として、最近表示したことがないディレクトリを表示してみるとそれが分 かる。最初に表示した際、やや時間が掛かるのに気付くが、二度目にその内容 が表示される時は、結果が即座に出る。ディレクトリキャッシュはディレクト リの inode そのものを保存するのではない。それらは inode キャッシュ内に なければならず、ディレクトリキャッシュは、完全なディレクトリ名とその inode 番号だけを保存している。 10.2.1. VFS スーパーブロック マウントされたすべてのファイルシステムは、VFS スーパーブロックによって 表現される。VFS スーパーブロックは様々な情報を含んでいるが、次にその一 部を示す。 [see: include/linux/fs.h] デバイス (device) これは、そのファイルシステムが含まれているブロックデバイスのデバ イス識別子である。たとえば、システム上の最初の IDE ハードディス クである /dev/hda1 のデバイス識別子は、0x301 である。 inode ポインタ (inode pointer) inode ポインタ ``mounted'' は、そのファイルシステムの最初の inode をポイントするポインタである。inode ポインタ ``covered'' は、そのファイルシステムがマウントされたディレクトリを表す inode をポイントするポインタである。ルートファイルシステムの VFS スー パーブロックには、covered ポインタは含まれない。 ブロックサイズ (blocksize) バイト単位で表されるそのファイルシステムのブロックサイズ。たとえ ば、1024 バイト。 スーパーブロック操作ルーチン (superblock operations) そのファイルシステムに対する一連のスーパーブロック操作ルーチンへ のポインタ。それらの中には、VFS が inode とスーパーブロックの読 み書きに使用するルーチンがある。 ファイルシステムタイプ (file system type) マウントされたファイルシステムの ``file_system_type'' データ構造 体へのポインタ。 ファイルシステム固有の情報 (file system specific) そのファイルシステムにとって必要な情報へのポインタ 10.2.2. VFS inode EXT2 ファイルシステムと同様に、VFS 上のすべてのファイルやディレクトリ 等はユニークな VFS ``inode'' によって表現される。 [see: include/linux/fs.h] 個々の VFS inode 内の情報は、ファイルシステム固有のルーチンによって、 その基礎となるファイルシステム内の情報から組み立てられる。VFS inode は カーネルメモリ上にしか存在せず、システムにとって有益である間だけ VFS inode キャッシュに保存される。VFS inode に含まれる情報には種種のものが あるが、次にその一部を示す。 デバイス (device) これは、ファイルか当該 VFS inode が指すものを保持しているデバイ スのデバイス識別子である。 inode 番号 (inode number) これは、その inode の番号であり、それはそのファイルシステム内で ユニークな値である。デバイスと inode 番号の組み合わせは、その VFS 上において重複することはない。 モード (mode) ``EXT2'' と同様に、このフィールドが示すのは、その VFS inode が表 す対象とそれに対するアクセス権である。 ユーザ ID (user ids) 所有者の識別子。 時間 (times) 作成、修正、書き込みが起こった時間。 ブロックサイズ (block size) バイト単位で表されるそのファイルのブロックサイズ。たとえば 1024 バイト。 inode 操作 (inode operations) ルーチンのアドレス群へのポインタ。それらのルーチンはファイルシス テムに固有であり、たとえば、その inode が指すファイルを切り縮め るといった、当該 inode に対する操作を実行する。 カウント (count) 現在その VFS inode を使用しているコンポーネントの数。カウント 0 は、その inode が使用されておらず、破棄するか再利用されるべきこ とを意味する。 ロック (lock) このフィールドは VFS inode をロックする際に使用される。たとえ ば、ファイルシステムから読み出されている場合など。 ダーティー (dirty) その VFS inode が書き込みをされたかどうかを示す。もしそうである 場合、その基礎となるファイルシステムは適宜修正される必要がある。 ファイル (file) ファイルシステム固有の情報。 10.2.3. ファイルシステムの登録 +---file_systems | | | file_system_type file_system_type file_system_type | +-------------+ +-------------+ +-------------+ +->|*read_super()| +-->|*read_super()| +-->|*read_super()| |-------------| | |-------------| | |-------------| | name |"ext2"| name |"proc"| name |"iso9660" |-------------| | |-------------| | |-------------| | requires_dev| | | requires_dev| | | requires_dev| |-------------| | |-------------| | |-------------| | next |--+ | next |--+ | next | +-------------+ +-------------+ +-------------+ 図表(9.5) ファイルシステムの登録 Linux カーネルをビルドするとき、サポートされている個々のファイルシステ ムをカーネルに組み込むかどうか質問がなされる。カーネルがビルドされる と、ファイルシステム初期化コードには、組み込まれたファイルシステムすべ てに関する初期化ルーチンへの呼び出しコードが含められる。 [see: sys_setup(), in fs/filesystems.c] Linux のファイルシステムは、モジュールとしてもビルドすることができる。 その場合、それらは必要に応じてオンデマンドでロードされるか、insmod コ マンドを使用して手動でロードされる。ファイルシステムモジュールがロード されるときはいつも、それがカーネルに登録され、ロードが解除されると登録 が取り消される。個々のファイルシステムの初期化ルーチンは仮想ファイルシ ステムに登録され、 ``file_system_type'' データ構造体によって表現され る。その構造体にはファイルシステムの名前と VFS スーパーブロック読み込 みルーチンへのポインタが含まれている。 ``図表(9.5)''で は、file_system_type データ構造体が、``file_systems'' リストに登録さ れ、そのポインタによってポイントされる様子が示されている。個々の file_system_type データ構造体には次のような情報が含まれている。 [see: file_system_type, in include/linux/fs.h] スーパーブロック読み込みルーチン (superblock read routine) このルーチンは、具体的なファイルシステムがマウントされたときに VFS によって呼び出される。 ファイルシステム名 (file system name) ファイルシステムの名前。たとえば、ext2 など。 必要なデバイス (device needed) そのファイルシステムは、サポートするデバイスが必要か? すべての ファイルシステムがそれを保持するデバイスを必要とするわけではな い。たとえば、/proc ファイルシステムはブロックデバイスを必要とし ない。 どのファイルシステムが登録されているか知るには、/proc/filesystems を覗 けばよい。たとえば、次のように表示されるだろう。 ext2 nodev proc iso9660 10.2.4. ファイルシステムのマウント スーパーユーザがファイルシステムをマウントするとき、Linux はまず、シス テムコールに渡される引数の妥当性検査をしなければならない。mount コマン ドも基本的なチェックは行うが、現在のカーネルにどのファイルシステムへの サポートが組み込まれているかや、ユーザによって指定されたマウントポイン トが実際に存在するかといったことは知らないからである。次の mount コマ ンドについて考えてほしい。 $ mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom この mount コマンドはカーネルに 3 つの情報を渡す。ファイルシステム名、 ファイルシステムを含んでいる物理ブロックデバイス、そして 3 つ目に、現 存するファイルシステムのトポロジーにおいて、新しいファイルシステムがど こにマウントされるべきかということである。 [see: do_mount(), in fs/super.c] [see: get_fs_type(), in fs/super.c] 仮想ファイルシステムが最初にすべきことは、そのファイルシステムを見つけ ることである。そのために既知のファイルシステムのリストを走査するのだ が、それは ``file_systems'' によってポイントされたリストの中にある ``file_system_type'' データ構造体を個別に探すことによってなされる。そ して、該当する名前が見つかった場合、次にそのファイルシステムが現在のカ ーネルでサポートされていること、および現在のカーネルにそのファイルシス テムのスーパーブロックを読み出すためのファイルシステム固有のルーチンの アドレスが含まれていることが分かる。合致するファイルシステム名を見つけ られなかった場合でも、カーネルがオンデマンドでカーネルモジュールをロー ドするようビルドされているなら(詳細は``「モジュール」''を参照のこと)、 処理は継続する。その場合、カーネルは元の作業を続ける前にカーネルデーモ ンに対して適切なファイルシステムをデマンドローディングするようリクエス トする。 次に、mount によって渡された物理デバイスがまだマウントされていない場 合、新しいファイルシステムのマウントポイントとなるディレクトリの VFS inode を見つけなければならない。その VFS inode は、inode キャッシュに あるか、マウントポイントがあるファイルシステムをサポートするブロックデ バイスから読み出すかされる。inode が分かったら、それがディレクトリかど うか、すでに他のファイルシステムがそこにマウントされていないかどうかを 確認するためのチェックがなされる。複数のファイルシステムに対するマウン トポイントとしては、同一のディレクトリは使用できないからである。 この時点で、VFS のマウント処理コードは VFS スーパーブロックを割り当て て、そのファイルシステムのスーパーブロック読み込みルーチンに対してその マウント情報を渡さなければならない。VFS スーパーブロックのすべては ``super_block'' データ構造体の ``super_blocks'' 配列に保存されているの で、そのひとつが、当該マウントのために割り当てられなければならない。ス ーパーブロック読み込みルーチンは、それが物理デバイスから読み出しだ情報 を基にして VFS スーパーブロックのフィールドを適切な情報で埋めなければ ならない。 EXT2 ファイルシステムに関しては、この情報のマッピングもしく は変換は非常に簡単である。しかし、MS-DOS ファイルシステムのようなその 他のファイルシステムに関しては、それほど簡単なタスクではない。ファイル システムが何であれ、VFS スーパーブロックのフィールドを埋めることは、そ れをサポートするブロックデバイスから、それが示すすべての情報を読み出さ なければならないことを意味する。ブロックデバイスから読み出しができない 場合、もしくはデバイスがそのタイプのファイルシステムを含まない場合 は、mount コマンドは失敗に終わる。 +---vfsmntlist | | | vfsmount | +-----------+ +->| mnt_dev | 0x0301 |-----------| |mnt_devname| /dev/hda1 |-----------| |mnt_dirname| / |-----------| | mnt_flags | |-----------| VFS | mnt_sb |-+ super_block file_system_type |-----------| | +-------------+ +---------------+ | | +->| s_dev |0x0301 +->| *read_super() | |-----------| |-------------| | |---------------| | next | | s_blocksize |1024 | | name |"ext2" +-----------+ |-------------| | |---------------| | s_type |-------+ | requires_dev | |-------------| |---------------| | s_flags | | next | |-------------| +---------------+ | s_covered | |-------------| VFS | s_mounted |--+ inode | | | +---------+ +-------------+ +--->| i_dev | 0x0301 | i_ino | 42 | | +---------+ 図表(9.6) マウントされたファイルシステム マウントされた個々のファイルシステムは、``vfsmount'' データ構造体に よって記述される。図表(9.6)を見て欲しい。それらは ``vfsmntlist'' に よってポイントされたリスト上に待ち行列を作っている。 [see: add_vfsmnt(), in fs/super.c] 別のポインタである ``vfsmnttail'' は、リストの最後のエントリをポイント しており、``mru_vfsmnt'' ポインタは最も最近利用されたファイルシステム をポイントしている。個々の vfsmount データ構造体には、そのファイルシス テムを保持するブロックデバイスのデバイス番号、そのファイルシステムがマ ウントされているディレクトリ、そしてそのファイルシステムがマウントされ た時に割り付けられた VFS スーパーブロックへのポインタが含まれている。 さらに、VFS スーパーブロックは、該当する種類のファイルシステムに関する ``file_system_type'' データ構造体とそのファイルシステムのルート inode をポイントしている。この inode は、そのファイルシステムがロードされて いる間は、 VFS inode キャッシュのなかに保存される。 10.2.5. 仮想ファイルシステム内でのファイルの検索 仮想ファイルシステム上でファイルの VFS ``inode'' を探すためには、VFS は、名前に含まれる個々のディレクトリを表す VFS inode を問い合わせなが ら、ディレクトリごとにその名前解決をしなければならない。個々のディレク トリ問い合わせの際には、ファイルシステム固有の問い合わせルーチンが呼び 出される。そのルーチンのアドレスはその親ディレクトリを表す VFS inode 内に保持されている。これが上手くいくのは、個々のファイルシステムのルー トディレクトリの VFS inode は、そのシステムの VFS スーパーブロックに よってポイントされているので、必ず知ることができるからである。 実(real)ファイルシステムによって inode が問い合わせされるたびに、ディ レクトリ用のディレクトリキャッシュがチェックされる。ディレクトリキャッ シュにエントリがない場合、実ファイルシステムは VFS inode を基礎となる ファイルシステムからか、 inode キャッシュからのいずれかから取得する。 10.2.6. 仮想ファイルシステム上でのファイルの作成 (訳注: この部分の原文が存在しません。) 10.2.7. ファイルシステムのマウント解除 わたしの愛車である MG の整備マニュアルにはたいていの場合「組み立ては分 解の逆である」としか説明されていないが、これはファイルシステムのアンマ ウントに関する説明にも程度の差はあれ言えることだろう。 [see: do_unmount(), in fs/super.c] システム上のファイルシステム内にあるファイルのひとつが何らかのかたちで 使用されている場合、そのファイルシステムはアンマウントできない。それゆ えたとえば、プロセスが /mnt/cdrom のディレクトリやその子ディレクトリを 使用している場合、そのディレクトリはアンマウントできない。アンマウント すべきファイルシステムが何らかのかたちで使用されている場合、そのファイ ルシステムの VFS inode が VFS inode キャッシュにあるかもしれないので、 コードはそれをチェックするために、inode リストを走査し、そのファイルシ ステムがあるデバイスによって所有されている inode を探す。マウントされ たファイルシステムに関する VFS スーパーブロックがダーティー(dirty)な場 合、それは修正されているということなので、修正されたの内容をディスク上 のファイルシステムに書き込まれなければならない。ディスクへの書き込みが 終了したら、VFS スーパーブロックに占有されていたメモリは、カーネルの空 きメモリのプールに戻される。最後に、そのマウントに関する ``vfsmount'' データ構造体は、``vfsmntlist'' からのリンクを外され、解放される。 [see: remove_vfsmnt(), in fs/super.c] 10.2.8. VFS inode キャッシュ マウントされたファイルシステムを操作する際には、そのファイルシステムの VFS inode が何度も読み出されたり、ある場合には書き込まれたりする。仮想 ファイルシステムは、inode キャッシュを管理して、マウントされたすべての ファイルシステムへのアクセスを高速化している。 VFS inode が inode キャッシュから読み出されたとき、システムは物理デバイスへのアクセスを省 くことができる。 [see: fs/inode.c] VFS inode キャッシュは、ハッシュテーブルとして実装されていて、そのエン トリは、同じハッシュ値を持つ VFS inode のリストへのポインタとなってい る。inode のハッシュ値は、その inode 番号と、ファイルシステムを保持す る下位の物理デバイスのデバイス識別子によって計算される。仮想ファイルシ ステムが inode にアクセスする必要があるときは、システムはまずそのハッ シュ値を計算し、それを inode ハッシュテーブルへのインデックスとして使 用する。それによってシステムは、同じハッシュ値を持つ inode のリストへ のポインタを取得する。そして、個々の inode を順番に読み出して、探して いる inode と同一の inode 番号とデバイス識別子の両方を持つ inode を見 つける。 キャッシュに inode が見つかった場合、その inode のカウント値に 1 が足 され、その inode には他にもユーザがいて、ファイルシステムへのアクセス が継続していることが示される。見つからない場合、フリーの VFS inode を 見つけて、ファイルシステムがメモリから inode を読み込めるようにしなけ ればならない。 VFS がフリーの inode を取得する方法は、複数ある。システ ムがより多くの VFS inode を割り当ててもよい場合は、VFS は新規の inode を割り当てる。VFS はカーネルページを割り当てて、そのページを新規のフリ ー inode に分割し、それらを inode リストに入れる。システム上の VFS inode はすべて、inode ハッシュテーブルにあると同時に、``first_inode'' によってポイントされるリストにも存在する。システムがすでに使用できるす べての inode を使い切っている場合、システムは再利用されるのにふさわし い inode の候補を探さなければならない。候補は、0 の利用カウントを付け られた inode である。これは、システムが現在その inode を使用していない ことを示している。たとえば、ファイルシステムのルート inode のような本 当に重要な VFS inode は常に 0 以上の利用カウントを持っているので、再利 用候補には決してならない。再利用の候補が特定されたら、それは消去され る。VFS inode はダーティー(dirty)になっているかもしれないので、その場 合はファイルシステムに書き戻される必要があるが、それ以外にもその inode がロックされている場合があり、その際にシステムは処理を継続する前にロッ クが解除されるのを待たなければならない。VFS inode の候補は、再利用され る前に消去されなければならない。 新規の VFS inode を見つけ出す方法に関わらず、ファイルシステム固有のル ーチンが呼び出されて、基礎となる実ファイルシステムから読み出された情報 でその inode のフィールドを埋める必要がある。その情報が書き込まれてい る間、新しい VFS inode は、利用カウント 1 を設定され、有効な情報を所持 するようになるまでどこからもアクセスされないようにロックされる。 実際に必要な VFS inode を見つけだすために、ファイルシステムは他のいく つかの inode にアクセスしなければならない場合がある。これが起こるのは ディレクトリの読み込みのときである。最後のディレクトリの inode だけが 必要なのだが、その途中にあるディレクトリの inode も読み込まなければな らない。VFS inode キャッシュが使用され、それが満杯になると、ほとんど使 われない inode は破棄され、より利用頻度の高い inode がキャッシュに残 る。 10.2.9. ディレクトリキャッシュ よく利用されるディレクトリへのアクセスを高速化するために、VFS はディレ クトリエントリのキャッシュを管理している。 [see: fs/dcache.c] 実ファイルシステムからディレクトリの問い合わせがあると、それらの詳細が ディレクトリキャッシュに入れられる。たとえばディレクトリを表示させた り、その中のファイルをオープンするといった同じディレクトリへの問い合わ せが続く場合、そのディレクトリはディレクトリキャッシュで発見できる。短 い(15 文字までの)ディレクトリエントリのみがキャッシュされるのだが、短 いディレクトリ名が最も頻繁に使われるため、この方法は合理的である。たと えば、/usr/X11R6/bin は、X サーバの実行中に、非常に頻繁にアクセスされ る。 ディレクトリキャッシュはハッシュテーブルから成り、その個々のエントリ は、同じハッシュ値を持つディレクトリキャッシュエントリのリストをポイン トしている。ハッシュ関数は、そのファイルシステムを保持するデバイスのデ バイス番号とディレクトリ名とを利用して、そのハッシュテーブルへのオフ セットとインデックスとを計算する。それによって、キャッシュされたディレ クトリは高速に発見されることが可能になる。キャッシュ内でのエントリ検索 の問い合わせに時間がかかりすぎたり、エントリを見つけられないような キャッシュでは意味がない。 キャッシュを有効かつ最新に保つために、VFS は最長時間未使用( Least Recently Used, LRU)のディレクトリキャッシュエントリのリストを保持して いる。ディレクトリが問い合わせを受けて、そのディレクトリのエントリが初 めてキャッシュに入れられるとき、それは第一レベルの LRU リストの末尾に 付け加えられる。キャッシュが満杯の場合は、その LRU リストの先頭に存在 するエントリを削除することで付け加えられる。ディレクトリエントリが再度 アクセスされると、それは第二レベルの LRU キャッシュの末尾に入れられ る。これが満杯の場合も同様に、第二レベルの LRU キャッシュリストの先頭 にあるディレクトリエントリと入れ替わる形となる。第一レベルと第二レベル の LRU リストの先頭にあるエントリを立ち退かせるという方法は妥当であ る。リストの先頭にエントリがあることの唯一の理由は、それらが最近アクセ スされていないということだからである。アクセスがあれば、リストの後尾近 くに移る。第二レベルの LRU キャッシュリストにあるエントリは、第一レベ ルの LRU キャッシュリストにあるエントリよりも安全である。なぜなら、第 二レベルにあるエントリは、問い合わせを受けただけでなく、繰り返し参照さ れているからである。 REVIEW NOTE: Do we need a diagram for this? 10.3. バッファキャッシュ buffer_head buffer_head hash_table +---------+ +-----------+ +-----------+ ---------->| | +->| b_dev |0x0301 +->| b_dev |0x0801 | | | |-----------| | |-----------| | | | | b_blocknr |42 +-+ | b_blocknr |17 | | | |-----------| | |-----------| | | | | b_state | | | b_state | |---------| | |-----------| | |-----------| | |-+ | b_count | | | b_count | |---------| |-----------| | |-----------| | | | b_size |1024 | | b_size |2048 | | |-----------| | |-----------| | | | | | | | | | | | | | | | | |-----------| | |-----------| | | | b_next |-----+ | b_next | | | |-----------| |-----------| | | | b_prev | | b_prev | | | |-----------| |-----------| | | | | | | | | |-----------| |-----------| | | | b_data | | b_data | | | +-----------+ +-----------+ | | | | buffer_head | | +-----------+ | | +->| b_dev |0x0301 |---------| | |-----------| | |-+ | b_blocknr |39 |---------| |-----------| | | | b_state | |---------| |-----------| ~ ~ ~ ~ ~ ~ ~ ~ 図表(9.7) バッファキャッシュ マウントされたファイルシステムが利用される場合、ファイルシステムはデー タブロックを読み書きするためにブロックデバイスに対する多くのリクエスト を行う。データブロックの読み書きするためのリクエストはすべて、標準のカ ーネルルーチンの呼び出しを経由して、``buffer_head'' データ構造体で定義 されたデバイスドライバに渡される。この構造体はブロックデバイスドライバ に必要なすべての情報を提供する。デバイス識別子は固有のデバイスを識別 し、ブロック番号はドライバにどのブロックを読み出すべきかを示す。すべて のブロックデバイスは、同一サイズのブロックの線形的な集合であると見看さ れる。物理ブロックデバイスへのアクセスをスピードアップするため に、Linux はブロックバッファのキャッシュを管理する。システム上のすべて のブロックバッファは、使用されていない新しいバッファでさえ、このバッ ファキャッシュのどこかに保存される。このキャッシュはすべての物理ブロッ クデバイス間で共有される。どのような時にもそのキャッシュには多くのブ ロックバッファがある。それらはシステムのブロックデバイスのひとつに属し ていて、しばしば種々の異なる状態(``state'')にある。有効なデータがバッ ファキャッシュから入手可能な場合、それによってシステムが物理デバイスに アクセスする手間が省かれる。ブロックデバイスに対する読み込みや書き出し で使用されたブロックバッファはすべて、バッファキャッシュに入れられる。 時間の経過によって、それらは、より必要性の高いバッファと交代するために キャッシュから削除されるか、頻繁にアクセスされるためキャッシュに残るか する。 キャッシュ内のブロックバッファは、それを保持するデバイスの識別子とバッ ファのブロック番号とによってユニークに識別される。バッファキャッシュは ふたつの機能別部分から構成される。最初の部分は、空のブロックバッファの リストである。サポートされるバッファサイズごとにひとつのリストがあり、 システム上の空のブロックバッファは、初めて作成されたり、削除されたとき に、それらのリスト上の待ち行列に置かれる。現在サポートされているバッ ファサイズは、512, 1024, 2048, 4096, そして 8192 バイトである。第二の 部分は、キャッシュそのものである。これはハッシュテーブルであり、その ハッシュテーブルは同じハッシュインデックスを持つバッファに対するポイン タの配列である。ハッシュのインデックスは、該当ブロックを保持するデバイ スの識別子とデータブロックのブロック番号から生成される。``図 表(9.7)''では、ハッシュテーブルがいくつかのエントリとともに示されてい る。ブロックバッファはフリーリストのいずれかひとつにあるか、バッファ キャッシュの中に存在する。バッファキャッシュにあるときは、それらは最長 時間未使用(LRU) リスト上のキューにも置かれる。LRU リストは個々のタイプ のバッファごとに存在し、システムがそのタイプのバッファ上で処理をする 際、たとえば、バッファ内の新しいデータをディスクに書き出す際などに使用 される。バッファタイプとはその状態 (state)を反映するものであり、Linux では現在次のようなタイプがサポートされている。 クリーン (clean) 未使用で新しいバッファ ロック済み (locked) ロックされ、書き込まれるのを待つバッファ。 ダーティ (dirty) ダーティバッファ。それには新しい有効なデータが含まれ、書き込みが される予定だが、今のところ書き込みのスケジュールが回って来ていな いもの。 共有 (shared) 共有バッファ。 非共有 (unshared) 以前は共有であったが、現在は共有されていないバッファ。 ファイルシステムは、基礎となる物理デバイスからバッファを読み出す必要が あるときには、バッファキャッシュからブロックを取得しようとする。バッ ファキャッシュから取得できない場合、適切なサイズのフリーリストから未使 用バッファを取得し、その新しいバッファがバッファキャッシュに入る。ファ イルシステムが必要とするバッファがバッファキャッシュにある場合、バッ ファは更新(update)されている場合とされていない場合がある。更新されてい ないか、それが未使用ブロックバッファである場合は、ファイルシステムは、 デバイスドライバによって適切なデータブロックがディスクから読み出される ようリクエストする。 すべてのキャッシュと同様に、バッファキャッシュは、効率よく実行され、 バッファキャッシュを利用するブロックデバイス間で公平にキャッシュエント リが割り当てられるように管理されなければならない。Linux は bdflush カ ーネルデーモンを使用して、キャッシュに関する多くの細かな作業を実行して いるが、それらのいくつかは、キャッシュが使用された結果として自動的に実 行される。 10.3.1. bdflush カーネルデーモン bdflush カーネルデーモンは、システムがダーティ(dirty, 更新待ち) バッ ファを多く持ちすぎている場合に、システムに対して動的に反応するシンプル なカーネルデーモンである。 [see: bdflush(), in fs/buffer.c] データを保持するバッファは、時々ディスクへと書き戻さなければならない。 そのデーモンは、起動時にはシステム上のカーネルスレッドとして起動され る。少しややこしいのだが、それは "kflush" と呼ばれ、その名前は、システ ム上のプロセスを表示する ps コマンドを使うと見ることができる。たいてい このデーモンはスリープ状態で、システム上のダーティバッファの数が多くな りすぎるのを待っている。バッファが割り当てられたり破棄されたりすると、 システム上のダーティバッファの数がチェックされる。システム上のバッファ の総数のパーセンテージとしてダーティバッファが多くなりすぎる と、bdflush は目を覚ます。デフォルトの境界線は 60% だが、システムが バッファの不足で困っている場合は、 bdflush は強制的に目覚めされられ る。この値は、update コマンドを使って確認や変更が可能である。 # update -d bdflush version 1.4 0: 60 Max fraction of LRU list to examine for dirty blocks 1: 500 Max number of dirty blocks to write each time bdflush activated 2: 64 Num of clean buffers to be loaded onto free list by refill_freelist 3: 256 Dirty block threshold for activating bdflush in refill_freelist 4: 15 Percentage of cache to scan for free clusters 5: 3000 Time for data buffers to age before flushing 6: 500 Time for non-data (dir, bitmap, etc) buffers to age before flushing 7: 1884 Time buffer cache load average constant 8: 2 LAV ratio (used to determine threshold for buffer fratricide). データを書き込まれて更新待ち状態になり、bdflush が合理的な数のダーティ バッファをディスクに書き戻そうとする場合はいつも、ダーティバッファのす べてが、``BUF_DIRTY'' LRU リストにリンクされる。この数も確認可能であ り、update コマンドによって制御できる。デフォルト値は 500 である。(上 記を参照) 10.3.2. update プロセス update コマンドは単なるコマンドではなく、デーモンでもある。 (システム 初期化の際に)スーパーユーザ権限で起動されると、それは定期的に古くなっ たダーティバッファのすべてをディスクに書き戻す(フラッシュする)。これが 実行される際には、bdflush と似通ったシステムサービスルーチンが呼び出さ れる。 [see: sys_bdflush(), in fs/buffer.c] ダーティバッファは(システムサービスルーチン)終了時に、元のディスクに書 き戻すため、システム時間が(バッファの構造体に)付け加えられる。update コマンドが実行されるときは、いつも、システム上のダーティバッファすべて を検査し、フラッシュすべき時刻を過ぎているものを探す。フラッシュすべき 時刻をすぎたすべてのバッファは、ディスクに書き戻される。 10.4. /proc ファイルシステム /proc ファイルシステムは、Linux の仮想ファイルシステムの能力を如実に示 すものである。そのファイルシステムは実在しない。(これも、Linux の魔術 のひとつである。) /proc ディレクトリも、そのサブディレクトリも、その中 のファイルも実在しない。では、どうして cat /proc/devices というコマン ドを実行できるのか? /proc ファイルシステムは、実ファイルシステムと同 様に、仮想ファイルシステムに登録されている。しかし、VFS が /proc ファ イルシステムを呼び出して、そのファイルやディレクトリをオープンするため に inode をリクエストしたときに、 /proc ファイルシステムは、カーネル内 の情報からそのファイルやディレクトリを(動的に)作成する。たとえば、カー ネルの /proc/devices ファイルはそのデバイスを記述するカーネルのデータ 構造体から生成されている。 /proc ファイルシステムは、カーネル内部の実行状態への窓を、ユーザが読み とれるような形で提供する。Linux カーネルモジュール(``「モジュー ル」''の章を参考)のような Linux サブシステムのいくつかは、/proc ファイ ルシステム内にエントリを作成している。 10.5. デバイススペシャルファイル Linux は、Unix のあらゆるバージョンと同様に、ハードウェアデバイスをス ペシャルファイルで表現している。たとえば、/dev/null はヌル(null)デバイ スである。デバイスファイルはシステム上のデータ空間を使用しない。それ は、単なるデバイスドライバへのアクセスポイントである。EXT2 ファイルシ ステムと Linux VFS はどちらもデバイスドライバを特別なタイプの inode と して実装している。デバイスファイルには、キャラクタスペシャルファイルと ブロックスペシャルファイルの二種類がある。カーネル自体の内部で、デバイ スドライバによるそうしたファイルのオープンやクローズといった、ファイル 操作の意味が定義されている。キャラクタデバイスでは、キャラクタモードで の I/O 操作が可能であり、ブロックデバイスではすべての I/O 操作がバッ ファ経由で行われるよう要求される。デバイスファイルに対する I/O リクエ ストがなされると、そのリクエストはシステム上の適切なデバイスドライバに 送られる。これはしばしば実際のドライバでなく、 SCSI デバイスドライバ層 といったあるサブシステムの疑似デバイス(pseudo-device) ドライバである場 合がある。デバイスファイルは、デバイスタイプを識別するメジャー番号と、 同じメジャー番号の中での実際のユニット(unit)もしくはインスタンス (instance)を識別するマイナー番号とで参照される。たとえば、システム上の 最初の IDE ディスクコントローラ上にある IDE ディスクは、メジャー番号 3 であり、IDE ディスクの最初のパーティションはマイナー番号 1 となる。し たがって、/dev/hda1 の ls -l の結果は次のようになる。 [see: Linux の全メジャー番号については、include/linux/major.h] $ ls -l /dev/hda1 brw-rw---- 1 root disk 3, 1 Nov 24 15:09 /dev/hda1 カーネル内部では、すべてのデバイスが ``kdev_t'' データタイプによってユ ニークに記述される。このデータは 2 バイト長であり、最初のバイトにはデ バイスのマイナー番号が含まれ、二番目のバイトにはメジャー番号が保持され ている。 [see: include/linux/kdev_t.h] 上記の IDE デバイスは、カーネル内の 0x0301 に保持されている。ブロック デバイスやキャラクタデバイスを表す EXT2 ``inode'' は、デバイスのメジャ ー番号とマイナー番号を、ブロックを直接ポイントする(配列の) 先頭のポイ ンタに保持している。それが VFS によって読み出されるとき、それを表す VFS ``inode'' データ構造体は、``i_rdev'' フィールドに正しいデバイス識 別子を設定する。 (-- (脚注 1): Linux には、大勢の開発者がいる。いくつかのオペレーティン グシステムには、もっと大勢の弁護士がいるらしい。--) 11. ネットワーク ネットワーキングと Linux とはほとんど同義語である。本当の意味で、Linux はインターネットもしくは World Wide Web(WWW) の産物である。開発者やユ ーザたちはウェブを使って情報やアイデアやコードを交換しているし、Linux そのものが組織におけるネットワークへの要求を満たすために利用されてい る。この章では、TCP/IP と総称されるネットワークプロトコルを Linux がど のようにサポートしているかを解説する。 TCP/IP プロトコルが設計されたのは、ARPANET に接続されたコンピュータ間 での通信をサポートするためであった。合衆国政府によって設立されたアメリ カの研究用ネットワークである ARPANET は、ネットワークの基本概念を作り 出したパイオニアであり、その中には、パケットスイッチング(packet switching)や、あるプロトコルが別のプロトコルのサービスを利用するという プロトコル層(protocol layer)といった概念がある。 ARPANET は 1988 年に 解散されたが、その後継者(NSF(``脚注 1'') NET やインターネット)はそれ以 上のものに成長した。今日 World Wide Web と呼ばれるものは、ARPANET から 発展しており、それ自体が TCP/IP プロトコルによってサポートされてい る。Unix は ARPANET 上で広範囲に使用されが、最初にリリースされたネット ワークバージョンの Unix は 4.3 BSD であった。Linux におけるネットワー クの実装は、(いくつかの拡張機能とともに)BSD ソケットと TCP/IP 全般をサ ポートしている点で、4.3 BSD をモデルとするものである。BSD ソケットとい うプログラミングインターフェイスが採用されたのは、その人気ゆえであ り、Linux とその他の Unix プラットフォーム間でのアプリケーションの移植 を容易にするためであった。 11.1. TCP/IP ネットワーキングの概要 この章では、TCP/IP ネットワーキングにおける基本原則を概説する。しか し、本書のテーマからして、徹底した記述というわけではない。 IP ネットワ ークでは、すべてのマシンが IP アドレスを割り当てられる。これはマシンを ユニークに識別する 32 ビットの数である。WWW は広大で今も成長過程にあ り、それに接続された IP ネットワークとすべてのマシンはユニークな IP ア ドレスを割り当てられなければならない。 IP アドレスは、たとえば 16.42.0.9 のようにドットで区切られた 4 つの数字で表現される。この IP アドレスは実際には、ネットワークアドレスとホストアドレスのふたつの部分 に分かれる。( IP アドレスにはいくつかのクラスがあるので) それらの部分 のサイズは定まっていないが、仮に 16.42.0.9 を例に取り、ネットワークア ドレスを 16.42 とし、ホストアドレスを 0.9 であるとする。ホストアドレス はさらにサブネットとホストアドレスとに再分割できる。ここでも 16.42.0.9 を例に取ると、サブネットアドレスは 16.42.0 であり、ホストアドレスは 9 となるだろう。 IP アドレスの細分化により、組織はネットワークを再分割で きる。たとえば、16.42 が ACME コンピュータ会社のネットワークアドレスで あるとすると、 16.42.0 はサブネット 0 であり、16.42.1 はサブネット 1 となる。これらのサブネットは、異なる建物の中にあり、おそらく専用線かも しくは無線によって接続されているだろう。IP アドレスはネットワーク管理 者によって割り当てられるので、IP サブネットを持つことはネットワーク管 理を分散するよい方法である。IP サブネットの管理者は、その IP サブネッ トワーク内で自由に IP アドレスを割り当てることができるからである。 しかし、一般的に IP アドレスは覚えるのがやや困難である。名前のほうがよ り簡単であり、linux.acme.com のほうが 16.42.0.9 よりもずっと覚えやす い。だが、ネットワーク名を IP アドレスに変換するには多少の仕組みが必要 となる。そうした名前は、/etc/hosts ファイル内で静的に指定できる が、Linux は、 DNS (Domain Name System) サーバに問い合わせて、名前を IP アドレスに変換することもできる。その場合、ローカルホストはひとつ以 上の DNS サーバの IP アドレスを知る必要があり、それは /etc/resolv.conf で指定される。 ウェブページを読む時などに他のマシンに接続する際はいつも、相手のマシン とのデータ交換に IP アドレスが使用される。このデータは IP パケットに含 まれていて、個々のパケットは、送信元の IP アドレスと送信先の IP アドレ ス、チェックサムその他の情報を含んだ IP ヘッダを持っている。チェックサ ムは IP パケットのデータから導き出されるもので、それによって、おそらく 電話回線上のノイズなどで、転送中に IP パケットが壊れなかったかどうかを IP パケットの受信者が判断できるようになる。アプリケーションによって送 信されるデータは、処理しやすいように、より小さなパケットへと分割され る。IP データパケットのサイズは接続メディアによって異なり、イーサネッ トパケットは一般に PPP パケットよりもサイズが大きい。送信先のホストは そのデータパケットを組み立て直して、それを受信すべきアプリケーションに 渡さなければならない。多くのグラフィックイメージを含むウェブページに比 較的低速なシリアル回線経由でアクセスする場合、このデータの分割と統合の 過程を視覚的に理解することができる。 同一の IP サブネットに接続されたホスト間ではお互いに直接 IP パケットを 送信することができるが、それ以外のすべてのパケットは特別なホストである ゲートウェイ (gateway)に送信される。ゲートウェイ(あるいはルー タ(router))は、複数の IP サブネットに接続されていて、あるサブネット上 で受信した IP パケットが、別のサブネット宛であった場合に、その IP パ ケットを送信先に向けて送りなおす。たとえば、サブネット 16.42.1.0 と 16.42.0.0 がゲートウェイによって接続されている場合、サブネット 0 から サブネット 1 へと送信されるパケットは、ルーティングが可能なゲートウェ イに送られなければならない。ローカルホストはルーティングテーブルを設定 し、それによって正しいマシンに IP パケットを経路付けすることができる。 すべての IP の送信先に関して、ルーティングテーブル内にひとつのエントリ がある。Linux は、それを参照することで、どのホストに送ればよいかを判断 して、IP パケットを宛先に届けている。これらのルーティングテーブルは、 複数のアプリケーションがネットワークを使用したり、ネットワークトポロジ ーが変更された場合など、時間の経過によって動的に変化する。 イーサネットフレーム +----+------------+------------+------+---------//---+----------+ |PRE |送信先 |送信元 |プロ | |フレーム | |など|イーサネット|イーサネット|トコ | データ |チェック | | |アドレス |アドレス |ル | |シーケンス| +----+------------+------------+------+---------//---+----------+ | | +-------------------------------------+ +-----------+ |/ |/ * IP データグラム * +-+-+--------+-+-+-+------+--------+--------+--------+-------//--+ | | |パケット| | | |プロト|チェック|送信元 |送信先 | | | | |長 | | | |コル |サム |IP |IP | データ | | | | | | | | | |アドレス|アドレス| | +-+-+--------+-+-+-+------+--------+--------+--------+-------//--+ | | +------------------------------------------+ | |/ |/ * TCP セグメント * +--------+--------+----------+----+-+-+---------//-----+ |送信元 |送信先 |シーケンス|ACK | | | | |TCP |TCP |番号 |番号| | | データ | |アドレス|アドレス| | | | | | +--------+--------+----------+----+-+-+---------//-----+ 図表(10.1) TCP/IP プロトコル層 IP(Internet Protocol) プロトコルは、他のプロトコルがデータを運ぶために 利用する、転送のための層(layer)である。TCP (Transmission Control Protocol)は、エンドシステム間における信頼性のあるプロトコルで、自己の パケットを送受信するために IP を使用する。 IP が独自のヘッダを持つよう に、TCP も独自のヘッダを持つ。TCP はコネクション (connection)に基づく プロトコルなので、両端のアプリケーションは、間に多くのサブネットワーク やゲートウェイ、ルータがある場合でも、単一の仮想的コネクションによって 接続される。TCP はふたつのアプリケーション間で信頼性をもってデータの受 け渡しを行うので、データの喪失や重複が生じないことが保証される。TCP が IP を利用してパケットを転送するとき、IP パケット内に含まれるデータは TCP パケットそのものである。個々の通信ホスト上で IP 層は、IP パケット の送受信に関して責任を持つ。 UDP (User Datagram Protocol)も IP 層を使 用して自分のパケットを転送するが、 TCP と異なり、UDP は信頼性のあるプ ロトコルではなく、単にデータグラム(datagram) サービスを提供するだけで ある。このように、IP は、他のプロトコルによって利用されている。した がって、IP パケットを受け取った際、受信した IP 層は、どの上位プロトコ ル層に対してその IP パケットに含まれるデータを渡すべきか認識できなけれ ばならない。これを簡単に実現するために、すべての IP ヘッダには、プロト コル識別子が記述された 1 バイトの情報がある。TCP が IP 層に IP パケッ トの送信を依頼するとき、IP パケットのヘッダには、そのパケットが TCP パ ケットを含むものであることを伝える情報が記述される。受信した IP 層は、 そのプロトコル識別子を使用して、どの上位層(この場合は、 TCP)に受信した データを渡すべきかを判断する。また、アプリケーションが TCP/IP 経由で通 信を行うとき、それらは相手方の IP アドレスだけでなく、アプリケーション のポート番号をも指定しなければならない。ポート番号はアプリケーションを ユニークに特定するものであり、標準的なネットワークアプリケーションは標 準のポート番号を使用する。たとえば、ウェブサーバは 80 番ポートを使用し ている。これら登録済みのポート番号は、/etc/services ファイルで確認でき る。 このようなプロトコルの多層構造は、TCP, UDP や IP だけに留まらない。す なわち、IP プロトコル自身が様々な異なる物理メディアを使用して IP パ ケットを他の IP ホストに転送している。したがって、それらの物理メディア 自体も独自のプロトコルヘッダを付け加えている。イーサネット層がその一例 であり、他にも PPP 層や SLIP 層などがある。イーサネットのネットワーク では、多くのホストを同時に単一の物理ケーブルに接続することが可能であ る(訳注: 10BASE5 など。現在、単一の物理ケーブルが使われることは少なく なりました)。送信されたイーサネットフレーム(frame)は、そのケーブルに接 続されたすべてのホストから見ることが出来るので、個々のイーサネットデバ イスはユニークな(ハードウェア)アドレスを持たなければならない。特定 の(ハードウェア)アドレスに対して送信されたイーサネットフレームはそのア ドレスのホストに受信され、ネットワークに接続されたそれ以外のすべてのホ ストからは無視される。こうしたユニークなアドレスはイーサネットデバイス の製造時にデバイスに組み込まれていて、通常、イーサネットカードの SROM(``脚注2'') に保存されている。イーサネットのアドレスは 6 バイト長 で、たとえば、08-00-2B-00-49-A4 といったものになっている。イーサネット のアドレスにはマルチキャスト(``訳注: 用語集(multicast)'')の目的に予約 されたものがあり、そうした送信先アドレスを設定されて送信されたイーサ ネットフレームは、そのネットワーク上のすべてのホストで受信される。イー サネットフレームは、多種のプロトコルを(データとして)運ぶことができるの で、 IP パケットの場合と同様に、イーサネットフレームのヘッダにはプロト コル識別子が含まれている。これによって、イーサネット層は IP パケットを 正確に受信して、それを IP 層に渡すことができる。 イーサネットのような(ケーブルに繋がったすべてのホストにフレームが流れ る) マルチコネクションのプロトコル経由で IP パケットを送信するために は、IP 層は、 IP ホストのイーサネットアドレスを知らなければならない。 というのも、IP アドレスは単にアドレス割り当ての概念にすぎず、イーサ ネットデバイス自体が独自の物理アドレスを持っているからである。すなわ ち、IP アドレスはネットワーク管理者の意志によって割り当てや再割り当て が可能であるのだが、ネットワークハードウェアは、自分の物理アドレスが付 加されたイーサネットフレームか、あるいはすべてのマシンで受信すべき特別 なマルチキャストアドレスにしか反応しないからである。 Linux は、ARP (Address Resolution Protocol)を使用して、マシンが、IP アドレスをイーサ ネットアドレスのような実際のハードウェアアドレスに変換できるようにして いる。ある IP アドレスに関連付けられたハードウェアアドレスを知ろうとす るホストは、変換したい IP アドレスを含めた ARP リクエストパケットを、 マルチキャストアドレスを付けてネットワーク上のすべてのノード(node)に送 信する。その IP アドレスを持つターゲットホスト(target host)は、自分の 物理アドレスを含めた ARP リプライによってそれに応答する。ARP はイーサ ネットデバイスだけでしか利用できないわけではなく、``FDDI'' などのそれ 以外の物理メディアに対しても IP アドレスの解決が可能である。 ARP が利 用できないネットワークデバイスはそれが出来ない旨マークされているので、 その場合、Linux がARP を試すことはない。ARP の逆の機能も存在しており、 リバース ARP もしくは RARP は、ハードウェアアドレスを IP アドレスに変 換する。これはゲートウェイによって利用されるもので、ゲートウェイは、リ モートネットワークにある IP アドレスが書かれた ARP リクエストに応答す る際にそれを使用する。 11.2. Linux の TCP/IP ネットワーク層 ネットワーク アプリケーション /| | User --------------------------------+---------------------------- | Kernel |/ +-----*------+ | BSD | | ソケット | +-----*------+ ソケット /| インターフェイス |/ +-----*------+ | INET | | ソケット | +--*--*--*---+ /| /| /| +----+ | +----+ |/ | |/ +---*---+ | +---*---+ | | | | | | TCP | | | UDP | +---*---+ | +---*---+ プロトコル層 /| | /| | | | |/ |/ |/ +------*-------*-------*-------+ | |<-+ | IP | | +---*----------*----------*----+ | +-------+ /| /| /| +->| | | | | | ARP | |/ |/ |/ +->| | +---*---+ +---*---+ +---*----+ | +-------+ ネットワーク | | | | | |<-+ デバイス | PPP | | SLIP | |Ethernet| +-------+ +-------+ +--------+ 図表(10.2) Linux のネットワーク層 ネットワークプロトコル自体が多層構造を持つのと同様に、図表(10.2)で は、Linux がインターネットプロトコルアドレスファミリ(address family)を ソフトウェアの多層構造として実装していることが示されている。BSD ソケッ トは、BSD ソケットだけに関する汎用のソケット管理ソフトウェアとしてサポ ートされている。 BSD ソケットをサポートするのが INET ソケット層で、こ れは、IP ベースの TCP や UDP プロトコルのためのエンドポイント通信を管 理する。 UDP(User Datagram Protocol)はコネクション管理を行わないプロト コルであるが、TCP(Transmission Control Protocol)は信頼性のある一対 一(end to end)のプロトコルである。UDP パケットが送信されるとき、Linux はそれが安全に送信先に届いたかどうか分からないし、気にもしない。TCP パ ケットは番号付けされているので、TCP コネクションの両方のエンド(end, 端)は、送信されたデータが正しく受信されたかどうかを確認できる。 IP 層 には、インターネットプロトコル(IP)を処理するためのコードが含まれてい る。このコードは、受信したデータの IP ヘッダを取得し、その IP パケット が TCP か UDP のどちらかの上位層に宛てたものかを判断して、適切な上位層 にルーティングする。IP 層の下で Linux のネットワーキングをサポートして いるのが、PPP やイーサネットといったネットワークデバイスである。ネット ワークデバイスとは必ずしも物理デバイスを意味するものではない。ループ バックデバイス(loopback device)などは純粋なソフトウェアデバイスだから である。 mknod で作成される Linux の標準的なデバイスファイルとは異な り、ネットワークデバイスはその基礎となるソフトウェアが物理デバイスを検 出し初期化した場合にだけ現れる。カーネルに適切なイーサネットデバイスド ライバを組み込んだ場合のみ、/dev/eth0 を見ることができる。ARP プロトコ ルは、IP 層と ハードウェアアドレス解決のために ARP をサポートする下位 のプロトコルとの間に位置している。 11.3. BSD ソケットインターフェイス BSD ソケットインターフェイスは、汎用インターフェイスであり、ネットワー クの様々な形態をサポートするだけでなく、プロセス間通信の仕組みでもあ る。ソケットは通信リンクの一方のエンド(end, 端)を記述するもので、ふた つの通信プロセスが個別にソケットを持ち、両者の間の通信リンクのそれぞれ のエンドを記述する。ソケットはパイプの特殊なケースであると考えることも 可能だが、パイプと異なり、ソケットは保持できるデータ容量に制限がな い。Linux は、いくつかのソケットクラス (socket class)をサポートしてお り、それらはアドレスファミリ(address family)と呼ばれている。これは、個 々のクラスが独自の通信方式を持っているからである。 Linux は次のような アドレスファミリもしくはドメイン(domain)をサポートしている。 UNIX Unix ドメインソケット。 INET インターネットアドレスファミリ(internet address family)は、 TCP/IP 経由の通信をサポートしている。 AX25 アマチュア無線 X25。 IPX Novell IPX。 APPLETALK Appletalk DDP X25 X25。 アドレスファミリもしくはドメインには、いくつかのソケットタイプ(socket type) があり、それらはコネクションをサポートするサービスのタイプを表し ている。すべてのサービスタイプをサポートしていないアドレスファミリもあ る。 Linux BSD ソケットは次のいくつかのソケットタイプをサポートしてい る。 Stream このソケットは、双方向で順次送達確認の行われる信頼性のあるデータ ストリーム (data stream)であり、通信途上でのデータの喪失や破壊、 複製が生じないよう保証されたものである。Stream ソケットは、イン ターネット(INET)アドレスファミリの TCP プロトコルによってサポー トされている。 Datagram このソケットは、双方向でのデータ通信を提供するが、stream ソケッ トと異なり、メッセージが到達する保証はない。到達した場合でも、順 番通り届いたか、さらに複製や破壊はないかといったことは保証されな い。このタイプのソケットは、インターネットアドレスファミリの UDP プロトコルによってサポートされている。 Raw これは、プロセスが、下位層のプロトコルに直接(それゆえ、 生(raw))アクセスするものである。これを使うと、たとえば、raw ソ ケットをイーサネットデバイスに対してオープンし、生(raw)の IP デ ータトラフィックを見ることが可能になる。 Reliable Delivered Messages これは、datagram ソケットと非常に類似しているが、データの到着は 保証される。 Sequenced Packets これは、データパケットサイズが固定されていることを除いて、stream パケットと同じである。 Packet これは、標準的な BSD ソケットタイプではない。Linux 固有の拡張で あり、これを使うと、プロセスがデバイスレベルで直接パケットにアク セスできる。 ソケットを利用して通信を行うプロセスは、クライアントサーバモデ ル(client server model)を使用する。サーバはサービスを提供し、クライア ントがそのサービスを利用する。その典型がウェブサーバであり、サーバが ウェブページを提供し、クライアント、もしくはブラウザがそのページを読み 出す。ソケットを使うサーバは、まずソケットを作成し、それに名前を結びつ ける(bind)。その名前のフォーマットはそのソケットのアドレスファミリに依 存し、事実上サーバのローカルアドレスになる。ソケットの名前もしくはアド レスは、``sockaddr'' データ構造体を使って指定される。 INET ソケット は、IP ポート番号をそれに結びつける。登録済みポート番号は、 /etc/services ファイルで見ることができる。たとえば、ウェブサーバのポー ト番号は 80 である。アドレスをソケットに結びつけたら、サーバは、そのア ドレスを指定したコネクションリクエストが来るまで待機(listen)している。 リクエストの発信者であるクライアントはソケットを作成し、サーバのター ゲットアドレスを指定して、ソケット上でコネクションリクエストを出 す。INET ソケットの場合、サーバのアドレスは、その IP アドレスとポート 番号である。入って来たリクエストは、様々なプロトコル層を上昇して、サー バの待機中(listening)のソケット上で(処理されるのを)待つ。サーバが入っ て来たリクエストを受信した場合、サーバは、それを受け入れる(accept)か拒 絶するかのいずれかを行う。リクエストが受け入れられる場合、サーバは受け 入れるための新しいソケットを作成しなければならない。入って来るコネク ションリクエストのために、いったん待機状態(listening)に入ったソケット は、コネクションをサポートするために使用することはできない。コネクショ ンが確立されたら、両者は自由にデータの送受信ができる。最後に、それ以上 コネクションが必要なくなったとき、コネクションは遮断(shutdown)できる。 その際、転送中のデータパケットがあっても正しく処理されるよう注意を払う 必要がある。 BSD ソケット上での操作が厳密に何を意味するかは、その基礎となるアドレス ファミリによって異なる。TCP/IP コネクションの設定は、アマチュア無線 X.25 コネクションの設定とは非常に異なる。仮想ファイルシステムの場合の ように、Linux は、BSD ソケット層(layer)を使ってソケットインターフェイ スを抽象化している。 BSD ソケット層側は、アプリケーションプログラムに 対する BSD ソケットインターフェイスと関係付けられ、アプリケーション側 は、アドレスファミリごとに独立した固有のソフトウェアによりサポートされ ている。カーネルに組み込まれたアドレスファミリは、カーネルの初期化時 に、BSD ソケットインターフェイスと伴に登録される。その後、アプリケー ションが BSD ソケットを作成して使用する場合、BSD ソケットとそれがサポ ートするアドレスファミリとの間で関連付けがなされる。この関連付けは、デ ータ構造体とアドレスファミリ固有のサポートルーチンとをクロスリンクする ことにより実現される。たとえば、アドレスファミリ固有のソケット作成ルー チンがある場合、アプリケーションが新規にソケットを作成しようとする際に は、BSD ソケットインターフェイスは、そのルーチンを使用してソケットを作 成する。 一般に、カーネル設定の際には、いくつものアドレスファミリとプロトコルが ビルドされ、protocols 配列に組み込まれる。それらはそれぞれ、INET と いった名前とその初期化ルーチンのアドレスとによって表現される。ブート時 にソケットインターフェイスが初期化されるとき、個々のプロトコル初期化ル ーチンが呼び出される。ソケットアドレスファミリにとって、これは、一組の プロトコル操作を登録することを意味する。これは、一組のルーチンであり、 それぞれがアドレスファミリ固有のプロトコル操作を実行する。登録されたプ ロトコル操作ルーチンは、``pops'' 配列に保存されるが、それは ``proto_ops'' データ構造体へのポインタの配列である。 [see: include/linux/net.h] proto_ops データ構造体は、アドレスファミリタイプと、アドレスファミリ固 有のソケット操作ルーチンに対する一連のポインタから構成されている。 pops 配列は、アドレスファミリ識別子によるインデックスが付けられてい て、たとえば、インターネットアドレスファミリの識別子(AF_INET)は 2 であ る。 files_struct +-------------+ | count | |-------------| |close_on_exec| |-------------| | open_fs | |-------------| | fd[0] | file |-------------| +----------+ | fd[1] |-->| f_mode | |-------------| |----------| | | | f_pos | | | |----------| | | | f_flags | |-------------| |----------| | fd[255] | | f_count | +-------------+ |----------| | f_owner | |----------| BSD Socket | f_op |------------------->File Operations |----------| | f_inode |-+ inode lseek,ioctl |----------| | +---------+ read, close |f_version | +->| | write, fasync +----------+ | | select +-->| socket | | |---------|SOCK_STREAM | | type |------->Address Family | | ops |----+ Socket | | data | | Operations | +---------+ | | +-------------+ +----+-----------------+ | sock | | +----------+ | +-->| type |SOCK_STREAM |----------| | | protocol | | |----------| | | socket |--+ |----------| | | | | +----------+ 図表(10.3) Linux BSD ソケットデータ構造体 11.4. INET ソケット層 INET ソケット層は、TCP/IP を含むインターネットアドレスファミリをサポー トするものである。上記で説明したように、これらのプロトコルは多層構造に なっていて、ひとつのプロトコルが別のプロトコルのサービスを利用する。 Linux の TCP/IP のコードとデータ構造はその多層構造を反映している。BSD ソケット層に対する INET ソケット層のインターフェイスは、一連のインター ネットアドレスファミリのソケット操作ルーチンを経由するものであ り、Linux は、ネットワークの初期化時にそれらのルーチンを BSD ソケット 層と伴に登録する。それらは、登録された他のアドレスファミリと一緒に ``pops'' 配列に保存される。BSD ソケット層は、登録済みの INET ``proto_ops'' データ構造体から、INET 層ソケットをサポートするルーチン を呼び出して操作を実行する。たとえば、アドレスファミリを INET と指定し た BSD ソケット作成リクエストは、その基礎にある INET ソケット作成関数 を使用する。 BSD ソケット層は、それらの個々の操作をする際に、BSD ソ ケットを表す ``socket'' データ構造体を INET 層に渡す。 BSD ソケットが TCP/IP 固有の情報でごちゃごちゃにならないように、INET ソケット層は、独 自のデータ構造体である ``sock'' を使用し、この構造体を BSD ソケット層 の socket データ構造体にリンクする。 [see: include/net/sock.h] このリンク関係は、図表(10.3)で示されている。 INET ソケット層は sock デ ータ構造体を BSD socket データ構造体にリンクする際、BSD socket の dataポインタを使用する。これは、それ以降の INET ソケットの呼び出しで、 簡単に sock データ構造体が取り出せることを意味する。 sock データ構造体 のプロトコル操作ルーチンへのポインタは、その構造体作成時に設定される が、設定の内容はリクエストされたプロトコルによって異なる。 TCP/IP がリ クエストされた場合、sock データ構造体のプロトコル操作ルーチンへのポイ ンタは、TCP コネクションに必要な TCP プロトコル操作ルーチンのセットを ポイントする。 11.4.1. BSD ソケットの作成 システムコールで新規ソケットを作成する際は、アドレスファミリ、ソケット タイプ、プロトコルに関する識別子を渡す。 [see: sys_socket(), in net/socket.c] まず、リクエストされたアドレスファミリを使って、``pops'' 配列内に合致 するアドレスファミリがあるかどうか検索される。たとえば、特定のアドレス ファミリがカーネルモジュールとして実装されている場合は、処理を継続する 前に kerneld デーモンによって該当するモジュールがロードされることにな る。新規の ``socket'' データ構造体が割り当てられて、 BSD ソケットが表 現される。実際、socket データ構造体は、物理的には VFS ``inode'' データ 構造体の一部なので、socket の割り当ては、 VFS inode の割り当てを意味す る。これは奇妙に思われるかもしれないが、ソケットは、通常ファイルの操作 と同じ方法で操作され得ることを考えてほしい。すべてのファイルが VFS inode データ構造体によって表現されるので、ファイル操作をサポートするに は、BSD ソケットは VFS inode データ構造体によっても表現されなければな らないのである。 新規に作成された BSD ``socket'' データ構造体には、アドレスファミリ固有 のソケットルーチンへのポインタが含まれていて、このルーチンは、``pops'' 配列から取り出された ``proto_ops'' データ構造体に設定される。そのタイ プは、リクエストされたソケットタイプ、すなわち SOCK_STREAM, SOCK_DGRAM 等に設定される。アドレスファミリ固有の作成ルーチンは、proto_ops データ 構造体に保存されたアドレスを使用して呼び出される。 未使用のファイル記述子が、カレントプロセスの ``fd'' 配列から割り当てら れて、それがポイントする ``file'' データ構造体が初期化される。この際に 同時に為される処理として、そのファイル操作ポインタのポイント先が、 BSD ソケットインターフェイスによってサポートされる BSD ソケットファイル操 作ルーチン群へと設定される。それ以後の何らかの操作は、ソケットインター フェイスへと誘導され、ソケットインターフェイスがさらにアドレスファミリ 操作ルーチンを呼び出すことで、それがサポートするアドレスファミリへと誘 導される。 11.4.2. アドレスを INET BSD ソケットに bind する 入って来るインターネットコネクションリクエストを待機(listen)するために は、個々のサーバが INET BSD ソケットを作成して、それにアドレスを結びつ け(bind) なければならない。bind 操作の大部分は、その基礎にある TCP と UDP プロトコル層のサポートを受けた INET ソケット層内部で処理される。ア ドレスを結びつけ(bind)られたソケットは、それ以外の通信には使用できな い。これは、ソケットの状態(state)が TCP_CLOSE でなければならないことを 意味する。``sockaddr'' 構造体から bind 操作ルーチンに渡される情報の中 には、結びつけ(bind)られるべき IP アドレスと、オプションとしてポート番 号がある。通常、bind された IP アドレスは、ネットワークデバイスに割り 当てられたものであり、そのデバイスは INET アドレスファミリをサポートす るもので、そのデバイスのインターフェイスは起動されていて使用可能でなけ ればならない。そのインターフェイスが現在アクティブであるかを確認するに は、ifconfig コマンドを使用すればよい。 IP アドレスは、アドレス(のビッ ト)をすべて 1 にするか、すべて 0 にするかいずれかによって、IP ブロード キャストアドレス(``訳注:broadcast address'')にすることもできる。それは 特別なアドレスで、「全員に送信」(``脚注3'')することを意味する。マシン が透過的なプロキシかファイアウォールである場合、IP アドレスの指定を任 意の IP アドレスとしても構わないが、スーパーユーザの権限を持ったプロセ スだけが任意の IP アドレスを結びつけ(bind)できる。結びつけ(bind)られた IP アドレスは ``sock'' データ構造体の recv_addr(訳注: ``recv_saddr''?) と ``saddr'' に保存される。それらは、ハッシュへの問い合わせの際と、IP アドレスを互いに送信しあう場合に使用される。ポート番号はオプションであ り、指定されない場合、サポートするネットワークに未使用の番号を割り当て るよう要請がなされる。慣習では、1024 未満のポート番号は、スーパーユー ザの特権がないプロセスでは使用できない。基礎となるネットワークがポート 番号を割り当てる場合、 1024 以上の番号を常に割り当てる。 パケットが基礎となるネットワークデバイスで受信されると、パケットは正し い INET ソケットと BSD ソケットへと伝達されて、処理されなければならな い。そのために、UDP と TCP は、ハッシュテーブルを管理していて、それを 使って、到着した IP パケット内にあるアドレスを問い合わせて、正しい socket/sock ペアへと IP パケットを伝達する。TCP はコネクション指向のプ ロトコルなので、UDP パケットの場合よりも TCP パケットを処理する場合の ほうが関係する情報量が多い。 UDP は、割り当てられた UDP ポートを ``udp_hash'' というハッシュテーブ ルで管理している。そのハッシュテーブルは、``sock'' データ構造体へのポ インタから構成されていて、sock 構造体はポート番号に基づいてハッシュ関 数によりインデックス付けされている。UDP のハッシュテーブルは、持ち得る ポート番号の数よりも小さい(udp_hash は 128 か、UDP_HTABLE_SIZE の数の エントリしか持たない)ので、テーブル内のエントリのなかには、個々の sock が次へのポインタを持つことでリンクされた sock データ構造体の連結リスト をポイントしているものがある。 TCP は、複数のハッシュテーブルを管理しているので、UDP よりずっと複雑で ある。しかし、TCP は、bind 操作をする間、実際に bind すべき ``sock'' データ構造体をそのハッシュテーブルに付け加えるわけではなく、要求された ポート番号が現在使用されていないかどうかチェックするだけである。 sock データ構造体は、listen 操作の間に TCP のハッシュテーブルに付け加えられ る。 REVIEW NOTE: What about the route entered? 11.4.3. INET BSD ソケット上でコネクションを確立する ソケットが作成されると、そのソケットは、相手側からの(inbound)コネク ションリクエストを待機(listen)するために使用されていない限り、自己側か らの(outbound) コネクションリクエストのために使用できる。 UDP のような コネクションレスのプロトコルの場合、このソケット操作は限定された意味し か持たないのだが、TCP のようなコネクション指向のプロトコルの場合だと、 それはふたつのアプリケーション間で仮想サーキットを構築することを意味す る。 自己側からの(outbound)コネクションリクエストが可能となるのは、INET BSD ソケットを使用し、しかも使用する INET BSD ソケットが適切な状 態(state)にある場合だけである。すなわち、そのソケット上で既にコネク ションが確立していないこと、およびそれが相手側からの(inbound)コネク ションリクエストの待機のために使用されていない場合である。これは、BSD ``socket'' データ構造体が SS_UNCONNECTED の状態になければならないこと を意味する。 UDP プロトコルはアプリケーション間で仮想コネクションを確 立することはなく、送信されたメッセージはすべてデータグラム(datagram)で あるので、送信メッセージが送信先に届くかどうかは確定しない。しか し、UDP に関しても BSD ソケット操作である connect はサポートされてい る。UDP INET BSD ソケット上での connect 操作は、単にリモートアプリケー ションのアドレスとして、その IP アドレスと IP ポート番号を設定する。さ らに、ルーティングテーブルのエントリのキャッシュを設定して、(当該ルー トが無効とならない限り)その BSD ソケットから送信された UDP パケットが 再度ルーティング情報をチェックする必要がないようにする。キャッシュされ たルーティング情報は、INET ``sock'' データ構造体内の ``ip_route_cache'' ポインタによってポイントされる。アドレス情報が指定 されない場合、このキャッシュされたルーティング情報と IP アドレス情報 が、その BSD ソケットを使って送信されるメッセージのために自動的に使用 される。そして、UDP は sock の状態(state)を TCP_ESTABLISHE に変更す る。 TCP BSD ソケット上での connect 操作の場合、TCP は、コネクション情報を 含んだ TCP セグメント(segment)を作成して、指定された IP 送信先に送らな ければならない。 TCP セグメントには、コネクション情報、開始セグメント のシーケンス番号(sequence number)、接続を開始したホスト側が処理できる セグメントの最大サイズ(maximum segment size, MSS)、送信および受信の際 のウィンドウサイズ(window size)、等が含まれる。 TCP では、すべてのセグ メントに番号が付けられるので、シーケンス番号の初期値には、最初のセグメ ントの番号が利用される。Linux は、悪意のあるアタックを防ぐために、妥当 な範囲の乱数値を選択している。TCP コネクションの一方から送信され、相手 側で問題なく受信されたセグメントに対してはすべて、データ破損なく成功裡 に到達した旨の送達確認(acknowledgement)がなされる。送達確認のないセグ メントは再送される。送信と受信のウィンドウサイズとは、送達確認が送信さ れないまま存在しうる未解決セグメントの数である。最大セグメントサイズ は、コネクションリクエストを発信した側で使用されているネットワークデバ イスに依存する。受信側のネットワークデバイスがそれよりも小さい最大セグ メントサイズしかサポートしていない場合、そのコネクションは小さい方を使 用する。自己側からの(outbound) TCP コネクションリクエストを発したアプ リケーションは、相手側アプリケーションがコネクションリクエストを受け入 れ(accept)るか拒絶( reject)するかを返答してくるまで待たなければならな い。その場合、TCP ソケットは相手側からのメッセージを期待しているので、 そのソケットは ``tcp_listening_hash'' に加えられて、それによって、相手 側からの TCP セグメントはその ``sock'' データ構造体へと誘導される。ま た、TCP はタイマーをスタートさせて、送信先のアプリケーションがリクエス トに対して応答しない場合、自己側からの(outbound)コネクションリクエスト をタイムアウトさせる。 11.4.4. INET BSD ソケット上での待機(listen) ソケットにアドレスが結びつけ(bind)られると、そのソケットは、結びつ け(bind) られたアドレスを指定した入ってくるコネクションリクエストを待 機する(listen)。ネットワークアプリケーションは、予めソケットにアドレス を結びつけ(bind)なくてもそのソケット上で待つことができる。その場 合、INET ソケット層が(そのプロトコルに関して)未使用なポート番号を探し て、自動的にそのソケットに結びつけ(bind)る。ソケット関数 listen は、ソ ケット状態 (state)を TCP_LISTEN 状態へと変更し、入って来る接続を許可す るために必要とされるそのネットワーク固有のすべての処理を行う。 UDP ソケットの場合、ソケット状態の変更で充分だが、TCP は、その際、アク ティブとなったことで、ソケットの ``sock'' データ構造体をふたつのハッ シュテーブルに付け加えなければならない。それらは、``tcp_bound_hash'' テーブルと ``tcp_listening_hash'' である。どちらも、IP ポート番号に基 づくハッシュ関数を経由して、インデックス付けされる。 入って来る(incoming) TCP コネクションリクエストがアクティブな待機中 (listening)のソケットで受信されたときはいつも、TCP は、それを表す新規 の ``sock'' データ構造体を作成する。この sock データ構造体は、コネク ションリクエストが最終的に受け入れ (accept)られたときに、TCP コネク ションのボトムハーフ(bottom half)となる。また TCP は、入って来たコネク ションリクエストを含む ``sk_buff'' を複製(cloning)して、それを、待 機(listen)状態の sockデータ構造体の ``receive_queue'' キューに登録す る。複製された sk_buff 構造体には、新規に作成された sock 構造体へのポ インタが含まれる。 11.4.5. コネクション要求を受け入れる(accept) UDP はコネクションの概念をサポートしていないので、INET ソケット上での コネクションリクエストの受け入れ(accept)は、TCP プロトコルだけに適用さ れる。先ず、待機(listen)状態のソケット上の accept 操作ルーチンは、その ``socket'' データ構造体から、新規の socket データ構造体を複製する。次 に accept 操作ルーチンは、入って来る(incoming)コネクションリクエストを 受け入れる(accept)ために、サポートするプロトコル層(layer)、すなわちこ の場合 INET 層に渡される。 INET プロトコル層は、その下位層のプロトコル がコネクションをサポートしていない場合、たとえば UDP であった場合は、 受け入れ(accept)操作に失敗する。下位層がコネクションをサポートしている 場合、accept 操作ルーチンは、実(real) プロトコル(この場合、TCP)へと渡 される。 accept 操作は、ブロックするかしないかのいずれかで行える。ブ ロックしない場合で、受け入れるべきコネクションがない場合、accept 操作 ルーチンは失敗し、新規に作成された socket データ構造体は破棄される。ブ ロックする場合、accept 操作ルーチンを実行しているネットワークアプリケ ーションは、待ち行列に加えられ、TCP コネクションリクエストが受信される までサスペンドする。コネクションリクエストが受信されたら、そのリクエス トを含む ``sk_buff'' は破棄され、sock データ構造体が INET ソケット層へ と返されて、それより前に新規作成された socket データ構造体にリンクされ る。新規 socket のファイル記述子(fd)番号が、ネットワークアプリケーショ ンに返されるので、アプリケーションは、新規に作成された INET BSD ソケッ トでのソケット操作を、そのファイル記述子を使用して行うことができるよう になる。 11.5. IP 層 11.5.1. ソケットバッファ(socket buffer) 個々の層が他層のサービスを利用するという、多層構造のネットワークプロト コルを持つことの問題点は、個々のプロトコルが、送信の際にはヘッ ダ(header)とテイル (tail)をデータに付加し、受信データの処理の際にはそ れを削除するといった操作が必要になることである。個々の層は自分のプロト コルヘッダとテイルがどこにあるのか探す必要があるので、このことがプロト コル間でのデータバッファの受け渡しをむずかしくしている。個々の層でバッ ファをコピーすることはひとつの解決策であるが、それでは非効率である。そ こで、Linux は、ソケットバッファもしくは ``sk_buff'' を使用してプロト コル間やネットワークデバイスとの間でデータを受け渡している。sk_buff に は、ポインタとデータ長(length)のフィールドが含まれているので、それに よって、個々のプロトコル層は、標準関数か「メソッド(methods)」経由でア プリケーションデータを操作できる。 sk_buff +-----------------+ | next | |-----------------| | prev | |-----------------| | dev | |-----------------| | | | | |-----------------| | head |---+ |-----------------| | | data |---+--+ |-----------------| | | | tail |---+--+--+ |-----------------| | | | | end |---+--+--+---+ * |-----------------|<--+ | | | /| | | | | | | | | | | | | * |-----------------|<-----+ | | | /| | | | | truesize | | 送信されるべき | | | | len | パケット | | | | | | | | | | |/ | | | | | * |-----------------|<--------+ | | | | | |/ | | | * +-----------------+<------------+ 図表(10.4) ソケットバッファ (sk_buff) 図表(10.4)は、``sk_buff'' データ構造体を示すものである。個々の sk_buff には、一組のデータが関連付けられている。 sk_buff には、4 つのデータポ インタがあり、それらを使ってソケットバッファ内のデータ操作と管理がなさ れる。それら 4 つのポインタとは、次のものである。 [see include/linux/skbuff.h] head メモリ内のデータ領域の始点を指す。これは、``sk_buff'' と関連する データブロックが割り当てられたときに確定する。 data プロトコルデータの現在の始点を指す。このポインタは、その時点で sk_buff を所有するプロトコル層に依存するため、値は可変である。 tail プロトコルデータの現在の終点を指す。このポインタも所有者たるプロ トコル層依存なので、値は可変である。 end メモリ内のデータ領域の終点を指す。これは、sk_buff が割り当てられ たときに確定する。 ``len'' と ``truesize'' のふたつのデータ長のフィールド(length field)が あり、それぞれ、現在のプロトコルパケットの長さとデータバッファ全体のサ イズを示している。``sk_buff'' 処理コードは、アプリケーションデータに対 するプロトコルヘッダとプロトコルテイルの付加および削除に対する標準的な 仕組みを提供する。それらは、sk_buff 内のデータ、テイル、len フィールド を安全に操作する。sk_buff 処理コードには、次のものがある。 push これは、data ポインタをデータ領域の始点方向に移動させ、len フィ ールドを増加(increment)する。これが使用されるのは、送信されるべ きデータの先頭にデータやプロトコルヘッダを付加するときである。 [see: skb_push(), in include/linux/skbuff.h] pull これは、data ポインタを始点とは反対側の、データ領域の終点方向へ と移動させて、len フィールドを減少(decrement)する。これが使用さ れるのは、受信したデータの先頭からデータやプロトコルヘッダを削除 するときである。 [see: skb_pull(), in include/linux/skbuff.h] put これは、tail ポインタをデータ領域の終点方向に移動させ、len フィ ールドを増加(increment)する。これが使用されるのは、送信されるべ きデータの終点にデータやプロトコル情報を付加するときである。 [see: skb_put(), in include/linux/skbuff.h] trim これは、tail ポインタをデータ領域の始点方向に移動させ、len フィ ールドを減少(decrement)する。これが使用されるのは、受信したデー タからデータやプロトコルテイルを削除するときである。 [see: skb_trim(), in include/linux/skbuff.h] sk_buff データ構造体には、さらにポインタが含まれている。 sk_buff は処 理の最中に sk_buff の二重連結循環リストに保存されるが、その際に使用さ れるポインタである。sk_buff をそのリストの先頭や終端に加える際や、そこ から削除する際には、そのための汎用的な sk_buff ルーチンが用意されてい る。 11.5.2. IP パケットの受信 ``「デバイスドライバ」''の章では、Linux のネットワークドライバがカーネ ルに組み込まれて、初期化される方法を説明した。それによって、一連の ``device'' データ構造体が ``dev_base'' リストにリンクされた。個々の device データ構造体は、そのデバイスを記述するとともに、ネットワーク層 がネットワークドライバに処理を実行させる必要があるときに呼び出すことが できるコールバックルーチンのセットを提供する。これらの関数の大部分はデ ータ送信とネットワークデバイスのアドレスに関するものである。ネットワー クデバイスがそのネットワークからパケットを受信したとき、受信したデータ を ``sk_buff'' データ構造体に変換しなければならない。ネットワークドラ イバは、それらを受信すると、受信したパケットから作成された sk_buff を ``backlog'' キューに付け加える。 [see: netif_rr(), in net/core/dev.c] もし backlog キューが大きくなりすぎた場合、受信した sk_buff は破棄され る。ネットワークのボトムハーフ(bottom half)は、仕事ができたので、実行 準備完了(ready to run)のフラグが立つ。 ネットワークボトムハーフハンドラがスケジューラによって実行されるとき、 そのハンドラは、まず送信待ちになっているネットワークパケットをすべて処 理した後に、どのプロトコル層に受信したパケットを渡すか判断し て、``sk_buff'' の ``backlog'' キューを処理する。 [see: net_bh(), in net/core/dev.c] Linux のネットワーク層が初期化されると、個々のプロトコルが登録さ れ、``packet_type'' データ構造体を ``ptype_all'' リストか ``ptype_base'' ハッシュテーブルに付け加える。 packet_type データ構造体 には、プロトコルタイプ、ネットワークデバイスへのポインタ、プロトコルの 受信データ処理ルーチンへのポインタ、最後にそのリストかハッシュの連結リ スト内の次の packet_type データ構造体へのポインタが含まれる。ptype_all 配列の連結リストは、すべてのネットワークデバイス上で受信された全パケッ トを探すために利用されるもので、したがって通常は使用されな い。ptype_base ハッシュテーブルはプロトコル識別子をハッシュインデック スとするもので、どのプロトコルが入って来たネットワークパケットを受信す べきかを決定するために利用される。ネットワークボトムハーフは、入って来 た sk_buff のプロトコルタイプが、上記いずれかのテーブルにあるひとつ以 上の packet_type エントリと合致するかどうか調べる。たとえば、すべての ネットワークトラフィックを調査して、そのプロトコルがひとつ以上のエント リにマッチした場合、その際には sk_buff は複製される。そして、その sk_buff は、合致したプロトコルの処理ルーチンへと送られる。 [see: ip_recv(), in net/ipv4/ip_input.c] 11.5.3. IP パケットの送信 パケットは、データ交換をしようとするアプリケーションによって送信される 他に、確立されたコネクションを維持したりコネクションを確立したりすると きに、ネットワークプロトコルによって生成される。しかし、データの生成方 法がどのようなものであっても、そのデータを保持するために ``sk_buff'' が作成され、プロトコル層を通過する際に、種々のヘッダが個々のプロトコル 層によって付加される。 ``sk_buff'' が送信されるには、ネットワークデバイスに渡される必要があ る。しかし、それにはまず、IP 等のプロトコルが、どのネットワークデバイ スを使用すべきか判断する必要がある。この判断は、そのパケットにとって何 が最適なルートなのかによって決まる。たとえば、PPP プロトコル経由で、モ デムによって単一のネットワークに接続されているコンピュータの場合、ルー トの選択は容易である。パケットは、ループバックデバイス経由でローカルホ ストに送られるか、PPP によるモデムのコネクションの反対側にあるゲート ウェイに送られるかのいずれかである。イーサネットで接続されたコンピュー タの場合、そのネットワークには多くのコンピュータが接続されているので、 選択はそれよりも難しくなる。 送信されるすべての IP パケットについては、IP がルーティングテーブルを 使用して送信先の IP アドレスに対するルートの解決をする。ルーティングテ ーブル内での個々の IP 送信先の問い合わせが成功した場合、使用すべきルー トを示した ``rtable'' データ構造体が返される。 [see: include/net/route.h] これには、使用すべき送信元 IP アドレス、そのネットワークの ``device'' データ構造体のアドレス、また時には予め組み込まれたハードウェアヘッダが 含まれる。このハードウェアヘッダはネットワークデバイス固有のもので、そ れには送信元と送信先の物理アドレスと、それ以外のメディア固有の情報が含 まれている。ネットワークデバイスがイーサネットデバイスの場合、ハード ウェアヘッダは``図表(10.1)''で示されるようなものとなり、送信元と送信先 のアドレスはイーサネットの物理アドレスとなる。ハードウェアヘッダはその ルートとともにキャッシュされるが、それは、そのルート上で送信されるべき 個々の IP パケットにはそのハードウェアのヘッダが付加されなければなら ず、その作成には時間がかかるからである。ハードウェアヘッダに物理アドレ スが含まれる場合、そのアドレスは ARP プロトコルを使用して解決されなけ ればならない。その場合、出て行く(outgoing)パケットは、その物理アドレス が解決されるまで送信されない。アドレスが解決されてハードウェアヘッダが 組み込まれたら、ハードウェアヘッダはキャッシュされ、当該インターフェイ スを使用して送信されるそれ以降の IP パケットは、ARP を使う必要がなくな る。 11.5.4. データの細分化(fragmentation) すべてのネットワークデバイスには最大パケットサイズ(maximum packet size)があり、それより大きなデータパケットは送信も受信もできない。IP プ ロトコルはこれに対処しており、データを小さな断片(fragment)に分割するこ とで、ネットワークデバイスが処理できるパケットサイズに合わせるように なっている。 IP プロトコルヘッダにはフラグメント(fragment)フィールドが あり、そこにはフラグ (flag)とフラグメントオフセット(fragment offset)が 含まれている。 IP パケットの送信準備ができたとき、IP はそのパケットを外に送信するネッ トワークデバイスを探す。そのデバイスは IP ルーティングテーブルで見つか る。 [see: ip_build_xmit(), in net/ipv4/ip_output.c] 個々の ``device'' データ構造体には、その最大転送単位 (単位はバイト) を 示すフィールドがあり、これは原語の maximum transfer unit を略し mtu フィールドと呼ばれる。デバイスの mtu が、送信を待つ IP パケットのパ ケットサイズよりも小さい場合、IP パケットはより小さな(mtu サイズの)フ ラグメントへと分割されなければならない。個々のフラグメントは ``sk_buff'' によって表される。その IP ヘッダは、そのパケットがフラグメ ントであること、そしてそれに含まれるデータがどのようなオフセット値を持 つかを示すためのマーク付けがなされる。その最後のパケットは、最後の IP フラグメントである旨をマークされる。細分化(fragmentation)の過程で、IP が sk_buff を割り当てられない場合は、その送信は失敗となる。 IP フラグメントの受信は、その送信よりもやや面倒である。IP フラグメント は、順序ばらばらに受信されるかもしれず、それらすべてが受信されてから組 み立て直す必要があるからである。 [see: ip_rcv(), in net/ipv4/ip_input.c] IP パケットが受信されるたびに、それが IP フラグメントか否かのチェック がなされる。最初にメッセージのフラグメントが受信された際、IP は、新規 に ``ipq'' データ構造体を作成し、その構造体が IP フラグメントの ``ipqueue'' リストにリンクされて、再構成されるのを待つ。より多くの IP フラグメントが受信されると、適切な ipq データ構造が分かるので、新規に ``ipfrag'' データ構造体が作成されて、そのフラグメントを記述する。個々 の ipq データ構造体は、IP 受信フレームのフラグメントを一意的に記述する が、その際には、送信元と送信先の IP アドレス、上位層プロトコルの識別 子、その IP フレームの識別子が使用される。フラグメントがすべて受信され たとき、それらは組み立てられて単一の ``sk_buff'' となり、次の上位層プ ロトコルへと渡され、処理される。個々の ipq にはタイマーが含まれてい て、それは有効なフラグメントが受信されるたびに再始動される。このタイマ ーが時間切れになった場合、ipq データ構造体と ipfrag とは破棄され、当該 データは転送中に喪失したとの推定がなされる。そのメッセージの再送は、上 位層のプロトコルの役割である。 11.6. Address Resolution Protocol (ARP) ARP(Address Resolution Protocol)の役割は、IP アドレスをイーサネットア ドレスのような物理的なハードウェアのアドレスへと変換することである。IP がこの変換を必要とするのは、送信のために(``sk_buff'' 形式の)データをデ バイスドライバに渡す直前である。 [see: ip_build_xmit(), in net/ipv4/ip_output.c] IP が実行する種々チェックの中には、そのデバイスがハードウェアヘッダを 必要とするか、必要とする場合はパケットのハードウェアヘッダを再構 成(rebuild)する必要があるかといった項目が存在する。 Linux は、ハード ウェアヘッダをキャッシュすることで、それらを頻繁に再構成しないで済むよ うにしている。ハードウェアヘッダの再構成が必要な場合、Linux は、デバイ ス固有のハードウェアヘッダ再構成ルーチンを呼び出す。 [see: eth_rebuild_header(), in net/ethernet/eth.c] すべてのイーサネットデバイスは同一の汎用ヘッダ再構成ルーチンを使用する が、そのルーチンが ARP サービスを利用して送信先 IP アドレスを物理アド レスに変換する。 ARP プロトコルそのものは非常にシンプルであり、ARP リクエスト(要 求、request) と ARP リプライ(応答、reply)のふたつのタイプのメッセージ から成っている。 ARP リクエストには、変換に必要な IP アドレスが含ま れ、応答には(上手くいけば) その IP の変換後のアドレスであるハードウェ アアドレスが含まれる。ARP リクエストは、ネットワークに接続されたすべて のホストにブロードキャストされるので、あるイーサネットネットワークにお いては、そのイーサネットに繋がるすべてのマシンが ARP リクエストを見る ことになる。要求された IP アドレスを所有するマシンがその ARP リクエス トに応答し、自分の物理アドレスを含めた ARP リプライを返す。 Linux 上の ARP プロトコル層は ``arp_table'' データ構造体のテーブルを中 心に構成されていて、個々の構造体が IP から物理アドレスへの変換を記述し ている。それらのエントリは、IP アドレスの変換が必要になったときに作成 され、それらが時間の経過で役に立たなくなったときに削除される。個々の arp_table データ構造体は次のようなフィールドを持つ。 last used その ARP エントリが最後に使用された時間。 last update その ARP エントリが最後に更新された時間。 flags これらはそのエントリの状態を表す。それが解決済みでまだ有効なアド レスかどうか、等である。 IP address そのエントリが示す IP アドレス。 hardware address 変換されたハードウェアアドレス。 hardware header これは、キャッシュされたハードウェアヘッダに対するポインタであ る。 timer これは ``timer_list'' のエントリであり、それは応答の返らない ARP リクエストをタイムアウトするために使用される。 retries その ARP リクエストがリトライした回数。 sk_buff queue IP アドレスの解決を待つ ``sk_buff'' エントリのリスト。 ARP テーブルは、ポインタテーブル(``arp_table'' の配列)から構成され、そ のポインタが arp_table エントリの連結リストを指している。エントリへの アクセス速度を上げるためにそれらはキャッシュされる。個々のエントリを探 す場合、エントリは、IP アドレスの末尾 2 バイトを取り出してテーブル内で インデックス付けされているので、そのインデックスを使ってエントリの連結 リストを辿れば、目的のエントリを見つけることができる。 Linux は、予め 作成されたハードウェアヘッダもキャッシュしており、それは arp_table エ ントリの中に ``hh_cache'' データ構造体の形で入れられている。 IP アドレス変換が要求されたが、適合する ``arp_table'' エントリが存在し ないとき、ARP は ARP リクエストメッセージを送信しなければならない。 ARP は、新規に arp_table エントリをテーブル内に作成し、アドレス変換を 要するネットワークパケットを含んだ ``sk_buff'' を 新規エントリの sk_buff キューに登録する。 ARP は ARP リクエストを送信し、ARP 時間切れ タイマーを走らせる。応答がない場合、ARP は何度かリクエストを再送してみ るが、それでも応答がない場合は、その arp_table のエントリを削除する。 エントリの削除は、その IP アドレスの変換を待っているキュー上にあるすべ ての sk_buff に通知される。そして、失敗を上位プロトコル層に送信するの は ARP プロトコル層の役割である。 UDP はパケットが喪失しても気にしない が、TCP は確立された TCP リンク上で再送を試みる。もし、IP アドレスの所 有者がハードウェアアドレスを付して応答を返した場合、その arp_table エ ントリは完成(complete)のマークが付けられ、キュー上にある該当する sk_buff はキューから削除されて、送信処理の過程にまわされる。ハードウェ アアドレスは、個々の sk_buff のハードウェアヘッダの中に書き込まれる。 ARP プロトコル層は、自身の IP アドレスを指定した ARP リクエストに対す る応答 (respond)もしなければならない。ARP は、物理層の ARP リクエスト のプロトコルタイプ(ETH_P_ARP)を登録し、``packet_type'' データ構造体を 生成する。これは、ネットワークデバイスが受信したすべての ARP パケット が ARP プロトコル層に渡されることを意味する。このことは、ARP リプラ イ(reply)の場合だけでなく、ARP リクエスト(request)を発するときも同様で ある。 ARP が ARP リプライを生成するときは、リクエストを受信したデバイ スの ``device'' 構造体に保存されたハードウェアアドレスを使用する。 ネットワークトポロジーは時間の経過によって変化することがあるので、IP アドレスが以前と異なるハードウェアアドレスに割り当てられることがある。 たとえば、ダイヤルアップサービスの中には、個々のコネクションが確立した 時点で IP アドレスを割り当てるものがある。ARP テーブルが最新の情報を保 持するようにするために、 ARP は定期的に実行されて、``arp_table'' の全 エントリを走査してタイムアウトになったものがないか確認がなされる。その 際、キャッシュされたハードウェアヘッダをひとつ以上含むエントリを削除し てしまわないよう注意が払われる。他のデータ構造がそれらのエントリに依存 しているので、削除してしまうことは危険だからである。ある種の arp_table エントリは永続的で、それらが解放されないよう、その旨マークされてい る。ARP テーブルは大きくなりすぎないよう制限されている。個々の arp_table エントリはカーネルメモリを消費するからである。新規にエントリ を割り当てる必要が生じたり、 ARP テーブルが最大サイズに達したときはい つも、一番古いエントリを探してそれらを削除することでテーブルが適度な大 きさに保たれる。 11.7. IP ルーティング IP ルーティング(経路づけ)機能は、特定の IP アドレス宛の IP パケットを どこに送信すべきかを決定する。IP パケットを送信する際は多くの選択肢が ある。送信先には到達可能だろうか? 到達可能なら、送信のためにどのネッ トワークデバイスを使用すべきか? 送信先に到達し得る利用可能なデバイス が複数ある場合、よりよいデバイスはどれか? IP ルーティングデータベース は、それらの疑問に答えるための情報を管理する。ふたつのデータベースが存 在するが、最も重要なのは、フォワーディング 情報データベース(Fowarding Information Database)である。これは既知の IP 送信先とそこへの最良の経 路を漏れなく収めたリストである。それより小規模だが高速なデータベースと してルートキャッシュ(route cache)があり、それは IP 送信先に関するすば やい経路問い合わせのために使用される。すべてのキャッシュと同様に、これ には頻繁にアクセスされる経路しか含まれておらず、その内容は、フォワー ディング情報データベースから取り込まれたものである。 経路(route)は、BSD ソケットインターフェイスへの IOCTL リクエストを経由 して追加と削除がなされる。それらはプロトコルに渡されて、処理され る。INET プロトコル層の場合、IP の経路の追加と削除ができるのは、スーパ ーユーザ権限を持ったプロセスだけである。経路は固定することも可能であ り、あるいは、時間の経過によって動的に変更することも可能である。ルー タ(router)(訳注: ルータとは、ネットワークから他のネットワークに、他の システムが送信したパケットを送り出すもの)でない限り、大部分のシステム では固定されたルートが使用されている。ルータはルーティングプロトコルを 実行して、それが既知の送信先の全 IP に対してルーティングできるよう常時 チェックしている。ルータでないシステムは、エンドシステム(end system)と 呼ばれる。ルーティングプロトコルは、たとえば GATED のようにデーモンと して実装されているが、それらも IOCTL BSD ソケットインターフェイス経由 で経路を追加したり削除したりしている。 11.7.1. ルートキャッシュ IP 経路の問い合わせがあるときはいつも、合致する経路がないか、まずルー トキャッシュがチェックされる。ルートキャッシュに合致する経路がない場 合、経路についてフォワーディング情報データベースが検索される。そこにも 経路が見つからない場合、IP パケットの送信は失敗し、アプリケーションに その旨通知がなされる。フォワーディング情報データベースに該当する経路が あり、ルートキャッシュにはない場合、新規のエントリが生成され、ルート キャッシュにその経路が付け加えられる。 ルートキャッシュは、``rtable'' データ構造体の連結リスト(chain)へのポイ ンタを含んだテーブル(``ip_rt_hash_table'') である。ルートテーブルに対 するインデックスは、IP アドレスの最下位から 2 バイトを基準としてハッ シュ関数により生成される。これらの 2 バイト情報は、たいていの場合送信 先によって異なっているので、ハッシュ値を散らすには最良である。個々の rtable エントリには、経路に関する情報が含まれていて、それには、送信先 IP アドレス、その IP アドレスに到達するために使用するネットワークデバ イス、使用可能なメッセージの最大サイズなどがある。またそれは、リファレ ンスカウント(reference count)、ユーサージカウント(usage count)、(jiffies を使って表された)最終時間のタイムスタンプ(timestamp of last time)といった情報も持っている。リファレンスカウントは、その経 路が使用されるたびにインクリメントされ、その経路を使用したネットワーク コネクションの数を示す。そして、この経路を使用したアプリケーションが終 了することによりデクリメントされる。ユーサージカウントはその経路が問い 合わせを受けるごとに加算され、ハッシュエントリの連結リスト内の rtable を処理するために使用される。ルートキャッシュ内のすべてのエントリは、 「最後に使用された時間を示すタイムスタンプ」を持っていて、それは rtable が古くなりすぎていないかの定期的なチェックに使用される。 [see ip_rt_check_expire(), in net/ipv4/route.c] その経路が最近使用されていない場合、それはルートキャッシュから破棄され る。経路がルートキャッシュに保存されている場合、それらは、最も利用頻度 が多いものがハッシュ連結リストの先頭に来るよう並べ替えられる。これに よって、経路問い合わせがあった際に、その発見が高速になる。 11.7.2. フォワーディング情報データベース(Forwarding Information Database) fib_zones fib_node +-------+ +----------+ | | +->| fib_next | |-------| | |----------| | | | | fib_dst | |-------| | |----------| | | | | fib_use | fib_info | | fib_zone | |----------| +-----------+ |-------| +-------------+ | | fib_info |->| fib_next | | |->| fz_next | | |----------| |-----------| |-------| |-------------| +-------+ | |fib_metric| | fib_prev | | | |fz_hash_table|->| | | |----------| |-----------| | | |-------------| |-------| | | fib_tos | |fib_gateway| | | | fz_list | | | | +----------+ |-----------| | | |-------------| |-------| | | fib_dev | | | | fz_nent | | | | |-----------| | | |-------------| | | | | fib_refcnt| | | | fz_logmask | |-------| | |-----------| | | |-------------| | |-+ | fib_window| |-------| | fz_mask | |-------| |-----------| | | +-------------+ | | | fib_flags | |-------| | | fib_node |-----------| | | |-------| +----------+ | fib_mtu | +-------+ | |--->| fib_next | |-----------| |-------| |----------| | fib_irtt | | | | fib_dst | +-----------+ | | |----------| | | | fib_use | fib_info |-------| |----------| +-----------+ | | | fib_info |->| fib_next | |-------| |----------| |-----------| | | |fib_metric| | fib_prev | +-------+ |----------| |-----------| | fib_tos | |fib_gateway| +----------+ |-----------| | fib_dev | |-----------| | fib_refcnt| |-----------| | fib_window| |-----------| | fib_flags | |-----------| | fib_mtu | |-----------| | fib_irtt | +-----------+ 図表(10.5) フォワーディング情報データベース フォワーディング情報データベース(図表(10.5)参照)には、IP アドレスから 見た、その時点においてシステム上で利用可能な経路の一覧が含まれる。それ は非常に複雑なデータ構造をしていて、妥当な効率を得られるようアレンジさ れてはいるが、高速で問い合わせが可能なデータベースではない。特に、送信 されるべきすべての IP パケットについて送信先をこのデータベースに問い合 わせるとしたら、検索は非常に遅いものになるだろう。ルートキャッシュが存 在するのは、そのためである。既存の適切な経路を使って IP パケットを高速 に送信するわけである。ルートキャッシュはこのフォワーディング情報データ ベースから派生したもので、よく利用されるエントリを表したものである。 個々の IP サブネットは ``fib_zone'' データ構造体によって表される。それ らのすべては fib_zones ハッシュテーブルからポインタ参照されている。 ハッシュのインデックスは、その IP サブネットのマスクから生成されてい る。同一サブネットに対するすべての経路(route)は、``fib_node'' と ``fib_info'' データ構造体のペアによって記述されていて、それらは、個々 の fib_zone データ構造体内の ``fz_list'' キューに登録されている。その サブネットの経路の数が増えた場合、ハッシュテーブルが生成され て、fib_node データ構造体の発見を容易にする。 同一の IP サブネットに対していくつかの経路がある場合があり、それらの経 路は複数のゲートウェイのひとつを通過できる。IP ルーティング層は、同一 サブネットへの複数の経路が同じゲートウェイを使用することは許さない。言 い換えると、あるサブネットへの経路が複数ある場合、個々の経路は必ず異な るゲートウェイを使用することが保証される。個々の経路に関連付けられるの がメトリック(metric)である。これは、その経路がどれだけ最短に近いかを示 す指標である。経路のメトリックは、本質的には、宛先のサブネットに到達す るまでに経由しなければならない IP サブネットの数である。メトリックが大 きくなるにつれて、その経路は最短経路から遠くなる。 (-- (脚注1) National Science Foundation (脚注2) Serial Read Only Memory (脚注3) で、何のために? 12. カーネルメカニズム この章では、Linux カーネルが提供しなければならない汎用的なタスクとメカ ニズムのいくつかについて解説する。それらは、カーネルの他の部分(すなわ ちこの章で述べられるいくつかのタスクやメカニズム以外の部分)が効率よく 協調動作するために必要なものである。 12.1. ボトムハーフハンドラ bh_active 31 0 bh_base +-----------------------------+ +---------------+ | | 0 | |-->ボトムハーフ | | |---------------| ハンドラ +-----------------------------+ | | (timers) |---------------| bh_mask | | 31 0 | | +-----------------------------+ | | | | | | | | | | +-----------------------------+ | | | | | | |---------------| | | |---------------| 31 | | +---------------+ 図表(11.1) ボトムハーフハンドラのデータ構造 カーネルにタスク処理をさせる場合、この時点では処理をさせたくない、とい う時がしばしばある。割り込みを処理させる場合は、特にそうである。割り込 みが起こると、プロセッサは実行中の処理を停止し、オペレーティングシステ ムがその割り込みを適切なデバイスドライバに伝達する。しかし、デバイスド ライバに割り込みを処理させる場合、割り込み処理中には、システム上で他の 処理を実行できない。したがって、デバイスドライバは、割り込みの処理にあ まり時間を掛けてはいけない。他方、その場で直ぐに処理せずとも、何の問題 もないケースもしばしば存在する。Linux のボトムハーフハンドラが発明され たのは、デバイスドライバやその他のカーネルルーチンがそうした仕事を一旦 キュー上に置いて、あとで処理できるようにするためである。図表(11.1)で は、ボトムハーフハンドラに関するカーネルのデータ構造体が示されている。 [see: include/linux/interrupt.h] 32 個までの異なるボトムハーフハンドラが存在し得る。 bh_base は、カーネ ルの個々のボトムハーフ処理ルーチンへのポインタのテーブルであ る。bh_active と bh_mask は独自のビットセットを持っていて、その値はど のようなハンドラがインストールされてアクティブになっているのかによる。 bh_mask にビット N がセットされた場合、bh_base の N 番目の要素がそのボ トムハーフハンドラのアドレスを含んでいる。bh_active にビット N がセッ トされた場合、スケジューラが合理的と考えた直後に N 番目のボトムハーフ ハンドラルーチンが呼び出される。それらのインデックスは静的に定義されて いる。タイマー(timer)ボトムハーフハンドラは最優先であり(index 0)、コン ソール(console)ボトムハーフハンドラはそれに次ぐ優先度を持つ(index 1)等 である。典型的には、ボトムハーフハンドラはそれらと関連したタスクのリス トを持っている。たとえば、イミディエート(immediate)ボトムハーフハンド ラはイミディエートタスクキュー(tq_immediate) を通じて処理を行うが、そ のキューには即座に(immediately)処理される必要のあるタスクが含まれる。 カーネルのボトムハーフハンドラのいくつかは、デバイス固有のものである が、それ以外は汎用的である。 TIMER このハンドラは、システムの定時的なタイマーが割り込むときに、いつ もアクティブとマークされ、カーネルのタイマーキューメカニズムを駆 動するために使用される。 CONSOLE このハンドラは、コンソールメッセージを処理するために使用される。 TQUEUE このハンドラは、tty メッセージを処理するために使用される。 NET このハンドラは、一般的なネットワーク処理をこなす。 IMMEDIATE これは汎用ハンドラであり、いくつかのデバイスドライバが後で行おう とする仕事をキューに入れるために使用される。 デバイスドライバやカーネルの他の部分が仕事を後回しにする必要があるとき はいつも、その仕事を、たとえばタイマーキューのような、適切なキューに置 き、カーネルにシグナルを送って、ボトムハーフ処理の実行が必要であること を伝える。それをするには、bh_active の適切なビットを設定すればよい。た とえば、ビット 8 が設定されるのは、ドライバが何かをイミディエートキュ ー上に置いて、ボトムハーフハンドラが実行されて処理されるのを望む場合で ある。 bh_active のビットマスクがチェックされるのは、個々のシステムコ ールの終了時であり、制御が呼び出しプロセスに戻る直前である。何らかの ビットがセットされていた場合、アクティブなボトムハーフハンドラが呼び出 される。ビット 0 がまず最初にチェックされて、次に 1 へと順次進んで、 31 までチェックされる。 [see: do_bottom_half(), in kernel/softirq.c] bh_active のビットは、個々のボトムハーフハンドラが呼び出されるたびにク リアされる。すなわち、bh_active は一時的なもので、スケジューラが呼び出 されたその時点では意味を持つが、次の呼び出しがなされる際には意味を持た ない。したがって、この方法を使えば、処理すべき仕事がない場合、ボトムハ ーフ処理ルーチンは呼び出されない。 12.2. タスクキュー(task queue) tq_struct tq_struct task_quequ +-------------+ +-------------+ ------------>| next |-------->| next |--------> |-------------| |-------------| | sync | | sync | |-------------| |-------------| | *routine() | | *routine() | |-------------| |-------------| | *data | | *data | +-------------+ +-------------+ 図表(11.2) タスクキュー タスクキューは、カーネルが仕事を後回しにする方法である。 [see: include/linux/tqueue.h] Linux には、仕事をキュー上に置いて、後で処理をする汎用的なメカニズムが ある。タスクキューはしばしばボトムハーフハンドラと一緒に使用される。タ イマータスクキュー(timer task queue)は、タイマーキューボトムハーフハン ドラ(timer queue bottom half handler)が実行されるときに処理される。タ スクキューはシンプルなデータ構造体であり、図表(11.2)に示されるよう に、``tq_struct'' データ構造体の単純な連結リストである。その個々の構造 体には、ルーチンのアドレスとデータへのポインタが含まれている。タスク キュー上の要素が処理されるとき、ルーチンが呼び出され、その際、ルーチン へはデータへのポインタが渡される。 カーネル内の何らかのルーチン、たとえばデバイスドライバは、タスクキュー を作成し利用することができるが、次の 3 つのタスクキューは、カーネルに よって作成され管理されている。 timer このキューは、次のシステムクロックティック(clock tick)の出来るだ け直後に実行されるべき仕事をキューイングするために使用される。ク ロックティックごとにこのキューがチェックされ、エントリが含まれて いるかどうか確認される。エントリが含まれていた場合、タイマーキュ ーボトムハーフハンドラがアクティブにされる。スケジューラが次に実 行される時には、タイマーキューボトムハーフハンドラが、その他すべ てのボトムハーフハンドラと伴に処理される。このキューとシステムタ イマーを混同してはいけない。後者は、ずっと洗練されたメカニズムを 持っている。 immediate このキューも、スケジューラがアクティブなボトムハーフハンドラを処 理するときに処理される。イミディエート(immediate)ボトムハーフハ ンドラは、タイマーキューボトムハーフハンドラほど優先順位は高くな いので、これらのタスクは後から実行される。 scheduler このタスクキューは、スケジューラ自身によって処理される。これは、 システム上の他のタスクキューをサポートするために使用されるので、 この場合、実行されるべきタスクは、タスクキューを処理するルーチ ン、たとえば、デバイスドライバである。 タスクキューが処理されるとき、キューの最初の要素に対するポインタがキュ ーから削除されて、ヌルポインタ(null pointer)と置き換えられる。実際、こ の削除処理は、排他的な操作(atomic operation)なので、割り込みできないよ うになっている。次に、キュー上の個々の要素が自分の処理ルーチンを順番に 呼び出す。キュー上の要素はしばしば静的に割り当てられたデータである。し かし、割り当てられたメモリを破棄する固有のメカニズムは存在しない。タス クキュー処理ルーチンは単にリスト上の次の要素の処理に移るだけである。割 り当てられたカーネルメモリを確実に適宜再利用可能にするためには、タスク 自体がその処理をしなければならない。 12.3. タイマー(timer) timer_table timer_struct +-------------+ +-----------+ 0 | |-------->| expires | |-------------| |-----------| | | | *fn() | |-------------| +-----------+ | | | | timer_struct |-------------| +-----------+ | |-------->| expires | |-------------| |-----------| | | | *fn() | | | +-----------+ |-------------| | | 31 timer_active 0 |-------------| +----------------------------------+ 31 | | | | +-------------+ +----------------------------------+ timer_head timer_head timer_head +--------------+ +--------------+ +--------------+ | next |------->| next |------->| next | |--------------| |--------------| |--------------| | prev |<-------| prev |<-------| prev | |--------------| |--------------| |--------------| | expires | | expires | | expires | |--------------| |--------------| |--------------| | data | | data | | data | |--------------| |--------------| |--------------| | *function() | | *function() | | *function() | +--------------+ +--------------+ +--------------+ 図表(11.3) システムタイマー オペレーティングシステムには、将来の特定の時間にタスクを処理できるよう なスケジューリング機能が必要である。そして、スケジューリングにより、タ スクを比較的正確な時間に実行できるようなメカニズムが必要とされる。この 点、オペレーティングシステムをサポートしようとするプロセッサには、必ず プログラム可能なインターバルタイマーがあり、それが定期的にプロセッサに 割り込みを掛けている。この定期的な割り込みは、システムクロックティック と呼ばれる。それは、メトロノームに似た働きをして、システムの活動を指揮 している。 Linux は時刻管理に対して非常にシンプルな見方をしている。 Linux は、時間を起動時からのクロックティック(clock tick)によって計る。 すべてのシステムタイマーはその計測を基にしており、その計測方法は、広く 利用されている変数名にちなんで jiffies と呼ばれている。 Linux には二種類のシステムタイマーがある。どちらのキュールーチンも、あ るシステム時間に呼び出されるのだが、それらは実装においてやや異なってい る。 ``図表(11.3)'' では、両方のメカニズムが示されている。 [see: include/linux/timer.h] ひとつめは、古いタイマーメカニズムであり、``timer_struct'' データ構造 体への 32 個のポインタによる静的配列とアクティブタイマーのマスク ``timer_active'' から構成されている。タイマーがタイマーテーブル内のど こに置かれるかは、静的に定義されている。 (これは、ボトムハーフハンドラ のテーブル bh_base にやや似ている。) エントリがこのテーブルに付け加え られるのは、大抵の場合システム起動時である。ふたつめは、それより新しい メカニズムであり、``timer_list'' データ構造体の連結リストを使用する。 リストの要素は、タイマー終了までの残り時間(expire time)の長さの順番で 並べられている。 どちらの方法の場合でも、タイマーの残り時間を計る際の指標として jiffies を使用する。それゆえ、5 秒間だけ実行されるタイマーは、まず 5 秒という 時間を jiffies の単位に変換して、それを現在のシステム時間に加算するこ とで、タイマーが終了すべき時刻を jiffies 形式のシステム時間として獲得 する。システムクロックティッごとに、タイマーボトムハーフハンドラはアク ティブとマークされ、それによって、スケジューラが次に起動されるときに、 タイマーキューが処理される。タイマーボトムハーフハンドラは、両方のタイ プのシステムタイマーを処理する。古いほうのシステムタイマーの場 合、timer_active ビットマスクがチェックされ、設定ビットが確認される。 [see: timer_bh(), in kernel/sched.c] [see: run_old_timers(), in kernel/sched.c] [see: run_timer_list(), in kernel/sched.c] アクティブタイマーの終了時間が経過した場合 (終了時間の値がシステムの jiffies の現在値よりも小さい場合)、そのタイマールーチンが呼び出され、 アクティブビットはクリアされる。新しいシステムタイマーの場 合、timer_list 構造体の連結リストのエントリがチェックされる。すべての 終了時間タイマーがリストから削除され、そのルーチンが呼び出される。新し いタイマーのメカニズムには、タイマールーチンに引数を渡せるという利点が ある。 12.4. 待ち行列(wait queue) プロセスは、何らかのシステムリソースを待たなければならない状態になるこ とがよくある。たとえば、あるプロセスがファイルシステム上のあるディレク トリを記述する VFS inode を必要としていのだが、その inode がバッファ キャッシュにない時などである。その場合、そのプロセスは、そのフィアルシ ステムを保持する物理メディアから当該 inode が持ってこられるまで待って から、処理を継続しなければならない。 [see: include/linux/wait.h] wait_queue *task *next 図表(11.4) 待ち行列 Linux カーネルはシンプルなデータ構造体である ``wait_queue'' (図 表(11.4)を参照)(訳注: この図表は原文においても GIF ファイルではありま せん。)を使用しているが、これは、``task_struct'' のプロセスへのポイン タと、待ち行列内の次の要素へのポインタから構成されている。 プロセスが待ち行列の終端に付け加えられるとき、そのプロセスは、割り込み 可能か割り込み不可能かのいずれの場合もあり得る。割り込み可能なプロセス は、たとえばタイマーの終了といったイベントや、待ち行列上で待っている間 に受信したシグナルによって割り込まれるかもしれない。待ち行列上のプロセ スの状態(state)はそれを反映していて、その状態は INTERRUPTIBLE(割り込み 可能) か UNINTERRUPTIBLE(割り込み不可) のいずれかである。待ち行列上に 置かれたプロセスは 現時点では動作させつづけられず、スケジューラが次ぎ のプロセスを選んだときに、その待ちプロセスはサスペンド状態とな る。(``脚注 1'') 待ち行列が処理されるとき、待ち行列上のすべてのプロセスの状 態(state)は、 RUNNING(実行中)にセットされる。プロセスが実行キューから 削除されていた場合は、再度実行キュー上に置かれる。スケジューラが次に実 行されるとき、待ち行列上にあるプロセスは、もう今は待ち状態ではないの で、実行されるべき候補者となっている。待ち行列上のプロセスがスケジュー ラによって実行されるとき、それが最初に行うことは、自分を待ち行列上から 削除することである。待ち行列はシステムリソースへのアクセスを同期させる ためにも使用できるので、Linux はそれらを使って、セマフォを実装してい る。 12.5. バズロック (Buzz Locks) これは、スピンロック(spin locks)と呼ばれることのほうが多い仕組みであ り、データ構造やコードを保護するプリミティブな方法である。これは、一度 にひとつのプロセスしか重要なコード領域に入れないようにするものである。 Linux においては、データ構造体のフィールドへのアクセスを制限するために 利用されるのだが、その際、一つの単精度整数フィールドを使用したロックの 仕組みを提供している。その領域に入ろうとする個々のプロセスは、ロックの 初期値を 0 から 1 へと変更しようとする。現在値が 1 の場合、タイトルー プ(tight loop)のコード内でスピニング (spining)しながら、再度それを試み る。そのロック機構を保持するメモリ領域へのアクセスは排他的(atomic)なも のでなければならず、その値の読み出し、それが 0 であることの検証、そし てその値の 1 への変更といった処理は、他のプロセスから割り込まれること がない。大部分の CPU アーキテクチャでは、特別な命令によってこの仕組み をサポートしているが、キャッシュされていないメインメモリを利用してバズ ロック(buzz lock)を実現することもできる。 占有中のプロセスがコードの重要領域から離れるとき、バズロックの値をデク リメントして、その値を 0 に戻す。ロック上でスピニングしているプロセス は、この時に読み出しを行うと、0 を読み出す。それを最初に読み出したプロ セスが、その値をインクリメントして 1 にし、重要領域へと入る。 12.6. セマフォ(semaphore) セマフォは、コードやデータ構造の重要な領域を保護するために利用される。 たとえば、ディレクトリを記述する VFS inode のような重要なデータへの個 々のアクセスは、そのプロセスに代わってカーネルコードによって実行され る。あるプロセスが使用している重要なデータ構造体に対する、他のプロセス による変更を許すことは非常に危険である。これを実現するひとつの方法は、 アクセスされる重要なデータの周囲でバズロックを使用することだが、これは 単純なアプローチなので、良好なシステムパフォーマンスは期待できない。そ れに替えて、Linux はセマフォを使用して、コードやデータの重要領域へのア クセスを一度にひとつのプロセスにしか許さないようにしている。リソースへ アクセスしようとするそれ以外のプロセスは、領域がフリーになるまで待たさ れる。待ちプロセスはサスペンドされ、システム上の他のプロセスは通常通り 実行を続けることが可能である。 Linux の ``semaphore'' データ構造体には、次のような情報が含まれてい る。 [see: include/asm/semaphore.h] (i386, alpha) count このフィールドは、そのリソースを利用しようとするプロセスの数を監 視するものである。正の値は、リソースが利用可能であることを意味す る。負または 0 の値は、待ち状態のプロセスがあることを意味する。 初期値 1 は、そのリソースを利用できるのは一度にたったひとつのプ ロセスだけであることを意味する。プロセスがそのリソースを利用した いとき、プロセスはその値(count)をデクリメントし、このリソースの 利用を終了したとき、プロセスはその値をインクリメントする。 waking このフィールドは、そのリソースを待っているプロセス数のうち、その リソースが解放されたときに起こされることになっているプロセスの数 を示すものである。 wait queue プロセスがそのリソースを待っているとき、それらのプロセスはこの待 ち行列上に置かれる。 lock waking フィールドにアクセスしているときは、バズロックが利用され る。 セマフォの初期値が 1 であるとすると、最初にやって来たプロセスは、その カウントが正であることを確認し、そこから 1 を引いて 0 にする。これでプ ロセスは、コードかリソースの重要部分を「占有」したことになり、それがセ マフォによって保護される。プロセスがその重要な領域を離れるとき、セマ フォのカウントをインクリメントする。最善のケースは、その重要領域を所持 しようとする他のプロセスが存在しない場合である。Linux は、この最も一般 的なケースで効率よく動くようにセマフォを実装している。 あるプロセスの占有中に他のプロセスが重要領域に入ろうとする場合、そのプ ロセスも count をデクリメントする。その count は現在 負(-1)となるの で、そのプロセスは重要領域に入れず、そのかわり占有中のプロセスが退出す るまで待たなければならない。Linux は待ちプロセスをスリープさせ、占有プ ロセスがその重要領域から退出する際にそのプロセスを目覚めさせる。待ちプ ロセスは自分自身をセマフォの待ち行列に登録して、waking フィールドの値 をチェックしながらループ状態に入り、waking が 0 でなくなった時にスケ ジューラを呼び出す。 重要領域の占有者がセマフォの count をインクリメントして、もしその値が 0 以下ならば、そのリソースを待ちながらスリープしているプロセスが存在す る。最良のケースでは、セマフォの count は初期値である 1 に戻されている ので、それ以上の仕事は必要がない。占有プロセスは waking フィールドの値 をインクリメントして、セマフォの待ち行列でスリープ状態にあるプロセスを 目覚めさせる。待ちプロセスが起きると、waking フィールドの値は 1 なの で、重要領域に入ってもよいことがわかる。プロセスは waking フィールドの 値をデクリメントし、その値を 0 に戻し、実行を続ける。セマフォの waking フィールドへのアクセスはすべて、セマフォのロックを使用したバズロックに より保護されている。 (-- (脚注 1)REVIEW NOTE: タスクを割り込み可能な状態で停止させて、次に スケジューラが実行される際に、そのタスクが実行されるとはどういうこと か? 待ち行列上でスリープ状態にあるプロセスは、目覚めさせられるまで、 実行されないはずである。--) 13. モジュール この章では、Linux カーネルが、ファイルシステムなどの機能を、必要な時だ け動的にロードする仕組みについて説明する。 Linux は、モノリシックなカーネル(monolithic kernel)を使用している。す なわちそれは、単一で巨大なプログラムであり、そこでは、カーネルのあらゆ る機能別コンポーネントが、すべての内部データ構造やルーチンにアクセスで きる。それとは反対の仕組みとして、マイクロカーネル(micro-kernel)構造が ある。その場合、カーネルの機能別部分は、個別のユニットに分割され、ユ ニット間での厳格な通信メカニズムが設定される。モノリシックカーネルで は、新規コンポーネントをカーネルに追加するには、その設定プロセスを経由 するので、やや時間がかかる。たとえば、NCR 810 SCSI の SCSI ドライバを 使いたいのだが、まだそれをカーネルに組み込んでいないとする。その際は、 新しいカーネルを設定し、ビルドすることで、はじめて NCR 810 が使用でき るようになる。しかし、違う選択肢も存在する。Linux では、必要に応じて、 オペレーティングシステムのコンポーネントを動的にロードしたり、アンロー ドしたりできる。 Linux カーネルモジュール(module)とは、システム起動後 ならいつでも動的にカーネルにリンクすることができるコード群である。それ らは、必要がなくなればカーネルとのリンクを解消して削除することができ る。大部分の Linux カーネルモジュールは、デバイスドライバや、ネットワ ークドライバなどの仮想ドライバ、あるいはファイルシステムなどである。 Linux カーネルモジュールは、insmod や rmmod コマンドを使用して、明示的 にロードとアンロードが可能である。また、カーネル自身も、カーネルデーモ ン(kerneld)によって、モジュールのロードとアンロードを必要に応じて行う ことができる。必要なときに動的にロードできるコードというのは、カーネル のサイズを最小に抑え、それに柔軟性を与えるという意味で非常に魅力的な機 能である。わたしの現在の Intel カーネルはモジュールを広範に使っている ので、 406 K バイトで済んでいる。VFAT ファイルシステムはたまにしか使わ ないため、 VFAT パーティションがマウントされた時に自動的に VFAT ファイ ルシステムがロードされるように、わたしのカーネルはビルドされてい る。VFAT パーティションをアンマウントしたとき、システムはわたしがもは や VFAT ファイルシステムモジュールを必要としていないことを検出して、シ ステムからそれを削除する。新しいカーネルコードを試すときでも、試行のた びにカーネルをリビルドして再起動をかける必要がないので、その点でもモ ジュールは便利である。しかし、その見返りとして、カーネルモジュールを使 うと若干のパフォーマンスの低下とメモリの消費が起こる。ローダブルモジュ ールにするために提供しなければならないコードが少量あり、そのコードと付 加的なデータ構造とが加わるために、メモリの消費量がやや増加する。また、 あるレベルで間接処理が導入されるので、モジュールの場合、カーネルリソー スへのアクセスがやや非効率になる。 Linux モジュールは、いったんロードされると、通常のカーネルコードと同様 にカーネルの一部となる。すなわち、他のカーネルコードと同一の権利と責任 を持つ。言い換えると、Linux カーネルモジュールは、すべてのカーネルコー ドやデバイスドライバと同様に、カーネルをクラッシュさせることもできる。 モジュールが必要なカーネルリソースを使用するためには、まずそれらのリソ ースを見つけだす必要がある。たとえば、モジュールが kmalloc() やカーネ ルメモリ割り当てルーチンを呼び出す必要がある場合などを考えてほしい。ビ ルドされた時点では、モジュールは、メモリ内のどこに kmalloc() があるの かを知らない。したがって、モジュールがロードされたとき、カーネルは、モ ジュールが動き出す前に、モジュール内の kmalloc() への参照をすべて解決 しなければならない。カーネルは、カーネルのシンボルテーブル内にカーネル リソースのすべてのリストを保持しているので、モジュールのロード時に、モ ジュールからのそうしたリソースへの参照を解決することができる。 Linux では、モジュールをスタック化できる。すなわち、あるモジュールは、他のモ ジュールのサービスを前提とし、そのサービスを利用できる。たとえば、VFAT ファイルシステムモジュールは、FAT ファイルシステムのモジュールのサービ スを必要とする。VFAT ファイルシステムは、多かれ少なかれ FAT ファイルシ ステムの拡張であるからである。他のモジュールのサービスやリソースを必要 とするモジュールというのは、モジュールがカーネルそのもののサービスやリ ソースを必要とする状況と非常に類似している。必要とするサービスが、先に ロードされた他のモジュールの中だけにあるからである。個々のモジュールが ロードされると、カーネルは、カーネルシンボルテーブルを変更して、新しく ロードされたモジュールがエクスポートしたリソースやシンボルのすべてをそ こに付け加える。これが意味するのは、次のモジュールがロードされたとき、 そのモジュールは既にロードされているモジュールのサービスにアクセス出来 るということである。 モジュールをアンロードしようとするとき、カーネルはそのモジュールが使用 されていないことを知る必要があり、モジュールにこれからアンロードされる ことを知らせる方法をも必要とする。そして、その方法によって、モジュール は、カーネルから削除される前に、カーネルメモリや割り込みなどの割り当て られたシステムリソースを解放することが出来る。モジュールがアンロードさ れるとき、カーネルは、そのモジュールがカーネルシンボルテーブルにエクス ポートしたすべてのシンボルを削除する。 適切に書かれなかったために、ロードされたモジュールはオペレーティングシ ステムをクラッシュさせることが出来るということの他にも、モジュールの使 用は別の危険ももたらす。現在使用しているカーネルよりも、古いか新しいバ ージョンのカーネル用にビルドされたモジュールをロードした場合、何が起こ るだろうか?これが問題を起こすのは、たとえばモジュールがカーネルルーチ ンをコールして、誤った引数を渡した場合である。カーネルは、オプションと してではあるが、モジュールのロード時にそのモジュールを厳格にバージョン チェックすることで、この問題が起こらないようにしている。 13.1. モジュールのローディング +----module_list | | module module | +----------+ +----------+ +->| next |------------------->| next | |----------| |----------| | ref | +->| ref | |----------| | |----------| | symtab |------+ | | symtab |-------+ |----------| | | |----------| | | name |"fat" | | | name |"vfat" | |----------| | | |----------| | | size | | | | size | | |----------| | | |----------| | | addr | +----+ | | addr | +-----+ |----------| | | |----------| | | state | | symbol_table | | state | | symbol_table |----------| | +----------+ | |----------| | +----------+ |*cleanup()| +->| size | | |*cleanup()| +->| size | +----------+ |----------| | +----------+ |----------| | n_symbols| | | n_symbols| |----------| | |----------| | n_refs | | | n_refs | |----------| | |----------| | | | | | | symbols | | | symbols | | | | | | |----------| | |----------| |references|-+ |references| | | | | +----------+ +----------+ 図表(12.1)カーネルモジュールのリスト カーネルモジュールをロードする方法はふたつある。ひとつは、insmod コマ ンドによって手動でカーネルに組み込む方法。もうひとつの、ずっと賢明なや り方は、必要に応じてモジュールを組み込む方法である。これは、デマンドロ ーディング(demand loading)と呼ばれる。ユーザがカーネル内に存在しない ファイルシステムをロードする時など、モジュールの必要性をカーネルが感知 したとき、カーネルは、カーネルデーモン(kerneld)に適切なモジュールをロ ードするようリクエストする。 カーネルデーモンは、普通のユーザプロセスなのだが、スーパーユーザの権限 を持っている。通常はシステム起動時に実行が開始されるが、それが実行され ると、カーネルデーモンはカーネルに対するプロセス間通信(IPC)のチャンネ ルを開く。このリンクはカーネルによって使用され、kerneld に対して様々な タスクの実行を依頼するためのメッセージが送信される。 [see: include/linux/kerneld.h] kerneld の主要な機能は、カーネルモジュールのロードとアンロードだが、他 のタスクの処理も可能である。たとえば、必要なときにシリアル回線上で PPP リンクを開始し、必要がなくなったらそれを切断するといったこともでき る。kerneld 自身がそうしたタスクを実行するのではなく、insmod などの必 要なプログラムを起動してそれに仕事をさせる。kerneld は、カーネルの代理 人(agent)のような存在であり、カーネルを代理して仕事のスケジューリング を行う。 insmod ユーティリティは、リクエストされたカーネルモジュールを発見し て、それをロードしなければならない。デマンドローディングでロードされた カーネルモジュールは通常 /lib/modules/kernel-version に保存される。カ ーネルモジュールは他のプログラムと同じくリンクされたオブジェクトファイ ルなのだが、リロケータブル(relocatable)イメージとしてリンクされている 点で異なる。すなわち、特定の固定的なアドレスから実行されるようにリンク されたのではないイメージであるということである。それらは a.out か ELF かのいずれかのフォーマットを取る。insmod は、特権的なシステムコールを 発行して、カーネルによってエクスポートされたシンボルを見つけだす。 [see: sys_get_kernel_syms(), in kernel/module.c] それらは、シンボル名と、そのアドレスなどの値とを含んだペアとして保存さ れる。カーネルがエクスポートしたシンボルテーブルは、カーネルが管理する モジュールのリストの最初の ``module'' データ構造体に保持され、その構造 体は、``module_list'' ポインタによってポイントされる。 [see: include/linux/module.h] カーネルのシンボルテーブルに追加されるのは、明示的に追加が指示されたシ ンボルだけである。シンボルテーブルは、カーネルのコンパイルとリンクがな される時に作成されるのだが、すべてのシンボルがモジュールにエクスポート されるわけではない。たとえば、request_irq というシンボルがあるが、これ は、ドライバが特定のシステム割り込みを制御しようとする場合に呼び出され るカーネルルーチンである。わたしの現在のカーネルでは、このシンボルは 0x0010cd30 という値を持っている。エクスポートされたカーネルシンボルと その値を簡単に見るには、/proc/ksyms を見るか、ksyms ユーティリティを使 えばよい。 ksyms ユーティリティを使えば、エクスポートされたすべてのカ ーネルシンボルを見ることも、ローダブルモジュールによってエクスポートさ れたシンボルだけを見ることもできる。insmod コマンドは、モジュールを仮 想メモリから読み出して、カーネルがエクスポートしているシンボルを使うこ とで、カーネルルーチンへの参照のなかでまだ解決されていない参照を適宜解 決(fixup)する。この解決は、メモリ内のモジュールイメージにパッチを当て るという形式を取っている。insmod は、シンボルのアドレスをモジュール内 の適切な場所に書き込む。 insmod によって、エクスポートされたカーネルシンボルに対するモジュール の参照が解決されると、insmod は、再度特権的なシステムコールを使って、 新しいカーネルを保持するのに充分なスペースをくれるようカーネルに依頼す る。カーネルは、新規の ``module'' データ構造体と、新しいモジュールを保 持するのに充分なメモリ容量を割り当てて、それをカーネルモジュールリスト の最後尾に置く。その新しいモジュールは UNINITIALIZED とマークされる。 [see: sys_create_module(), in kernel/module.c] ``図表(12.1)''では、VFAT と FAT のふたつのモジュールがカーネルにロード された後のカーネルモジュールのリストが示されている。図表では示されてい ないが、リストの先頭のモジュールは pseudo-module となっていて、これは カーネルのエクスポートされたシンボルのテーブルを保持するためだけにそこ に置かれているものである。lsmod コマンドを使えば、ロードされているすべ てのモジュールとその依存関係を表示することができる。lsmod は単に /proc/modules を再構成しただけのもので、/proc/modules の方は、カーネル の module データ構造体のリストから作成されたものである。モジュール用に 割り当てられたメモリは、 insmod がモジュールにアクセスできるよ う、insmod プロセスのアドレス空間にマップされる。 insmod は、モジュー ルを割り当てられたアドレス空間にコピーしてモジュールの置き場所を変更す ることで、割り当てを受けたカーネルアドレス空間からモジュールが実行され るようにする。こうした処理が必要なのは、異なる Linux システム上ではモ ジュールを同じアドレスにロードするよう要求できないだけでなく、同一シス テム上でも同じアドレスに二度ロードするよう要求することはできないからで ある。このアドレスの変更の際には、モジュールイメージが適切なアドレスを 持つように修正がなされる。 新しいモジュールもカーネルにシンボルをエクスポートするので、insmod は エクスポートされるイメージのテーブルを作成しなければならない。すべての カーネルモジュールには、モジュール初期化と削除のルーチンが含まれていな ければならず、それらのシンボルはわざとエクスポートされないのだ が、insmod は、カーネルにそのルーチンのアドレスを渡さなければならない ので、それらのアドレスを知っていなければならない。これらすべてが上手く いった場合、insmod はモジュールを初期化する準備が整ったため、特権的な システムコールを発行して、カーネルに対して、モジュールの初期化ルーチン と削除ルーチンのアドレスを渡す。 [see: sys_init_module(), in kernel/module.c] 新しいモジュールがカーネルに組み込まれるとき、カーネルの一連のシンボル が更新されなければならず、新規モジュールによって利用されることになるモ ジュールに対しても修正が加えられなければならない。他のモジュールから依 存されているモジュールは、そのシンボルテーブルの末尾にある参照リストを 管理しなければならず、その参照リストは自己の ``module'' データ構造体に よってポイントされなければならない。 ``図表(12.1)''では、VFAT ファイル システムモジュールは、FAT ファイルシステムモジュールに依存している。し たがって、FAT モジュールには、VFAT モジュールへの参照が含まれていなけ ればならない。その参照は、VFAT モジュールがロードされたときに付け加え られる。カーネルはモジュール初期化ルーチンを呼び出し、それが成功した場 合、モジュールのインストールを行う。モジュールの削除ルーチンのアドレス は、そのモジュールの module データ構造体に保存され、モジュールがアンロ ードされるときに、カーネルによって呼び出される。最後に、モジュールの状 態が RUNNING にセットされる。 13.2. モジュールのアンロード モジュールは rmmod コマンドを使って削除することが可能であるが、オンデ マンドでロードされたモジュールは使用されなくなったとき kerneld によっ て自動的にシステムから削除される。アイドルタイマー(idle timer)が時間切 れになった際はいつも、kerneld はシステムコールを発行し、オンデマンドで ロードされたモジュールのなかで使用されなくなったものすべてをシステムか ら削除するようリクエストする。タイマーの値は、kerneld を始動した時に セットされる。わたしの kerneld のタイマー設定は 180 秒にしてある。たと えば、iso9660 CD-ROM をマウントして、その iso9660 ファイルシステムがロ ーダブルモジュールであった場合、CD-ROM がアンマウントされてからしばら くすると、iso9660 モジュールはカーネルから削除される。 他のカーネルの一部が、あるモジュールに依存している間は、そのモジュール はアンロードできない。たとえば、ひとつ以上の VFAT ファイルシステムをマ ウントしている場合、VFAT モジュールはアンマウントできない。lsmod の出 力を見れば、個々のモジュールが依存関係についてもカウントを持っているこ とが分かる。 Module: #pages: Used by: msdos 5 1 vfat 4 1 (autoclean) fat 6 [vfat msdos] 2 (autoclean) カウントは、そのモジュールに依存しているカーネル実体(entity)の数を表し ている。上記の例では、vfat と msdos モジュールはどちらも fat モジュー ルに依存しているので、fat のカウントは 2 となっている。vfat と msdos モジュールは両方ともそれに依存する 1 つの実体を持っているが、その実体 とはマウントされたファイルシステムである。もし、わたしがもうひとつ VFAT ファイルシステムをマウントしたとすると、 vfat モジュールのカウン トは 2 になるだろう。モジュールのカウントは、イメージの最初のロングワ ード(longword)の中に保持されている。 そのフィールドには、AUTOCLEAN と VISITED フラグも保持されるので、やや 情報過多ともいえるフィールドになっている。これらふたつのフラグは、デマ ンドローディングでロードされたモジュールに対して使用される。そうしたモ ジュールが AUTOCLEAN とマークされるのは、システムがそのモジュールを自 動的にアンロードする対象と認識できるようにするためである。 VISITED フ ラグは、ひとつ以上のシステムコンポーネントがそのモジュールを使っている ことを示している。VISITED フラグは、他のコンポーネントがそのモジュール と使うときは必ずセットされる。オンデマンドでロードされたが使用されなく なったモジュールを削除するかどうかを kerneld がシステムに尋ねたとき は、システムはシステム上のすべてのモジュールを見回して、使用されなく なったという条件に該当するものを捜す。システムは AUTOCLEAN とマークさ れていて RUNNING 状態にあるモジュールだけを見る。該当するモジュールが VISITED フラグをクリアした場合、システムはそのモジュールを削除するが、 モジュールがフラグをクリアしない場合は、システムがその VISITED フラグ をクリアして、システム上の次のモジュールの調査に移る。 モジュールがアンロード可能な場合、そのモジュールの削除ルーチンが呼び出 され、そのルーチンが、モジュールに割り当てられたカーネルリソースを解放 する。 [see: sys_delete_module(), in kernel/module.c] その ``module'' データ構造体は DELETED とマークされ、カーネルモジュー ルのリストから削除される。(削除されたモジュールが依存していた)他のモ ジュールは、削除モジュールがもはや依存するものではなくなったので、参照 リストを修正しないといけない。削除モジュールが必要としていたカーネルメ モリはすべて割り当てを解放される。 14. プロセッサ Linux は、多数のプロセッサ上で実行されている。この章では、それらのいく つかについて簡単なアウトラインを紹介する。 14.1. x86 TBD (訳注: 原文に解説はありません。``bibliography の 9'' を参考にしてくだ さい。) 14.2. ARM ARM は、省電力、ハイパフォーマンスの 32 ビット RISC アーキテクチャを実 装するプロセッサである。これは、携帯電話や PDA(Personal Data Assistants) などの組み込みデバイスで広く使用されている。ARM は、31 個 の 32 ビットレジスタを持っていて、そのうち 16 個はどのモードでも使用可 能である。このプロセッサの命令(instructions)は、シンプルなロードストア 命令(メモリから値をロードし、操作を実行し、その結果をメモリに戻す)であ る。このプロセッサが持つひとつの興味深い特徴は、すべての命令が条件命 令(conditional)であることである。たとえば、レジスタの値をテストした場 合、次に同じ条件でテストするまで、好きなだけ好きな時に、条件的に命令を 実行することができる。それ以外の興味深い特徴としては、値をロードすると きに、演算処理とシフト処理とが出来ることである。またいくつかのモードで 実行可能であり、このプロセッサのシステムモードは、ユーザモードから SWI(software interrupt)経由で入ることが出来る。 ARM は、統合可能(synthasiable)コアを使用している。しかし、ARM (会社 名)自体がこのプロセッサを製造しているわけではない。ARM の提携会社(たと えば、Intel や LSI といった会社)が、ARM の設計したアーキテクチャをシリ コンに実装している。このプロセッサは、コプロセッサ(co-processor)のイン ターフェイス経由で他のプロセッサとも緊密に連携させることができるので、 メモリ管理ユニットのバリエーションが多数ある。それらは、シンプルなメモ リ保護スキームから複雑なページ階層のものまで幅広くカバーしている。 14.3. Alpha AXP プロセッサ Alpha AXP のアーキテクチャは、64 ビットのロード・ストア型の RISC アー キテクチャであり、処理速度重視で設計されている。すべてのレジスタは 64 ビット長であり、32 個の整数レジスタと 32 個の浮動小数点レジスタを持 つ。整数レジスタ 31 番と浮動小数点レジスタ 31 番とは、ヌル(null)オペレ ーションのために使用される。すなわち、それらのレジスタからの読み出しは 0 を生成し、それらへの書き込みは何の効果ももたらさない。すべての命令は 32 ビット長であり、メモリオペレーションは読み出しか書き込みかのどちら かである。このアーキテクチャは、実装がアーキテクチャに準拠したものであ る限り、異なる実装も許している。 メモリにストアされた値を直接操作する命令は存在しない。すべてのデータ操 作はレジスタ間で行われる。それゆえ、メモリのカウンタの値をインクリメン トしたい場合、まずそれをレジスタに読み出して、それを修正し、メモリに書 き込まなければならない。ある命令がレジスタかメモリ内のどこかの位置に書 き込みを行い、別の命令がそのレジスタかそのメモリ位置を読み出した場合に のみ、相互の命令が干渉しあってしまう。 Alpha AXP の興味深い特徴のひと つは、フラグを生成できる命令があることである。たとえば、ふたつのレジス タ値が同じであるかテストし、その結果はプロセッサのステータスレジスタに はストアされないが、代わりに第三のレジスタに結果をストアするといったも のである。これは一見奇妙に思えるかもしれないが、ステータスレジスタへの 依存を取り除くことによって、個々のサイクルごとに多重命令(multiple instruction)を発することが可能な CPU の製造を、非常に容易にする。相互 に関係のないレジスタ上での命令は、互いの命令の結果を待つことなく、実行 される。もしステータスレジスタがひとつしかない場合、それにはもっと時間 がかかるだろう。メモリを直接操作する命令がないこと、レジスタ数が非常に 多いことも、多重命令の発行を容易にするものである。 Alpha AXP アーキテクチャは、一連のサブルーチンを使用するが、それは PALcode(privileged architecture library code)と呼ばれる。 PALcode は、Alpha AXP アーキテクチャの CPU 実装なので、オペレーティングシステ ムやハードウェアごとに異なったものとなる。それらのサブルーチンは、オペ レーティングシステムにコンテキストスイッチングや割り込み、例外処理やメ モリ管理のプリミティブを提供するものである。それらのサブルーチンは、ハ ードウェアか CALL_PAL 命令によって呼び出し可能である。 PALcode は、標 準的な Alpha AXP アセンブラと、いくつかの特定実装に固有の拡張を付け加 えて書かれたもので、たとえば、プロセッサの内部レジスタなどの低レベルの ハードウェア機能への直接的なアクセス手段を提供している。 PALcode は PALmode で実行されるが、このモードは特権モードであり、いくつかのシステ ムイベントの発生を停止して、PALcode が物理的なシステムハードウェアを完 全に制御することを許すものである。 15. Linux カーネルソース この章では、特定のカーネル関数を見たいとき、カーネルソースのどこから探 し始めればよいのかについて説明する。 本書は、C プログラミング言語の知識を前提とするものではなく、カーネルの 動作の仕組みを理解するのに Linux カーネルソースを持っていることを要求 するものでもない。とはいえ、カーネルソースを見て、Linux オペレーティン グシステムへの理解を深める努力をすることはよい結果をもたらすことだろ う。この章では、カーネルソースの概観を説明し、それらの構造や特定のコー ドを探す時の目安となるものについて述べる。 15.1. Linux カーネルソースの入手場所 メジャーな Linux ディストリビューション( Craftworks, Debian, Slackware, Red Hat などなど)ならどこでも、カーネルソースが同梱されてい る。通常、システムにインストールされた Linux カーネルは、そうしたソー スからビルドされている。やむを得ない事情から、それらのソースは少々古い ものになりがちなので、 ``「ウェブと FTP サイト」''で述べるウェブサイト から最新のソースを入手したいと考えるかもしれない。ソースコードは、 ftp://ftp.cs.helsinki.fi で保存されていて、それ以外のウェブサイトはす べてそのミラーである。 (訳注: 現在のプライマリサイト は、http://www.kernel.org です。) それゆえ、Helsinki のウェブサイトに 最新のソースがあることになるが、MIT や Sunsite でも決してひどく古いわ けではない。 ウェブにアクセスできない場合でも、多くの CD-ROM ベンダがあり、それらが 世界のメジャーなウェブサイトのスナップショットを妥当な値段で提供してい る。四半期や、あるいは毎月更新される定期購買サービスを提供しているベン ダまである。ローカルな Linux ユーザグループも、ソースコードの提供元で ある。 Linux カーネルソースは、非常にシンプルなバージョン番号管理をしている。 中央の番号、x.y.z の y が、偶数番号のカーネル(たとえば、 2.0.30)は、安 定した、リリース版カーネルで、奇数番号のカーネル(たとえば、2.1.42) は、開発版カーネルである。本書は、安定版 2.0.30 のソースツリーに基づい ている。開発版カーネルは最新機能のすべてを備え、最新デバイスをすべてサ ポートしている。それらは安定しない場合があるかもしれず、それはあなたが Linux に求めるものではないかもしれないが、Linux コミュニティーが最新版 カーネルを試しているということは重要である。それによって、最新版はコ ミュニティー全体でテストされるからである。安定版でないカーネルを使う場 合、システム全体のバックアップは常に価値あるものであることを忘れないで ほしい。 カーネルソースへの変更は、パッチファイルで配布される。パッチユーティリ ティは、ソースファイルのセットに一連の編集を加えるために使用される。た とえば、 2.0.29 のカーネルソースツリーを持っていて、2.0.30 のソースツ リーに移行したい場合は、2.0.30 のパッチを入手して、そのパッチ(ソース編 集スクリプト)をそのカーネルソースに適用すればよい。 $ cd /usr/src/linux $ patch -p1 < patch-2.0.30 そうすれば、ソースツリー全体を、おそらく低速なシリアル回線を使ってコピ ーしなくても済む。カーネルパッチ(正式版もそうでないものも)の便利な入手 先は、 http://www.linuxhq.web.site である。 15.2. カーネルソースの構造 ソースツリーの最上ディレクトリである /usr/src/linux には、 (次のよう な)多くのディレクトリがある。 <#if output="html"> (訳注: JF サイト上でご覧の場合、一部のファイルしか表示されません。ソー ス全体を見るには、``日本語訳について''を参照してください。) <#/if> arch arch サブディレクトリには、アーキテクチャ(architecture)固有のカ ーネルコードが含まれている。さらに深いサブディレクトリがあり、た とえば、i386 や alpha といったサポートするアーキテクチャごとに分 かれている。 include include サブディレクトリには、カーネルコードをビルドするのに必要 なインクルードファイル(include files)の大部分が含まれている。さ らに深いサブディレクトリもあり、サポートするアーキテクチャごとの ファイルが含まれている。include/asm サブディレクトリは、たとえば include/asm-i386 といったそのアーキテクチャに必要な実際の include ディレクトリへのソフトリンク(シンボリックリンク)となって いる。アーキテクチャを変更するには、カーネルの makefile を編集 し、Linux カーネル設定プログラムを再実行する必要がある。 init このディレクトリには、カーネルの初期化(initialization)コードが含 まれているので、カーネルの動作の仕組みをこれから見ようとする場合 はよい出発点になる。 mm このディレクトリには、メモリ管理(memory management)コードのすべ てが含まれている。アーキテクチャ固有のメモリ管理コードについて は、arch/i386/mm/fault.c といった arch/*/mm ディレクトリ以下にあ る。 drivers システム上のデバイスドライバ(device drivers)は、すべてこのディレ クトリに置かれている。このディレクトリはさらに再分割され、block といったデバイスドライバのクラスごとに分かれている。 ipc このディレクトリには、カーネルのプロセス間通信(inter-process communications)に関するコードが含まれている。 modules このディレクトリは、ビルドされたモジュール(module)を保存するため だけに使用されている。 fs ファイルシステム(file system)コードのすべてはここに置かれてい る。このディレクトリはさらに再分割されていて、vfat や ext2 と いったサポートするファイルシステムごとに分かれている。 kernel 主要なカーネル(kernel)コードが置かれている。ここでも、アーキテク チャ固有のカーネルコードは、arch/*/kernel にある。 net カーネルのネットワーク(network)関係のコードが置かれている。 lib このディレクトリには、カーネルのライブラリ(library)コードが含ま れている。アーキテクチャ固有のライブラリは、arch/*/lib で見つけ ることができる。 scripts このディレクトリには、カーネルを設定するときに使用されるスクリプ ト(script) (たとえば、awk や tk スクリプト)が含まれている。 15.3. どこから見るべきか Linux カーネルくらい巨大で複雑なプログラムになると、見ようとする者を威 圧するかのような感がなくもない。まるで辿るべき一端がなくて、解けない球 状の巻き糸のようである。カーネルの一部を見ていると、しばしばいくつかの 他の関連ファイルへと導かれて、すぐに自分で何を探していたのか分からなく なる。以下では、節に付けた題名について、ソースツリーのどこを見れば最良 かヒントを述べる。 15.3.1. システムの起動と初期化 Intel ベースのシステムなら、カーネルが起動するのは、loadlin.exe か LILO がカーネルをメモリにロードして制御をカーネルに渡したときである。 その部分に関しては、arch/i386/kernel/head.S を見ること。head.S はアー キテクチャ固有の設定をいくつか行ってから、init/main.c 内にある main() ルーチンへジャンプする。 15.3.2. メモリ管理 これに関するコードの大部分は mm にあるが、アーキテクチャ固有のコードは arch/*/mm にある。ページフォルト処理コードは mm/memory.c にあり、メモ リマッピングとページキャッシュのコードは mm/filemap.c にある。バッファ キャッシュは、mm/buffer.c(訳注: fs/buffer.c) で実装されており、スワッ プキャッシュは mm/swap_state.c と mm/swapfile.c にある。 15.3.3. カーネル 関連する汎用コードの大部分は、kernel にあるが、アーキテクチャ固有のコ ードは arch/*/kernel にある。スケジューラは kernel/sched.c にあ り、fork のコードは kernel/fork.c にある。ボトムハーフ処理のコードは include/linux/interrupt.h に含まれている。 task_struct データ構造体 は、include/linux/sched.h にある。 15.3.4. PCI PCI 仮想ドライバは drivers/pci/pci.c にあり、システム全体の定義は include/linux/pci.h にある。個々のアーキテクチャには固有の PCI BIOS コ ードがあり、Alpha AXP のものは arch/alpha/kernel/bios32.c にある。 15.3.5. プロセス間通信 これらはすべて ipc にある。すべての System V IPC オブジェクトは ipc_perm データ構造体を含んでいるが、これは include/linux/ipc.h にあ る。System V メッセージは ipc/msg.c で実装されていて、共有メモリは ipc/shm.c 、セマフォは ipc/sem.c にある。パイプは、ipc/pipe(訳注: fs/pipe.c) で実装されている。 15.3.6. 割り込み処理 カーネルの割り込み処理コードは、ほとんどすべてマイクロプロセッサ(そし て、しばしばプラットフォーム)固有のものである。Intel の割り込み処理コ ードは、 arch/i386/kernel/irq.c にあり、その定義は include/asm- i386/irq.h にある。 15.3.7. デバイスドライバ Linux カーネルソースコードの大部分は、デバイスドライバのものである。 Linux デバイスドライバのソースのすべては、drivers にあるが、そのディレ クトリはデバイスタイプによってさらに分岐している。 /block IDE(ide.c) などのブロックデバイスドライバ。ファイルシステムを置 くことができるすべてのデバイスがどのように初期化されるのかを見た い場合は、drivers/block/genhd.c にある device_setup() を見るべき である。それは、ハードディスクの初期化だけでなく、NFS ファイルシ ステムをマウントするためにネットワークを必要とする際にはネットワ ークデバイスをも初期化する。ブロックデバイスには、IDE ベースと SCSI ベースの両方のデバイスが含まれる。 /char ここは、tty やシリアルポート、マウスなどのキャラクタベースのデバ イスを探すときに見る場所である。 /cdrom Linux 上のすべての CD-ROM のコード。特定の CD-ROM デバイス(たと えば、 Soundblaster CD-ROM)がここに置かれている。IDE CD ドライバ は drivers/block の ide-cd.c にあり、SCSI CD ドライバは drivers/scsi の scsi.c にあるので、注意すること。 /pci ここには、PCI 仮想ドライバのためのソースがある。PCI サブシステム がマップされ初期化される方法を見るのに適した場所であ る。arch/alpha/kernel/bios32.c にある Alpha AXP PCI 修 正(fixup)コードも見るに値するだろう。 /scsi ここは、SCSI コードのすべてがある場所であり、Linux でサポートさ れている SCSI デバイスのすべてのドライバが置かれている場所であ る。 /net ここは、DEC Chip 21040 PCI イーサネットドライバ(tulip.c)といった ネットワークデバイスドライバが見つかる場所である。 /sound ここは、すべてのサウンドカードドライバがある場所である。 15.3.8. ファイルシステム EXT2 ファイルシステムのソースはすべて、fs/ext2 ディレクトリにあり、デ ータ構造の定義は、include/linux/ext2_fs.h と ext2_fs_i.h, ext2_fs_sb.h にある。仮想ファイルシステムのデータ構造体は、 include/linux/fs.h に記 述されており、そのコードは fs/* にある。バッファキャッシュは、update デーモンと一緒に fs/buffer.c で実装されている。 15.3.9. ネットワーク ネットワークのコードは、net に保存されていて、その大部分の include ファイルは include/net にある。BSD ソケットコードは、net/socket.c にあ り、IP version 4 INET ソケットコードは、net/ipv4/af_inet.c にある。汎 用のプロトコルサポートコード(sk_buff 処理ルーチンなど)は、 net/core に あり、TCP/IP ネットワークのコードは net/ipv4 にある。ネットワークデバ イスドライバは、drivers/net にある。 15.3.10. モジュール カーネルモジュールコードは、カーネル内にある部分とその modules のパッ ケージに入っている部分とがある。カーネルコードはすべて kernel/module.c にあり、そのデータ構造とカーネルデーモンである kerneld のメッセージ は、include/linux/module.h と include/linux/kerneld.h とにそれぞれ入っ ている。 ELF オブジェクトファイルの構造は、include/linux/elf.h で見る ことができる。 16. Linux データ構造 この章では、主要なデータ構造体を列挙している。これらは、Linux で使用さ れ、本書においても述べられているものである。これらの構造体には、本書で の体裁を整えるために、若干の編集が加えられている。 16.1. arp_table(訳注) [in net/ipv4/arp.c] struct arp_table { struct arp_table *next; /* Linked entry list */ unsigned long last_used; /* For expiry */ unsigned long last_updated; /* For expiry */ unsigned int flags; /* Control status */ u32 ip; /* ip address of entry */ u32 mask; /* netmask - used for generalised proxy arps (tridge)*/ unsigned char ha[MAX_ADDR_LEN]; /* Hardware address */ struct device *dev; /* Device the entry is tied to */ struct hh_cache *hh; /* Hardware headers chain */ /* * The following entries are only used for unresolved hw addresses. */ struct timer_list timer; /* expire timer */ int retries; /* remaining retries */ struct sk_buff_head skb; /* list of queued packets */ }; 16.2. blk_dev_struct blk_dev_struct データ構造体が使用されるのは、バッファキャッシュ経由で ブロックデバイスを利用できるように、ブロックデバイスを登録するためであ る。それらは、blk_dev 配列内に保持される。 [in include/linux/blkdev.h] struct blk_dev_struct { void (*request_fn)(void); struct request * current_request; struct request plug; struct tq_struct plug_tq; }; ... ... extern struct blk_dev_struct blk_dev[MAX_BLKDEV]; 16.3. buffer_head buffer_head データ構造体は、バッファキャッシュにあるブロックバッファの 情報を保持するためのものである。 [in include/linux/fs.h] /* bh state bits */ #define BH_Uptodate 0 /* 1 if the buffer contains valid data */ #define BH_Dirty 1 /* 1 if the buffer is dirty */ #define BH_Lock 2 /* 1 if the buffer is locked */ #define BH_Req 3 /* 0 if the buffer has been invalidated */ #define BH_Touched 4 /* 1 if the buffer has been touched (aging) */ #define BH_Has_aged 5 /* 1 if the buffer has been aged (aging) */ #define BH_Protected 6 /* 1 if the buffer is protected */ #define BH_FreeOnIO 7 /* 1 to discard the buffer_head after IO */ struct buffer_head { /* First cache line: */ unsigned long b_blocknr; /* block number */ kdev_t b_dev; /* device (B_FREE = free) */ kdev_t b_rdev; /* Real device */ unsigned long b_rsector; /* Real buffer location on disk */ struct buffer_head *b_next; /* Hash queue list */ struct buffer_head *b_this_page; /* circular list of buffers in one page */ /* Second cache line: */ unsigned long b_state; /* buffer state bitmap (above) */ struct buffer_head *b_next_free; unsigned int b_count; /* users using this block */ unsigned long b_size; /* block size */ /* Non-performance-critical data follows. */ char *b_data; /* pointer to data block */ unsigned int b_list; /* List that this buffer appears */ unsigned long b_flushtime; /* Time when this (dirty) buffer * should be written */ unsigned long b_lru_time; /* Time when this buffer was * last used. */ struct wait_queue *b_wait; struct buffer_head *b_prev; /* doubly linked hash list */ struct buffer_head *b_prev_free; /* doubly linked list of buffers */ struct buffer_head *b_reqnext; /* request queue */ }; 16.4. device システム上のすべてのネットワークデバイスは、device データ構造体によっ て表現される。 [in include/linux/netdevice.h] struct device { /* * This is the first field of the "visible" part of this structure * (i.e. as seen by users in the "Space.c" file). It is the name * the interface. */ char *name; /* I/O specific fields */ unsigned long rmem_end; /* shmem "recv" end */ unsigned long rmem_start; /* shmem "recv" start */ unsigned long mem_end; /* shared mem end */ unsigned long mem_start; /* shared mem start */ unsigned long base_addr; /* device I/O address */ unsigned char irq; /* device IRQ number */ /* Low-level status flags. */ volatile unsigned char start, /* start an operation */ interrupt; /* interrupt arrived */ unsigned long tbusy; /* transmitter busy */ struct device *next; /* The device initialization function. Called only once. */ int (*init)(struct device *dev); /* Some hardware also needs these fields, but they are not part of the usual set specified in Space.c. */ unsigned char if_port; /* Selectable AUI,TP, */ unsigned char dma; /* DMA channel */ struct enet_statistics* (*get_stats)(struct device *dev); /* * This marks the end of the "visible" part of the structure. All * fields hereafter are internal to the system, and may change at * will (read: may be cleaned up at will). */ /* These may be needed for future network-power-down code. */ unsigned long trans_start; /* Time (jiffies) of last transmit */ unsigned long last_rx; /* Time of last Rx */ unsigned short flags; /* interface flags (BSD)*/ unsigned short family; /* address family ID */ unsigned short metric; /* routing metric */ unsigned short mtu; /* MTU value */ unsigned short type; /* hardware type */ unsigned short hard_header_len; /* hardware hdr len */ void *priv; /* private data */ /* Interface address info. */ unsigned char broadcast[MAX_ADDR_LEN]; unsigned char pad; unsigned char dev_addr[MAX_ADDR_LEN]; unsigned char addr_len; /* hardware addr len */ unsigned long pa_addr; /* protocol address */ unsigned long pa_brdaddr; /* protocol broadcast addr*/ unsigned long pa_dstaddr; /* protocol P-P other addr*/ unsigned long pa_mask; /* protocol netmask */ unsigned short pa_alen; /* protocol address len */ struct dev_mc_list *mc_list; /* M'cast mac addrs */ int mc_count; /* No installed mcasts */ struct ip_mc_list *ip_mc_list; /* IP m'cast filter chain */ __u32 tx_queue_len; /* Max frames per queue */ /* For load balancing driver pair support */ unsigned long pkt_queue; /* Packets queued */ struct device *slave; /* Slave device */ struct net_alias_info *alias_info; /* main dev alias info */ struct net_alias *my_alias; /* alias devs */ /* Pointer to the interface buffers. */ struct sk_buff_head buffs[DEV_NUMBUFFS]; /* Pointers to interface service routines. */ int (*open)(struct device *dev); int (*stop)(struct device *dev); int (*hard_start_xmit) (struct sk_buff *skb, struct device *dev); int (*hard_header) (struct sk_buff *skb, struct device *dev, unsigned short type, void *daddr, void *saddr, unsigned len); int (*rebuild_header)(void *eth, struct device *dev, unsigned long raddr, struct sk_buff *skb); void (*set_multicast_list)(struct device *dev); int (*set_mac_address)(struct device *dev, void *addr); int (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd); int (*set_config)(struct device *dev, struct ifmap *map); void (*header_cache_bind)(struct hh_cache **hhp, struct device *dev, unsigned short htype, __u32 daddr); void (*header_cache_update)(struct hh_cache *hh, struct device *dev, unsigned char * haddr); int (*change_mtu)(struct device *dev, int new_mtu); struct iw_statistics* (*get_wireless_stats)(struct device *dev); }; ... ... extern struct device *dev_base; 16.5. device_struct device_struct データ構造体は、キャラクタデバイスとブロックデバイスを登 録するために使用される。(それらは、デバイス名と、そのデバイスに使用で きるファイル操作ルーチンのセットとを保持する。) chrdevs と blkdevs 配 列にある個々の有効な番号は、キャラクタデバイスとブロックデバイスとをそ れぞれに表したものである。 [in fs/devices.c] struct device_struct { const char * name; struct file_operations * fops; }; (訳注) static struct device_struct chrdevs[MAX_CHRDEV] = { { NULL, NULL }, }; static struct device_struct blkdevs[MAX_BLKDEV] = { { NULL, NULL }, }; 16.6. dma_chan(訳注) [in kernel/dma.c] struct dma_chan { int lock; const char *device_id; }; 16.7. ext2_inode(訳注) [in include/linux/ext2_fs.h] struct ext2_inode { __u16 i_mode; /* File mode */ __u16 i_uid; /* Owner Uid */ __u32 i_size; /* Size in bytes */ __u32 i_atime; /* Access time */ __u32 i_ctime; /* Creation time */ __u32 i_mtime; /* Modification time */ __u32 i_dtime; /* Deletion Time */ __u16 i_gid; /* Group Id */ __u16 i_links_count; /* Links count */ __u32 i_blocks; /* Blocks count */ __u32 i_flags; /* File flags */ union { struct { __u32 l_i_reserved1; } linux1; struct { __u32 h_i_translator; } hurd1; struct { __u32 m_i_reserved1; } masix1; } osd1; /* OS dependent 1 */ __u32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ __u32 i_version; /* File version (for NFS) */ __u32 i_file_acl; /* File ACL */ __u32 i_dir_acl; /* Directory ACL */ __u32 i_faddr; /* Fragment address */ union { struct { __u8 l_i_frag; /* Fragment number */ __u8 l_i_fsize; /* Fragment size */ __u16 i_pad1; __u32 l_i_reserved2[2]; } linux2; struct { __u8 h_i_frag; /* Fragment number */ __u8 h_i_fsize; /* Fragment size */ __u16 h_i_mode_high; __u16 h_i_uid_high; __u16 h_i_gid_high; __u32 h_i_author; } hurd2; struct { __u8 m_i_frag; /* Fragment number */ __u8 m_i_fsize; /* Fragment size */ __u16 m_pad1; __u32 m_i_reserved2[2]; } masix2; } osd2; /* OS dependent 2 */ }; 16.8. ext2_inode_info(訳注) [in include/linux/ext2_fs_i.h] struct ext2_inode_info { __u32 i_data[15]; __u32 i_flags; __u32 i_faddr; __u8 i_frag_no; __u8 i_frag_size; __u16 i_osync; __u32 i_file_acl; __u32 i_dir_acl; __u32 i_dtime; __u32 i_version; __u32 i_block_group; __u32 i_next_alloc_block; __u32 i_next_alloc_goal; __u32 i_prealloc_block; __u32 i_prealloc_count; int i_new_inode:1; /* Is a freshly allocated inode */ }; 16.9. fib_info(訳注) [in net/ipv4/route.c] struct fib_info { struct fib_info *fib_next; struct fib_info *fib_prev; __u32 fib_gateway; struct device *fib_dev; int fib_refcnt; unsigned long fib_window; unsigned short fib_flags; unsigned short fib_mtu; unsigned short fib_irtt; }; 16.10. fib_node(訳注) [in net/ipv4/route.c] struct fib_node { struct fib_node *fib_next; __u32 fib_dst; unsigned long fib_use; struct fib_info *fib_info; short fib_metric; unsigned char fib_tos; }; 16.11. fib_zone(訳注) [in net/ipv4/route.c] struct fib_zone { struct fib_zone *fz_next; struct fib_node **fz_hash_table; struct fib_node *fz_list; int fz_nent; int fz_logmask; __u32 fz_mask; }; 16.12. file オープンされたファイル、ソケット等は、それぞれ file データ構造体によっ て表される。 [in include/linux/fs.h] struct file { mode_t f_mode; loff_t f_pos; unsigned short f_flags; unsigned short f_count; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct file *f_next, *f_prev; int f_owner; /* pid or -pgrp where SIGIO should be sent */ struct inode * f_inode; struct file_operations * f_op; unsigned long f_version; void *private_data; /* needed for tty driver, and maybe others */ }; 16.13. files_struct file_struct データ構造体は、プロセスがオープンしたファイルについて記述 するものである。 [in include/linux/sched.h] struct files_struct { int count; fd_set close_on_exec; fd_set open_fds; struct file * fd[NR_OPEN]; }; [in include/linux/fs.h] #define NR_OPEN 256 16.14. file_system_type(訳注) [in include/linux/fs.h] struct file_system_type { struct super_block *(*read_super) (struct super_block *, void *, int); const char *name; int requires_dev; struct file_system_type * next; }; [in fs/super.c] static struct file_system_type *file_systems = (struct file_system_type *) NULL; 16.15. free_area(訳注) [in mm/page_alloc.c] /* * Free area management * * The free_area_list arrays point to the queue heads * of the free areas of different sizes */ #define NR_MEM_LISTS 6 /* The start of this MUST match the start of "struct page" */ struct free_area_struct { struct page *next; struct page *prev; unsigned int * map; }; #define memory_head(x) ((struct page *)(x)) static struct free_area_struct free_area[NR_MEM_LISTS]; (訳注: 参考 linux-2.0.11) struct free_area_struct { struct page list; unsigned int * map; }; 16.16. fs_struct [in include/linux/sched.h] struct fs_struct { int count; unsigned short umask; struct inode * root, * pwd; }; 16.17. gendisk gendisk データ構造体は、ハードディスクに関する情報を保持している。これ らは、初期化の過程で、ディスクが見いだされ、パーティションが検出される 際に使用される。 [in include/linux/genhd.h] struct hd_struct { long start_sect; long nr_sects; }; struct gendisk { int major; /* major number of driver */ const char *major_name; /* name of major driver */ int minor_shift; /* number of times minor is shifted to get real minor */ int max_p; /* maximum partitions per device */ int max_nr; /* maximum number of real devices */ void (*init)(struct gendisk *); /* Initialization called before we do our thing */ struct hd_struct *part; /* partition table */ int *sizes; /* device size in blocks, copied to blk_size[] */ int nr_real; /* number of real devices */ void *real_devices; /* internal use */ struct gendisk *next; }; (訳注) extern struct gendisk *gendisk_head; /* linked list of disks */ 16.18. hh_cache(訳注) [in include/linux/netdevice.h] struct hh_cache { struct hh_cache *hh_next; void *hh_arp; /* Opaque pointer, used by * any address resolution module, * not only ARP. */ int hh_refcnt; /* number of users */ unsigned short hh_type; /* protocol identifier, f.e ETH_P_IP */ char hh_uptodate; /* hh_data is valid */ char hh_data[16]; /* cached hardware header */ }; 16.19. ide_drive_t(訳注) [in drivers/block/ide.h] typedef struct ide_drive_s { special_t special; /* special action flags */ unsigned present : 1; /* drive is physically present */ unsigned noprobe : 1; /* from: hdx=noprobe */ unsigned keep_settings : 1; /* restore settings after drive reset */ unsigned busy : 1; /* currently doing revalidate_disk() */ unsigned removable : 1; /* 1 if need to do check_media_change */ unsigned using_dma : 1; /* disk is using dma for read/write */ unsigned forced_geom : 1; /* 1 if hdx=c,h,s was given at boot */ unsigned unmask : 1; /* flag: okay to unmask other irqs */ unsigned no_unmask : 1; /* disallow setting unmask bit */ unsigned no_io_32bit : 1; /* disallow enabling 32bit I/O */ unsigned nobios : 1; /* flag: do not probe bios for drive */ unsigned slow : 1; /* flag: slow data port */ unsigned autotune : 2; /* 1=autotune, 2=noautotune, 0=default */ #if FAKE_FDISK_FOR_EZDRIVE unsigned remap_0_to_1 : 1; /* flag: partitioned with ezdrive */ #endif /* FAKE_FDISK_FOR_EZDRIVE */ unsigned no_geom : 1; /* flag: do not set geometry */ ide_media_t media; /* disk, cdrom, tape, floppy */ select_t select; /* basic drive/head select reg value */ byte ctl; /* "normal" value for IDE_CONTROL_REG */ byte ready_stat; /* min status value for drive ready */ byte mult_count; /* current multiple sector setting */ byte mult_req; /* requested multiple sector setting */ byte tune_req; /* requested drive tuning setting */ byte io_32bit; /* 0=16-bit, 1=32-bit, 2/3=32bit+sync */ byte bad_wstat; /* used for ignoring WRERR_STAT */ byte sect0; /* offset of first sector for DM6:DDO */ byte usage; /* current "open()" count for drive */ byte head; /* "real" number of heads */ byte sect; /* "real" sectors per track */ byte bios_head; /* BIOS/fdisk/LILO number of heads */ byte bios_sect; /* BIOS/fdisk/LILO sectors per track */ unsigned short bios_cyl; /* BIOS/fdisk/LILO number of cyls */ unsigned short cyl; /* "real" number of cyls */ void *hwif; /* actually (ide_hwif_t *) */ struct wait_queue *wqueue; /* used to wait for drive in open() */ struct hd_driveid *id; /* drive model identification info */ struct hd_struct *part; /* drive partition table */ char name[4]; /* drive name, such as "hda" */ #ifdef CONFIG_BLK_DEV_IDECD struct cdrom_info cdrom_info; /* for ide-cd.c */ #endif /* CONFIG_BLK_DEV_IDECD */ #ifdef CONFIG_BLK_DEV_IDETAPE idetape_tape_t tape; /* for ide-tape.c */ #endif /* CONFIG_BLK_DEV_IDETAPE */ #ifdef CONFIG_BLK_DEV_IDEFLOPPY void *floppy; /* for ide-floppy.c */ #endif /* CONFIG_BLK_DEV_IDEFLOPPY */ #ifdef CONFIG_BLK_DEV_IDESCSI void *scsi; /* for ide-scsi.c */ #endif /* CONFIG_BLK_DEV_IDESCSI */ } ide_drive_t; 16.20. ide_hwif_t(訳注) [in drivers/block/ide.h] typedef struct hwif_s { struct hwif_s *next; /* for linked-list in ide_hwgroup_t */ void *hwgroup; /* actually (ide_hwgroup_t *) */ unsigned short io_base; /* base io port addr */ unsigned short ctl_port; /* usually io_base+0x206 */ ide_drive_t drives[MAX_DRIVES]; /* drive info */ struct gendisk *gd; /* gendisk structure */ ide_tuneproc_t *tuneproc; /* routine to tune PIO mode for drives */ #if defined(CONFIG_BLK_DEV_HT6560B) || defined(CONFIG_BLK_DEV_PROMISE) ide_selectproc_t *selectproc; /* tweaks hardware to select drive */ #endif ide_dmaproc_t *dmaproc; /* dma read/write/abort routine */ unsigned long *dmatable; /* dma physical region descriptor table */ unsigned short dma_base; /* base addr for dma ports (triton) */ byte irq; /* our irq number */ byte major; /* our major number */ char name[5]; /* name of interface, eg. "ide0" */ byte index; /* 0 for ide0; 1 for ide1; ... */ hwif_chipset_t chipset; /* sub-module for tuning.. */ unsigned noprobe : 1; /* don't probe for this interface */ unsigned present : 1; /* this interface exists */ unsigned serialized : 1; /* serialized operation with mate hwif */ unsigned sharing_irq: 1; /* 1 = sharing irq with another hwif */ #ifdef CONFIG_BLK_DEV_PROMISE unsigned is_promise2: 1; /* 2nd i/f on promise DC4030 */ #endif /* CONFIG_BLK_DEV_PROMISE */ #if (DISK_RECOVERY_TIME > 0) unsigned long last_time; /* time when previous rq was done */ #endif #ifdef CONFIG_BLK_DEV_IDECD struct request request_sense_request; /* from ide-cd.c */ struct packet_command request_sense_pc; /* from ide-cd.c */ #endif /* CONFIG_BLK_DEV_IDECD */ #ifdef CONFIG_BLK_DEV_IDETAPE ide_drive_t *tape_drive; /* Pointer to the tape on this interface */ #endif /* CONFIG_BLK_DEV_IDETAPE */ } ide_hwif_t; ... ... #ifndef _IDE_C extern ide_hwif_t ide_hwifs[]; /* master data repository */ #endif 16.21. inode VFS inode データ構造体は、ディスク上のファイルやディレクトリの情報を保 持するものである。 [in include/linux/fs.h] struct inode { kdev_t i_dev; unsigned long i_ino; umode_t i_mode; nlink_t i_nlink; uid_t i_uid; gid_t i_gid; kdev_t i_rdev; off_t i_size; time_t i_atime; time_t i_mtime; time_t i_ctime; unsigned long i_blksize; unsigned long i_blocks; unsigned long i_version; unsigned long i_nrpages; struct semaphore i_sem; struct inode_operations *i_op; struct super_block *i_sb; struct wait_queue *i_wait; struct file_lock *i_flock; struct vm_area_struct *i_mmap; struct page *i_pages; struct dquot *i_dquot[MAXQUOTAS]; struct inode *i_next, *i_prev; struct inode *i_hash_next, *i_hash_prev; struct inode *i_bound_to, *i_bound_by; struct inode *i_mount; unsigned short i_count; unsigned short i_flags; unsigned char i_lock; unsigned char i_dirt; unsigned char i_pipe; unsigned char i_sock; unsigned char i_seek; unsigned char i_update; unsigned short i_writecount; union { struct pipe_inode_info pipe_i; struct minix_inode_info minix_i; struct ext_inode_info ext_i; struct ext2_inode_info ext2_i; struct hpfs_inode_info hpfs_i; struct msdos_inode_info msdos_i; struct umsdos_inode_info umsdos_i; struct iso_inode_info isofs_i; struct nfs_inode_info nfs_i; struct xiafs_inode_info xiafs_i; struct sysv_inode_info sysv_i; struct affs_inode_info affs_i; struct ufs_inode_info ufs_i; struct socket socket_i; void *generic_ip; } u; }; (訳注) [in fs/inode.c] static struct inode * first_inode; 16.22. ipc_perm ipc_perm データ構造体は、System V IPC オブジェクトのアクセス許可情報を 記述するものである。 [in include/linux/ipc.h] struct ipc_perm { key_t key; ushort uid; /* owner euid and egid */ ushort gid; ushort cuid; /* creator euid and egid */ ushort cgid; ushort mode; /* access modes see mode flags below */ ushort seq; /* sequence number */ }; 16.23. ipfrag(訳注) [in include/net/ip.h] struct ipfrag { int offset; /* offset of fragment in IP datagram */ int end; /* last byte of data in datagram */ int len; /* length of this fragment */ struct sk_buff *skb; /* complete received fragment */ unsigned char *ptr; /* pointer into real fragment data */ struct ipfrag *next; /* linked list pointers */ struct ipfrag *prev; }; 16.24. ipq(訳注) [in include/net/ip.h] struct ipq { unsigned char *mac; /* pointer to MAC header */ struct iphdr *iph; /* pointer to IP header */ int len; /* total length of original datagram */ short ihlen; /* length of the IP header */ short maclen; /* length of the MAC header */ struct timer_list timer; /* when will this queue expire? */ struct ipfrag *fragments; /* linked list of received fragments */ struct ipq *next; /* linked list pointers */ struct ipq *prev; struct device *dev; /* Device - for icmp replies */ }; [in net/ipv4/ip_fragment.c] static struct ipq *ipqueue = NULL; 16.25. irqaction [in include/linux/interrupt.h] irqaction データ構造体は、システムの割り込みハンドラーを記述するために 使用されるものである。 struct irqaction { void (*handler)(int, void *, struct pt_regs *); unsigned long flags; unsigned long mask; const char *name; void *dev_id; struct irqaction *next; }; 16.26. irq_action [in arch/*/kernel/irq.c](i386, alpha) static struct irqaction *irq_action[16] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; 16.27. kdev_t(訳注) [in include/linux/kdev_t.h] typedef unsigned short kdev_t; 16.28. linux_binfmt Linux が理解するバイナリファイルのフォーマットは、それぞれ linux_binfmt データ構造体によって表現される。 [in include/linux/binfmts.h] struct linux_binfmt { struct linux_binfmt * next; long *use_count; int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(int fd); int (*core_dump)(long signr, struct pt_regs * regs); }; 16.29. mem_map_t mem_map_t データ構造体(ページとも呼ばれる)は、物理メモリにある個々のペ ージに関する情報を保持するために使用される。 [in include/linux/mm.h] typedef struct page { /* these must be first (free area handling) */ struct page *next; struct page *prev; struct inode *inode; unsigned long offset; struct page *next_hash; atomic_t count; unsigned flags; /* atomic flags, some possibly updated asynchronously */ unsigned dirty:16, age:8; struct wait_queue *wait; struct page *prev_hash; struct buffer_head *buffers; unsigned long swap_unlock_entry; unsigned long map_nr; /* page->map_nr == page - mem_map */ } mem_map_t; ..... ..... extern mem_map_t * mem_map; [in include/linux/pagemap.h] extern struct page * page_hash_table[PAGE_HASH_SIZE]; 16.30. mm_struct mm_struct データ構造体は、タスクもしくはプロセスの仮想メモリを記述する ために使用される。 [in include/linux/sched.h] struct mm_struct { int count; pgd_t * pgd; unsigned long context; unsigned long start_code, end_code, start_data, end_data; unsigned long start_brk, brk, start_stack, start_mmap; unsigned long arg_start, arg_end, env_start, env_end; unsigned long rss, total_vm, locked_vm; unsigned long def_flags; struct vm_area_struct * mmap; struct vm_area_struct * mmap_avl; struct semaphore mmap_sem; }; 16.31. module(訳注) [in include/linux/module.h] struct module { struct module *next; struct module_ref *ref; /* the list of modules that refer to me */ struct symbol_table *symtab; const char *name; int size; /* size of module in pages */ void* addr; /* address of module */ int state; void (*cleanup)(void); /* cleanup routine */ }; [in kernel/module.c] static struct module kernel_module; static struct module *module_list = &kernel_module; 16.32. msg(訳注) [in include/linux/msg.h] struct msg { struct msg *msg_next; /* next message on queue */ long msg_type; char *msg_spot; /* message text address */ time_t msg_stime; /* msgsnd time */ short msg_ts; /* message text size */ }; 16.33. msqid_ds(訳注) [in include/linux/msg.h] struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /* first message on queue */ struct msg *msg_last; /* last message in queue */ time_t msg_stime; /* last msgsnd time */ time_t msg_rtime; /* last msgrcv time */ time_t msg_ctime; /* last change time */ struct wait_queue *wwait; struct wait_queue *rwait; ushort msg_cbytes; /* current number of bytes on queue */ ushort msg_qnum; /* number of messages in queue */ ushort msg_qbytes; /* max number of bytes on queue */ ushort msg_lspid; /* pid of last msgsnd */ ushort msg_lrpid; /* last receive pid */ }; ... ... #define MSGMNI 128 /* <= 1K */ /* max # of msg queue identifiers */ [in ipc/msg.c] static struct msqid_ds *msgque[MSGMNI]; 16.34. packet_type(訳注) [in include/linux/netdevice.h] struct packet_type { unsigned short type; /* This is really htons(ether_type). */ struct device * dev; int (*func) (struct sk_buff *, struct device *, struct packet_type *); void *data; struct packet_type *next; }; [in net/core/dev.c] struct packet_type *ptype_base[16]; struct packet_type *ptype_all = NULL; /* Taps */ 16.35. pci_bus システム上のすべての PCI バスは、pci_bus データ構造体によって表現され ている。 [in include/linux/pci.h] struct pci_bus { struct pci_bus *parent; /* parent bus this bridge is on */ struct pci_bus *children; /* chain of P2P bridges on this bus */ struct pci_bus *next; /* chain of all PCI buses */ struct pci_dev *self; /* bridge device as seen by parent */ struct pci_dev *devices; /* devices behind this bridge */ void *sysdata; /* hook for sys-specific extension */ unsigned char number; /* bus number */ unsigned char primary; /* number of primary bridge */ unsigned char secondary; /* number of secondary bridge */ unsigned char subordinate; /* max number of subordinate buses */ }; ... ... extern struct pci_bus pci_root; /* root bus */ 16.36. pci_dev PCI-PCI ブリッジと PCI-ISA ブリッジを含む、システム上のすべての PCI デ バイスは、pci_dev データ構造体によって表現される。 [in include/linux/pci.h] /* * There is one pci_dev structure for each slot-number/function-number * combination: */ struct pci_dev { struct pci_bus *bus; /* bus this device is on */ struct pci_dev *sibling; /* next device on this bus */ struct pci_dev *next; /* chain of all devices */ void *sysdata; /* hook for sys-specific extension */ unsigned int devfn; /* encoded device & function index */ unsigned short vendor; unsigned short device; unsigned int class; /* 3 bytes: (base,sub,prog-if) */ unsigned int master : 1; /* set if device is master capable */ /* * In theory, the irq level can be read from configuration * space and all would be fine. However, old PCI chips don't * support these registers and return 0 instead. For example, * the Vision864-P rev 0 chip can uses INTA, but returns 0 in * the interrupt line and pin registers. pci_init() * initializes this field with the value at PCI_INTERRUPT_LINE * and it is the job of pcibios_fixup() to change it if * necessary. The field must not be 0 unless the device * cannot generate interrupts at all. */ unsigned char irq; /* irq generated by this device */ }; ... ... extern struct pci_dev *pci_devices; /* list of all devices */ 16.37. pipe_inode_info(訳注) [in include/linux/pipe_fs_i.h] struct pipe_inode_info { struct wait_queue * wait; char * base; unsigned int start; unsigned int len; unsigned int lock; unsigned int rd_openers; unsigned int wr_openers; unsigned int readers; unsigned int writers; }; #define PIPE_WAIT(inode) ((inode).u.pipe_i.wait) #define PIPE_BASE(inode) ((inode).u.pipe_i.base) #define PIPE_START(inode) ((inode).u.pipe_i.start) #define PIPE_LEN(inode) ((inode).u.pipe_i.len) #define PIPE_RD_OPENERS(inode) ((inode).u.pipe_i.rd_openers) #define PIPE_WR_OPENERS(inode) ((inode).u.pipe_i.wr_openers) #define PIPE_READERS(inode) ((inode).u.pipe_i.readers) #define PIPE_WRITERS(inode) ((inode).u.pipe_i.writers) #define PIPE_LOCK(inode) ((inode).u.pipe_i.lock) #define PIPE_SIZE(inode) PIPE_LEN(inode) ... ... 16.38. proto_ops(訳注) [in include/linux/net.h] struct proto_ops { int family; int (*create) (struct socket *sock, int protocol); int (*dup) (struct socket *newsock, struct socket *oldsock); int (*release) (struct socket *sock, struct socket *peer); int (*bind) (struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len); int (*connect) (struct socket *sock, struct sockaddr *uservaddr, int sockaddr_len, int flags); int (*socketpair) (struct socket *sock1, struct socket *sock2); int (*accept) (struct socket *sock, struct socket *newsock, int flags); int (*getname) (struct socket *sock, struct sockaddr *uaddr, int *usockaddr_len, int peer); int (*select) (struct socket *sock, int sel_type, select_table *wait); int (*ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg); int (*listen) (struct socket *sock, int len); int (*shutdown) (struct socket *sock, int flags); int (*setsockopt) (struct socket *sock, int level, int optname, char *optval, int optlen); int (*getsockopt) (struct socket *sock, int level, int optname, char *optval, int *optlen); int (*fcntl) (struct socket *sock, unsigned int cmd, unsigned long arg); int (*sendmsg) (struct socket *sock, struct msghdr *m, int total_len, int nonblock, int flags); int (*recvmsg) (struct socket *sock, struct msghdr *m, int total_len, int nonblock, int flags, int *addr_len); }; [in net/socket.c] static struct proto_ops *pops[NPROTO]; 16.39. request request データ構造体は、システム上のブロックデバイスに対してリクエスト を送るために使用される。そのリクエストは、常に、バッファキャッシュに対 するデータブロックの読み書きでなければならない。 [in include/linux/blkdev.h] struct request { volatile int rq_status; #define RQ_INACTIVE (-1) #define RQ_ACTIVE 1 #define RQ_SCSI_BUSY 0xffff #define RQ_SCSI_DONE 0xfffe #define RQ_SCSI_DISCONNECTING 0xffe0 kdev_t rq_dev; int cmd; /* READ or WRITE */ int errors; unsigned long sector; unsigned long nr_sectors; unsigned long current_nr_sectors; char * buffer; struct semaphore * sem; struct buffer_head * bh; struct buffer_head * bhtail; struct request * next; }; (訳注) [in drivers/block/ll_rw_blk.c] static struct request all_requests[NR_REQUEST]; 16.40. rtable rtable データ構造体は、ある IP ホストにパケットを送信する際に取るべき ルートに関する情報を保持するもので、IP ルートキャッシュの内部で使用さ れる。 [in include/net/route.h] struct rtable { struct rtable *rt_next; __u32 rt_dst; __u32 rt_src; __u32 rt_gateway; atomic_t rt_refcnt; atomic_t rt_use; unsigned long rt_window; atomic_t rt_lastuse; struct hh_cache *rt_hh; struct device *rt_dev; unsigned short rt_flags; unsigned short rt_mtu; unsigned short rt_irtt; unsigned char rt_tos; }; [in net/ipv4/route.c] struct rtable *ip_rt_hash_table[RT_HASH_DIVISOR]; 16.41. Scsi_Cmnd(訳注) (簡略化しています) [in drivers/scsi/scsi.h] typedef struct scsi_cmnd { struct Scsi_Host * host; Scsi_Device * device; unsigned char target, lun, channel; unsigned char cmd_len; unsigned char old_cmd_len; struct scsi_cmnd *next, *prev, *device_next, *reset_chain; /* These elements define the operation we are about to perform */ unsigned char cmnd[12]; unsigned request_bufflen; /* Actual request size */ void * request_buffer; /* Actual requested buffer */ /* These elements define the operation we ultimately want to perform */ unsigned char data_cmnd[12]; unsigned short old_use_sg; /* We save use_sg here when requesting * sense info */ unsigned short use_sg; /* Number of pieces of scatter-gather */ unsigned short sglist_len; /* size of malloc'd scatter-gather list */ unsigned short abort_reason;/* If the mid-level code requests an * abort, this is the reason. */ unsigned bufflen; /* Size of data buffer */ void *buffer; /* Data buffer */ unsigned underflow; /* Return error if less than this amount is * transfered */ unsigned transfersize; /* How much we are guaranteed to transfer with * each SCSI transfer (ie, between disconnect / * reconnects. Probably == sector size */ struct request request; /* A copy of the command we are working on */ unsigned char sense_buffer[16]; /* Sense for this command, if needed */ unsigned long serial_number; unsigned long serial_number_at_timeout; int retries; int allowed; int timeout_per_command, timeout_total, timeout; unsigned volatile char internal_timeout; unsigned flags; int this_count; void (*scsi_done)(struct scsi_cmnd *); void (*done)(struct scsi_cmnd *); /* Mid-level done function */ Scsi_Pointer SCp; /* Scratchpad used by some host adapters */ unsigned char * host_scribble; int result; /* Status code from lower level driver */ unsigned char tag; /* SCSI-II queued command tag */ unsigned long pid; /* Process ID, starts at 0 */ } Scsi_Cmnd; 16.42. Scsi_Device(訳注) [in drivers/scsi/scsi.h] typedef struct scsi_device { struct scsi_device * next; /* Used for linked list */ unsigned char id, lun, channel; unsigned int manufacturer; /* Manufacturer of device, for using * vendor-specific cmd's */ int attached; /* # of high level drivers attached to * this */ int access_count; /* Count of open channels/mounts */ struct wait_queue * device_wait;/* Used to wait if device is busy */ struct Scsi_Host * host; void (*scsi_request_fn)(void); /* Used to jumpstart things after an * ioctl */ struct scsi_cmnd *device_queue; /* queue of SCSI Command structures */ void *hostdata; /* available to low-level driver */ char type; char scsi_level; char vendor[8], model[16], rev[4]; unsigned char current_tag; /* current tag */ unsigned char sync_min_period; /* Not less than this period */ unsigned char sync_max_offset; /* Not greater than this offset */ unsigned char queue_depth; /* How deep a queue to use */ unsigned writeable:1; unsigned removable:1; unsigned random:1; unsigned has_cmdblocks:1; unsigned changed:1; /* Data invalid due to media change */ unsigned busy:1; /* Used to prevent races */ unsigned lockable:1; /* Able to prevent media removal */ unsigned borken:1; /* Tell the Seagate driver to be * painfully slow on this device */ unsigned tagged_supported:1; /* Supports SCSI-II tagged queuing */ unsigned tagged_queue:1; /* SCSI-II tagged queuing enabled */ unsigned disconnect:1; /* can disconnect */ unsigned soft_reset:1; /* Uses soft reset option */ unsigned sync:1; /* Negotiate for sync transfers */ unsigned single_lun:1; /* Indicates we should only allow I/O to * one of the luns for the device at a * time. */ unsigned was_reset:1; /* There was a bus reset on the bus for * this device */ unsigned expecting_cc_ua:1; /* Expecting a CHECK_CONDITION/UNIT_ATTN * because we did a bus reset. */ } Scsi_Device; ... ... extern Scsi_Device * scsi_devices; 16.43. Scsi_Device_Template [in drivers/scsi/hosts.h] struct Scsi_Device_Template { struct Scsi_Device_Template * next; const char * name; const char * tag; long * usage_count; /* Used for loadable modules */ unsigned char scsi_type; unsigned char major; unsigned char nr_dev; /* Number currently attached */ unsigned char dev_noticed; /* Number of devices detected. */ unsigned char dev_max; /* Current size of arrays */ unsigned blk:1; /* 0 if character device */ int (*detect)(Scsi_Device *); /* Returns 1 if we can attach this device */ int (*init)(void); /* Sizes arrays based upon number of devices * detected */ void (*finish)(void); /* Perform initialization after attachment */ int (*attach)(Scsi_Device *); /* Attach devices to arrays */ void (*detach)(Scsi_Device *); }; ... ... extern struct Scsi_Device_Template * scsi_devicelist; 16.44. Scsi_Disk(訳注) [in drivers/scsi/sd.h ypedef struct scsi_disk { unsigned capacity; /* size in blocks */ unsigned sector_size; /* size in bytes */ Scsi_Device *device; unsigned char ready; /* flag ready for FLOPTICAL */ unsigned char write_prot; /* flag write_protect for rmvable dev */ unsigned char sector_bit_size; /* sector_size = 2 to the bit size power */ unsigned char sector_bit_shift; /* power of 2 sectors per FS block */ unsigned ten:1; /* support ten byte read / write */ unsigned remap:1; /* support remapping */ unsigned has_part_table:1; /* has partition table */ } Scsi_Disk; extern Scsi_Disk * rscsi_disks; 16.45. Scsi_Host(訳注) (簡略化しています) [in drivers/scsi/hosts.h] struct Scsi_Host { struct Scsi_Host * next; unsigned short extra_bytes; volatile unsigned char host_busy; char host_no; /* Used for IOCTL_GET_IDLUN, /proc/scsi et al. */ unsigned long last_reset; struct wait_queue *host_wait; Scsi_Cmnd *host_queue; Scsi_Host_Template * hostt; unsigned int max_id; unsigned int max_lun; unsigned int max_channel; struct Scsi_Host * block; unsigned wish_block:1; /* These parameters should be set by the detect routine */ unsigned char *base; unsigned int io_port; unsigned char n_io_port; unsigned char irq; unsigned char dma_channel; unsigned int unique_id; int this_id; int can_queue; short cmd_per_lun; short unsigned int sg_tablesize; unsigned unchecked_isa_dma:1; unsigned use_clustering:1; unsigned loaded_as_module:1; void (*select_queue_depths)(struct Scsi_Host *, Scsi_Device *); unsigned long hostdata[0]; /* Used for storage of host specific stuff */ }; 16.46. Scsi_Host_Template(訳注) (簡略化しています。) [in drivers/scsi/hosts.h] typedef struct SHT { /* Used with loadable modules so we can construct a linked list. */ struct SHT * next; /* Used with loadable modules so that we know when it is safe to unload */ long * usage_count; struct proc_dir_entry *proc_dir; int (*proc_info)(char *, char **, off_t, int, int, int); const char *name; int (* detect)(struct SHT *); int (*release)(struct Scsi_Host *); const char *(* info)(struct Scsi_Host *); int (* command)(Scsi_Cmnd *); int (* queuecommand)(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); int (* abort)(Scsi_Cmnd *); int (* reset)(Scsi_Cmnd *, unsigned int); int (* slave_attach)(int, int); int (* bios_param)(Disk *, kdev_t, int []); int can_queue; int this_id; short unsigned int sg_tablesize; short cmd_per_lun; unsigned char present; unsigned unchecked_isa_dma:1; unsigned use_clustering:1; } Scsi_Host_Template; extern struct Scsi_Host * scsi_hostlist; [in drivers/scsi/hosts.c] static Scsi_Host_Template builtin_scsi_hosts[] = ... ... 16.47. sem(訳注) [in include/linux/sem.h] struct sem { short semval; /* current value */ short sempid; /* pid of last operation */ }; 16.48. semaphore セマフォは、重要なデータ構造やコード領域を保護するために利用される仕組 みである。 [in include/asm/semaphore.h](i386, alpha] struct semaphore { int count; int waking; int lock ; /* to make waking testing atomic */ struct wait_queue *wait; }; 16.49. semid_ds(訳注) [in include/linux/sem.h] struct semid_ds { struct ipc_perm sem_perm; /* permissions .. see ipc.h */ time_t sem_otime; /* last semop time */ time_t sem_ctime; /* last change time */ struct sem *sem_base; /* ptr to first semaphore in array */ struct sem_queue *sem_pending; /* pending operations to be processed */ struct sem_queue **sem_pending_last; /* last pending operation */ struct sem_undo *undo; /* undo requests on this array */ ushort sem_nsems; /* no. of semaphores in array */ }; [in ipc/sem.c] static struct semid_ds *semary[SEMMNI]; 16.50. sem_queue(訳注) [in include/linux/sem.h] struct sem_queue { struct sem_queue * next; /* next entry in the queue */ struct sem_queue ** prev; /* previous entry in the queue, *(q->prev) == q */ struct wait_queue * sleeper; /* sleeping process */ struct sem_undo * undo; /* undo structure */ int pid; /* process id of requesting process */ int status; /* completion status of operation */ struct semid_ds * sma; /* semaphore array for operations */ struct sembuf * sops; /* array of pending operations */ int nsops; /* number of operations */ }; 16.51. sem_undo(訳注) [in include/linux/sem.h] struct sem_undo { struct sem_undo * proc_next; /* next entry on this process */ struct sem_undo * id_next; /* next entry on this semaphore set */ int semid; /* semaphore set identifier */ short * semadj; /* array of adjustments, one per semaphore */ }; 16.52. shmid_ds(訳注) [in include/linux/shm.h] struct shmid_ds { struct ipc_perm shm_perm; /* operation perms */ int shm_segsz; /* size of segment (bytes) */ time_t shm_atime; /* last attach time */ time_t shm_dtime; /* last detach time */ time_t shm_ctime; /* last change time */ unsigned short shm_cpid; /* pid of creator */ unsigned short shm_lpid; /* pid of last operator */ short shm_nattch; /* no. of current attaches */ /* the following are private */ unsigned short shm_npages; /* size of segment (pages) */ unsigned long *shm_pages; /* array of ptrs to frames -> SHMMAX */ struct vm_area_struct *attaches; /* descriptors for attaches */ }; [in ipc/shm.c] static struct shmid_ds *shm_segs[SHMMNI]; 16.53. sigaction(訳注) [in include/asm/signal.h](i386, alpha) struct sigaction { __sighandler_t sa_handler; sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void); }; 16.54. signal_struct(訳注) [in include/linux/sched.h] struct signal_struct { int count; struct sigaction action[32]; }; 16.55. sk_buff sk_buff データ構造体は、ネットワークデータがプロトコル階層を移動する際 に、そのデータを記述するために使用されるものである。 [in include/linux/skbuff.h] struct sk_buff { struct sk_buff *next; /* Next buffer in list */ struct sk_buff *prev; /* Previous buffer in list */ struct sk_buff_head *list; /* List we are on */ int magic_debug_cookie; struct sk_buff *link3; /* Link for IP protocol level buffer chains*/ struct sock *sk; /* Socket we are owned by */ unsigned long when; /* used to compute rtt's */ struct timeval stamp; /* Time we arrived */ struct device *dev; /* Device we arrived on/are leaving by */ union { struct tcphdr *th; struct ethhdr *eth; struct iphdr *iph; struct udphdr *uh; unsigned char *raw; /* for passing file handles in a unix domain socket */ void *filp; } h; union { /* As yet incomplete physical layer views */ unsigned char *raw; struct ethhdr *ethernet; } mac; struct iphdr *ip_hdr; /* For IPPROTO_RAW */ unsigned long len; /* Length of actual data */ unsigned long csum; /* Checksum */ __u32 saddr; /* IP source address */ __u32 daddr; /* IP target address */ __u32 raddr; /* IP next hop address */ __u32 seq; /* TCP sequence number */ __u32 end_seq; /* seq [+ fin] [+ syn] + datalen */ __u32 ack_seq; /* TCP ack sequence number */ unsigned char proto_priv[16]; volatile char acked, /* Are we acked ? */ used, /* Are we in use ? */ free, /* How to free this buffer */ arp; /* Has IP/ARP resolution finished */ unsigned char tries, /* Times tried */ lock, /* Are we locked ? */ localroute, /* Local routing asserted for this frame */ pkt_type, /* Packet class */ pkt_bridged, /* Tracker for bridging */ ip_summed; /* Driver fed us an IP checksum */ #define PACKET_HOST 0 /* To us */ #define PACKET_BROADCAST 1 /* To all */ #define PACKET_MULTICAST 2 /* To group */ #define PACKET_OTHERHOST 3 /* To someone else */ unsigned short users; /* User count - see datagram.c,tcp.c */ unsigned short protocol; /* Packet protocol from driver. */ unsigned int truesize; /* Buffer size */ atomic_t count; /* reference count */ struct sk_buff *data_skb; /* Link to the actual data skb */ unsigned char *head; /* Head of buffer */ unsigned char *data; /* Data head pointer */ unsigned char *tail; /* Tail pointer */ unsigned char *end; /* End pointer */ void (*destructor)(struct sk_buff *); /* Destruct function */ __u16 redirport; /* Redirect port */ }; 16.56. sk_buff_head(訳注) [in include/linux/skbuff.h] struct sk_buff_head { struct sk_buff * next; struct sk_buff * prev; __u32 qlen; /* Must be same length as a pointer for using debugging */ #if CONFIG_SKB_CHECK int magic_debug_cookie; #endif }; [in net/core/dev.c] static struct sk_buff_head backlog; 16.57. sock 個々の sock データ構造体は、BSD ソケットに関するプロトコル固有の情報を 保持するためのものである。例えば、INET(Internet Address Domain)ソケッ トの場合、このデータ構造体には、TCP/IP と UDP/IP 固有の情報のすべてが 保持されることになる。 [in include/net/sock.h] struct sock { /* This must be first. */ struct sock *sklist_next; struct sock *sklist_prev; struct options *opt; atomic_t wmem_alloc; atomic_t rmem_alloc; unsigned long allocation; /* Allocation mode */ __u32 write_seq; __u32 sent_seq; __u32 acked_seq; __u32 copied_seq; __u32 rcv_ack_seq; unsigned short rcv_ack_cnt; /* count of same ack */ __u32 window_seq; __u32 fin_seq; __u32 urg_seq; __u32 urg_data; __u32 syn_seq; int users; /* user count */ /* * Not all are volatile, but some are, so we * might as well say they all are. */ volatile char dead, urginline, intr, blog, done, reuse, keepopen, linger, delay_acks, destroy, ack_timed, no_check, zapped, broadcast, nonagle, bsdism; unsigned long lingertime; int proc; struct sock *next; struct sock **pprev; struct sock *bind_next; struct sock **bind_pprev; struct sock *pair; int hashent; struct sock *prev; struct sk_buff *volatile send_head; struct sk_buff *volatile send_next; struct sk_buff *volatile send_tail; struct sk_buff_head back_log; struct sk_buff *partial; struct timer_list partial_timer; long retransmits; struct sk_buff_head write_queue, receive_queue; struct proto *prot; struct wait_queue **sleep; __u32 daddr; __u32 saddr; /* Sending source */ __u32 rcv_saddr; /* Bound address */ unsigned short max_unacked; unsigned short window; __u32 lastwin_seq; /* sequence number when we last updated the window we offer */ __u32 high_seq; /* sequence number when we did current fast retransmit */ volatile unsigned long ato; /* ack timeout */ volatile unsigned long lrcvtime; /* jiffies at last data rcv */ volatile unsigned long idletime; /* jiffies at last rcv */ unsigned int bytes_rcv; /* * mss is min(mtu, max_window) */ unsigned short mtu; /* mss negotiated in the syn's */ volatile unsigned short mss; /* current eff. mss - can change */ volatile unsigned short user_mss; /* mss requested by user in ioctl */ volatile unsigned short max_window; unsigned long window_clamp; unsigned int ssthresh; unsigned short num; volatile unsigned short cong_window; volatile unsigned short cong_count; volatile unsigned short packets_out; volatile unsigned short shutdown; volatile unsigned long rtt; volatile unsigned long mdev; volatile unsigned long rto; volatile unsigned short backoff; int err, err_soft;/* Soft holds errors that don't cause failure but are the cause of a persistent failure not just 'timed out' */ unsigned char protocol; volatile unsigned char state; unsigned char ack_backlog; unsigned char max_ack_backlog; unsigned char priority; unsigned char debug; int rcvbuf; int sndbuf; unsigned short type; unsigned char localroute; /* Route locally only */ /* * This is where all the private (optional) areas that don't * overlap will eventually live. */ union { struct unix_opt af_unix; #if defined(CONFIG_ATALK) || defined(CONFIG_ATALK_MODULE) struct atalk_sock af_at; #endif #if defined(CONFIG_IPX) || defined(CONFIG_IPX_MODULE) struct ipx_opt af_ipx; #endif #ifdef CONFIG_INET struct inet_packet_opt af_packet; #ifdef CONFIG_NUTCP struct tcp_opt af_tcp; #endif #endif } protinfo; /* * IP 'private area' */ int ip_ttl; /* TTL setting */ int ip_tos; /* TOS */ struct tcphdr dummy_th; struct timer_list keepalive_timer; /* TCP keepalive hack */ struct timer_list retransmit_timer; /* TCP retransmit timer */ struct timer_list delack_timer; /* TCP delayed ack timer */ int ip_xmit_timeout; /* Why the timeout is running */ struct rtable *ip_route_cache; /* Cached output route */ unsigned char ip_hdrincl; /* Include headers ? */ #ifdef CONFIG_IP_MULTICAST int ip_mc_ttl; /* Multicasting TTL */ int ip_mc_loop; /* Loopback */ char ip_mc_name[MAX_ADDR_LEN]; /* Multicast device name */ struct ip_mc_socklist *ip_mc_list; /* Group array */ #endif /* * This part is used for the timeout functions (timer.c). */ int timeout; /* What are we waiting for? */ struct timer_list timer; /* This is the TIME_WAIT/receive * timer when we are doing IP */ struct timeval stamp; /* * Identd */ struct socket *socket; /* * Callbacks */ void (*state_change)(struct sock *sk); void (*data_ready)(struct sock *sk,int bytes); void (*write_space)(struct sock *sk); void (*error_report)(struct sock *sk); }; (訳注)[in net/ipv4/udp.c] struct sock *udp_hash[UDP_HTABLE_SIZE]; (訳注)[in net/ipv4/tcp.c] struct sock *tcp_established_hash[TCP_HTABLE_SIZE]; ... struct sock *tcp_listening_hash[TCP_LHTABLE_SIZE]; ... struct sock *tcp_bound_hash[TCP_BHTABLE_SIZE]; 16.58. sockaddr(訳注) [in include/linux/socket.h] struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }; 16.59. socket 個々の socket データ構造体は、BSD ソケットに関する情報を保持するもので ある。しかし、これは単独で存在するのではなく、VFS inode データ構造体の 一部として存在するものである。 [in include/linux/net.h] struct socket { short type; /* SOCK_STREAM, ... */ socket_state state; long flags; struct proto_ops *ops; /* protocols do most everything */ void *data; /* protocol data */ struct socket *conn; /* server socket connected to */ struct socket *iconn; /* incomplete client conn.s */ struct socket *next; struct wait_queue **wait; /* ptr to place to wait on */ struct inode *inode; struct fasync_struct *fasync_list; /* Asynchronous wake up list */ struct file *file; /* File back pointer for gc */ }; 16.60. super_block(訳注) [in include/linux/fs.h] struct super_block { kdev_t s_dev; unsigned long s_blocksize; unsigned char s_blocksize_bits; unsigned char s_lock; unsigned char s_rd_only; unsigned char s_dirt; struct file_system_type *s_type; struct super_operations *s_op; struct dquot_operations *dq_op; unsigned long s_flags; unsigned long s_magic; unsigned long s_time; struct inode * s_covered; struct inode * s_mounted; struct wait_queue * s_wait; union { struct minix_sb_info minix_sb; struct ext_sb_info ext_sb; struct ext2_sb_info ext2_sb; struct hpfs_sb_info hpfs_sb; struct msdos_sb_info msdos_sb; struct isofs_sb_info isofs_sb; struct nfs_sb_info nfs_sb; struct xiafs_sb_info xiafs_sb; struct sysv_sb_info sysv_sb; struct affs_sb_info affs_sb; struct ufs_sb_info ufs_sb; void *generic_sbp; } u; }; ... ... extern struct super_block super_blocks[NR_SUPER]; 16.61. swap_control(訳注) [in include/linux/swapctl.h] typedef struct swap_control_v5 { int sc_max_page_age; int sc_page_advance; int sc_page_decline; int sc_page_initial_age; int sc_max_buff_age; int sc_buff_advance; int sc_buff_decline; int sc_buff_initial_age; int sc_age_cluster_fract; int sc_age_cluster_min; int sc_pageout_weight; int sc_bufferout_weight; int sc_buffer_grace; int sc_nr_buffs_to_free; int sc_nr_pages_to_free; enum RCL_POLICY sc_policy; } swap_control_v5; typedef struct swap_control_v5 swap_control_t; extern swap_control_t swap_control; [in mm/swap.c] /* * Constants for the page aging mechanism: the maximum age (actually, * the maximum "youthfulness"); the quanta by which pages rejuvenate * and age; and the initial age for new pages. */ swap_control_t swap_control = { 20, 3, 1, 3, /* Page aging */ 10, 2, 2, 4, /* Buffer aging */ 32, 4, /* Aging cluster */ 8192, 8192, /* Pageout and bufferout weights */ -200, /* Buffer grace */ 1, 1, /* Buffs/pages to free */ RCL_ROUND_ROBIN /* Balancing policy */ }; 16.62. swap_info_struct(訳注) [in include/linux/swap.h] struct swap_info_struct { unsigned int flags; kdev_t swap_device; struct inode * swap_file; unsigned char * swap_map; unsigned char * swap_lockmap; int lowest_bit; int highest_bit; int cluster_next; int cluster_nr; int prio; /* swap priority */ int pages; unsigned long max; int next; /* next entry on swap list */ }; extern int nr_swap_pages; extern int nr_free_pages; extern atomic_t nr_async_pages; extern int min_free_pages; extern int free_pages_low; extern int free_pages_high; [in mm/swap.c] /* * We identify three levels of free memory. We never let free mem * fall below the min_free_pages except for atomic allocations. We * start background swapping if we fall below free_pages_high free * pages, and we begin intensive swapping below free_pages_low. * * Keep these three variables contiguous for sysctl(2). */ int min_free_pages = 20; int free_pages_low = 30; int free_pages_high = 40; /* We track the number of pages currently being asynchronously swapped out, so that we don't try to swap TOO many pages out at once */ atomic_t nr_async_pages = 0; 16.63. task_struct 個々の task_struct データ構造体は、システム上の、ひとつのプロセスもし くはタスクについて記述するものである。 [in include/linux/sched.h] struct task_struct { /* these are hardcoded - don't touch */ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ long counter; long priority; unsigned long signal; unsigned long blocked; /* bitmap of masked signals */ unsigned long flags; /* per process flags, defined below */ int errno; long debugreg[8]; /* Hardware debugging registers */ struct exec_domain *exec_domain; /* various fields */ struct linux_binfmt *binfmt; struct task_struct *next_task, *prev_task; struct task_struct *next_run, *prev_run; unsigned long saved_kernel_stack; unsigned long kernel_stack_page; int exit_code, exit_signal; /* ??? */ unsigned long personality; int dumpable:1; int did_exec:1; int pid; int pgrp; int tty_old_pgrp; int session; /* boolean value for session group leader */ int leader; int groups[NGROUPS]; /* * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->p_pptr->pid) */ struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct wait_queue *wait_chldexit; unsigned short uid,euid,suid,fsuid; unsigned short gid,egid,sgid,fsgid; unsigned long timeout, policy, rt_priority; unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; long utime, stime, cutime, cstime, start_time; /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; unsigned long swap_address; unsigned long old_maj_flt; /* old value of maj_flt */ unsigned long dec_flt; /* page fault count of the last time */ unsigned long swap_cnt; /* number of pages to swap on next pass */ /* limits */ struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; char comm[16]; /* file system info */ int link_count; struct tty_struct *tty; /* NULL if no tty */ /* ipc stuff */ struct sem_undo *semundo; struct sem_queue *semsleeping; /* ldt for this task - used by Wine. If NULL, default_ldt is used */ struct desc_struct *ldt; /* tss for this task */ struct thread_struct tss; /* filesystem information */ struct fs_struct *fs; /* open file information */ struct files_struct *files; /* memory management info */ struct mm_struct *mm; /* signal handlers */ struct signal_struct *sig; #ifdef __SMP__ int processor; int last_processor; int lock_depth; /* Lock depth. We can context switch in and out of holding a syscall kernel lock... */ #endif }; ... ... extern struct task_struct *task[NR_TASKS]; ... extern struct task_struct *current_set[NR_CPUS]; /* * On a single processor system this comes out as current_set[0] when cpp * has finished with it, which gcc will optimise away. */ #define current (0+current_set[smp_processor_id()]) /* Current on this processor */ extern unsigned long volatile jiffies; [in include/linux/tasks.h] #define NR_TASKS 512 (訳注: 以下のコードは、patch-1.3.99 で入りましたが、おそらく利用される ことのないまま、v2.2.0 以降で消滅しています。) [in kernel/sched.c] #ifdef PAST_2_0 /* This process is locked to a processor group */ if (p->processor_mask && !(p->processor_mask & (1<i_mmap */ /* for shm areas, the circular list of attaches */ /* otherwise unused */ struct vm_area_struct * vm_next_share; struct vm_area_struct * vm_prev_share; /* more */ struct vm_operations_struct * vm_ops; unsigned long vm_offset; struct inode * vm_inode; unsigned long vm_pte; /* shared mem */ }; 16.69. vm_operations_struct(訳注) [in include/linux/mm.h] struct vm_operations_struct { void (*open)(struct vm_area_struct * area); void (*close)(struct vm_area_struct * area); void (*unmap)(struct vm_area_struct *area, unsigned long, size_t); void (*protect)(struct vm_area_struct *area, unsigned long, size_t, unsigned int newprot); int (*sync)(struct vm_area_struct *area, unsigned long, size_t, unsigned int flags); void (*advise)(struct vm_area_struct *area, unsigned long, size_t, unsigned int advise); unsigned long (*nopage)(struct vm_area_struct * area, unsigned long address, int write_access); unsigned long (*wppage)(struct vm_area_struct * area, unsigned long address, unsigned long page); int (*swapout)(struct vm_area_struct *, unsigned long, pte_t *); pte_t (*swapin)(struct vm_area_struct *, unsigned long, unsigned long); }; 16.70. wait_queue(訳注) [in include/linux/wait.h] struct wait_queue { struct task_struct * task; struct wait_queue * next; }; 17. 有益なウェブと FTP サイト 以下では、便利なウェブサイトや FTP サイトについて述べる。 http://www.azstarnet.com/~axplinux/ これは、David Mosberger-Tag の Alpha AXP Linux のウェブサイト で、Alpha AXP HOWTO のすべてが集まった場所である。また、ここに は、CPU のデータシートといった Linux と Alpha AXP に固有の情報に 関する膨大なリンク集がある。 (訳注: 現在 は、http://www.alphalinux.org/ にリダイレクトされます。) http://www.redhat.com/ Red Hat のウェブサイト。多くの有益なリンクがある。 ftp://sunsite.unc.edu/ 多くのフリーソフトを集めたメジャーサイト。Linux 固有のソフトウェ アは、 pub/Linux にある。 http://www.intel.com/ Intel のウェブサイト。Intel チップの情報を探すときに便利である。 http://www.ssc.com/lj/index.html Linux Journal は、非常にすばらしい Linux 雑誌である。その優れた 記事は、年間購読して読むに値する。 (訳注: 現在は、http://www2.linuxjournal.com/ となっています。) http://www.blackdown.org/java-linux.html Linux 上での Java 情報に関するプライマリサイト。 ftp://tsx-11.mit.edu/ftp/pub/linux/ MIT の Linux FTP サイト。 ftp://ftp.cs.helsinki.fi/pub/Software/Linux/Kernel/ Linux のカーネルソース。 (訳注: 現在は、http://www.kernel.org/ となっています。) http://www.linux.org.uk/ UK Linux ユーザグループ。 (http://sunsite.unc.edu/mdw/linux.html) http://www.linuxdoc.org/ (訳注: 現在のページです。)" Linux Documentation Project のホームページ。 http://www.digital.com/ Digital Equipment Corporation のメインサイト。 (DEC は Compaq に 買収されたため、Compaq のサイトにリダイレクトされます。) http://altavista.digital.com/ DEC の Altavista サーチエンジン。ウェブとニュースグループに関す る情報を検索するには、よい場所である。 http://www.linuxhq.com/ Linux HQ ウェブサイトには、公式および非公式の最新パッチが置かれ ている。それ以外にも、アドバイスやリンクがあるので、手持ちのシス テムに最も適したカーネルソースを入手したい場合に役に立つだろ う。(訳注: 同様に、 http://www.kernelnotes.org/ もご覧くださ い。) http://www.amd.com/ AMD のウェブサイト http://www.cyrix.com/ Cyrix のウェブサイト。 http://www.arm.com/ ARM のウェブサイト。 以下は、訳注です。 http://www.linux.or.jp/link/kernel.html 「日本の Linux 情報」にあるカーネル関係のリンク集 http://jungla.dit.upm.es/~jmseyas/linux/kernel/hackers-docs.html カーネル関連文書のリンク集 http://www.linux.or.jp/JF/ JF ホームページ http://www.gnu.org/home.ja.html 日本語 GNU ホームページ http://arashi.debian.or.jp/~mhatta/fdl.ja.txt GNU Free Documentation License(GNU FDL)の日本語訳 18. LDP 宣言(LDP Manifesto) 以下は、Linux Documentation Project(LDP) の「趣意宣言(Manifesto)」であ る。 (訳注: 原著では、1998 年 9 月 21 日発行の Michael K. Johnson による LDP Manifesto が記載されていますが、翻訳時では既に更新されているので、 最新版を掲載します。) =============== 改訂者: David S. Lawyer (dave@lafn.org) 最終改訂日: 2000年7月18日 この文書は、Linux Documentation Project の目標と現状、文書作成の方法、 およびライセンス条項について述べたものです。 18.1. 概要 Linux Documentation Project は、GNU/Linux オペレーティングシステムにつ いての良質でフリーな文書を開発するために活動しています。LDP の最大の目 的は、Linux に関するすべての事柄の文書化を、共同で押し進めることで す。HOWTO や Guide の作成がこれにあたります。さらにわれわれは、簡単に 利用と検索ができる文書システムを構築したいと考えています。man ページや info 文書、HOWTO その他の統合はこれにあたります。 LDP のゴールは、Linux に関する正式なフリー文書のセットを作ることです。 オンラインでの(ダウンロード可能な)文書は、Linux を取りまく世界の様々な 変化に対応して頻繁にアップデートできるという利点がありますが、われわれ は、同じ文書をオフラインで読むために CD-ROM や印刷された書籍としても提 供したいと考えています。 LDP の文書の出版に興味がある方は、``「LDP 文 書の出版」''をご覧ください。 LDP は、本質的にボランティアによる緩やかな共同体であり、最小限の中枢組 織しか持っていません。運営に興味のある方は、是非活動に参加してくださ い。われわれは、参加者がインフォーマルに協調し、メーリングリストで議論 することが、最良のやり方であると思っています。意見が食い違うときは、互 いの考え方の違いを理解し、納得のいく合意に達するようにしています。 18.2. 現在のプロジェクトとその参加方法 現在の LDP の主要な活動は、HOWTO を書くことです。何らかの HOWTO を書こ うと思った場合は、既に同一のトピックがないかどうかまずチェックしてくだ さい。もし既に存在するときは、その文書の管理者に連絡を取って、手助けを 申し出るのがよいと思います。HOWTO がない場合は、新規の HOWTO の作成を 考えることになるでしょう。詳細については、LDP Author Guide (以前は HOWTO-HOWTO と呼ばれていたものです) と HOWTO-INDEX をご覧ください。 Guide は、システム管理などの幅広いトピックを扱う書籍サイズの LDP 文書 です。われわれは、C プログラミングやデバイスの man ページも管理してい ます。 他にも、HOWTO の間違いや不明確なところのチェックや、ウェブサイトの改 善、 Linux の統合文書システムの開発といった仕事があります。そうし た(HOWTO の執筆以外の)プロジェクトに興味があれば、現在の LDP コーディ ネイターである Guylhem Aznar ( guyhem@metalab.unc.edu )と連絡を取る か、LDP の feedback@@linuxdoc.org にメールを送ってください。 18.3. LDP ウェブサイト LDP には、世界中に 250 以上のミラーサイトがあるので、そこから LDP 文書 の検索やダウンロードが可能です。メインサイト は、http://www.linuxdoc.org です。そこでミラーサイトのリストを見つけ て、近くのミラーサイトを利用してください。 18.4. 文書作成の方法 以下は、LDP 文書で現在利用されている約束ごとです。これとは異なる方法で 新規文書を書こうとする場合は、まずそのプランをわれわれに教えてくださ い。 o すべての HOWTO 文書は、LinuxDoc か DocBook かのどちらかの SGML フォ ーマットで作成される必要があります。LinuxDoc のほうがシンプルであ り、DocBook は、機能が追加されているのでやや複雑です。 o Guide は、LDP が作成した完全な書籍であり、主に印刷を目的とした文書 なので、昔から LaTeX 形式になっています。しかし、Guide の著者たち は、DocBook DTD を使った SGML 形式に移行しています。DocBook DTD な ら、印刷にもオンラインにも対応した、様々な出力形式の文書を作成でき るからです。LaTeX を使う場合は、スタイルファイルがあるので、それを 使えば印刷時の体裁に関して他の LDP 文書との整合性を維持できます。 o man ページは、Unix の標準的なオンラインマニュアルなので、Unix の標 準である nroff man (あるいは、BSD mdoc )マクロで作成されます。 18.5. ライセンス上の必要事項 誰でも、LDP 文書(あるいは、それ以外の LDP の成果)を、あらゆるメディア やフォーマットで、複製や配布(販売、譲渡)することができなければなりませ ん。著者に著作料金を支払わなくてよいことが必要です。文書を任意に変更で きることまでは要求されていませんが、推奨はされています。 上記の条件を満たす独自のライセンス条項を付けることができますが、予め用 意されたライセンスを使うことも可能です。LDP では、自由に利用できるライ センスの鋳型を用意しています。GPL を利用しようとする人たちもいますが、 自分でライセンスを作る人たちもいます。文書専用の GPL を作成しようとす るプロジェクトが進行中なので、そのライセンスを使うがおそらく非常に妥当 な選択となるでしょう。 (訳注: 現在 GFDL の名称で入手が可能です。日本語 訳は``「ウェブと FTP サイト」''を参照してください。) 個々の文書のコピーライトには、主要な著作者の名前を記してください。"The Linux Documentation Project" は、正式な権利帰属主体ではないので、コピ ーライト保持者としては使用できません。 18.6. ライセンスの鋳型 以下は、著作権表示のサンプルとライセンスの鋳型ですので、これを作品に対 して使用することもできます。(訳注: ライセンスは原文と併記しています。) Copyright (c) 2000 by John Doe (あなたの名前に変えてください。) Please freely copy and distribute (sell or give away) this document in any format. It's requested that corrections and/or comments be fowarded to the document maintainer. You may create a derivative work and distribute it provided that you: この文書は、任意のフォーマットで、自由に複製及び(販売や譲渡による)配布 が可能です。訂正ならびにコメントは、文書管理者宛てにお送りください。二 次的著作物の作成とその配布は、以下の条件を満たす限り許可されます。 1. Send your derivative work (in the most suitable format such as sgml) to the LDP (Linux Documentation Project) or the like for posting on the Internet. If not the LDP, then let the LDP know where it is available. 作成した二次的著作物は、(SGML などの最適なフォーマットで)インター ネット上の LDP (Linux Documentation Project) かそれに類する団体まで 送付すること。LDP 以外に送付するときは、どこで入手可能かを LDP に知 らせること。 2. License the derivative work with this same license or use GPL. Include a copyright notice and at least a pointer to the license used. 二次的著作物にはこの文書と同一のライセンスか GPL を付与すること。そ れには、著作権表示、及び(使用したライセンスの全文もしくは全文掲載が できない場合は)少なくともそのライセンスを参照できるポインタを含める こと。 3. Give due credit to previous authors and major contributors. 原作品の著者並びに主要な貢献者の氏名を明記すること。 If you're considering making a derived work other than a translation, it's requested that you discuss your plans with the current maintainer. 翻訳以外の二次的著作物の作成を考えている場合は、現在の文書管理者とその プランについて話し合ってください。 18.7. LDP 文書の出版 もしあなたが、LDP 文書の配布(出版)に関心を持つ出版社の方なら、以下をお 読み下さい。 上記に示したライセンス要項に照らして、誰でも LDP 文書の逐語的複製を出 版や配布することが可能です。わざわざ許可を求める必要はありません。しか し、 LDP 文書に基づく翻訳や二次的著作物を配布しようとしている場合は、 ライセンスがそれを必要とする限り、配布前に著者と連絡を取って許可を得る 必要があります。 営利目的で LDP 文書を販売することも、もちろん可能です。われわれは、そ うして欲しいと考えています。しかし、LDP は自由な配布が可能なので、誰で もそれを複製し、配布できることには留意してください。したがって、自由に コピーされたその本の一部は、他の部分の著作権を侵害することなく、コピー しやすい個別の実体として流通する可能性があります。 われわれは、LDP 文書の販売で得た何らかの利益から著作権料を支払うよう要 求はしません。しかし、営利目的で LDP 文書を販売した場合は、著者に著作 料を提供するか、その収益の一部を著者か LDP 全体、あるいは Linux コミュ ニティーに寄付することを提案したいと思います。販売している LDP 文書の フリーコピーを何冊か著者に送付しようと思うかもしれないでしょう。LDP と Linux コミュニティーに対してあなたが示したサポートは、非常に高い評価を 受けるでしょう。 われわれは、LDP 文書の出版や配布のプランがある場合は、知らせてほしいと 思っています。どういう形で入手可能になるのか知りたいからです。もし LDP 文書を出版している場合、あるいは出版を計画している場合は、 feedback@linuxdoc.org までメールで連絡してください。誰が何をしているの か知りたいのです。 われわれは、Linux ソフトウェアの配布元が、ソフトウェアと一緒に CD-ROM で、 LDP 文書を配布するようお願いしています。LDP 文書は、「正式 な」Linux 文書として利用されることを意図したものであるので、配布元がソ フトウェアに LDP 文書をバンドルしているのを見るのが楽しみなのです。 19. The GNU General Public License 以下の文書は、GNU General Public License (GPL もしくは copyleft) であ り、 Linux はこのライセンスに従っている。それをここに記載したの は、Linux の著作権の内容に関するいくつかの誤解を解くためである。Linux は、シェアウェアでも、パブリックドメインソフトウェアでもない。Linux カ ーネルの大半は、1993 年に Linus Torvalds が著作権を取得しており、それ 以外のソフトウェアやカーネルの部分についても、それぞれの作成者が著作権 を持っている。したがって、Linux は、著作権の設定されたソフトウェアであ り、その上で、以下に示す GPL の条項のもとに再配布が可能なのである。 (訳注: 以下は、こちら にある引地信 之さん、引地美恵子さんの日本語訳として公開されているものをお借りしまし た。 GNU の他の文書に関しては、``「ウェブと FTP サイト」'' をご覧くだ さい。*これまで GPL を翻訳してくださった方の氏名を間違えていました。 お詫びするとともに、上記のように訂正します(2001/07/22)*) GNU 一般公有使用許諾書 ======================= 1991 年6 月,バージョン2 Copyright (C) 1989,1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA 何人も、以下の内容を変更しないでそのまま複写する場合に限り、本使用許諾 書を複製したり頒布することができます。 はじめに -------- ほとんどのソフトウェアの使用許諾は、ソフトウェアを共有し、変更するユー ザの自由を奪うことを意図しています。それに対して、我々のGNU 一般公有使 用許諾は、フリー・ソフトウェアを共有したり変更する自由をユーザに保証す るためのもの、即ちフリー・ソフトウェアがそのユーザ全てにとってフリーで あることを保証するためのものです。本使用許諾は、Free Software Foundation のほとんど全てのソフトウェアに適用されるだけでなく、プログ ラムの作成者が本使用許諾に依るとした場合のそのプログラムにも適用するこ とができます。(その他のFree Software Foundation のソフトウェアのいくつ かは、本許諾書ではなく、GNU ライブラリ一般公有使用許諾で保護されま す。) あなたは自分のプログラムにもこれを適用できます。我々がフリー・ソ フトウェアについて言う場合は自由のことに言及しているのであって、価格の ことではありません。我々の一般公有使用許諾の各条項は、次の事柄を確実に 実現することを目的として立案されています。 o フリー・ソフトウェアの複製物を自由に頒布できること(そして、望むなら あなたのこのサービスに対して対価を請求できること)。 o ソース・コードを実際に受け取るか、あるいは、希望しさえすればそれを 入手することが可能であること。 o 入手したソフトウェアを変更したり、新しいフリー・プログラムの一部と して使用できること。 o 以上の各内容を行なうことができるということをユーザ自身が知っている こと。 このようなユーザの権利を守るために、我々は、何人もこれらの権利を否定し たり、あるいは放棄するようにユーザに求めることはできないという制限条項 を設ける必要があります。これらの制限条項は、ユーザが、フリー・ソフト ウェアの複製物を頒布したり変更しようとする場合には、そのユーザ自身が守 るべき義務ともなります。例えば、あなたがフリー・ソフトウェアの複製物を 頒布する場合、有償か無償かにかかわらず、あなたは自分の持っている権利を 全て相手に与えなければなりません。あなたは、相手もまたソース・コードを 受け取ったり入手できるということを認めなければなりません。さらにあなた は、彼らが自分たちの権利を知るように、これらの条項を知らしめなければな りません。 我々は次の2つの方法でユーザの権利を守ります。(1) ソフトウェアに著作権 を主張し、(2) 本使用許諾の条項の下でソフトウェアを複製・頒布・変更する 権利をユーザに与えます。 また、各作成者や我々自身を守るために、本フリー・ソフトウェアが無保証で あることを全ての人々が了解している必要があります。さらに、他の誰かに よって変更されたソフトウェアが頒布された場合、受領者はそのソフトウェア がオリジナル・バージョンではないということを知らされる必要があります。 それは、他人の関与によって原開発者に対する評価が影響されないようにする ためです。 最後に、どのフリー・プログラムもソフトウェア特許に絶えず脅かされていま す。我々は、フリー・プログラムの再頒布者が個人的に特許権を取得し、事実 上そのプログラムを自分の財産にしてしまうという危険を避けたいと願ってい ます。これを防ぐために我々は、いずれの特許も、誰でも自由に使用できるよ うに使用許諾されるべきか、あるいは何人に対しても全く使用させないかの、 いずれかにすべきであることを明らかにしてきました。 複写・頒布・変更に対する正確な条項と条件を次に示します。 GNU 一般公有使用許諾の下での複製、頒布、変更に関する条項と条件 ============================================================== 1. 本使用許諾は、本一般公有使用許諾の各条項に従って頒布されるという著 作権者からの告知文が表示されているプログラムやその他の作成物に適用 されます。以下において「プログラム」とは、そのようなプログラムや作 成物を指すものとし、また、「プログラム生成物」とは、上述した「プロ グラム」自身、または、著作権法下における全ての派生物;すなわち、そ の「プログラム」の全部又は一部を、そのまま又は変更して、且つ/又は 他の言語に変換して、内部に組み込んだ作成物を意味します。(以下、言語 変換は「変更」という用語の中に無条件に含まれるものとします。) 本使 用許諾によって許諾を受ける者を「あなた」と呼びます。 複製、頒布、変更以外の行為は本使用許諾の対象としません。それらは本 使用許諾の範囲外です。「プログラム」を実行させる行為に関して制約は ありません。「プログラム」の出力は、( 「プログラム」を実行させて作 成させたかどうかとは無関係に) その内容が「プログラム生成物」である 場合に限り本使用許諾の対象となります。これが当てはまるかどうかは、 「プログラム」が何をするものかに依ります。 2. あなたは、どのような媒体上へ複製しようとする場合であっても、入手し た「プログラム」のソース・コードをそのままの内容で複写した上で適正 な著作権表示と保証の放棄を明確、且つ適正に付記する場合に限り、複製 又は頒布することができます。その場合、本使用許諾及び無保証に関する 記載部分は、全て元のままの形で表示してください。また、「プログ ラ ム」の頒布先に対しては、「プログラム」と共に本使用許諾書の写しを渡 してください。複製物の引き渡しに要する実費は請求することができま す。また、あなた独自の保証を行なう場合はそれを有償とすることができ ます。 3. 次の各条件を全て満たしている限り、あなたは、「プログラム」又はその 一部分を変更して「プログラム生成物」とすることができ、さらに、変更 版や右作成物を上記第 2 項に従って複製又は頒布することもできます。 o (a) ファイルを変更した旨とその変更日とを、変更したファイル上に明 確に表示すること。 o (b) 変更したか否かを問わず、凡そ「プログラム」又はその一部分を内 部に組み込んでいるか又はそれから派生した生成物を頒布する場合に は、その全体を本使用許諾の条項に従って第三者へ無償で使用許諾する こと。 o (c) 変更したプログラムが実行時に通常の対話的な方法でコマンドを読 むようになっているとすれば、最も普通の方法で対話的にそのプログラ ムを実行する時に、次の内容を示す文言がプリンタへ印字されるか、或 いは画面に表示されること。 o 適切な著作権表示。 o 無保証であること(あなたが独自に保証する場合は、その旨)。 o 頒布を受ける者も、本使用許諾と同一の条項に従って「プログラ ム」を再頒布できること。 o 頒布を受ける者が本使用許諾書の写しを参照する方法。 (例外として、「プログラム」自体は対話的であっても起動時の文言を 通常は印字しないのならば、あなたの「プログラム生成物」はこのよう な文言を印字する必要はありません。) これらの要件は変更された作成物にも全て適用されます。その変更版の或 る部分が「プログラム」の派生物ではなく、しかもそれ自体独立で異なる 作成物だと合理的に考えられる場合、あなたがそれらを別の作成物として 頒布した時は、本使用許諾とその条項はそれらの部分には適用されませ ん。しかし、それらを「プログラム生成物」の一部として頒布する場合 は、全体が本使用許諾の条項に従って頒布されなければならず、使用許諾 を受ける他の全ての者に対する許諾もプログラム全体にわたって与えられ なければならず、結果として、誰が書いたかにかかわらず、全ての部分に 本使用許諾が適用されなければなりません。 このように、本条項の意図するところは、完全にあなたによって書かれた 作成物について、権利を要求したり、あなたと権利関係を争うことではあ りません。むしろその目的は、作成物が「プログラム生成物」である場合 にその派生物や集合物の頒布を規制することにあります。 さらに、「プログラム」(又は「プログラム生成物」) と「プログラム生成 物」とはならない他のプログラムとを、単に保管や頒布のために同一の媒 体上にまとめて記録したとしても、本使用許諾は他のプログラムには適用 されません。 4. あなたは、以下のうちいずれか1 つを満たす限り、上記第2 項及び第3 項 に従って「プログラム」(又は、上記第3 項で言及している「プログラム生 成物」)をオブジェクト・コード又は実行可能な形式で複製及び頒布するこ とができます。 o (a) 対応する機械読み取り可能なソース・コード一式を一緒に引き渡す こと。その場合、そのソース・コードの引き渡しは上記第2 項及び第 3 項に従って、通常ソフトウェアの交換に用いられる媒体で行なわれるこ と。 o (b) 少なくとも3 年間の有効期間を定め、且つその期間内であれば対応 する機械読み取り可能なソース・コード一式の複製を、ソース頒布に関 わる実費以上の対価を要求せずに提供する旨、及びその場合には上記 第2 項及び第3 項に従って、通常ソフトウェアの交換に用いられる媒体 で提供される旨を記載した書面を、第三者に一緒に引き渡すこと。 o (c) 対応するソース・コード頒布の申し出に際して、あなたが得た情報 を一緒に引き渡すこと。(この選択肢は、営利を目的としない頒布で あって、且つあなたが上記の (b) 項に基づいて、オブジェクト・コー ド或いは実行可能形式のプログラムしか入手していない場合に限り適用 される選択項目です。) なお、ソース・コードとは、変更作業に適した記述形式を指します。ま た、実行可能形式のファイルに対応するソース・コード一式とは、それに 含まれる全モジュールに対応する全てのソース・コード、及びあらゆる関 連のインタフェース定義ファイル、及び実行を可能にするコンパイルとイ ンストールの制御に関する記述を指します。特別な例外として、実行可能 なファイルが動作するオペレーティング・システムの主要な構成要素(コン パイラ、カーネルなど) と共に(ソース・コード又はバイナリのどちらか で) 頒布されているものについては、その構成要素自体が実行形式に付随 していない場合に限り、頒布されるソース・コードに含める必要はありま せん。 実行可能形式またはオブジェクト・コードの頒布が、指示された場所から の複製のためのアクセス権の賦与である場合、同じ場所からのソース・コ ードの複製のための同等なアクセス権を賦与すれば、たとえ第三者にオブ ジェクト・コードと共にソースの複製を強いなくとも、ソース・コードを 頒布したものとみなします。 5. 本使用許諾が明示的に許諾している場合を除き、あなたは、「プログラ ム」を複製、変更、サブライセンス、頒布することができません。本使用 許諾に従わずに「プログラム」を複製、変更、サブライセンス、頒布しよ うとする行為は、それ自体が無効であり、且つ、本使用許諾があなたに許 諾している「プログラム」の権利を自動的に消滅させます。その場合、本 使用許諾に従ってあなたから複製物やその権利を得ている第三者は、本使 用許諾に完全に従っている場合に限り、引続き有効な使用権限を持つもの とします。 6. あなたはまだ同意の印として署名していないので、本使用許諾を受け入れ る必要はありません。しかし、あなたに「プログラム」又はその派生物を 変更又は再頒布する許可を与えるものは本使用許諾以外にはありません。 これらの行為は、あなたがもし本使用許諾を受け入れないのであれば、法 律によって禁じられます。従って、あなたが「プログラム」(又は「プログ ラム生成物」) の変更又は頒布を行えば、それ自体であなたは本使用許諾 を受け入れ、且つ、「プログラム」又はその「プログラム生成物」の複 製、頒布、変更に関するこれらの条項と条件の全てを受け入れたことを示 します。 7. あなたが「プログラム」(又はその「プログラム生成物」) を再頒布すると 自動的に、その受領者は、元の使用許諾者から、本使用許諾の条項に従っ て「プログラム」を複製、頒布、変更することを内容とする使用許諾を受 けたものとします。あなたは、受領者に許諾された権利の行使について、 さらに制約を加えることはできません。あなたには、第三者に本使用許諾 の受け入れを強いる責任はありません。 8. 裁判所の判決、又は特許侵害の申し立て、又は(特許問題に限らない) 何ら かの理由の結果として、あなたに課せられた条件が本使用許諾と相入れな いものであったとしても(裁判所の命令、契約、その他によるものであ れ)、本使用許諾の条件が免除されるものではありません。本使用許諾によ る責務と、その他の何らかの関連責務を同時に満たす態様で頒布すること ができないならば、あなたは「プログラム」を全く頒布してはいけませ ん。例えば、特許権の内容が、あなたから直接又は間接に複製を受け取っ た全ての人に使用料のないプログラムの再頒布を許さないものであれば、 あなたがかかる特許上の要請と本使用許諾の両方を満足させる方法は、 「プログラム」の頒布を完全に断念することだけです。 本条項の或る部分が何らかの特別な状況下で無効または適用不可能になっ た場合、本条項のその他の残りの部分が適用されるように意図されてお り、また、本条項は全体としてその他の状況に当てはまるように意図され ています。 本条項の目的は、特許やその他の財産権を侵害したり、そのような権利に 基づく主張の妥当性を争うようにあなたに勧めることではありません。本 条項の唯一の目的は、フリー・ソフトウェアの頒布システムの完全性を守 ることで、それは公有使用許諾の実践によって履行されます。多くの人々 が、このシステムの一貫した適用を信頼して、このシステムを通じて頒布 されている幅広い範囲のソフトウェアに惜しみない貢献をしてくれまし た。作成者や寄贈者が他の何らかのシステムを通じてソフトウェアを頒布 したいと決めることは彼らの自由意志であり、使用許諾を受ける者はその 選択を強いることはできません。 本条項は、本使用許諾の他の条項の意味内容が何であるかを完全に明らか にすることを意図しています。 9. 「プログラム」の頒布・使用が、ある国において特許又は著作権で保護さ れたインタフェースのどちらかで制限される場合、「プログラム」を本使 用許諾下においた原著作権保持者は、その国を除外する旨の明示的な頒布 地域制限を加え、それ以外の(除外されない) 国に限定して頒布が許される ようにすることができます。そのような場合、その制限を本使用許諾の本 文にあたかも書かれているかのように本使用許諾の中に組み入れられるも のとします。 10. Free Software Foundation は随時、本一般公有使用許諾の改訂版、又は新 版を公表することがあります。そのような新しいバージョンは、現行のバ ージョンと基本的に変わるところはありませんが、新しい問題や懸案事項 に対応するために細部では異なるかもしれません。 各バージョンは、バージョン番号によって区別します。「プログラム」中 に本使用許諾のバージョン番号の指定がある場合は、その指定されたバー ジョンか、又はその後に Free Software Foundation から公表されている いずれかのバージョンから1 つを選択して、その条項と条件に従ってくだ さい。「プログラム」中に本使用許諾のバージョン番号の指定がない場合 は、Free Software Foundation が公表したどのバージョンでも選択するこ とができます。 11. 「プログラム」の一部を頒布条件の異なる他のフリー・プログラムに組み 込みたい場合は、その開発者に書面で許可を求めてください。Free Software Foundation が著作権を持っているソフトウェアについて は、Free Software Foundation へ書面を提出してください。このような場 合に対応するために我々は例外的処理をすることもありますが、その判断 基準となるのは、次の2 つの目標の実現に合致するか否かという点です。 即ち、1つは我々のフリー・ソフトウェアの全ての派生物をフリーな状態 に保つことであり、もう1つはソフトウェアの共有と再利用とを広く促進 させることです。 無保証 ------ 12. 「プログラム」は無償で使用許諾されますので、適用法令の範囲内で、 「プログラム」の保証は一切ありません。著作権者やその他の第三者は全 く無保証で「そのまま」の状態で、且つ、明示か暗黙であるかを問わず一 切の保証をつけないで提供するものとします。ここでいう保証とは、市場 性や特定目的適合性についての暗黙の保証も含まれますが、それに限定さ れるものではありません。「プログラム」の品質や性能に関する全てのリ スクはあなたが負うものとします。「プログラム」に欠陥があるとわかっ た場合、それに伴う一切の派生費用や修理・訂正に要する費用は全てあな たの負担とします。 13. 適用法令の定め、又は書面による合意がある場合を除き、著作権者や上記 許諾を受けて「プログラム」の変更・再頒布を為し得る第三者は、「プロ グラム」を使用したこと、または使用できないことに起因する一切の損害 について何らの責任も負いません。著作権者や前記の第三者が、そのよう な損害の発生する可能性について知らされていた場合でも同様です。な お、ここでいう損害には通常損害、特別損害、偶発損害、間接損害が含ま れます(データの消失、又はその正確さの喪失、あなたや第三者が被った損 失、他のプログラムとのインタフェースの不適合化、等も含まれますが、 これに限定されるものではありません)。 以上 注意 **** 英文文書(GNU General Public Licence) を正式文書とする。この和文文書は 弁護士の意見を採り入れて、できるだけ正確に英文文書を翻訳したものである が、法律的に有効な契約書ではない。 和文文書自体の再配布に関して **************************** いかなる媒体でも次の条件がすべて満たされている場合に限り、本和文文書を そのまま複写し配布することを許可する。また、あなたは第三者に対して本許 可告知と同一の許可を与える場合に限り、再配布することが許可されていま す。 o 受領、配布されたコピーに著作権表示および本許諾告知が前もって載せら れていること。 o コピーの受領者がさらに再配布する場合、その配布者が本告知と同じ許可 を与えていること。 o 和文文書の本文を改変しないこと。 あなたの新しいプログラムにこれらの条項を適用する方法 ==================================================== あなたが新しくプログラムを作成し、それを公用に供したい場合は、プログラ ムをフリー・ソフトウェアにして、全ての人々が以上の各条項に従ってこれを 再頒布や変更をすることができるようにするのが最良の方法です。 そうするためには、プログラムに以下の表示をしてください。その場合、無保 証であるということを最も効果的に伝えるために、ソース・ファイルの冒頭に その全文を表示すれば最も安全ですが、その他の方法で表示する場合でも、 「著作権表示」と全文を読み出す為のアドレスへのポインタだけはファイル上 に表示しておいてください。 プログラム名とどんな動作をするものかについての簡単な説明の行 ------------------------------------------------------------ Copyright (C) 19 ○○年、著作権者名 ---------- 本プログラムはフリー・ソフトウェアです。あなたは、Free Software Foundation が公表したGNU 一般公有使用許諾の「バージョン2」或いはそれ以降の各バージョ ンの中からいずれかを選択し、そのバージョンが定める条項に従って本プログラム を再頒布または変更することができます。 本プログラムは有用とは思いますが、頒布にあたっては、市場性及び特定目的適合 性についての暗黙の保証を含めて、いかなる保証も行ないません。詳細については GNU 一般公有使用許諾書をお読みください。 あなたは、本プログラムと一緒にGNU 一般公有使用許諾の写しを受け取ってい るはずです。そうでない場合は、Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA へ手紙を書いてください。 また、ユーザが電子メイルや書信であなたと連絡をとる方法についての情報も 書き添えてください。 プログラムが対話的に動作する場合は、対話モードで起動した時に次のような 短い告知文が表示されるようにしてください。 Gnomovision バージョン69、Copyright (C) 19 ○○年著作権者名 ---------- Gnomovision は完全に無保証です。詳細は show w とタイプしてください。これは フリー・ソフトウェアなので、特定の条件の下でこれを再頒布することができます。 詳細は show c とタイプしてください。 上記のshow w やshow c は各々、本一般公有使用許諾の関連する部分を表示す るコマンドを指します。もちろん、あなたが使うこれらのコマンドはshow w やshow c といった呼び名でなくても構いません。さらに、それらのコマンド はあなたのプログラムに合わせる為に、マウスでクリックしたりメニュー形式 にすることもできます。 また、必要と認めた場合には、あなたの雇い主(あなたがプログラマとして働 いている場合) や在籍する学校から、そのプログラムに対する「著作権放棄」 を認めた署名入りの書面を入手してください。ここにその文例を載せます。名 前は変えてください。 Yoyodyne, Inc. は、James Hacker が開発したプログラム`Gnomovision' (コンパイ ラにつなげるプログラム) についての著作権法上の全ての権利を放棄する。 Ty Coon の署名, 1 April 1989 -------------- Ty Coon, 副社長 本一般公有使用許諾は、あなたのプログラムを財産権の対象となっている他の プログラムに組み込むことは認めていません。あなたのプログラムがサブルー チン・ライブラリであって、あなたがそのライブラリを財産権の対象となって いる他のアプリケーションとリンクさせることによって、さらに有用なものに しようとする場合には、本使用許諾書の代わりに、GNU ライブラリ一般公有使 用許諾書に従ってください。 20. 用語集 Argument 関数やルーチンは処理する引数を渡される。 ARP Address Resolution Protocol. IP アドレスを物理ハードウェアアドレ スに変換するために使用される。 Ascii American Standard Code for Information Interchange. アルファベッ トの個々の文字は、8 ビットコードで表される。Ascii は、書かれた文 字を保存するのに最も頻繁に利用されている。(訳注: Ascii コード は、7 ビットです。) Bit 1 か 0 (オンかオフ)を表す最小単位のデータ。 Bottom Half Handler カーネル内で待ち行列に登録された仕事を処理するルーチン。 broadcast address(訳注) (訳注)古い体系には、オールゼロブロードキャストという方式もあった そうです。現在、すべてのビットが 0 の場合は、自ホストを表しま す。以下は、 RFC 1700 からの抄訳です。 IP-address ::= { , } or IP-address ::= { , , } また、"-1" という表記を使う場合、フィールドにすべてビット 1 が含まれる ことを意味する。よく利用される特殊ケースに次のようなものがある。 (a) {0, 0} そのネットワークのそのホスト。送信元アドレスとしてのみ、使用できる。 ... (c) { -1, -1} 制限されたブロードキャスト。送信先アドレスとしてのみ、使用できる。 このアドレスを付加されたデータグラムは、送信元の(サブ)ネットから 外にフォワードされてはならない。 (d) {, -1} ブロードキャストは、指定されたネットワークに送られる。送信先アドレス としてのみ、使用できる。 (e) {, , -1} ブロードキャストは、指定されたサブネットに送られる。送信先アドレスと してのみ、使用できる。 (f) {, -1, -1} ブロードキャストは、サブネット化されたネットワークのすべてのサブネッ トに送信される。送信先アドレスとしてのみ、使用できる。 (RFC 1700 , J.Postel, October 1994) buddy algorithm(buddy system)(訳注) 「このシステムでは、割り当てられるブロックのサイズは 2 の累数で なければならない (すなわち、4 バイト、8 バイト、16 バイト、32 バ イトなど)。また、最小限の割り当てサイズも例えば 8 バイトというよ うに決まっている。ブロックは、ペアで処理される。各ブロックには、 片割れ、いいかえると「相棒(buddy, アメリカの口語で、「友達」を意 味する。状況によっては「敵」を意味することもある)」が存在する。 (p.119, 「Unix カーネルの魔法」、B.Goodheart, J.Cox, 櫻川貴司監 訳, プレンティスホール, 1997) Byte 8 ビットのデータ。 C 高水準プログラミング言語。Linux カーネルの大部分は C で書かれて いる。 CISC Complex Instruction Set Computer. RISC の反対概念。非常に多く の、しばしば複雑なアセンブリ命令をサポートするプロセッサ。x86 ア ーキテクチャは CISC アーキテクチャである。 CPU Central Processing Unit. コンピュータのメインエンジ ン。microprocessor と processor も参照のこと。 clock tick(訳注) 「(訳注)クロックチックやタイマチックという言葉は、「タイマ割り込 みと次のタイマ割り込みの間の時間」という意味でも使われる言葉です が、ここでは、タイマ割り込みというイベント自体を指していま す。」(p.143 注1, 「Linux デバイスドライバ」 , Alessandro Rubini, 山崎康宏 山崎邦子訳, O'Reilly Japan, 1998) 「すべての OS は、何らかの方法でシステム時間を測定し維持しなけれ ばならない。通常、システム時間を実装するには、ハードウェアに一定 の間隔で割り込みをトリガさせる。この割り込みルーチンが時間を「カ ウント」する。Linux では、システム時間はシステム開始以降の 「ティック」数で測定される。1 ティックは(訳注:デフォルトで) 10 ミリ秒に等しいから、タイマ割り込みは 1 秒に 100 回トリガされる。 時間は次の変数に格納される。 unsigned long volatile jiffies: (p.57, 「Linux カーネルインターナル」, M.Beck,Harald Bohme 他, 株式 会社クイック訳, ピアソン, アスキー, 1999) Data Structure これは、メモリ内のデータセットで、複数のフィールドから構成されて いる。 Device Driver 特定のデバイスを制御するソフトウェア。たとえば、NCR 810 デバイス ドライバは、 NCR 810 SCSI デバイスを制御する。 DMA Direct Memory Access (訳注) CPU 以外がメモリに直接アクセスする機能・仕組み。DMA は、初期の大 型コンピュータなど、ROM を持たないシステムで、人がメモリにデータ を書き込む際に使用されたのが最初である。 「今日の入出力システムに使用されているアイデアの多くは、IBM 360 の初期モデルで初めて実用に供せられた。IBM 360 は DMA を本格的に 採用した最初のマシンである。」「IBM は 1964 年に、IBM 360 ファミ リを発表し、....」 (p648/p390, 「コンピュータの構成と設計: ハー ドウェアとソフトウェアのインターフェイス(第 2 版)」, D. Patterson/J. Hennessy, 成田光顕 訳, 日経 BP 社, 1999) ELF Executable and Linkable Format. Unix System Laboratory で設計さ れたオブジェクトファイルのフォーマット。今日では、Linux 上で最も 一般的に使用されるフォーマットとしての完全に定着している。 EIDE Extended IDE Executable image マシン命令とデータを含む一定の構造を持ったファイル。このファイル は、プロセスの仮想メモリにロードされ、実行されることが可能であ る。program も参照のこと。 FDDI(訳注) Fiber Distributed Data Interface。当初は光ファイバーケーブル専用 で設計された。リング状の主幹ネットワークに、ツリー状の枝がぶらさ がる。 Function 何らかの行為を実行するソフトウェア。たとえば、ふたつの数字の大き い方を返すなど。 IDE Integrated Disk Electronics. Image executale image を参照のこと。 IP Internet Protocol. IPC Interprocess Communication(プロセス間通信). Interface ルーチンを呼び出したりデータ構造を渡す際の標準的な方法。たとえ ば、コードのふたつのレイヤー(layer, 層)間のインターフェイスと は、特定のデータ構造を渡したり返したりするルーチンであるといえる だろう。Linux の VFS は、インターフェイスの典型である。 IRQ Interrupt ReQuest. ISA Industry Standard Architecture. これは標準規格であるが、フロッピ ーディスクドライバなどのシステムコンポーネントに使われる、今日で は、やや古くなったデータバスインターフェイスである。 Kernel Module 動的にロードされるカーネル機能であり、ファイルシステムやデバイス ドライバなどで利用される。 Kilobyte 一千バイトのデータ。K バイトとも表記される。 (ネットワークで 1000 , それ以外では 1024 バイト) magic number(訳注) カーネル付属文書 magic-number.txt に詳しい解説があります。日本語 訳は、JF のカーネル 2.2 付属文書のページをご覧ください。 major number(訳注) カーネル付属文書 devices.txt に詳しい解説があります。日本語訳 は、JF のカーネル 2.2 付属文書のページをご覧ください。 Megabyte 百万バイトのデータ。M バイトとも表記される。 Microprocessor 高度に集積された CPU。現在の大部分の CPU はマイクロプロセッサで ある。 Module ファイル。アセンブリ言語の命令か C のような高水準言語かの形式の CPU 命令を含んでいる。 multicast(訳注) 一般に、ユニキャスト(unicast)は、「一対一」、マルチキャス ト(multicast)は、「一対特定多数」、ブロードキャス ト(broadcast)は、「一対全部」の関係に立ちます。文中では、ブロー ドキャストの概念をマルチキャストと表現している部分がいくつかあり ます。イーサネットアドレスについては、ETHER TYPES を参考にしてください。 network trailer(訳注) 「Trailer カプセル化フレームフォーマットは 4.2 BSD の頃にメモリ 間のコピーを効率よく行うために特に VAX のページングシステムに特 化したフレームフォーマットである。現在では既に使われなくなってい る(RFC 893)。」(p.37, 「インターネット標準 クイックリファレン ス」, 野坂昌己著, O'Reilly Japan, 1999) Object file マシンコードとデータを含むファイル。実行イメージとなるには、他の オブジェクトファイルやライブラリとリンクされる必要がある。 Page 物理メモリは、同一サイズのページに分割されている。 Pointer メモリ内の位置。メモリ内での他の位置であるアドレスを含んでいる。 Process これは、プログラムの実行ができる実体である。プロセスは、動作中の プログラムであると考えることができる。 Processor Microprocessor の略称。CPU と同義。 PCI Peripheral Component Interconnect. コンピュータシステムの周辺機 器を相互に接続する方法を述べた標準規格である。 Peripheral システムの CPU に代わって仕事をする情報処理機能を持ったプロセッ サ。たとえば、 IDE コントローラチップなど。 Program 一貫性をもった CPU 命令のセット。"hello world" の表示といったタ スクを実行する。executable image も参照のこと。 Protocol プロトコルは、ネットワークの言語であり、ふたつの協調するプロセス やネットワークレイヤー間でアプリケーションデータを転送する際に使 用される。 Register チップ内の一定の場所であり、情報や命令の保持のために使用される。 Register File プロセッサ内のレジスタのセット。 RISC Reduced Instruction Set Computer. CISC の反対概念。これは、単純 な処理をする少ないアセンブリ命令しか持たないプロセッサ。ARM と Alpha プロセッサはどちらも RISC アーキテクチャである。 Routine 関数と似ているが、厳密に言うと、ルーチンは値を返さない。 SCSI Small Computer System Interface. Shell これはプログラムであり、オペレーティングシステムと人間との間のイ ンターフェイスとして機能している。コマンドシェルとも呼ば れ、Linux 上で最も一般的に利用されているシェルは、bash シェルで ある。 signal mask(訳注) 「(プロセス)シグナルマスクは、プロセスに届かないように、現在ブ ロックされているシグナルの集合を定義している。マスクの i 番目の ビットが 1 であれば、シグナル番号 i のシグナルが届かないようにブ ロックされる。」「ここで重要なのは、シグナルをプロセスに届かない ようにブロックするということが、そのシグナルを SIG_IGN で無視す ることと同じではないという点である。無視されるシグナルは、将来適 切な時点で送り出されるまで保持されるのではなく、捨てられてしまう のである。」(p.138, UNIX C プログラミング, David A. Curry, アス キー書籍編集部監訳, アスキー出版, 1997) SMP Symmetrical multiprocessing. 複数のプロセッサを持つシステムであ り、それらのプロセッサ間では仕事が公平に分担される。 (訳注: Symmetric Multi-Processors ともいう。) Socket ソケットとは、ネットワークコネクションの一方の端を表すも の。Linux は、 BSD ソケットインターフェイスをサポートしている。 Software CPU 命令(アセンブラと C のような高水準言語の両方)とデータ。大部 分の場合は、プログラムと言い換えられる。 sparse address mapping scheme(訳注) (Alpha CPU は、32-bit と 64-bit のロード・ストアしかサポートして おらず、 8-bit や 16-bit のロード・ストアができない旨説明し、そ のことの長所を述べた後で) 「バイト単位のロード・ストアが出来ない ことで、ソフトウェアセマフォや、I/O サブシステムの設計が影響を受 けた。この I/O に関する問題に対する Digital(DEC) の解決策は、I/O 転送の際にいくつかの下位アドレスライン(low-order address lines) を使ってデータサイズを指定し、後でそれをデコードすることでバイト 単位の転送を実現するというものであった。この所謂 Sparse Addressing は、アドレス空間を浪費するとともに、その結果として I/O 空間が非連続 (non-contiguous)なものになってしまった。...」 (chpter 8, Alpha-HOWTO, Neal Crook, LDP, 1997) System V 1983 年に作成された Unix の派生ソフトウェア。これには、何にもま して、System V IPC メカニズムが含まれていた。 TCP Transmission Control Protocol. Task Queue Linux カーネル内で仕事を後回しにするメカニズム。 type of interrupts(訳注) 割り込みハンドラには、「高速ハンドラ(faster version of IRQ handler)」と「低速ハンドラ」がある。/proc/interrupts で + 記号の 付いた割り込み番号は、「高速ハンドラ」であることを示す。 「2 種類の割り込みハンドラの主な違いは、高速ハンドラは割り込みの アトミックな処理を保証するのに対し、低速ハンドラはしない点で す。... 要するに、CPU の「割り込みを有効にする」フラグは高速なハ ンドラの実行中にはオフにされ、どんな割り込みにも対処しないように しています。一方、低速なハンドラが呼び出されると、カーネルは割り 込みの報告を CPU 内で再度有効にし、低速なハンドラ実行中にも他の 割り込みに対処できるようにします。」(p.202, 「Linux デバイスドラ イバ」, Alessandro Rubini, 山崎康宏・山崎邦子訳, O'Reilly Japan, 1998) 高速ハンドラの説明 [in include/asm-i386/irq.h] 低速ハンドラの説明 [in arch/i386/kernel/irq.c] UDP User Datagram Protocol. Virtual Memory システム上の物理メモリを実際の以上の容量に見せるためのハードウェ アとソフトウェアとのメカニズム。 word(訳注) 「コンピュータにおいては、並列に演算されるビットを示す。」 (p.321, ANSI C 言語辞典, 平林雅英, 技術評論社, 1997) 「CPU とメモリ間で一回に受け渡されるデータの基本単位。...8 ビッ ト CPU は 1 ワードが 8 ビット、16 ビット CPU は 1 ワードが 16 ビット、32 ビット CPU は 1 ワードが 32 ビットとなることが多 い。」 (p.848, 標準パソコン用語辞典, 監修赤堀侃司, 秀和システム, 1999) 21. 著作権と配布条件 Legal Notice (訳注: 本章は、原文に付属する著作権と配布条件です。) UNIX は、Univel の登録商標です。 Linux は、Linus Torvalds の登録商標で あり、Unix とは無関係です。 Copyright (c) 1996,1997,1998,1999 David A Rusling 3 Foxglove Close, Wokingham, Berkshire RG41 3NF, UK david.rusling@arm.com 本書(The Linux Kernel)は、次の条件に従うかぎり、無料で、全体もしくは一 部を複製および配布することが可能です。 o 上記著作権表示とこの配布条件とが、全体もしくは一部の複製上のすべて に完全なかたちで保持されなければなりません。 o 翻訳もしくは派生文書を作成した場合、配布に先だって著者に連絡し、そ の承認を得なければなりません。 o この文書の一部のみを配布する場合、この文書全体が入手可能であること を告知し、全体を入手する方法を示されなければなりません。 o 書評での抜粋及び他の文書での引用としてこの文書の僅かな部分を抜き書 きする場合は、引用元を適切に明示する限り、この許可条項を表記する必 要はありません。 学術目的での利用については、これらの規定の適用例外となる場合があるの で、著者に連絡を取り、尋ねてください。こうした制限は著者としての我々を 守るためのものであり、学習者や教育者への制限を課すことを意図するもので はありません。 この文書内のすべてのソースコード(文書自体を含む)には、GNU General Public License が適用されます。左記ライセンスは、匿名 FTP サイトであ る、 prep.ai.mit.edu:/pub/gnu/COPYING で入手が可能です。本書の ``The GNU General Public Lincense'' は、そのライセンスのコピーとなっていま す。 22. Bibliography 1. Richard L. Sites, Alpha Architecture Reference Manual, Digital Press (訳注: alpha 関連文書 は、http://ftp.digital.com/pub/Digital/info/semiconductor/literature/dsc- library.html から入手が可能です。) 2. Matt Welsh and Lar Kaufman, Running Linux, O'Reilly & Associates, Inc, ISBN 1-56592-100-3 (訳注:「Running Linux, 導入からネットワーク構築まで」, 山崎康宏 技 術監修、小嶋隆一 訳、1996年4月発行, O'Reilly ジャパン, ISBN 4-900900-00-1) 3. PCI Special Interest Group, PCI Local Bus Specification (訳注: 関連リンク http://www.pcisig.com) 4. PCI Special Interest Group, PCI BIOS ROM Specification 5. PCI Special Interest Group, PCI to PCI Bridge Architecture Specification 6. Intel, Peripheral Components, Intel 296467, ISBN 1-55512-207-8 7. Brian W. Kernighan and Dennis Richie, The C Programming Language Prentice Hall, ISBN 0-13-11-362-8 (訳注: 「プログラミング言語 C 」,石田晴久 訳, 1989, 共立出版, ISBN 4-320-02692-6) 8. Steven Levy, Hackers, Penguin, ISBN 0-14-023269-9 9. Intel, Intel 486 Prosessor Family: Programmer's Reference Manual, Intel (訳注: おそらく同種の内容の文書として、 Intel Architecture Software Developer's Manual, Volume 1,2,3 があります。インテルのサイト http://www.intel.co.jp/jp/developer/vtune/cbts/refman.htm でご確認 ください。) 10. Comer D. E, Interworking with TCP/IP, Volume 1 - Principle, Protocols and Architecture, Prentice Hall International Inc (訳注: 「TCP/IP によるネットワーク構築 Vol.1 (第 2 版)」,村井純/ 楠 本博之訳, 共立出版, 1993, ISBN 4-320-02667-5) 11. David Jagger, ARM Architecture Reference Manual, Prentice Hall, ISBN 0-13-736299-4 以下は、訳注です。 o 「Linux カーネルインターナル」, M.Beck, H.Bohme,他, 株式会社クイッ ク 訳, ASCII, 1999, ISBN4-7561-3135 o 「Linux デバイスドライバ], Alessandro Rubini, 山崎康宏 山崎邦子共 訳, O'Reilly Japan, 1998, ISBN4-900900-73-7 o 「Unix カーネルの魔法」, B.Goodheart, J.Cox, 櫻川貴司 監訳, プレン ティスホール, 1997, ISBN4-931356-04-4 o 「Linux 2.0 カーネルブック」, R.Card, E.Dumas, 他, 臼田昭司 伊藤敏 他共訳, Ohmsha, 1999, ISBN4-274-07879-5 o 「Linux Core Kernel Commentary」, Scott Maxwell, 小嶋隆一 訳, 小学 館, 2000, ISBN4-7978-2006-3 ... 23. 日本語訳について この文書は、David A. Rusling 著 The Linux Kernel の翻訳です。HTML 版と テキスト版があります。 訳文の HTML 版には、原文の PostScript 版にあるソースコードへのリファレ ンスをリンクとして埋め込みました。ただ、JF サイトには、本文で直接参照 される最低限のソースファイルしか上げていません。ソース全体をご覧になる には、この文書の HTML ソース と、カーネ ル 2.0.33 のソースをダウンロードし、任意の場所に展開して、この文書のサ ブディレクトリ The-Linux-Kernel-images 下に linux という名前でソースへ のシンボリックリンクを張ってください。全ソースファイルが簡単に参照でき るようになります。 カーネルソースは、リングサーバー ftp://ftp.ring.gr.jp/archives/linux/kernel.org/kernel/v2.0 や、お近く のミラーサイトからダウンロードできます。 23.1. 参考文献 この文書は、カーネル 2.0.33 に基づいています。2.2.x での変更について は、 The linux-kernel mailing list FAQ に簡単な説明があります。 2.4.x については、ChangeLog に 「素晴らしき Linux 2.4 の世界」 という記事が あり、LDP に、Linux Kernel Internal という文書があります。 この文書以外のカーネル関連文書については、/usr/src/linux/Documentation 内の kernel-docs.txt や、Kernel links に 参考文献があります。また、本書では触れられていない Linux の起動につい ての文書として、JF に 「コメントから読む Linux カーネル」 があります。 市販されている日本語の Linux カーネル関連書籍について は、``bibliography'' をご覧下さい。 23.2. 謝辞 翻訳に際しては、JF Project のメンバー、特に次の皆さんにお世話になりま した。 川嶋 勤 さん 武井 伸光 さん 山森 浩幸 さん 瀬戸口 崇 さん 瀬藤 誠 さん 加藤 大典 さん 野本 浩一 さん 四亭 さん 水原 さん 佐野 武俊 さん toshi さん 福田 さん 引地 信之さん 武井さんと川嶋さんには、全編にわたり査読していただきました。特に川嶋さ んには、不明な点について繰り返し教えていただきました。山森さん、瀬戸口 さん、瀬藤さんには、何度も校正をお手伝いしていただきました。佐野さん、 野本さん、丁寧な査読ありがとうございました。ご協力いただいたすべての方 に謹んで感謝いたします。 尚、誤訳や、誤字脱字等がありましたら、JF メーリングリスト か、訳者までお知らせください。 千旦裕司