カーネルモジュール作成メモ

2009/6/23 更新( Debian5 での準備追加)
2008/4/29 更新(ブロックデバイス追加)
2008/3/18 作成

目次

[1] はじめに
[2] 準備
[3] 簡単なモジュール
[4] モジュールパラメータ
[5] procファイルシステム
[6] キャラクタデバイス
[7] ブロックデバイス

[1] はじめに

Linux(kernel-2.6系)のカーネルモジュール(ドライバ)を作成する。 テストした環境は次の通りである。
OS: Fedora7
カーネル: kernel-2.6.23.15-80.fc7
OS: Debian5
カーネル: kernel-2.6.26-2-686


[2] 準備

(2-1) Fedora7,Fedora8 の場合

Fedoraでカーネルモジュールを作成するにあたり、まず
fedora wikiを参考にしながら カーネルモジュールをコンパイルする環境を整える。 なおfedoraでは可能な限りカーネルもジュールはユーザー権限でコンパイルすることが推奨されている。

まずrpmdevtoolsを使ってユーザのホームディレクトリでRPMビルドすることが出来る環境を作る。
$ su
# yum install rpmdevtools
# yum install yum-utils
# exit

以下ユーザ権限で

$ cd
$ rpmdev-setuptree
カーネルのsrc.rpm(今回は kernel-2.6.23.15-80.fc7 ) をダウンロードして展開する。
$ su
# yum-builddep kernel-2.6.23.15-80.fc7
# exit

以下ユーザ権限で

$ rpm -Uvh kernel-2.6.23.15-80.fc7.src.rpm
$ ls ~/rpmbuild/SOURCES/  

rpmが展開されているのを確認したらソースを展開する

$ cd ~/rpmbuild/SPECS/
$ rpmbuild -bp --target=`uname -m` kernel-2.6.spec
$ cd ~/rpmbuild/BUILD/kernel-2.6.23/linux-2.6.23.i686/

ソースが展開されているのを確認したらmake出来るか確認する。

$ make

(補足) Fedora8の場合はこのままモジュールを作成してビルドするとmodprobeしたときに バージョンチェックで弾かれるようなので、バージョン文字列を変えて uname -r の結果と同じにする。 Fedora7の場合は必要ないようだ。
$ cd ~/rpmbuild/BUILD/kernel-2.6.23/linux-2.6.23.i686/
$ cd include/linux
$ cp utsrelease.h utsrelease.h.org
$ vi utsrelease.h

#define UTS_RELEASE "2.6.23.15-80.fc7"

(2-2) Debian5 の場合

apt-get で環境を整えるだけである。

ルートになってバージョン確認

$ su -
# uname -a  

必要パッケージとソースの取得とインストール

# apt-get install kernel-package
# apt-get install linux-source-2.6.26
# exit

以下、一般ユーザとしてビルド(展開するディレクトリはどこでも良い)

$ mkdir ~/kernel
$ cd ~/kernel
$ tar xvfj /usr/src/linux-source-2.6.26.tar.bz2
$ cd linux-source-2.6.26/

uname -a の結果から Makefile の EXTRAVERSION を変更する。
例えば EXTRAVERSION = -2-686

$ vi Makefile

今のカーネルのconfigをコピーしてmake

$ cp /boot/config-2.6.26-2-686 .config
$ make


[3] 簡単なモジュール

はじめに、insmod、rmmodでロード、アンロードするだけの簡単なモジュールを作成する。
#include <linux/module.h>
#include <linux/kernel.h>

// モジュール初期化
static int __init modtest_module_init( void )
{
    printk( KERN_INFO "modtest is loaded\n" );
    return 0;
}


//  モジュール解放
static void __exit modtest_module_exit( void )
{
    printk( KERN_INFO "modtest is removed\n" );
}

module_init( modtest_module_init );
module_exit( modtest_module_exit );

