魔術師見習いのノート

プロフィール

魔術師見習い
Author魔術師見習い-_-.
Twitter魔術師見習い

コンピュータ関係のメモを主に書きます.

MENU

Linux デバイスドライバ -Hello-

投稿日:
タグ:

目次

キーワード

  • カーネルメッセージ
  • ライセンス
  • ログレベル

本稿はLinuxデバイスドライバ(モジュール)プログラミングに関するメモ.第1弾.

Linuxは実行中にカーネルの機能の追加や削除できる. それらの処理には次のコマンドを使用する.

insmod
カーネルにモジュールを追加するコマンド.
rmmod
カーネルからモジュールを取り除くコマンド.
modprobe
モジュールをカーネルに追加したりカーネルから取り除いたりする高レベルインタフェースなコマンド.モジュールをロードする場合,多くの点でinsmodと同じだが,ロード対象のモジュールが必要とするモジュールもロードする.ただしmodprobeはインストール済みのモジュールを格納したディレクトリからしか探さないため,作業ディレクトリから依存するモジュールをロードする時はは,insmodを使わなければならない.
これらのコマンドはシステム管理用のコマンドであり,通常rootでなければ実行できない.

Linuxのデバイスやモジュールのクラス

キャラクタ型デバイス
バイトストリームとして扱われるデバイスで,その機能を提供するのがキャラクタ型デバイスドライバ.通常キャラクタ型デバイスドライバには以下のシステムコールが実装される.
  • open
  • close
  • read
  • write
テキストコンソールやシリアルポートはキャラクタ型デバイスの一種である.
ブロック型デバイス
ブロック単位でアクセスするデバイス.ブロックとは512バイトあるいはそれ以外の2のべき乗のデータのかたまりのことである.代表的な例にはディスクが挙げられる.
ネットワークインタフェース
ネットワークインタフェースはデータパケットの送受信を担当する.ネットワークインタフェースのほとんどはハードウェアを伴うデバイスであるが,ループバックインタフェースのようにソフトウェアだけのデバイスもある.ネットワークデバイスドライバの通信は他の2つのデバイスドライバとは異なり,ストリーム指向ではない.それゆえ対応するファイルノードを持たない.

デバイスドライバとアプリケーション

デバイスドライバとアプリケーションの違いについて,いくつか例を示す.

  • アプリケーションはリソースを大雑把に解放するか全く解放しないが,デバイスドライバは取り除かれる際に全ての資源を解放しないとシステムが再起動されるまでリソースが解放されない.
  • アプリケーション(Cのコード)はライブラリをリンクされるのでそこで定義された関数やオブジェクトを呼び出すことができる.しかしデバイスドライバはカーネルだけにリンクされるので,呼び出せる関数らはカーネルが公開しているものに限る.
  • 基本的にデバイスドライバはカーネル空間の中で実行され,アプリケーションはユーザ空間の中で実行される.
  • デバイスドライバは複数のシステムによって呼び出されることが珍しくない.それゆえデバイスドライバは再投入可能であるべき.
  • アプリケーションが利用するスタック領域に比べ,デバイスドライバが利用するスタック領域はわずか.そのため大きな自動変数を宣言するのは望ましくない.
__で始まる関数名はインタフェースの下位レベルのコンポーネントであり,使用には注意が必要.

Hello, World

学習に用いた本はオライリーの「LINUX デバイスドライバ」でLinux 2.6を対象としている.

実行環境

カレントディレクトリには後述するhello.cとMakefile,Linuxカーネル3.0.9のソースコードをビルドしたものを配置する.後述する実行中のカーネルはこのビルドしたものである.

ソフトウェア内容
OS(Linux)3.0.9
ディストリビューションUbuntu 12.04
makeGNU make 3.81
ブートローダgrub 2
デバイスドライバの作成にはカーネルソースが必要であり,そこに使用する関数 や変数などを宣言したヘッダがある.ただしビルドされていない状態では必要な ヘッダが不足していた.なおLinuxカーネルはThe Linux Kernel Archivesから取得できる.カーネルのコンパイル方法についてはさまざまあり,ここではそれらについて詳しく語ることはしないが,流れとしては次の通りである.
  1. 設定ファイル(.config)の作成と編集.
  2. カーネルのコンパイル
  3. コンパイルしたカーネルをインストール
  4. ブートローダの設定を書き換える.
