シンプルブロックデバイスドライバ


Rev.2を表示中。最新版はこちら

ブロックファイルの読み書きは、ファイルのinodeと参照するファイルのオフセット及びパーティション位置から、対応するブロックをデバイスの絶対位置として算出します。カーネル(vfs)はブロック単位で管理して、ブロックは2のべき乗のセクタを有しています。(物理デバイスはセクタ管理で、セクタ数の倍数というのは、無駄にセクタを使わないと言う事と、ビット処理の実装のためゆえの2のべき乗と言うことです。)

参照ブロックはbioに設定され、submit_io()関数をコールすることで、ブロックデバイス下のDevice.gd->queue->make_request_fnのエレベータと称するコールバック関数が、デバイスのrequest_queにリストされているrequestにリストされます。必要とあれば新規にrequestを作成したり、request間をマージしたりいたします。

実デバイスとのやりとりは、カーネル管理下で適時必要とされるタイミングで、Device.gd->queue->request_fnコールバック関数がコールされ、かかるrequest_queueのrequestのbioと実デバイスとのやりとりを行います。

下記サンプルは、O'REILLYのLinux Device Driversのシンプルブロックデバイスドライバサンプルです。

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>     /* everything... */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>

MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "1.4";

static int major_num = 0;
module_param(major_num, int, 0);
static int logical_block_size = 512;
module_param(logical_block_size, int, 0);
static int nsectors = 1024; /* How big the drive is */
module_param(nsectors, int, 0);


#define KERNEL_SECTOR_SIZE 512


static struct request_queue *Queue;

static struct sbd_device {
    unsigned long size;
    spinlock_t lock;
    u8 *data;
    struct gendisk *gd;
} Device;


static void sbd_transfer(struct sbd_device *dev, sector_t sector,
    unsigned long nsect, char *buffer, int write) {
    unsigned long offset = sector * logical_block_size;
    unsigned long nbytes = nsect * logical_block_size;

    if ((offset + nbytes) > dev->size) {
        printk (KERN_NOTICE "sbd: Beyond-end write (%ld %ld)\n", offset, nbytes);
        return;
    }
    if (write)
        memcpy(dev->data + offset, buffer, nbytes);
    else
        memcpy(buffer, dev->data + offset, nbytes);
}

static void sbd_request(struct request_queue *q) {
    struct request *req;

    req = blk_fetch_request(q);
    while (req != NULL) {
        if (req == NULL || (req->cmd_type != REQ_TYPE_FS)) {
            printk (KERN_NOTICE "Skip non-CMD request\n");
            __blk_end_request_all(req, -EIO);
                continue;
        }
        sbd_transfer(&Device, blk_rq_pos(req), blk_rq_cur_sectors(req),
                                           req->buffer, rq_data_dir(req));
        if ( ! __blk_end_request_cur(req, 0) ) {
            req = blk_fetch_request(q);
        }
    }
}

int sbd_getgeo(struct block_device * block_device, struct hd_geometry * geo) {
long size;

size = Device.size * (logical_block_size / KERNEL_SECTOR_SIZE);
geo->cylinders = (size & ~0x3f) >> 6;
geo->heads = 4;
geo->sectors = 16;
geo->start = 0;
return 0;
}

static struct block_device_operations sbd_ops = {
    .owner  = THIS_MODULE,
    .getgeo = sbd_getgeo
};

static int __init sbd_init(void) {
    Device.size = nsectors * logical_block_size;
    spin_lock_init(&Device.lock);
    Device.data = vmalloc(Device.size);
    if (Device.data == NULL)
        return -ENOMEM;
        Queue = blk_init_queue(sbd_request, &Device.lock);
        if (Queue == NULL)
            goto out;
    blk_queue_logical_block_size(Queue, logical_block_size);

    major_num = register_blkdev(major_num, "sbd");
    if (major_num < 0) {
        printk(KERN_WARNING "sbd: unable to get major number\n");
        goto out;
    }

    Device.gd = alloc_disk(16);
    if (!Device.gd)
        goto out_unregister;
    Device.gd->major = major_num;
    Device.gd->first_minor = 0;
    Device.gd->fops = &sbd_ops;
    Device.gd->private_data = &Device;
    strcpy(Device.gd->disk_name, "sbd0");
    set_capacity(Device.gd, nsectors);
    Device.gd->queue = Queue;
    add_disk(Device.gd);

    return 0;

out_unregister:
    unregister_blkdev(major_num, "sbd");
out:
    vfree(Device.data);
    return -ENOMEM;
}