MODULE_DESCRIPTION( "modtest" );
MODULE_LICENSE( "GPL2" );
module_initマクロで指定した関数がロード時に実行され、 module_exitマクロで指定した関数がアンロード時に実行される。

__init を宣言した関数は初期化後にメモリから削除され、 __exit を宣言した関数はモジュール開放時のみにメモリに読み込まれるという意味なので、 これらを宣言した関数は初期化、開放時以外に呼び出してはいけない。

printkはログを書き出す関数で内容はdmesgで確認できる。

MODULE_DESCRIPTION, MODULE_LICENSEマクロは /sbin/modinfo を実行したときに表示される モジュール情報である。

Makefileは次の通りである。
obj-m := modtest.o

ROOTDIR  := ~/rpmbuild/BUILD/kernel-2.6.23/linux-2.6.23.i686/
PWD   := $(shell pwd)

default:
	$(MAKE) -C $(ROOTDIR) M=$(PWD) modules

clean:
	rm -f *.o *.ko
makeして、実際に
$ su
# /sbin/insmod modtest.ko
# /sbin/lsmod | grep modtest (確認用)
# /sbin/rmmod modtest
# exit
を実行したときのdmsegは次の様になる。
modtest is loaded
modtest is removed
modprobeで組み込みたい場合はモジュールをドライバディレクトリにコピーしてdepmod -a を実行する。
$ su
# cp modtest.ko /lib/modules/2.6.23.15-80.fc7/kernel/drivers/misc/
# /sbin/depmod -a

# /sbin/modprobe modtest
# /sbin/lsmod | grep modtest (確認用)
# /sbin/rmmod modtest
# exit


[4] モジュールパラメータ

モジュールパラメータを設定すると、ロード時にパラメータを設定したり、 /sys/module 以下に出来る仮想的なパラメータファイルを通じてモジュールと情報をやりとりできるようになる。 現在のモジュールの内部状態が分かるのでデバッグなどでよく使われる。

パラメータを設定するにはmodule_paramマクロを使う。
#include <linux/module.h>
#include <linux/kernel.h>

static char* teststr = "abcdefghijklmn";
module_param( teststr, charp, S_IRUGO );
MODULE_PARM_DESC( teststr, "test parm");

// モジュール初期化
static int __init modtest_module_init( void )
{
    printk( KERN_INFO "modtest is loaded %s\n", teststr );
    return 0;
}

//  モジュール解放
static void __exit modtest_module_exit( void )
{
    printk( KERN_INFO "modtest is removed\n" );
}

module_init( modtest_module_init );
module_exit( modtest_module_exit );

MODULE_DESCRIPTION( "modtest" );
MODULE_LICENSE( "GPL2" );
上の例の場合は char* teststr をモジュールパラメータとしているので /sys/module/modtest/parameters/teststr にパラメータファイルが出来る。

module_paramの第2引数は変数の型で、整数なら int、文字列なら charp などを指定する。
module_paramの第3引数はパラメータファイルのパーミッションであり、 S_IRUGO は S_IRUSR | S_IRGRP | S_IROTH ということなので、
$ ls -al /sys/module/modtest/parameters/teststr
-r--r--r-- 1 root root 4096 2008-03-18 10:54 /sys/module/modtest/parameters/teststr
と表示される。 もし S_IRUGO | S_IWUSR とすれば、パラメータファイルに対して書き込みも出きるようになるので モジュールの動作中にパラメータを書き換えることが出来る。

またMODULE_PARM_DESCマクロは/sbin/modinfoしたときに表示するパラメータ情報である。

さて、これをmakeしてパラメータを渡さないときとパラメータを渡したときの動作の違いを見てみる。
$ su
# /sbin/insmod modtest.ko
# cat /sys/module/modtest/parameters/teststr 
abcdefghijklmn
# /sbin/rmmod modtest

