Skip to main content.
Google custom search

NetBSD ドキュメンテーション: カーネルプログラミング FAQ

Misc


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 イーサネットドライバーは以下のファイルで 構成されています。

autoconf の説明もご覧ください。

この autoconf の素材は、どのように動作するのか? (トップ)

autoconf の仕組みは、その動作方法を一度理解してしまえば非常に単純なものです。 実行時にデバイスプローブツリーがどのように構築されて使われるのかについて、 正確な詳細は無視したいのであれば、 個々ののドライバーに関して必要なことは以下のとおりです。

  1. 各ドライバーは、 3 個の構成要素からなる構造体を規定します - 構成要素は、そのプライベート構造体のサイズ、プローブ関数、アタッチ関数です。 これはコンパイルされて実行時に使われます - たとえば以下のようになります:
    struct cfattach foo_baz_ca = {
        sizeof(struct foo_baz_softc), foo_baz_match, foo_baz_attach
    };
  2. カーネル起動に際して、このデバイスをアタッチする時に、 autoconf のコードはデバイスのプローブルーチンを呼んで、 親へのポインター (struct device *parent)・ アタッチタグ構造体へのポインター (void *aux)・ 適切な autoconf ノード (struct cfdata *cf) を渡します。 ドライバーは、呼ばれるべき場所かどうかを判断することになっています (通常、ロケーションおよびコンフィギュレーション情報がアタッチタグによって渡されます)。 そこがしかるべき場所だった場合は、プローブルーチンは 1 を返すべきです。 もしデバイスがそこになければ、プローブルーチンは 0 を返す必要があります。 いずれの場合も、いかなる状態も保持されてはいけません
  3. プローブが成功して戻ると、 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 アドレスをさがし、それをシリアル、またはパラレルドライバーへ渡す だけです。

他の関連するリンク (トップ)


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