Skip to main content.
Google custom search

NetBSD ドキュメンテーション: 擬似デバイスの書き方

擬似デバイスの書き方


擬似デバイスの書き方

序説 (トップ)

このドキュメントはカーネルドライバーを書いてみようという方へのガイドです。 ここではシンプルな擬似デバイスドライバーの書き方を扱います。 カーネルの構築、makefile 関連や新しいカーネルのインストールに関する他の奥義 はこれらはこのドキュメントには含まれてなく(別に)必要です。 また、カーネルプログラミングそれ自体も含まれていなく - これは、ユーザーレベルプログラミングとは多くの面でとても違います。 という断り書きをした上で、この文章を読めば、あなたのコードを カーネルに加えて動作させることができるようになります。

あなたのコード (トップ)

pseudo_dev_skel.c ファイルが 擬似デバイスとファイルの骨格となります。 pseudo_dev_skel.h では kernel 関数プロトタイプと ioctl データ構造 と加えて ioctl 番号自体を定義します。 注として、普通のデバイスドライバーと違って、 擬似デバイスドライバーには、プローブルーチンは必要ないので持っていません。 このため、autoconfig フレームワークを扱う必要がなく、話が簡単になります。 この skeleton ファイルは擬似デバイスの open, close, ioctl calls をサポートします。 これは、実際の擬似デバイスにおいて最低限実用的なコールのセットです。 read, write, mmap や ほかの デバイス 関数 をサポートするためのほかのコールもありますが、 詳細を除けば全て open, close, ioctl のパターンと同様に扱えます。

たぶん、最初の重要な決定は、新しいデバイスをなんと呼ぶかを決めることです。 関数コール名の前にデバイス名をくっつけた kernel 構造体をつくるのに 便利なマクロがたくさんあることが知られていて、これは 作りたいカーネルコンフィグファイルエントリーの助けになるでしょう。 config ファイルエントリーはヘッダーファイル名と合わせる必要はありません。 この skeleton ドライバー では、 skeleton 擬似デバイスと呼ぶ事にしたので skeleton というエントリーを コンフィグファイルに作る事になります。 つまり、 attach, open, close, ioctl 関数 呼び出しcall の名前を、それぞれ skeletonattach, skeletonopen, skeletonclose, skeletonioctl とすると言う事です。 もう1つの重要なのはデバイスの種類を決めることで - あなたのコードが、カーネルと、もちろん、あなたのコード自体と、どう相互作用するかで キャラクター型 か ブロック デバイス のどちらかにします。 ブロック型対キャラクター型のデバイスの決定は、 ドライバーが会話する根底の ハードウェアにかなり依存し、 もしドライバーがデータの読書きを一定の塊で行なうデバイスなら ブロックデバイスが良い選択で、そのようなデバイスの例として、 ハードディスクはたいてい 512 byte セクターをブロックとして読み書きします。 ハードウェアが一度に読み書きするのが 1 byte のデータなら、 普通、キャラクター型デバイスが最もよい選択で、 例えば、シリアルラインドライバーがそうです。 注として、いくつかのドライバーはデバイスのアクセスに ブロックモードとキャラクターモードの両方をサポートしていて、 この場合、キャラクターモードはしばしば "raw" デバイスと呼ばれます。 それは、アクセス時に データブロック抽出操作(the data blocking abstractions operating) なしに ハードウェアのアクセスができるからです。 擬似デバイスでは、考慮するべき根底のハードウェアがないので、より柔軟に選べます。 擬似デバイスを使ってする内容により選択し、ブロック型デバイスは ハードディスクやそのようなもののエミュレートには便利でしょう。 skeleton ドライバーは キャラクター型デバイスです。