# /sbin/insmod modtest.ko teststr="hoge"
# cat /sys/module/modtest/parameters/teststr 
hoge
# /sbin/rmmod modtest
# exit
ちなみにdmsegは次の様になる。
modtest is loaded abcdefghijklmn
modtest is removed

modtest is loaded hoge
modtest is removed


[5] procファイルシステム

ユーザがモジュールとデータをやりとりするには何かしらのインターフェースを用意する必要がある。 もっとも簡単な方法はprocファイルシステム(procfs)を使用する方法である。

procfsとは /proc以下に仮想ファイルをマウントし、 それをインターフェースとしてモジュールとユーザー空間内でデータの やりとりをするものである。例として /proc/driver/modtest というインターフェースを作成するモジュールは 次の様になる。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h> // create_proc_entry
#include <asm/uaccess.h> // copy_from_user

// /proc/PROCNAME にインタフェースを作る
#define PROCNAME "driver/modtest"

#define MAXBUF 64
static char modtest_buf[ MAXBUF ];
static int buflen;

// 書き込み
static int proc_write( struct file *filp, const char *buf, unsigned long len, void *data )
{
    if( len >= MAXBUF ){
        printk( KERN_WARNING "proc_write len = %lu\n", len );
        return -ENOSPC;
    }

    if( copy_from_user( modtest_buf, buf, len ) ) return -EFAULT;
    modtest_buf[ len ] = '\0';
    buflen = len;

    printk( KERN_INFO "proc_write %s\n", modtest_buf );

    return len;
}


// 読み込み
static int proc_read( char *page, char **start, off_t offset, int count, int *eof, void *data )
{
    unsigned long outbyte = 0;

    if( offset > 0 ){
        *eof = 1;
        return 0;
    }

    outbyte = sprintf( page, "%s", modtest_buf );
    printk( KERN_INFO "proc_read len = %lu\n", outbyte );

    *eof = 1;
    return outbyte;
}


// モジュール初期化
static int __init modtest_module_init( void )
{
    // /proc/PROCNAME にインターフェース作成
    struct proc_dir_entry* entry;
    entry = create_proc_entry( PROCNAME, 0666, NULL );
    if( entry ){
        entry->write_proc  = proc_write;
        entry->read_proc  = proc_read;
        entry->owner = THIS_MODULE;
    }
    else{
        printk( KERN_ERR "create_proc_entry failed\n" );
        return -EBUSY;
    }

    printk( KERN_INFO "modtest is loaded\n" );

    return 0;
}


//  モジュール解放
static void __exit modtest_module_exit( void )
{
    // インターフェース削除
    remove_proc_entry( PROCNAME, NULL );

    printk( KERN_INFO "modtest is removed\n" );
}


module_init( modtest_module_init );
module_exit( modtest_module_exit );

MODULE_DESCRIPTION("modtest");
MODULE_LICENSE( "GPL2" );
ここで modtest_module_init()内のcreate_proc_entry()が /proc 以下にインターフェースを作っている 部分で、今回は /proc/driver/modtest に作成している。また、続く entry->write_proc = proc_write;と entry->read_proc = proc_read; でインターフェース に対して読み書きを行ったときに呼び出される関数を指定している。

なおproc_write()内にあるcopy_from_user()はユーザー空間のメモリ内容からカーネル空間のメモリに 内容をコピーする関数である。

では実際にechoとcatを使って実際にモジュールとデータのやりとりをしてみる。
モジュールをロード

$ su
# /sbin/insmod modtest.ko
# ls -al /proc/driver/modtest  ( インターフェースが出来てるか確認 )
-rw-rw-rw- 1 root root 0 2008-03-17 18:25 /proc/driver/modtest

一般ユーザ権限で読み書き出来るか確認

# exit
# echo hoge > /proc/driver/modtest
$ cat /proc/driver/modtest
hoge

モジュールをアンロード

$ su
# /sbin/rmmod modtest
# exit
dmsegは次の様になる。
modtest is loaded
proc_write hoge

proc_read len = 5
modtest is removed