static void __exit sbd_exit(void)
{
    del_gendisk(Device.gd);
    put_disk(Device.gd);
    unregister_blkdev(major_num, "sbd");
    blk_cleanup_queue(Queue);
    vfree(Device.data);
}

module_init(sbd_init);
module_exit(sbd_exit);
実行結果
[root@localhost lkm]# insmod sbd.ko
[root@localhost lkm]# ls -li /dev/sbd*
13999 brw-rw---- 1 root disk 252, 0 10月 30 00:16 /dev/sbd0

[root@localhost lkm]# mkfs.ext2 /dev/sbd0
mke2fs 1.41.14 (22-Dec-2010)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
64 inodes, 512 blocks
25 blocks (4.88%) reserved for the super user
First data block=1
Maximum filesystem blocks=524288
1 block group
8192 blocks per group, 8192 fragments per group
64 inodes per group

Writing inode tables: done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 33 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.

[root@localhost lkm]# mount /dev/sbd0 /mnt4
[root@localhost lkm]# echo "hogehoge" > /mnt4/hoge.txt
[root@localhost lkm]# ls -l /mnt4
合計 13
-rw-r--r-- 1 root root     9 10月 30 00:22 hoge.txt
drwx------ 2 root root 12288 10月 30 00:19 lost+found
blk_init_queue()でrequest_queueが作成され、Device.gd->queueに設定され、ブロック読み書き時のデバイス実参照時、equest_queueのrequest_fnに設定されたsbd_request()がコールされます。(同時にrequest_queueのmake_request_fnに、エレベータ関数のopコールバックも設定されます。)

sbd_request()で request_queueのrequestのbio単位で、デバイスバッファとやり取りします。req->cmd_type != REQ_TYPE_FSは、requestがファイル参照のリクエストかどうかと言うことです。requestにはデバイスの制御コマンドとしてのコード、シャットダウンコマンド等の他のリクエストも有しています。

サンプルは未処理です。この時__blk_end_request_all()をコールすることで、そのrequestを削除し、request_queueの先頭に次のrequestが接続します。故に、関数内の変数reqは、次にrequestということで、改めてblk_fetch_request()をコールする必要がありません。

sbd_transfer()では、実デバイスとやり取りをブロック単位で行います。blk_rq_pos(req)はrequestの先頭セクタ位置、blk_rq_cur_sectors()はrequestの先頭のbioのセクタ数、rq_data_dir()はread/writeフラグです。

やり取りが終了すると、__blk_end_request_cur()をコールし、request先頭(読み書きした)のbioのみを削除します。この時requestの先頭セクタ位置/セクタ数等も更新されます。返り値がfalseの場合、requestにはこれ以上のbioはありません。従って次のrequestのbioがデバイスへ読み書きの対象となるため、blk_fetch_request()で改めてrequestを取得しています。

sbd_transfer()は、dev->dataのセクタ位置をオフセットとして、req->bufferと読み書きします。dev->dataは本サンプルに特化したもので、実際はデバイス依存のデバイスとのやり取りとなります。

サンプルのように、リアルタイム性を向上させるため、request単位でなくbio単位で実デバイスとやり取りするのが一般的のようですが、メモリとしてのデバイスなら、むしろrequest単位でやり取りした方がいいかと思います。sbd_transfer()のセクタ数をrequestのセクタ数とし、__blk_end_request_cur()でなく__blk_end_request_all()でrequestを削除すればいいはずです。(未検証)

補足

ブロックデバイスの実デバイスへの読み書きは、request_queueのrequest_fnコールバックが行います。従ってキャラクタデバイスのようなread/writeコールバック関数を有しません。

デバイスコールバックの.getgeoは、ioctrlの HDIO_GETGEOでコールすることで、blkdev_ioctl()と、そして disk->fops->getgeo()とすることで、シリンダ数/ヘッド数/セクタ数/先頭位置のstruct hd_geometryが取得でき、fdisk等で無地なデバイスを設定する際のデバイス情報が取得できるように成っています。サンプルはfdiskでパーティションを作ることも可能です。


最終更新 2014/10/29 19:21:46 - north
(2014/10/29 19:19:58 作成)


検索

アクセス数
3575196
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。