それを決定したら、コードを編集することができますが、 その前にファイルをどこに置くか決めることが必要です。 複数のアーキテクチャーで使われる擬似デバイスを書くなら、 そのドライバーのコードの置き場は /usr/src/sys/dev です。 もし擬似デバイスが個別のアーキテクチャーに特有の物なら、 ドライバーのコードは個別のディレクトリーの下に置き、 i386 の例では、 /usr/src/sys/arch/i386/i386 です。 インクルードファイルについては、 アーキテクチャー非依存のデバイスは /usr/src/sys/sys に、 アーキテクチャー特有のデバイスでは、 アーキテクチャー個別のディレクトリーの下の include ディレクトリー、 例えば i386 アーキテクチャーでは /usr/src/sys/arch/i386/include に置くべきです。 どちらの場合でも、適切な Makefile を更新してください。 それで、インクルードファイルがインストールされます。 pseudo_dev_skel.c の先頭に struct skeleton_softc を書いてください。 デバイス名に "_softc" が書き添えられた名前で softc 構造体の宣言が必要で、 この構造体の最初の要素は struct device 型である必要があり、 エントリー名は重要ではありませんが、最初にある必要があり、 autoconfig システムが softc struct が宣言されていることをあてにしているから で、その最初の要素は struct device です。 デバイスハンドル minor 番号 ごとに softc struct が必要です。 softc 構造体は minor デバイスがそれを維持する状態の情報を、必要に応じて ただの struct device より多くの要素を持つことができます。

関数 (トップ)

ユーザーレベルプログラムによるアクセスには カーネルインターフェースはデバイスに対しては関数のコールを通して行ないます。 デバイスは後に示すように全ての関数をサポートする必要はありませんが、 実用的なデバイスは最低限オープンとクローズをサポートする必要はあります。 関数名はデバイス名を前につけることを思い出してください。 この関数は:

  1. attach()

    この関数はカーネルが初期化するときに一度呼び出されます。 これは、あらゆる変数の設定に使われ、 後のコールや、バッファーが必要とするカーネルメモリー割り当て で参照されます。 attach 関数には引数が一つ渡されます。これはこのドライバーが扱う デバイスの数です。

  2. open()

    その名が示すように、ユーザーレベルプログラムが デバイスに対して open(2) コールをした時に呼び出されます。 一番簡単な場合、 open 関数は単に success で戻るだけです。 より一般に open call は バッファーの要求と可能なら allocate することや 他ドライバー関数のコールをサポートするため 他のドライバーの状態を初期化することを有効にします。 open call は下記のパラメーターが使え:

    • dev

      open しようとするデバイスのマイナー番号。

    • flags

      open call に渡されるフラグ

    • mode

      open のモード

    • proc

      オープン要求をしているプロセスの proc 構造体のポインター これは、そのプロセスが 信頼できるものである(資格証明を有効にしてよい)ことを示します。

  3. close()

    open されていたデバイスを close します。 ドライバーによって、これはただ単純に success で戻るだけのものだったり、 あるいは、割り当てられていたメモリーの解放や ドライバーの状態変数を、もはや open されてはいないと示すように変更する 必要があったりします。 close 関数呼び出しのパラメーターは、 open で記述したのと同じです。

  4. read()

    デバイスからデータを読み出します。 この関数のパラメーターは:

    • dev

      デバイスのマイナー番号。

    • uio

      uio 構造体へのポインターです。 read 関数は uio 構造体 にユーザーが欲しいデータを満たして戻ります。

    • flags

      flags

  5. write()

    デバイスにデータを書きこみます。 write 関数 のパラメーターは、 read 関数のものと同じで - ただ、違うのは uio 構造体はデバイスに書きこまれるデータを含んでいることです。

  6. ioctl()

    デバイスに ioctl をします。 ioctl call のパラメーターは:

    • dev

      デバイスのマイナー番号。

    • cmd

      実行される ioctl コマンドです。 このコマンドはヘッダーファイル内で定義され カーネルコードおよびユーザーレベルコードが参照します。 例はサンプルのヘッダーを見てください。

    • data

      ユーザーレベルコードで使えるパラメーターのポインターです。 このパラメーターは ioctl の実装と、実際に発行されたコマンドに依存します。

    • flags

      flags

    • proc

      ioctl 要求を出したユーザーレベルプロセスに関連する proc 構造体 です。

  7. stop()

    tty 様式デバイスで出力を停止します。

    • tty

      デバイスに結び付けられた tty

    • flags

      flags

  8. poll()

    デバイスからデータが読めるかチェックします。 パラメーターは:

    • dev

      デバイスが使っているマイナー番号。

    • events

      ユーザーレベルコールがポーリングするイベント。

    • proc

      ioctl 要求を出したユーザーレベルプロセスに関連する proc 構造体 です。

  9. mmap()

    ドライバーのバッファーをユーザーレベルプログラムのメモリー空間に mmap する 権限をサポートします。パラメーターは:

    • dev

      デバイスが使っているマイナーデバイス番号。

    • offset

      mmap 開始時のバッファーの先頭からのオフセット。

    • prot

      mmap の挙動のタイプで、 読みこみ専用、書きこみ専用、読み書き可能かのいずれかです。 デバイスドライバーは全てをサポートする必要はありません。