[6] キャラクタデバイス

procfsは一般的には簡易的なデータのやりとりに用いるべきで、 本格的なデータのやりとりは /dev 以下に作ったデバイススペシャルファイルを通じて行うべきである。 ここではキャラクタデバイススペシャルファイルを作成する。 キャラクタデバイスとは1バイト単位でのデータの入出力を取扱ってバッファ処理を行わないデバイスである。 以下が例である。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h> // alloc_chrdev_region
#include <linux/cdev.h> // cdev_init
#include <asm/uaccess.h> // copy_from_user, copy_to_user

#define MODNAME "modtest"

#define MINOR_COUNT 1 // 接続するマイナー番号数

static dev_t dev_id;  // デバイス番号
static struct cdev c_dev; // キャラクタデバイス構造体

// 内部バッファ
#define MAX_BUFLEN 64
unsigned char cdev_buf[ MAX_BUFLEN ];
static int cdev_buflen = 0;


// スペシャルファイルをオープンした
static int cdev_open( struct inode *inode, struct file *filp ) 
{
    printk( KERN_INFO "cdev_open\n" );
    return 0;
}


// スペシャルファイルをクローズした
static int cdev_release( struct inode* inode, struct file* filp )
{
    printk( KERN_INFO "cdev_release\n" );
    return 0;
}


// スペシャルファイルからの読み込み
static ssize_t cdev_read( struct file* filp, char* buf, size_t count, loff_t* offset )
{
    int len = cdev_buflen;
    if( ! len ) return 0;

    printk( KERN_INFO "cdev_read count = %d\n", count );

    if( copy_to_user( buf, cdev_buf, len ) ){
        printk( KERN_WARNING "copy_to_user failed\n" );
        return -EFAULT;
    }

    *offset += len;
    cdev_buflen = 0;

    return len;
}


// スペシャルファイルへの書き込み
static ssize_t cdev_write( struct file* filp, const char* buf, size_t count, loff_t* offset )
{
    if( count >= MAX_BUFLEN ) return -EFAULT;

    printk( KERN_INFO "cdev_write count = %d\n", count );

    if( copy_from_user( cdev_buf, buf, count ) ){
        printk( KERN_WARNING "copy_from_user failed\n" );
        return -EFAULT;
    }

    cdev_buf[ count ] = '\0';
    printk( KERN_INFO "cdev_buf = %s\n", cdev_buf );

    *offset += count;
    cdev_buflen = count;

    return count;
}


// ファイルオペレーション構造体
// スペシャルファイルに対して読み書きなどを行ったときに呼び出す関数を登録する
static struct file_operations modtest_fops ={
    .owner   = THIS_MODULE,
    .open    = cdev_open,
    .release = cdev_release,
    .read    = cdev_read,
    .write   = cdev_write,
    .ioctl   = NULL,
};


// モジュール初期化
static int __init modtest_module_init( void )
{
    int ret;

    // キャラクタデバイス番号の動的取得
    ret = alloc_chrdev_region( &dev_id, // 最初のデバイス番号が入る
                               0,  // マイナー番号の開始番号
                               MINOR_COUNT, // 取得するマイナー番号数
                               MODNAME // モジュール名
        );
    if( ret < 0 ){
        printk( KERN_WARNING "alloc_chrdev_region failed\n" );
        return ret;
    }

    // キャラクタデバイス初期化
    // ファイルオペレーション構造体の指定もする
    cdev_init( &c_dev, &modtest_fops );
    c_dev.owner = THIS_MODULE;

    // キャラクタデバイスの登録
    // MINOR_COUNT が 1 でマイナー番号の開始番号が 0 なので /dev/modtest0 が
    // 対応するスペシャルファイルになる
    ret = cdev_add( &c_dev, dev_id, MINOR_COUNT );
    if( ret < 0 ){
        printk( KERN_WARNING "cdev_add failed\n" );
        return ret;
    }

    printk( KERN_INFO "modtest is loaded\n" );
    printk( KERN_INFO "major = %d\n", MAJOR( dev_id ) );
    printk( KERN_INFO "minor = %d\n", MINOR( dev_id ) );

    return 0;
}


