funini.com 自由研究 モジュール
0. ドライバに関するキーワード - ブロックデバイス / キャラクタデバイス - 種類による流儀 (PCI, USB, ...) - DMA (CPUを介さずに、デバイスから直接メモリとデータをやり取り) - IRQ (割り込み) - file IO (read/write, ...) - ioctl - メモリマップ (連続した物理メモリを確保) 1. load / unload するだけのドライバ 1.1. ソース 以下のようなソースを書く。 hello.c ---------------------------------------- #include #include MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void){ printk(KERN_ALERT "Hello, world!\n"); } static void hello_exit(void){ printk(KERN_ALERT "Moimoi!\n"); } module_init(hello_init); module_exit(hello_exit); ---------------------------------------- - モジュールの初期化/終了関数は、module_init/module_exit で呼び出す 1.2. ビルド・テスト Makefile を書く。 ---------------------------------------- obj-m := hello.o ---------------------------------------- ビルドする。~/src/linux/linux-2.6" のところは、適宜書き換える。 # make -C ~/src/linux/linux-2.6 M=`pwd` modules 出来た hello.ko を組み込む。 # insmod hello.ko # lsmod (hello.ko が表示されることを確認) # rmmod hello.ko さらに、dmesg や /var/log/messages に 上記の printk メッセージが 出ていることを確認する。 1.3. 変数・シンボルについて - モジュール内の変数や関数をカーネルのグローバルシンボルにするには、 EXPORT_SYMBOL を呼び出す。EXPORT_SYMBOL_GPL で export すると、 GPL のモジュールだけがそのシンボルを使えるようになる。 - モジュール内でグローバル変数を宣言すると、それはモジュール内の 全ての関数から使うことができる。ただし、1つのモジュールが複数の デバイスを担当する場合も共通になってしまうので注意が必要。 (普通は、例えば read の実装であれば、read のハンドラ関数の引数から 取得する。 例) int hoge; // 複数のデバイスがあったときにこまる my_read(){ hoge; } //EXPORT_SYMBOL(my_read); file_operations ops = { .read = my_read; }; 2. major/minor number とデバイスファイル 2.1. キャラクタデバイスとブロックデバイス デバイスドライバは、基本的にキャラクタデバイスかブロックデバイスの いずれかである。/dev/XXX に見えているのは基本的にどちらかであり、 どちらも open/close できる。 どちらであるかは、/dev で ls -l したときの先頭のアルファベットで分かる。 + キャラクタデバイス - ファイルと同様に使える (open/close/read/write を実装) - キャラクタ単位でのアクセス - キャッシュは使えない (raw アクセス) + ブロックデバイス - mount できる。上にファイルシステムを乗せることができる。 - ブロック(固定長)単位でのアクセス (デバイスの特性に応じたブロックサイズ) - バッファキャッシュを使用できる 2.2. /dev/XXX と major number の関係 /dev/XXX にあるのは、mknod した「デバイスファイル」である。 これは、実際のデバイスの有無に関わらず以下のコマンドで作ることが出来る。 # mknod /dev/scull0 c 101 0 101 が major number, 0 が minor number である。 メジャー番号はデバイスに固有だけど、被らないように OS が自動的に割り振ってくれる デバイスを接続したときに、/proc/devices に出てくる。 CentOS などでは、udev の機能で自動的にデバイスファイルが作られたり消されたりする (udev をポーリングしているユーザプログラムがあり、それを見てデバイスファイルを 作ったり消したりする) 2.3. Minor 番号の登録 Major 番号を登録し、/dev/hoge を使う /dev/XXX にデバイスが見えるようにするには、Major 番号と Minor 番号を allc_chrdev_region で登録する。 Major 番号はモジュール (ドライバ) によって一意だが、Minor 番号は複数 持つことができる。(例えば hda1-hda4 はMajor 番号が共通で、Minor 番号が 異なる) この Minor 番号の数をカウントと呼ぶ。 int res = alloc_chrdev_region(&dev,SCULL_MINOR_START, SCULL_COUNT, SCULL_NAME); if(res){ printk(KERN_WARNING "scull: could not allocate device\n"); return res; }else{ printk(KERN_WARNING "scull: registered with major number:%i\n", MAJOR(dev))\ ; } printk("scull: major number:%i\n", MAJOR(dev)); 3. vmalloc と kmalloc と ... http://wiki.bit-hive.com/linuxkernelmemo/pg/kmalloc,vmalloc kmalloc - slab から取得 - 連続したページ vmalloc - 連続してるとは限らないページ - X. 割り込み ハードウェア的に発生するので、そのハンドラを書く必要がある。 - はじめに irq の番号を取得 (hw に依存する) - 番号 & haneler を OS に登録 - handler: 割り込みが自分のデバイスが発行したものがチェック - 割り込み処理 - return - 実際の処理の流れ - HW 割り込み発生 - OS が受ける - ドライバのハンドラが呼ばれる - upper の処理 X. ハードウェアへの書き込み - レジスタ - レジスタの read/write bttvp.h #define btread(adr) readl(btv->bt848_mmio+(adr)) | 66 sd X. メモリ readv と writev: 性能があがる たくさんの読み書きを同時に modprobe: insmod の依存性を解決してくれる クリーンアップ __exit のマークをつける モジュールバラメーター copy_to_user / copy_from_user をつかってのみアクセスする X. ロック セマフォには up(), down(), down_interruptable() を用いる down_interruptable は、返り値をチェックする必要がある (down はいらない) - デッドロックを防ぐために、資源の取り方にはいくつかの慣習がある - セマフォが先、スピンロックが後 - ローカルが先、グローバルが後 tips - 2,4,8 バイトのときはput_user のほうが copy_to_user より速い drivers/media/video/bt8xx X. ドライバの読み方 - まずは module_init と module_exit で検索し、init/exit をさがす - 次に open/close を探す - そのデバイスの ioctl があれば、そのハンドラ・仕様をチェックする 1. init からたど module_init(bttv_init_module); module_exit(bttv_cleanup_module); 例えば、 video for linux の ioctl:s メモリ bttv-driver.c