このデバイスドライバーがサポートする関数を、 キャラクター型デバイス用の struct cdevswstruct bdevsw のいずれかまたは両方に含める必要があります。 この構造体の名前は、モジュール名の後に _cdevsw または _bdevsw を付けたものです。 ここで例示する疑似デバイスは、キャラクター型デバイスとしてのみふるまうので、 構造体の名前は skeleton_cdevsw とします。 なお、この構造体には、すべてのデバイスインターフェース関数のエントリーがありますが、 デバイス側で実装するのは、この関数の一部分だけでかまいません。 使われないスタブ関数をすべてのデバイスに実装させるのではなく、 頭に no がついた (たとえば noread, nowrite など)、 あらかじめ宣言されたスタブが用意されており、これらは呼ばれた時に ENODEV または、何の操作もせずに成功を返す null (たとえば nullread, nullwrite など) を返します。 cdevsw や bdevsw にある関数でドライバーがサポートしていないものに対しては、 あらかじめ宣言されたスタブのものを使うだけです。

カーネルに新しいデバイスを知らせる (トップ)

擬似デバイスのコーディングをしたら、 アクセスできるよう、カーネルに組み込みます。 注として、カーネルが擬似デバイスを組みこむ方法は 通常のデバイスの場合と、いろいろ違います。 擬似デバイスは、通常のデバイス検出と auto-configuration を飛ばし、 実行時の代わりにソースレベルでカーネル構造体の中にエントリーを作ったり そうではなかったりするからです。 カーネルがあなたのコードを使うようにするには、 これらのファイルを変更する必要があって:

  1. /usr/src/sys/conf/majors or /usr/src/sys/<arch>/conf/majors.<arch>

    この各ファイルには、 NetBSD におけるデバイスメジャー番号のリストが含まれます。 /usr/src/sys/conf/majors にあるのは、機種独立なデバイス、すなわち NetBSD が対応しているすべてのアーキテクチャーで利用可能なデバイスのメジャー番号です。 デバイスが特定のアーキテクチャーにのみ関連するものである場合は、 /usr/src/sys/<arch>/conf/majors.<arch> (<arch> は、関連するアーキテクチャーに置き換えます) ファイルを使う必要があります。 この各ファイルに含まれるエントリーは、以下の形式をとります。

    device-major    prefix		type      number	condition

    この行の正確な文法は、 config(5) マニュアルページで説明されていますが、 ここで例示するドライバーのためにその内容を説明しておきます。

    • device-major

      このエントリーが、 デバイスメジャー番号のエントリーであることを示すキーワードです。

    • prefix

      すべてのドライバーの関数に対して、 関数名が自動生成される時に与えられる接頭辞です。 ここでの例においては、 skeleton とします。

    • type

      メジャーデバイスの型で、 char または block のいずれかです。 char と block それぞれの type と number の組を繰り返し書くことで、 両方の型のデバイスを指定することができます。

    • number

      デバイスのメジャー番号です。次の空いている番号を選びます。 なお、/dev にデバイスノードを作る必要があるので、 選んだ番号を控えておきます。

    • condition

      このデバイスがカーネルに含まれる条件です。 これは、conf ファイル (後述) に含めた疑似デバイスのエントリーと一致させるようにします。

    ここで例示する skeleton 疑似デバイスは、キャラクター型デバイスとし、 ドライバーは i386 アーキテクチャーに依存するものとします。 このような仮定をしたので、 /usr/src/sys/arch/i386/conf/majors.i386 ファイルを編集すればよいことになります。すると、 メジャー番号 140 が使えることがわかるので、以下のような行を追加します。

    device-major	skeleton	char	140	skeleton

config(1) に新しいデバイスを知らせる (トップ)