//  モジュール解放
static void __exit modtest_module_exit( void )
{
    // キャラクタデバイス削除
    cdev_del( &c_dev );

    // デバイス番号の返却
    unregister_chrdev_region( dev_id, MINOR_COUNT );

    printk( KERN_INFO "modtest is removed\n" );
}


module_init( modtest_module_init );
module_exit( modtest_module_exit );

MODULE_DESCRIPTION( MODNAME );
MODULE_LICENSE( "GPL2" );
まずmodtest_module_init()内において alloc_chrdev_region()を用いて空いてるデバイス番号を取得し、 cdev_init()、cdev_add()でデバイスを登録する。またcdev_init()の引数として file_operations modtest_fops を指定している。file_operations 構造体にはスペシャルファイルを開いたり、読み書きした時に呼び出される 関数のアドレスを指定する。デバイスの削除はmodtest_module_exit()内でcdev_del()、unregister_chrdev_region()により行う。

ただし上の例をそのままinsmodしても /dev 以下に自動的にデバイススペシャルファイルが出来るわけではないので mknodを使って手動で作成する必要がある。 ここでmknodでデバイススペシャルファイルを作るにはモジュールのmajor番号を知る必要があるが、 現在のmajor番号は /proc/devices から取得することが出来る。
# cat /proc/devices | grep modtest
251 modtest

awk を使って番号だけ取得

# awk "\$2==\"modtest\" {print \$1}" /proc/devices
251
この場合はは251がデバイス番号になっていることが分かる。よってmknodの書式は

mknod パス タイプ メジャー番号 マイナー番号

であるので mknod /dev/modtest0 c 251 0 とするとスペシャルファイルが出来る。

では実際に上のサンプルを動かしてみよう。
モジュールをロードしてスペシャルファイルを作成

$ su
# /sbin/insmod modtest.ko
# cat /proc/devices | grep modtest
251 modtest

# mknod /dev/modtest0 c 251 0
# chmod 666 /dev/modtest0

ls -al して 先頭に c が表示されてメジャー、マイナー番号が251, 0 となっているか確認

# ls -al /dev/modtest0 
crw-rw-rw- 1 root root 251, 0 2008-03-18 13:21 /dev/modtest0

一般ユーザに戻ってスペシャルデバイスに対して入出力を行う

# exit
$ echo abc > /dev/modtest0
$ cat /dev/modtest0 
abc

$ echo hoge > /dev/modtest0
$ cat /dev/modtest0 
hoge

モジュールをアンロードする

$ su
# /sbin/rmmod modtest
# rm -f /dev/modtest0
# exit
dmesgは次の様になる
modtest is loaded
major = 251
minor = 0

cdev_open
cdev_write count = 4
cdev_buf = abc
cdev_release

cdev_open
cdev_read count = 4096
cdev_release

cdev_open
cdev_write count = 5
cdev_buf = hoge
cdev_release

cdev_open
cdev_read count = 4096
cdev_release

modtest is removed


[7] ブロックデバイス

ブロックデバイスとはハードディスクのようにブロック(セクタ)単位でデータの入出力を取扱い、かつキューによるバッファ処理を行うデバイスである。 以下に簡単なRAMディスクドライバの例を示す。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>

#define MODNAME "modtest"

#define MINOR_COUNT 1 // 接続するマイナー番号数

// ブロックデバイス番号
static int modtest_major = 0;

// リクエストキュー
static struct request_queue *modtest_queue;
static spinlock_t modtest_lock;

