魔術師見習いのノート

プロフィール

魔術師見習い
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にのコマンドの詳細については省略する.

Linux デバイスドライバ -よりプログラムらしく-

投稿日:
タグ:

目次

本稿は「Linux デバイスドライバ -Hello-」の続きであり,よりプログラムらしいプログラムを作るためのコードをいくつか紹介する.

タグ

関数やオブジェクトはタグを付けることで特殊な扱いを受ける.以下にその例をいくつか示す.

__init
この宣言を行った関数は初期化時のみメモリを使用し,すぐに解放する.例を以下に示す.
static int __init hello_init(void)
{
  printk(KERN_ALERT "Hello, World\n");
  return 0;
}
__initdata
この宣言を行ったオブジェクトは__initでのみ使用されることを表す.
__exit
クリーンアップ用の関数であることを指定する.コンパイラはこの宣言が行われた関数を特殊なセクションに配置する.
static void __exit hello_init(void)
{
  printk(KERN_ALERT "Goodbye, World\n");
  return 0;
}
__exitdata
この宣言を行ったオブジェクトは__exitでのみ使用されることを表す.

モジュール引数

パラメータの値はロード時にinsmodかmodprobeによって割り当てられる.modprobeは設定ファイルからパラメータを読み込むことが可能.設定ファイルの名前は,/etc/modprobe.confや/etc/modprobe.d/内の拡張子.confのファイルである.マニュアル"man modprobe.conf"でフォーマットについて確認ができる.

パラメータの取得は,<linux/moduleparam.h>で定義される関数module_paramやmodule_param_arrayで行う.この関数もまたmodule_initらと同様関数の外で使用しないとエラーである.

module_param(variable, type, perm)
variable
代入する変数名を指定する.
type
型を指定する.指定可能な型は次の通り.
bool論理値
invboolboolの逆の値
charp文字列へのポインタ値.
short整数値符号付き
int
long
ushort符号なし
uint
ulong
perm
パーミッション値.誰がsysfsに現れるモジュールパラメータにアクセスできるかを制御する.この引数には<linux/stat.h>にある定義を使用する.例えば,"S_IRUGO|S_IWUSR"はルートがパラメータを変更することを許可する.
module_paramを利用した例を以下に示す.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>

static int i;

module_param(i, int, S_IRUGO);

static int __init init()
{
  printk(KERN_INFO "i=%d\n", i);
  return 0;
}
static void __exit fini(){}

module_init(init);
module_exit(fini);
コンパイルは依然の記事の通り.以下がコンパイルと実行結果である.
user% make -C ./linux-3.0.9 M=$PWD modules
user% sudo insmod param.ko i=100 array={1,2,3,4,5}
user% dmesg | tail -1
[41654.053664] name:insmod pid:27744
user% sudo rmmod param.ko
module_param_array(variable, type, num, perm)
モジュールが引数として配列を与えられた時にそれを取得する.variableには配列を記述する.
num
変数の数を指定する.

カレントプロセス

カーネルモジュールの処理はアプリケーションと異なり,順に実行される訳ではなく,個々のプロセスに関連付けられる.現在のプロセスに関する情報は,<asm/current.h>で定義されているcurrentによって取得できる.currentは<linux/sched.h>で定義されるstruct task_struct型へのポインタである.

#include <linux/init.h>
#include <linux/module.h>
#incldue <asm/current.h>
#incldue <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL")

static void info_proc(void)
{
  printk(KERN_INFO "name:%s pid:%i", 
    current->comm, current->pid);
}
static int __init init(void){ info_proc(); return 0; }
static void __exit fini(void){ info_proc(); }

module_init(init);
module_exit(fini);

エラー

Linuxカーネルのエラーコードは負数で,<linux/errno.h>で定義されている.例えば定数ENOMEMに関連付けたエラーを表す場合,関数は-ENOMEMを返す.

一覧