config(1) に新しい擬似デバイスを知らせるには、 /usr/src/sys/conf/files (アーキテクチャー独立なデバイスの場合) または /usr/src/sys/arch/<arch>/conf/files.<arch> (<arch> は関連するアーキテクチャー) のいずれかのファイルを編集します。 このファイルは 有効な デバイスの名前 と これらのデバイスと関連するファイルを config に教えます。 最初に擬似デバイスを定義するセクションを捜します。 この セクションは defpseudo で始まる行を持っています。 ここでは、ドライバーはアーキテクチャー依存であることを仮定しているので、 /usr/src/sys/arch/i386/conf/files.i386 を編集します。 正しいセクションを見つけると、こういう行を加えることができ:

defpseudo skeleton

config(1)に skeleton という擬似デバイスがあることを教えます。 つぎに、 config(1) に skeleton 擬似デバイスに関連するファイルを教えてあげる必要があります。 この場合はひとつのファイル があるだけですが、より複雑な擬似デバイスでは より多くのファイルがあるかもしれず、 同じ方法で必要とされるファイルごとに行を単純に追加していきます。 例では、このような1行が必要なだけで:

file dev/skeleton.c	   skeleton	needs-flag

行中の file はデバイスとファイルの関係の定義 を書くためのキーワードです。 2番目のフィールドは、ファイルの カーネルソースツリールートからの相対位置 (通常、 /usr/src/sys) です。 3番目のフィールドはドライバー名でこのファイルが関連しているもので、 この場合 skeleton - サンプル擬似デバイスです。 4番目 で 最後の フィールド は config(1)skeleton.h インクルードファイルを書かせるための制御フラグです。 ここでファイル名が skeleton.c になっていますが、例示したファイルを使った場合、 ファイル pseudo_dev_skel.cskeleton.c にリネームするか、このエントリーを (pseudo_dev_skel.c に) 変更するかする必要がありますので注意してください。 上述のとおり、私たちはこれを skeleton と呼んでいるので、ここは skeleton.c としたほうがおそらく素直でしょう。

カーネルコンフィグファイルに新しいデバイスを追加する (トップ)

config(1) にデバイスのことを教えてやったあと、 そのデバイスをカーネルコンフィグファイルに追加することは簡単です。 skeleton デバイスを追加するには、このような行を加え:

pseudo-device  skeleton

カーネルコンフィグファイルには、 前節での defpseudo 行で与えられた名に揃えた 擬似デバイス名を書きます。 新しい定義は options カーネルコンフィグファイル キーワードをつかうことで、 カーネル makefile に追加することができ、 config は cc コマンドに対し -D コマンドラインオプションを指定した makefile を作ります。

ユーザーレベルプログラムが新しいデバイスにアクセスすることを許す (トップ)

新しいカーネルを構築しインストールした後、 最後にすることとして、新しい擬似デバイスにアクセスできるように デバイスノードを作る必要があります。 そのデバイスノードはアクセスすることができる どんなファイルシステム上に作ることもできますが、 慣習によって、 デバイスノードは /dev に作られます。 デバイスノード をつくるために mknod(8)を使う必要があり、 4.i 節で書きとめた major 番号で デバイスノード を作ります。 この場合、 mknod(8) コマンドでこのようにでき:

# mknod /dev/skel c 140 0

これで、新しいデバイスをオープンして試すことができます。 sample.c ファイルは skeleton 擬似デバイスが元気であることを示します。 このファイルは、ここにある指示に従っていて /dev/skel が作られていると仮定していて、 このデバイスはオープンされ、パラメーター構造体は ioctl call でデバイスドライバーに移されます。 サンプルコードをコンパイルするには、コマンドラインで:

$ cc -o sample sample.c

これで、 sample というバイナリーができるでしょう。 注意: ヘッダーファイル置き場で、 pseudo_dev_skel.h を入れた システムインクルードファイルのディレクトリーで make includes をする必要があります。 さもなければ、コンパイラーはインクルードファイルが見つからないと訴えるでしょう。 プログラムをコンパイルして実行すると、 カーネルメッセージがコンソールと /var/log/messages の両方に現われ、 それはこのようなもので:

May 17 20:32:57 siren /netbsd: Got number of 42 and string of Hello World

SKELTEST ioctl 要求を受けた時に skeleton ioctl ハンドラーが表示する メッセージです; この番号と文字列は sample.cparam 構造体の中に入れてあったものです。


Back to NetBSD ドキュメンテーション: カーネル