// 仮想ディスク
struct gendisk *modtest_gd;
#define SECT_SIZE 512 // 仮想ディスクのセクタサイズ(byte)
#define SECT_NUM 2048 // 仮想ディスクのセクタ数
#define DISK_SIZE ( SECT_SIZE * SECT_NUM ) // ディスクサイズ(byte)
char* buffer = NULL;


//
// リクエストキューの処理
//
// 仮想ディスクに対して入出力がおこなわれると呼び出される
//
static void modtest_request( struct request_queue *queue )
{
    struct request *req;
    int sect, sects;
    int offset, len;
    int msg = 0;

    // キューから次のリクエストを取得
    while( ( req = elv_next_request( queue ) ) != NULL ){

        // ファイルシステムに対するリクエストでない
        // 例えば scsi コマンドなどは処理しない
        // ( linux/blkdev.h の enum rq_cmd_type_bits を参照 )
	if( ! blk_fs_request( req ) ){
            printk( KERN_NOTICE "skip no fs request\n" );
	    end_request( req, 0 );
	    continue;
	}

        sect = req->sector;
        sects = req->current_nr_sectors;

        offset = sect * SECT_SIZE;
        len = sects * SECT_SIZE;

        if( offset + len > DISK_SIZE ){
            printk( KERN_WARNING "buffer overflow\n");
            return;
        }

        if( msg )
            printk( KERN_INFO "modtest_request sec = %u, secs = %u, offset = %u, len = %u ", 
                    sect, sects, offset, len );

        // 仮想ディスクへの書き込み
        if( rq_data_dir( req ) == WRITE ){
            if( msg ) printk( KERN_INFO "write\n" );
            memcpy( buffer + offset, req->buffer, len );
        }

        // 仮想ディスクからの読み込み
        else{
            if( msg ) printk( KERN_INFO "read\n" );
            memcpy( req->buffer, buffer + offset, len );
        }

        // リクエスト完了処理( 0: 失敗,  1: 成功 )
        end_request( req, 1 );
    }
}


// ファイルオペレーション構造体
// スペシャルファイルに対してオープンなどの処理を行ったときに呼び出す関数を登録する
static struct block_device_operations modtest_fops = {
    .owner   = THIS_MODULE,
    .open    = NULL,
    .release = NULL,
    .ioctl   = NULL,
};


// モジュール初期化
static int __init modtest_module_init( void )
{
    // バッファ取得
    buffer = vmalloc( DISK_SIZE );
    if( buffer == NULL ) return -ENOMEM;

    // ブロックデバイス番号の動的取得
    modtest_major = register_blkdev( 0, MODNAME );
    if( modtest_major <= 0 ){
	printk( KERN_WARNING "register_blkdev failed\n" );
        return modtest_major;
    }

    // キューの作成
    spin_lock_init( &modtest_lock );
    modtest_queue = blk_init_queue(
        modtest_request, // キューに対して入出力が生じたら呼び出される関数
        &modtest_lock );
    if( ! modtest_queue ){
	printk( KERN_WARNING "blk_init_queue failed\n" );        
        unregister_blkdev( modtest_major, MODNAME );
        return -ENOMEM;
    }
    blk_queue_hardsect_size( modtest_queue, SECT_SIZE );

    // 仮想的な Generic ディスクの作成
    modtest_gd = alloc_disk( MINOR_COUNT );
    if( ! modtest_gd ){
	printk( KERN_WARNING "alloc_disk failed\n" );        
        blk_cleanup_queue( modtest_queue );
        unregister_blkdev( modtest_major, MODNAME );
        return -ENOMEM;
    }
    sprintf( modtest_gd->disk_name, "%s", MODNAME ); // スペシャルファイル名( /dev/modtest )
    modtest_gd->queue = modtest_queue; // キューとブロックデバイスを結びつける
    modtest_gd->major = modtest_major;
    modtest_gd->first_minor = 0;
    modtest_gd->fops = &modtest_fops;  // ディスクをオープンしたときなどに呼び出される関数
    set_capacity( modtest_gd, SECT_NUM );

    // 仮想ディスクの登録
    // スペシャルファイル( /dev/modtest )は自動的に /dev に作られる
    add_disk( modtest_gd );

    printk( KERN_INFO "modtest is loaded\n" );
    printk( KERN_INFO "major = %d\n", modtest_major );
    printk( KERN_INFO "bufsize = %d\n", DISK_SIZE );

    return 0;
}


