NetBSD ドキュメンテーション: カーネルプログラミング FAQ
Misc
- KNF とは何ですか
- pack された属性を使用する
- デバッグのための printf() の使い方
- 強制的に DDB に落とす
- カーネルに新しいドライバーを追加する
- この autoconf の素材は、どのように動作するのか?
- システムコールを追加する
- sysctl を追加する
- 仮想デバイスに mmap(2) を実装する方法
- ユーザーランドからカーネルの構造体へアクセスする
- 参考にできそうな簡単な PCI ドライバーはありませんか
- 他の関連するリンク
Misc
KNF とは何ですか (トップ)
KNF は Kernel Normal Form の略で、
/usr/share/misc/style
に記述されている
C のコーディングスタイルです。ソースツリーに src/share/misc/style
として含まれています。
pack された属性を使用する (トップ)
wire プロトコルデータフォーマットを記述する構造体の中では、いつも
`pack'
された属性を使用しています。
デバッグのための printf()
の使い方 (トップ)
カーネルドライバーのデバッグ用の情報を出力するもっとも簡単な方法は、
printf()
を使うことでしょう。カーネルの printf は コンソールに出力
されるので、多く出力しすぎ、システムが使いものにならないように注意
しなければなりません。
強制的に DDB に落とす (トップ)
カーネルのコンフィグファイルが 'options DDB
' を
含んでいる事を確認してください。
ファイルに '#include "opt_ddb.h"
' を記述し、
'Debugger()
' を使ってください。
カーネルに新しいドライバーを追加する (トップ)
全てのドライバーは、すくなくとも以下の二つの関数を必要とします。
-
xxxprobe()
( NetBSD がデバイスの存在を調べる時) -
xxxattach()
ルーチンはデバイスを設定、 アタッチします。
プローブとアタッチルーチンを書いたら、
/usr/src/sys/arch/<your-arch>/<your-arch>/conf.c
にエントリーを追加してください。そこには以下の二つのテーブルがあります。
-
cdevsw
キャラクターデバイス用。 -
bdevsw
ブロックデバイス用 (ブロックI/O とストラテジー・ルーチンとして使用する)。
ほとんどのエントリーは cdev_xxx_init()
という形式と
なります。これは標準的な Unix のデバイススイッチルーチンのプロトタイプ
のためのマクロです。
プローブとアタッチルーチンはブート時によばれます。
open()
、close()
、read()
、
write()
ルーチンは、メジャー番号がテーブルのインデックス
に一致するデバイススペシャルファイルがオープンされた時によばれます。
例えば、メジャー番号18 のデバイスをオープンした場合、cdevsw[]/bdevsw
の中のデバイス番号18の "open" ルーチンがよばれます。
ほとんどのドライバーはバス固有のアタッチコードとマシン独立のコアに分割 されています。例えば、PCI lance イーサネットドライバーは以下のファイルで 構成されています。
-
src/sys/dev/pci/files.pci
- アタッチ情報 ('le at pci'を見てください)。 -
src/sys/dev/pci/if_le_pci.c
- ドライバーのためのPCI バスのアタッチのコード。
-
src/sys/conf/files
- MI コアのアタッチ情報('le:'を見てください)。 -
src/sys/dev/ic/am7990.c
- MI ドライバー、24ビットアクセスのコード。 -
src/sys/dev/ic/am79900.c
- MI ドライバー、32ビットアクセスのコード。 -
src/sys/dev/ic/lance.c
- MI コアのドライバーのコード。
autoconf の説明もご覧ください。
この autoconf の素材は、どのように動作するのか? (トップ)
autoconf の仕組みは、その動作方法を一度理解してしまえば非常に単純なものです。 実行時にデバイスプローブツリーがどのように構築されて使われるのかについて、 正確な詳細は無視したいのであれば、 個々の“葉”のドライバーに関して必要なことは以下のとおりです。
- 各ドライバーは、 3 個の構成要素からなる構造体を規定します -
構成要素は、そのプライベート構造体のサイズ、プローブ関数、アタッチ関数です。
これはコンパイルされて実行時に使われます - たとえば以下のようになります:
struct cfattach foo_baz_ca = { sizeof(struct foo_baz_softc), foo_baz_match, foo_baz_attach };
- カーネル起動に際して、このデバイスをアタッチする時に、
autoconf のコードはデバイスのプローブルーチンを呼んで、
親へのポインター (
struct device *parent
)・ アタッチタグ構造体へのポインター (void *aux
)・ 適切な autoconf ノード (struct cfdata *cf
) を渡します。 ドライバーは、呼ばれるべき場所かどうかを判断することになっています (通常、ロケーションおよびコンフィギュレーション情報がアタッチタグによって渡されます)。 そこがしかるべき場所だった場合は、プローブルーチンは 1 を返すべきです。 もしデバイスがそこになければ、プローブルーチンは 0 を返す必要があります。 いずれの場合も、いかなる状態も保持されてはいけません。 - プローブが成功して戻ると、 autoconf は、デバイスの *_ca
で指定されたサイズのメモリー塊を割り当て、そのデバイスのアタッチルーチンを呼んで、
親へのポインター (
struct device *parent
)・ 今割り当てたメモリーへのポインター(struct device *self
)・ アタッチタグ構造体へのポインター (void *aux
) を渡します。 ドライバーは、正確なポートとメモリーを見つけ出し、資源を割り当て、 これに応じてドライバー内の構造体を初期化することになっています。 ドライバーのインスタンスに固有な情報は、 極力、ここで割り当てられたメモリーに保持すべきです。
例: PCI イーサネットドライバー 'baz' を考えましょう。 カーネルコンフィグは以下のようになっています:
pci* at mainbus? baz* at pci? dev ? function ?
実行時、 autoconf はマシンの PCI バス上の物理デバイスすべてに対して 繰り返し実行されます。各物理デバイスに対して、 autoconf は、 pci バス上にあることがカーネルに設定されているすべてのデバイスの ドライバーのプローブルーチンを呼ぶことを繰り返します。 いずれかのプローブルーチンがそのデバイスについて 1 を返すと、 autoconf はこれを中止し、上述の 3) で説明した作業をおこないます。 アタッチ関数が戻ると、 autoconf は次の物理デバイスの処理を続けます。
カーネルに新しいドライバーを追加するもご覧ください。
システムコールを追加する (トップ)
syscalls.master
にエントリーを追加し、syscall スタブを
の適当な場所に追加してください。src/lib/libc/sys/Makefile.inc
さらなる情報は、 NetBSD Internals Guide 内の HOWTO および関連ドキュメンテーションをご覧ください。
sysctl を追加する (トップ)
tech-kern メーリングリストに、この質問への答えが投稿 されているので、そちらを参照してください。
なお、 NetBSD 1.6 とそれ以降では、ベンダー特有の項目用として、 特別な “vendor” カテゴリーが予約されています。さらなる情報は sysctl(8) を参照してください。
仮想デバイスに mmap(2) を実装する方法 (トップ)
あなたの作ったデバイスは、おそらくキャラクターデバイスでしょう。もし、 そうであれば、デバイスページャーを使っているはずです。(VM システムは、 これらすべてを隠蔽しているので、心配しないでください)。
最初に、mmap インターフェースのために、適当なオフセットをいくつか選 んでください。例えば「mmap オフセット 0-M は オブジェクトAをあたえる、 N-O はオブジェクト B をあたえる」等のように。
これが終ると、mmap ルーチンは以下のように実装できます。
int foommap(dev_t dev, int off, int prot) { if (off & PAGE_MASK) panic("foommap"); if ((u_int)off >= FOO_REGION1_MMAP_OFFSET && (u_int)off < (FOO_REGION1_MMAP_OFFSET + FOO_REGION1_SIZE)) return (atop(FOO_REGION1_ADDR + ((u_int)off - FOO_REGION1_MMAP_OFFSET))); if ((u_int)off >= FOO_REGION2_MMAP_OFFSET && (u_int)off < (FOO_REGION2_MMAP_OFFSET + FOO_REGION2_SIZE)) return (atop(FOO_REGION1_ADDR + ((u_int)off - FOO_REGION2_MMAP_OFFSET))); /* Page not found. */ return (-1); }
さて、実際には単純なカーネルメモリーオブジェクトを mmap するので、 コードはもうすこし複雑になります(結局は仮想デバイスですから)。
これを動作させるためには、アロケートしたメモリーオブジェクトをページ調整
された境界に確実に mmap しなければなりません。もし、アロケートしたメモリー
のサイズ >= PAGE_SIZE
なら、これは保証されます。そうでなければ、
uvm_km_alloc()
を使い、アロケーションサイズをページサイズに切りあげてください。
若干の修正を加えると以下のようになります。
int foommap(dev_t dev, int off, int prot) { paddr_t pa; if (off & PAGE_MASK) panic("foommap: offset not page aligned"); if ((u_int)off >= FOO_REGION1_MMAP_OFFSET && (u_int)off < (FOO_REGION1_MMAP_OFFSET + FOO_REGION1_SIZE)) { if ((vaddr_t)foo_object1 & PAGE_MASK) panic("foommap: foo_object1 not page aligned"); if (pmap_extract(pmap_kernel(), foo_object1 + (u_int)off - FOO_REGION1_MMAP_OFFSET, &pa) == FALSE) panic("foommap: foo_object1 page not mapped"); return (atop(pa)); } if ((u_int)off >= FOO_REGION2_MMAP_OFFSET && (u_int)off < (FOO_REGION2_MMAP_OFFSET + FOO_REGION2_SIZE)) { if ((vaddr_t)foo_object2 & PAGE_MASK) panic("foommap: foo_object2 not page aligned"); if (pmap_extract(pmap_kernel(), foo_object2 + (u_int)off - FOO_REGION2_MMAP_OFFSET, &pa) == FALSE) panic("foommap: foo_object2 page not mapped"); return (atop(pa)); } /* Page not found. */ return (-1); }
ユーザーランドからカーネルの構造体へアクセスする (トップ)
良い例が
にあります。ここではディスクの統計情報を読んでいます。src/usr.bin/vmstat/dkstats.c
参考にできそうな簡単な PCI ドライバーはありませんか (トップ)
sys/dev/pci/puc.c
を参考にしてください。これは、最も簡単なドライバーの
ひとつです。PUC は、ひとつ以上のシリアル、パラレルポートを持つデバイス
で、通常、標準的なチップを使用しています(例えば、シリアルの 16550 UART)。
ドライバーは、単に シリアル、あるいはパラレルコントローラーのレジスター
の I/O アドレスをさがし、それをシリアル、またはパラレルドライバーへ渡す
だけです。
他の関連するリンク (トップ)
- driver(9) - デバイスドライバーが利用する NetBSD の自動コンフィギュレーションのインターフェース
- autoconf(9) - NetBSD の自動コンフィギュレーションフレームワークについての 全般的な説明
- config(9) - 自動コンフィギュレーションフレームワークの 「デバイス定義」言語
- bus_dma(9) - NetBSD のバスとマシン独立な DMA フレームワーク、 これに関する論文 (64k, PDF)
- bus_space(9) - NetBSD のバススペースの操作のためのインターフェース
- SCSI DMA はどのように動作するか - Tohru Nishimura による
- lazy FPU コンテキストスイッチはどのように動作するか - Tohru Nishimura による
- 昔の BSD のイーサネットドライバーの NetBSD-1.2D 以降への移植
- FreeBSD のネットワークドライバーを NetBSD に移植するためのメモ
Back to NetBSD ドキュメンテーション: カーネル