今回は/bootにあるconfig-から始まる名前のファイル,つまり既存のカーネルの設定を基にして,コンパイルを行う."make"を叩くとカーネルの 設定について質問され,それを全部答え終わるとコンパイルを開始する.ncursesが入っていれば"make menuconfig"を使ってより簡単に設定ファイルの修正ができる.ただしその辺りについての詳細は省略する.ちなみにコンパイル時間は結構長い.コンパイルが完了すると"make install"でインストールし,それが終わるとブートローダの設定を書き換える.私の環境ではログインやカーネルの設定を選択する画面を省略しているので,/etc/default/grubのGRUB_DEFAULTを変更し,"update-grub"で更新することで,標準で起動されるカーネルを変更した.

hello.c

はじめにモジュールをロードした際と取り除いた際にカーネルメッセージを出力する例を示す.

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
  printk(KERN_ALERT "Hello, World\n");
  return 0;
}

static void hello_exit(void)
{
  printk(KERN_ALERT "Goodbye, World\n");
}

module_init(hello_init);
module_exit(hello_exit);
3行目はライセンスの宣言.本によるとコード的に必須な記述ではないらしいが,ないと,次のようなエラーが出力される.
[ 7092.616521] hello: module license 'unspecified' taints kernel.
[ 7092.616525] Disabling lock debugging due to kernel taint
カーネルによって認識されるライセンスは次の6種類.
文字列意味
"GPL"GNU General Public Licenseの任意のバージョン
"GPL v2"GPL バージョン2のみ
"GPL and additional rights"
"Dual BSD/GPL"
"Dual MPL/GPL"
"Proprietary"所有者のある

printkはカーネルメッセージを出力する関数である.Cの文法と一致しないが,デバイスドライバのコーディングではこのような特殊な記述が多いようだ.KERN_ALERTはログレベル,すなわち重要度を表している.これにより,printkはメッセージの深刻さを分類する.ログレベルを示す文字列は次の8つである.深刻度は次の数字が小さいほど高い.

深刻度定数用途
1KERN_EMERG緊急メッセージ.通常クラッシュを起こすような状態を表す.
2KERN_ALERTすぐに対応が必要な状況を表す.
3KERN_CRIT危機的状況を表す.
4KERN_ERRエラー状況(例:ハードウェアのトラブル)を通知する.
5KERN_WARNING疑わしい状況についてのワーニング.
6KERN_NOTICE状況としては正常だが,注意が必要な状態.
7KERN_INFO情報メッセージ.設定時に検出したハードウェアに関する情報などを表示する.
8KERN_DEBUGデバッグ用のメッセージ
module_initとmodule_exitの行は,2つの関数の役割を示すための特殊なカーネルマクロである.module_initはモジュールがロードされた際に実行する関数,module_exitはモジュールが取り除かれる際(取り除かれる直前)に呼び出される関数をセットする.なお両者がセットした関数の型が異なることに注意.

Makefile

前述のソースコードのコンパイルは一般的なCのコンパイルと異なる.以下にそのために必要な特別なMakefileの文を記述する.

obj-m := hello.o
この1行を記述したコンパイルを次のように実行することで,カーネルのビルドシステムが前述のコードをコンパイルする.
user% make -C $PWD/linux-3.0.9 M=$PWD modules
Cオプションを使用した場合,makeは指定したディレクトリに移動してから処理を開始する."M=パス"はmodulesターゲットを作成する前に,モジュールのソースディレクトリにあるMakefileに戻ることを表す.正常にコンパイルができたならば,カレントディレクトリにいくつかのファイルとモジュールhello.koができているはずである.

実行例

実行例は次の通りである.

user% sudo insmod hello.ko
user% dmesg | tail -1
[ 5947.430306] Hello, World
user% sudo rmmod hello.ko
user% dmesg | tail -1
[ 6088.515992] Goodbye, World
dmesgはカーネルのメッセージを出力したり制御したりするコマンドである.sudoやtailにのコマンドの詳細については省略する.

一覧