//  モジュール解放
static void __exit modtest_module_exit( void )
{
    // Generic ディスク削除
    // スペシャルファイル ( /dev/modtest )は自動的に /dev から削除される
    del_gendisk( modtest_gd );
    put_disk( modtest_gd );

    // キューの削除
    blk_cleanup_queue( modtest_queue );

    // デバイス番号の返却
    unregister_blkdev( modtest_major, MODNAME );

    // バッファ解放
    vfree( buffer );

    printk( KERN_INFO "modtest is removed\n" );
}
	

module_init( modtest_module_init );
module_exit( modtest_module_exit );

MODULE_DESCRIPTION( MODNAME );
MODULE_LICENSE( "GPL2" );
まずキャラクタデバイスと同様に、modtest_module_init()内において register_blkdev()を用いて空いてるデバイス番号を取得する。 次に blk_init_queue() によって入出力で用いるキューを作成する。キューのサイズ(セクタサイズ)や、キューに対して入出力が生じたら 呼び出される関数などここで登録する(上の例ではmodtest_request() )。

次にalloc_disk()で仮想ディスクを作成し、set_capacity()でセクタ数をセットし、add_disk()で仮想ディスクを登録すれば /dev 以下にスペシャルファイルが自動的に出来上がる(上の例では /dev/modtest )。

最後に仮想ディスクやキューの削除はmodtest_module_exit()内でおこなう。

では実際に上の例を動かしてみる。
モジュールをロードしてスペシャルファイルを作成

$ su
# /sbin/insmod modtest.ko

デバイスが出来たか確認

# cat /proc/devices | grep modtest
252 modtest

# ls -al /sys/block/modtest/
合計 0
drwxr-xr-x  5 root root    0 2008-04-29 12:09 .
drwxr-xr-x 22 root root    0 2008-04-29 12:09 ..
-r--r--r--  1 root root 4096 2008-04-29 12:09 capability
-r--r--r--  1 root root 4096 2008-04-29 12:09 dev
drwxr-xr-x  2 root root    0 2008-04-29 12:09 holders
drwxr-xr-x  3 root root    0 2008-04-29 12:09 queue
-r--r--r--  1 root root 4096 2008-04-29 12:09 range
-r--r--r--  1 root root 4096 2008-04-29 12:09 removable
-r--r--r--  1 root root 4096 2008-04-29 12:09 size
drwxr-xr-x  2 root root    0 2008-04-29 12:09 slaves
-r--r--r--  1 root root 4096 2008-04-29 12:09 stat
lrwxrwxrwx  1 root root    0 2008-04-29 12:09 subsystem -> ../../block
--w-------  1 root root 4096 2008-04-29 12:09 uevent

# cat /sys/block/modtest/dev 
252:0

# ls -al /dev/modtest 
brw-r----- 1 root disk 252, 0 2008-04-29 12:41 /dev/modtest

ファイルシステムを作って適当な所にマウントする

# /sbin/mke2fs /dev/modtest
# mount /dev/modtest /mnt
# ls /mnt
lost+found
# mkdir /mnt/test
# chown ユーザ:ユーザ /mnt/test

一般ユーザに戻って /mnt/test を読み書きできるか確認

# exit
$ cd /mnt/test
$ echo hoge > hoge.txt
$ cat hoge.txt 
hoge

モジュールをアンロードする

$ cd -
$ su
# umount /mnt
# /sbin/rmmod modtest
# exit
dmesgは次の様になる
modtest is loaded
major = 252
bufsize = 1048576
modtest is removed