うにみえるが、そうならない場合もある。とくに、 STREAMS にかかわったりすると状況は一変する。また、商用 OS の宿命 で、過去のインターフェイスをある程度はサポートしなければな らないため、 1 つの目的を実現する方法カ数並存することがあ る。ドライバの開発者としては、過去の資産力そのまま使えるの で嬉しいかぎりだが、カーネルの保守は大仕事だ。そもそも、 1 つ の目的に対して複数のインターフェイスがあると、ドライパ・プ ログラムの初 , こ者は混乱しそうである。 Linux ネットワークの I / O 抽象化モデル L ⅲ ux における Ethernet などのネットワーク・ドライ バは、デバイスノードを作らない代わりに socket インタ ーフェイスを介して活用される。このモデルで扱うデータ は、バケットという可変長の論理的単位である。バケット は sk-buff 構造体 ($LINUX/incIude/linux/skbuff.h で定義されている ) に常内され、カーネル・モジュール間 を往き来する。データの入出力がバケット単位でおこなわ れることを除けば、送受信データに順番があるなど、キャ ラクタ・デバイスに似ている。 デバイスノードがないといっても、ドライバが実行する ことに大きな差があるわけではない。ユーザー・プログラ ムがドライバを直接叩けるインターフェイスがないという だけで、代わりにカーネル内部に対するインターフェイス を実装しなければならない。実際の動作を、デバイスノー ドのインターフェイスと対比してみよう。 ・デバイスの起動と停止 デバイスノードがあれば、 open システムコールをトリ ガーとしてデバイスを起動し、最終の close をもってデ バイスをシャットダウンするようなことができる。ネッ トワーク・デノヾイスでも、バケット送受信の開始と停止 などを逐次カーネルが指示してくるので、ドライバはそ れに従わなくてはならない。 手友きプログラマーが、この点をおざなりにしたらどう なるだろうか。たとえば、ドライバがメモリにロードさ れた時点でデバイスを起動し、その後は ( カーネルに指 示されても ) デバイスを停止せすに放っておいたとする。 容易に想像できるのは、データを要求しているプログラ ムがないにもかかわらずハードウェアを動かし続けるの は無駄なだけでなく、システムによけいな負担を強いる ことになる。より深刻な問題は、ドライバがメモリから 88 アンロードされたときに発生する。 Linux にも Solaris にも、ドライパ・モジュールを動的にロード / アンロー ドする機能がある。そして、ドライバがアンロードされ る際、デバイスはすべての活動を停止しなければならな い。ドライバがメモリにいない状態でデバイスカ働き続 けていると、最悪の場合はシステムのパニックを惹き起 ・バケットの送信と受信 これらは、デバイスノード・エントリポイントの write と read にそれぞれ相当する。ネットワーク・ドライバ には、 sk-buff 構造体に十タされたバケットを送信する 仕組みと、受信したバケットを sk-buff 構造体に入れて カーネルに渡す仕組みがなければならない。 ・デバイス・ノヾラメータの設疋 デバイスノードでは、 ioctl 工ントリポイントに相当す る。 socket では、ファイル・ディスクリプタというかた ちでユーザー・プログラムに渡され、これに対して ioctl システムコールが発行される場合がある。ただし、ほか の動作とは違い、かならずしもサポートする必要はない ( ドライバが ioctl をサポートしていなければ、ユーザ ・プログラムには EINVAL カされる ) 。 以上のように、データの性質や実装するインターフェイ スが異なっても、ドライバがなすべき処理は同じであるこ とが分かる。 こまでの説明を読んで、、、案外シンプルかも " と思った 人がいるかもしれない。残念ながら、 Linux のネットワー ク I/O 抽象化モデルの実装はスパゲティ状態で、すべてを 網羅しようとするとひどく苦労する。それには、以下のよ うにいくつかの理由がある。 1 つは、ネットワーク技術には多種多様なインターフェ イスやプロトコルが混在しているからだ。そして、ドライ バとその他のモジュールとのあいだで、情報の隠蔽がきち んとできていないからである。ドライバとその他のモジュ ールのインターフェイスでは、 $LINUX/include/linux netdevice. h で定義されている net-device 構造体がきわ めて重要な役割を担っている。ところが、この構造体は相 当に肥大化しており、ほかのモジュールのプライベートな 部分がドライバからまる見えになっている。さらに、 ISDN の場合は net-device ではなく isdn-net-dev 構造体であ UNIX MAGAZINE 2005 . 12
図 15 gld-mac-info 構〕当本 typedef struct gld—mac—info { gldm—private ; c addr ー t (*gldm-reset) ( ) ; i nt (*gldm-start) ( ) ; int (*gldm-stop) ( ) ; int (*gldm—set—mac—addr) ( ) ; int (*gldm-send) ( ) ; int (*gldm-set-promiscuous) ( ) ; int (*gldm—get-stats) ( ) ; int (*gldm-ioctl) ( ) ; i nt (*gldm—set—multicast) ( ) ; i nt (*gldm-intr) ( ) ; uint_t } gld—mac—info—t ; ができさえすればいい " というのなら、通常デバイスノード gldm-stop はそれぞれデバイスの起動と停止、 gldm-send を作成するドライバのほうが適している (socket や DLPI はバケット送信、 gldm-ioctl は ioctl の処理用のエント のオーバーヘッドが回避できる ) かもしれない。 リポイントである。これらのエントリポイントの引数には また、すべてに共通しているのは、、、言囚み (read)" や gld-mac-info 構造体が含まれるので、同じ種類のデバイ 、、書込み (write)" など、デバイスに対する動作があらかじ スカ夏数あっても、操イ付寸象のデバイスカ定できる。 め定義されており、ドライバはその動作を実行する関数へ 受信したバケットは、メッセージ・プロックにオ内して のポインタを指すということだ。これらは、、上位層→ドラ から gld-recv (9F) で gld モジュールに渡す。 イバ " 方向の切り口であり、 、、ドライパ・エントリポイン 細部は異なっていても、なんとなく L ⅲ ux に似ていると ト " と呼ばれる。 思うかもしれないが、それは gld モジュールがあるおかげ ドライバの仕事を助ける、、ドライバ→上位層 " 方向の切 である。 gld がなければ (STREAMS と DLPI サポート り口もあり、ドライバとカーネルを一体化させる。次回 を自分で組み込むとしたら ) 、見え方はまったく異なり、格 は、カーネルとドライバの協力関係を築く、、カーネルサー 段に難しくなる。 ビス " について解説する。 ( さじま・たかひろ ) 今回のまとめ [ 赭文献 ] ドライバは、デバイスの制御だけではなく、ユーザーやカ [ 1 ] Alessandro Rubini 、 Jonathan Corbet 著、山崎康宏、山 ーネルがデノヾイスを活用できるようにしなければならない 崎邦子、長原宏治、長原陽了訳 TLinux デバイスドライバ第 2 という役割を担っている。そこで、そのためのインターフ 片応、オライリー・ジャパン、 2002 年 [ 2 ] 佐島隆博著 fSoIaris デバイスドライバ』、オライリー ジ、ヤノヾ ェイスがあらかじめ決められている。ドライバは、使用す ン、 2003 年 るインターフェイスに合わせて送受信データを加工する必 [ 3 ] ルロ t 9 Device 〃 7 、加 e , Solaris 10 Software Devel- 要がある。この記事では、このインターフェイスを、、 I / O oper C011ection, Sun Microsystems, 2005 (http:// 抽象化モデル " と呼び、デバイスノードを用いた I/O とネ dOCS. sun. corn ットワーク I/O の一部を抜粋して解説した。 [ 4 ] STREAMS PT ・・四 ramm 9 Guide, Solaris 10 Soft- ware Developer C011ection, Sun Microsystems, 2005 ドライバを作成する際に重要なのは、対応する I/O 抽 (http://docs.sun.com/ 象化モデルを正しく選択することである。それには、デバ [ 5 ] Solaris 10 Reference Manual C011ection Sections, sec- イスがどのように使われるのかを十分にロ刳未しなければな tion 9E (DDI and DKI Driver Entry P0ints), 9F らない。 Ethernet インターフェイスひとつをとっても、 (DDI and DKI Kernel Functions), 9S (DDI and DKI Properties and Data Structures), Sun Microsystems, TCP/IP 通信を前提とするのなら、ここで小したモデル 2005 (http://docs.sun.com/ に合わせればよいだろう。だが、 ンヾケット・キャプチャ H ャ上〒上〒上〒上〒上〒上上 H 一↓ CQ CQ CQ 92 UNIX MAGAZINE 2005 . 12
: UN Ⅸデバイスドライバ [ 2 ] UNIX におけるデバイス 特集 佐島隆博 前回は、 、、デバイスドライバとは何か " という問いに対 はない。新しいインターフェイスの公開後も、旧いもののサポー トがしばらく継続される。私の経 0 も、 Solaris 8 で作成した する 1 つの答を駆け足でみてきた。デバイスとその接糸研彡 ドライバが、再コンパイルのみで Solaris 9 や Solaris 10 で問 態、カーネル内でのみえ方、ドライバによる制御、入出力デ 題なく動作することが多い。 ータのやりとりなどの要素をかいつまんで説明したが、、、基 本概念の共有 " が目的だったこともあり、やや抽象的すぎた 、デバイス " の定義 かもしれない。 ーヨ殳的には、デバイスとは入出力装置のことである。た 今回は、デバイスが UNIX システムで具体的にどのよう しかに、マウス、キーポード、グラフィック・ディスプレイ、 に表れ、使われているのかを実例を挙げてみていく。まず 外部記億、ネットワーク・インターフェイスなど、実在す は、ユーザー側の視からみたデバイスというものがカー る大半のデバイスはデータ入出力装置だ。ただし、 UNIX ネル内でどのような未をもつのかを解説し、続いて、ド システムではそのかぎりではなく、 ライバとカーネル間のインターフェイス (Driver-Kernel ・機能を、、データ入出力 " という概念に抽象化できるもの lnterface) について述べる。 なら、なんでもデバイスとなりうる とりあげる例 のである。なかには、 /dev/null のような実体のないデバ イス ( 仮想デバイス ) もある。有形無形にかかわらす、すべ 世の中で、、 UNIX" と一言で束ねられている OS に共通 てのデバイスには対応するドライバが必要だ。 しているのは、 POSIX 仕様で定められている上位層イン ドライバには、 2 つの役目がある。 1 つはデバイス・ハ ターフェイスだけであり、カーネル自体の実装や内部イン ードウェアの制御、もう 1 つはデバイスの機能をカーネル ターフェイスはそれぞれに異なっている。この特集では、 やユーザーが活用できるようにすることである。後者につ Linux と SoIaris を、それぞれフリーと商用の UNIX の いては、カーネルカ誤定しているいくつかの、、 I/O 抽象化 例としてとりあげる。文中に示すソースコードは、 Linux モデル " にあてはめなければならない。 I/O 抽象化モデル 2.6.13.4 と SoIaris 10 用のドライバから抜粋したもので とは、たとえば、デバイスに対する open ( 2 ) と close(2) ある。また、とくに断らないかぎり、コマンド出力は X86 が叮能で、 ioctl ( 2 ) によってデバイスのパラメータを設疋 版 Fedora core 2 と SPARC 版 Solaris 10 のものであ し、 read(2) と write(2) で 1 オクテット単位のデータ る。、、 $LINUX" という表記は、 Linux カーネルのソース 奐ができる、というような定義のことである。この例が、 ツリーのトップ・ディレクトリを示す。 シリアルポートのようなキャラクタ・デバイスに適用でき ることカ吩かるだろうか。 さすか用 OS というべきか、 Solaris のドライバ用インタ 要するに、ドライバしだいでユーザーからのデバイスの ーフェイスは、その多くが公開されている。これらのインターフ 見え方カ畯わるということだ。 Solaris では、ほばすべて ェイス定義はメーカーによりサポートされ、頻繁に変更されること 81 UNIX MAGAZINE 2005. 12
図 1 デバイスノード (Linux 窈列 ) # ls ー 1 /dev 2004 hda 2004 hdal 2004 Ⅱ u11 brw brw 1 1 1 1 1 1 て 00t root disk disk kmem root uucp て 00t 4 , 0 Feb 1 Feb 1 Feb 24 3 Feb 24 64 Feb Feb 5 24 24 24 24 のデバイスをファイルとして表しているが、個々のファイ ルが提供するインターフェイスはデバイスごとに異なる。 82 ど、キャラクタ・スペシャルデバイスはシリアルポートな - 殳には、プロック・スペシャルデバイスはディスクな いる。 のメジャーナンバーとマイナーナンバーが表示されて ・通常はファイルサイズが表示される箇所に、デバイス または c ( キャラクタ・スペシャルファイル ) である。 ・ファイルタイプが、 b ( プロック・スペシャルファイル ) の 2 点だけである。 ディレクトリ・エントリで通常ファイルと異なるのは、次 書込み、実行 " の許可が定できる。 グループ、その他 " の区別があり、それぞれに、、言囚み、 通常ファイルと同じく、デバイスノードにも、、オーナー が可能 ・ファイルシステムの機能を用いた簡易的なアクセス制御 コールで操作できる。 通常ファイルと同様に、 open などの汎用的なシステム ・ユーザープロセスからのアクセスか容易 ァイルシステムに置くことには、以下の 2 つの利点がある。 ファイルと同様であることが分かる。デバイスノードをフ 図 1 のデバイスノード・エントリを見ると、ほとんど通常 これらのファイルのことを、、デバイスノード " と呼ぶ。 表されるデバイスは / dev ディレクトリ以下にある ( 図 1 ) 。 やすいだろう。まず、 Linux を例にとると、ファイルとして これは、実際にシステムをみながら進めたほうが分かり ある。 Linux では、ファイルとして表されないものもたくさん 2004 2004 UN 工 X MAGAZINE 2005 . 12 、落し穴 " はデバイスそのものの制脚にカ功、わることだ。 デバイスを通じてやりとりされるデータのことである。一方、前回紹介した いように注意してほしい。ここで、キャッシュ可能 " などと書いているのは、 1 前号の「デバイスドライノヾならではの落し穴」の節で説明した大と混同しな は各ドライバに任されている。 IDE ディスクドライバの場 全体で固有の番号なのに対し、マイナーナンバーの使い方 使い方の違いがよく分かる。メジャーナンバーはシステム また、この出力例からは、デバイス・マイナーナンバーの じた人もいるのではないだろうか。 null と zero の正体が同じドライバであることを意外と感 よって制御されていることは自然であろう。だが、 rnem 、 ディスクなので、同じドライバ ( 同じメジャーナンバー ) に zero は 1 である。 hda と hdal は両方とも IDE ハード メジャーナンバーは 3 、 ttyS0 は 4 、そして mem 、 null と が割り振られている。図 1 の出力例では、 hda と hdal の り、それぞれに固有の番号 ( デバイス・メジャーナンバー ) システムには数多くのドライバがインストールされてお つにまとめたりするといった最適化が許される 1 この場合、データをキャッシュしたり、複数の書込みを 1 わっても結果カ理的に正しければよしとすることもある。 である。また、データ処理の過程で、読み書きの順序カ畯 バイスごとに異なり、たとえばディスクなら 512 バイト ータのある一塊を単位として扱う。、、一塊 " の大きさはデ 書きの川印は尊重される。プロックデバイスの場合は、デ 的な単位などは定義されていない。ただし、データの読み イスカ皺うデータはバイトストリームで、データ長の論理 たはその扱い方が異なるということだ。キャラクタ・デバ デルが違う " ということは、入出力されるデータの性質ま 、、 I/ O 抽象化モ 抽象化モデルにある。詳細は後述するが、 ど、というように曖昧に区別されるが、これらの違いは I / O 2004 ttyS0 mem
図 5 mknod の実行 (Linux) # ls ー 1 /dev/null crw—rw—rw— 1 root 1 , # pwd /saj ima # mknod mynull c 1 3 # ls ー 1 /saj ima/mynull 1 root root 1 , 3 Feb 24 2004 /dev/null 3 Sep 3 18 : 21 /sajima/mynull 図 6 SoIaris では、 /dev の下には /devices へのシンポリック・リンクしかない # ls ー 1 /dev/null lrwxrwxrwx 1 root other # ls ー 1 /devices/pseudo/mm@O :nu11 1 root SYS 27 Feb 29 2004 /dev/null ー > ./devices/pseud0/mm@O : Ⅱ u11 2 Sep 3 23 : 50 /devices/pseudo/mm@O :null 13 , スに変身しているのは、 STREAMS 機能の一部を活用し ているためだということを憶えておいてほしい。 デバイスノードの管理 L i nux の場合 L ⅲ ux のディストリビューションやカーネルの設疋によ っては、 /dev 以下のデバイスノードの数の多さに驚かされ る。単純に、、 ls /dev ー wc ー 1 " としただけでも、数千のノ ードがある。そのなかには、明らかにシステムにないもの (parport7 など ) や、アーキテクチャに無関係なもの ( X86 システム上の amigamouse など ) も含まれている。 これらは、 MAKEDEV(8) によって作成されたノード である。 /etc/makedev. d ディレクトリに設定ファイル があり、ノード名、メジャーナンバー、マイナーナンバー などカ第当されている。 MAKEDEV は、設疋ファイルで の指定どおりにデバイスノードを作成しているだけである。 デバイスノードを自分で作成したい場合は mknod ( 8 ) コマンドを使う ( 図 5 ) 。これで、 /sajima/mynull とい う null デバイスと同じものができた。 mynull の動作は通 常の null デバイスと同じであり、デバイスノードが /dev ディレクトリにある必要がないことも分かる。また、通常 ファイルと同様に、 rm コマンドでデバイスノードを削除で きる。 この仕組みは、 ( ディスクを無駄に消費することを除け ば ) - ヨ殳ユーザーへの景彡響が少ないが朝財票準のドライバの 作成や運用をおこなう立場からみるといささか問題がある。 自分で作成したドライバにメジャーナンバーを割り当てる 場合、ほかのドライバとの衝突を考慮しなければならない。 84 できあがったドライバを自分で使うときは、実験用に予約 されている範囲から適当に選べばよいが、インターネット でドライバを配布するような場合は、選択した番号がほか のシステムで衝突を起こす可能性がある。つまり、ドライ バをインストールする管理者カ噫識してメジャーナンバー を設定しなければならず、扱いやすいとはいえない。また、 空いている番号をカーネルに割り当ててもらう方法もある が、これも使いにくい。割り当てられたメジャーナンバー がコンソールなどに出力される場合、管理者はその番号を 確認してからデバイスノードを作成する必要がある。別の ドライバをインストールしたりすると、割り当てられる番 号カ畯わる可能性があるので、そのたびに確認してデバイ スノードを作りなおさなければならない ( たいていは、デ バイスカ駛えなくなったときに、、、あっ、そういえば・・ と思い出して対応することになるだろう。現実には、思い 出せればまだ救いがある ) 。 SoIaris の場合 SoIaris におけるデバイスノードの管理は、よりスマート である。前述したとおり、メジャーナンバーの割当ては自 動的におこなわれるので、衝突を心配する必要はない。デ バイスノードは、ドライバ主導によって / devices ディレク トリ以下に作成される。 /dev ディレクトリ以下には、 /de- vices にあるデバイスノードへのシンボリック・リンクだけ が置かれている ( 図 6 ) 。 /devices および /dev ディレクトリは、 devfs (7FS) と devfsadm(1M) の管理下にある。ドライバの要求に応じ てデバイスノードの作成と削除をおこない、 /dev ディレク トリからのシンボリック・リンクを張ったりする。これだ UN 工 X MAGAZINE 2005. 12
特集 図 2 SoIaris でのデバイス・メジャーナンバーの割当て兄び )i 砡忍 # more /etc/name—to—major UN Ⅸデバイスドライバ [ 2 ] sd 32 # 1s brw—r— brw—r— 1 root 1 て 00t 1 て 00t -IL /dev/sd* SYS SYS SYS 32 , 32 , 32 , 8 Feb 29 9 Feb 29 10 Feb 29 2004 /dev/sdla 2004 /dev/sdlb 2004 /dev/sdlc 図 3 デバイスとマイナーナンバー (SoIaris) # ls —IL /dev/null /dev/kmem /dev/mem /dev/zero 1 root 1 て 00t 1 root 1 root SYS SYS SYS SYS 13 , 13 , 13 , 13 , 図 4 ネットワーク・テパイスの表示がおかしい # more /etc/name—to—major hme 7 eri 8 tmrk 259 # ls —IL /dev/hme /dev/eri /dev/tmrk 1 て 00t 1 root 1 root SYS SYS SYS 2004 /dev/kmem 2004 /dev/mem 2 Sep 3 23 : 50 /dev/null 2004 /dev/zero 2004 /dev/eri 2004 /dev/hme 11 , 259 Ju1 15 20 : 34 /dev/tmrk 11 , 11 , 1 Feb 29 0 Feb 29 12 Feb 29 8 Feb 29 7 Feb 29 合は個々のディスクの識別にマイナーナンバーを使ってい るだけで、マイナーナンバーが異なるからといってディス クとしての扱い方か変わるわけではない。これに対し、メ モリドライバはマイナーナンバーによって、その動作を変 化させている。マイナーナンバーが 3 の場合は null デバ イスとして、 5 であったら zer 。デバイスとして振る舞うの だ。つまり、、、 nu Ⅱ " などのデバイスノード名によって動作 カ畯わるわけではない。同じ nu Ⅱという名前でも、マイナ ーナンバーが 5 であれば zero デバイスとなる。 Linux カーネルにおけるメジャーナンバーの割当ては 公式的にフリーズされており、現在は新規割当てはおこな われていない。ただし、実験用に予約されている番号枠 があり、ランタイムに空いている番号を自動的に取得する 仕組みもあるので、個人での実験には不自由はしないだろ う。詳細は、 Linux カーネル・ソースツリーの $LINUX/ Documentation/devices. txt に言当杢されている。 solaris では、デバイス・メジャーナンバーの割当てはド ライバのインストール時に自動的におこなわれる。 /etc/ name-to-major ファイルを参照すれば、現在の割当てが UNIX MAGAZINE 2005 . 12 どのようになっているカ認できる ( 図 2 ) 。 Linux と同様に、異なるマイナーナンバーで動作カ畯わ ることもある ( 図 3 ) 。 こで、何かおかしいと気づいた人がいるかもしれない。 ネットワーク・デバイスに関する表示がおかしいのである ( 図 4 ) 。 eri 、 hme と tmrk2 はハードウェアもドライバも異なる Ethernet デバイスだが、本来はメジャーナンバーである clone 11 # grep 11 /etc/name-to—major されているメジャーナンバー 11 とは何であろうか ? はすの番号がマイナーナンバーとなっている。 2 tmrk は私カ胙成したドライノヾで、 Solaris 標のものではない。 は深入りしないが、 Ethernet デバイスがクローンデノヾイ い通信サポート・フレームワークを継承している。 派生した OS で、 STREAMS というきわめて汎用性の高 solaris は、 SystemV Release 4 ( 以下、 SVR4) から 83
UN Ⅸデバイスドライバ [ 2 ] ドライバは I/O 抽象イヒモデルと ) 叫する アプリケーション プログラム open(2) read ( 2 ) write ( 2 ) ユーザー空間 カーネル空間 /dev/mydrv デバイスノード ー℃抽象化モデル 図 7 からのリターン 工ントリポイント 呼出し 工ントリポイント ドライバ 特集 アプリケーション プログラム socket ( 2 ) Iisten(2) send(2) recv(2) Socket レ O 抽象化モデル 工ントリポイント netif_rx() からのリターン 工ントリポイント 呼出し けみると Linux がひどく劣っていると思うかもしれない が、そうではない。 Linux でも、 udev という devfs の代 わりになるものが用意されており、 Fedora Core 4 ではデ フォルトで有効になっている。 Solaris でも、 devfs に移 行したのは Solaris 10 からである。 I/O 抽象化モデル さきほど述べたように、 I/O 抽象化モデルとは、入出力 データの扱いをモデル化したものである。カーネルは複数 のモデルをもち、このうちの 1 つまたは複数と連携をとる ようにドライバを実装することで、ハードウェア個別要素 の隠蔽が実現される ( 図 7 ) 。 I/O 抽象化モデルの実体は、カーネルとドライバカ陬り 交わす約束事である。アプリケーションを C 言語で作成し た場合、プログラムは main() 関数から起動される。これ はプログラマーカ噫図してそうするのではなく、あらかじ め、℃言言吾アプリケーション・プログラムの入口は main ( 関数 " と決められているからである。ドライバでも同様で、 、、エントリポイント " と呼ばれる入口の規定がある。ただ し、アプリケーションではエントリポイントが ma ⅲ ( ) 関 数だけなのに対し、ドライバには複数あり、さまざまな条 件によって実装が必顎かどうかカ畯わるようになっている。 以下では、 3 つの I/O 抽象化モデルを例に説明する。最 初にデバイスノードを用いたモデル、続いて Linux ネット UNIX MAGAZINE 2005. 12 ワーク、 Solaris ネットワークの順で解説する。 ドライバ デバイスノードを用いた I/O 抽象化モデル まず、デバイスノードがなぜ必要なのかを考えてみよう。 / dev ディレクトリ以下にあるノードに対し、どのような操 作をおこない、またどのような応答を期待しているのだろ うか。このあたりを整理すれば、おのずとどのようなモデ ルなのかがみえてくるはすである。 デバイスノードがある ( ユーザーからみえる ) というこ とは、ユーザー・プログラムから直接アクセスされること を前提としている。さらに、ファイルシステム上のファイ ルというかたちをとっているので、すくなくとも以下の操 作の対象となると考えられる。 ・ open ( 2 ) ・ close ( 2 ) ・ read ( 2 ) ・ write ( 2 ) ・ mmap ( 2 ) ・ ioctl ( 2 ) open と close は、デバイスノードへのアクセスの確立お よび終了のために不可欠だ。その他のシステムコールは、 デバイスによっては意未がないかもしれないが、 I/O 抽象 化モデルというレベルでは用意されているべきである。 Linux では、ファイルに対しておこなえる操作を file- operations という構造体で管理している ( 図 8 ) 。 図からも分かるとおり、中身のほとんどが関数へのポイ 85
図 14 多重化ドライバの活用 ユーザー空間 カーネル空間 arp(7P) ip(7P) ネットワーク・ドライバ ip(7P) arp(7P) ip(7P) ネットワーク・ドライバ 特集 arp(7P) ip(7P) ファイアウォール LJN Ⅸテパイスドライバ [ 2 ] 多重化ドライバ STREAMS モジュー丿レ STREAMS のは不可能なので、 定して話を進める 5 。 こでも Ethernet と TCP/IP に限 「、、デバイス " の . 疋義」の節で、 Ethernet デバイスのノ ードはクローンデバイスだと述べた。クローンデバイス・ ノードでは、 open されるたびに独立した stream が作成 される。通常、 1 つのデバイスノードを複数回 open する と、そのノードのリソースは複数のファイル・ディスクリ プタで共有される。これらのファイル・ディスクリプタか らの書込みは 1 つに多重化され、複数の読込みが同時に 発生するとデータの取り合いになってしまう。しかし、ク ローンデバイスでは、各ファイル・ディスクリプタカ虫自 の stream を占有するため、このような混乱は生じない。 ドライバは書込みがどの stream からきたかを知ることが できるし、複数の読込みが同時に起きている場合には、各 stream にデータをコピーして渡すような措置もとれる。 snoop (IM) コマンドでは、 TCP/IP 通信をおこないな がらバケット・キャプチャができが、それはこの機能を使 っているからである。 Ethernet ドライバの実装というと、 STREAMS クロ 5 本文の説明を読むと、ネットワーク・ドライバは STREAMS ドライノヾで なければならないと思うかもしれないが、そうではない。 tcp (7P) や ip など、 SoIaris に含まれるその他のモジュールが STREAMS ドライバ を崩是としているだけであり、これらのモジュールにいっさい依存しないのな ら、 STREAMS ドライバに固執する尼要はない。 UNIX MAGAZINE 2005. 12 ドライバ ーンドライバで DLPIv2 に対応していることになるが、 の部分を作るだけでも面倒なうえ、 STREAMS と DLPI の概念を明確に理解するまではかなり苦労する。そこで、 Ethernet というひろく普及したデバイスについては、ほ かのカーネル・モジュールとのやりとりをすべて引き受け てくれる便利なモジュールとして、、 Generic Lan Driver ( gld ( 7D ) ) " が用意されている。これを利用すれば、ドラ イパ・プログラマーの負担はかなり軽減されるが、 gld カ咽 定している Ethernet の領域を越えることはできない ( た とえば、ジャンボフレームには未対応で、最大バケットサ イズは 1 , 500 バイトといったような制約がある ) 。 gld を利用する場合には、各デバイス・インスタンスに /usr/include/sys/gld. h で定義されている gld-mac- info(9S) 構造体 6 を埋めなければならない。この構造体 は、 gld-mac-alloc (9F) 関数を用いて取得する。実際の 構造体は、図 15 に示したものより多くの変数をもっている が、 gld-mac-info (9S) のマニュアルページに言当のある もの以外は触らないほうがよい。 図 15 を見ると、 Linux の net-device 構造体と同じよ うなエントリポイントがあるのが分かる。 gldm-start と 6 この構 i 当本の定義を見ると、やたらに reserved フィールドがある。これ は SoIaris 2.6 日弋の勿であり、 SoIaris 9 までは当時の変数名カ第丕 ネットワーク・ドライバ されていた。現在では、パディングとしての役割しかない。 91
」」公をな」三 2005 年 12 月 1 日発行 ( 毎月 1 回 1 日発行 ) 第 20 巻第 12 号通巻 230 号昭和 63 年 9 月 5 日第三種郵便物認可 0 200 ツの」マガジン 00 円 をとセ諄ュリ一 ーっデ〉ラッ Xen で安全な 公開サーバーをる ・危険なネットワーク構成とは ・安全な仮想 DMZ ネットワヂク ・ Xen のパフォーマンス・テスト リ N Ⅸスドライバ ドライバの役割 ・デバイスのみえ方 ・デバイスノードの管理 ・ I/O 艫象化モテル 国立天文台のネットワーク 衛星回線の運用と課題 プログラミンク・テクニック newsy og がおこなう処理 ネットワーク・ミニ実験室 DNS のキャッシュ 働くリ N Ⅸ Xse Ⅳ e による新計算機環境 林檎の暮らし 初めての Mac OS X
図 8 file-operations 構〕本 ($LINUX/include/Iinux/fs. h) struct file—operations { struct module *owner ; loff_t (*llseek) (struct file * , loff—t, int) ; ssize—t (*read) (struct file * , char —user * , size—t, loff—t * ) ; ssize—t (*aio—read) (struct kiocb * , char —user * , size—t, loff—t) ; ssize—t (*write) (struct file * , const char —user * , size—t, loff—t * ) ; ssize—t (*aio—write) (struct kiocb * , const char ——user * , size—t , loff—t) ; int (*readdir) (struct file * , void * , filldir—t) ; unsigned int ( * P011 ) (struct file * , struct poll-table-struct * ) ; int int int int int int int int int (*ioctl) (struct inode * , struct file * , unsigned int , unsigned 10 Ⅱ g ) ; (*mmap) (struct file * , struct vm—area—struct * ) ; (*open) (struct inode * , struct file * ) ; (*flush) (struct file * ) ; (*release) (struct inode * , struct fi1e * ) ; (*fsync) (struct file * , struct dentry * , int datasync) ; (*aio—fsync) (struct kiocb * , int datasync) ; (*fasync) (int , struct file * , int) ; (*lock) (struct file * , int , struct file—lock * ) ; ssize—t (*readv) (struct fi1e * , const struct iovec * , unsigned 10 Ⅱ g , loff—t * ) ; ssize_t (*writev) (struct file * , const struct iovec * , unsigned 10 Ⅱ g , loff—t * ) ; ssize—t (*sendfile) (struct fi1e * , loff—t * , size—t, read—actor—t , void * ) ; ssize—t (*sendpage) (struct fi1e * , struct page * , int , size—t, loff—t * , int) ; unsigned 10 Ⅱ g (*get—unmapped—area) (struct file * , unsigned long , unsigned 10 Ⅱ g , unsigned 10 Ⅱ g , unsigned 10 Ⅱ g ) ; int (*check-flags) (int) ; int (*dir—notify) (struct file *filp, unsigned long arg) ; 図 9 、、デバイスのオープン " を実装した関数 static int mydrv—open(struct inode * , struct file * ) ; static struct file—operations mydrv—fops mydrv—open , / * open * / static int mydrv—open(struct inode *inodep , struct file *filep) ンタである。たとえば、 open という変数には、ファイル をオープンする際に呼び出される関数へのポインタを設定 する。通常のファイルであれば、それに適した処理を実装 した関数のアドレスが、各ファイルシステム・モジュール によって設疋されている。ドライバの場合は、、、デバイス をオープンする " という概念に則った処理をおこなう関数 ( 図 9 ) を実装し、そのアドレスを叩 en 変数に設定しなけ ればならない。 この構造体を埋めて register-chrdev() などのカーネル 関数を呼び出せば、 mydrv-open() は open システムコー ルのエントリポイントとして登録さ楸デバイスノードが オープンされたときに呼び出されるようになる ( 誌面の都 86 合上、で折り返しています。以下同様 ) 。 if (register—chrdev (MYDRV—MAJOR , 疇 - "mydrv" , &mydrv—fops) ) { 工ラー処理 file-operations 構造体の変数名をみれば、それぞれがど のシステムコールに対応しているか、おおよその見当はつ く。ただし、 close にどれが対応しているのカ功ゞ分かりにく い。デバイスノードが最後に close されたときは、 release 変数に設定されている関数カび出される。しかし、同時 に複数の open を許可していると、最終以前の close では UN 工 X MAGAZINE 2005. 12