块设备驱动程序的引入和简单应用

来源:互联网 发布:如何运用办公软件 编辑:程序博客网 时间:2024/06/07 04:00

先说说为什么要引入块设备驱动程序。
以FLASH为例,如果对flash的读写采用字符设备驱动程序的那一套的话,会产生效率低下的问题。flash是以块为单位进行操作的,假如在flash一个块中,要完成对扇区0和扇区1的改写,如果按照字符设备的方式,需要完成以下步骤:
①读出整个块到buffer
②修改buffer的扇区0
③擦除整个块
④烧写整块
⑤读出整个块到buffer
⑥修改buffer的扇区1
⑦擦除整个块
⑧烧写整块
但如果我们引入一些优化:
①读出整个块到buffer
②修改buffer的扇区0和扇区1
③擦除整个块
④烧写整块

这样我们就能够大大提高效率。因此,块设备驱动程序的基本思想就出来了:先不执行读写,而是放入队列;优化之后再执行。

下面介绍一下块设备驱动程序的框架。

app:      open,read,write "1.txt"---------------------------------------------  文件的读写文件系统: vfat, ext2, ext3, yaffs2, jffs2      (把文件的读写转换为扇区的读写)-----------------ll_rw_block-----------------  扇区的读写                       1. 把"读写"放入队列                       2. 调用队列的处理函数(优化/调顺序/合并)            块设备驱动程序     ---------------------------------------------硬件:        硬盘,flash

下面开始分析ll_rw_block(low level read/write block)

分析ll_rw_block        for (i = 0; i < nr; i++) {            struct buffer_head *bh = bhs[i];            submit_bh(rw, bh);                struct bio *bio; // 使用bh来构造bio (block input/output)                submit_bio(rw, bio);                    // 通用的构造请求: 使用bio来构造请求(request)                    generic_make_request(bio);                        __generic_make_request(bio);                            request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列                              // 调用队列的"构造请求函数"                            ret = q->make_request_fn(q, bio);                                    // 默认的函数是__make_request                                    __make_request                                        // 先尝试合并                                        elv_merge(q, &req, bio);                                        // 如果合并不成,使用bio构造请求                                        init_request_from_bio(req, bio);                                        // 把请求放入队列                                        add_request(q, req);                                        // 执行队列                                        __generic_unplug_device(q);                                                // 调用队列的"处理函数"                                                q->request_fn(q);

所以,怎么写块设备驱动程序呢?
1. 分配gendisk: alloc_disk
2. 设置
2.1 分配/设置队列: request_queue_t 和blk_init_queue// 它提供读写能力
2.2 设置gendisk其他信息 // 它提供属性: 比如容量
3. 注册: add_disk

接下来要写一个真正的驱动程序,分配一块内存,用内存来模拟块设备。
第一个程序:

static struct gendisk *ramblock_disk;static request_queue_t *ramblock_queue;static int major;static DEFINE_SPINLOCK(ramblock_lock);//定义自选锁static struct block_device_operations ramblock_fops = {    .owner  = THIS_MODULE,};#define RAMBLOCK_SIZE (1024*1024)static void do_ramblock_request(request_queue_t * q){    static int cnt = 0;    printk("do_ramblock_request %d\n", ++cnt);}static int ramblock_init(void){    /* 1. 分配一个gendisk结构体 */    ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */    /* 2. 设置 */    /* 2.1 分配/设置队列: 提供读写能力 */    ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);    ramblock_disk->queue = ramblock_queue;    /* 2.2 设置其他属性: 比如容量 */    major = register_blkdev(0, "ramblock");  /* 用cat /proc/devices可以看到块设备 */        ramblock_disk->major       = major;    ramblock_disk->first_minor = 0;    sprintf(ramblock_disk->disk_name, "ramblock");    ramblock_disk->fops        = &ramblock_fops;    set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);//单位是扇区,在内核层面默认扇区是521字节    /* 3. 注册 */    add_disk(ramblock_disk);    return 0;}static void ramblock_exit(void){    unregister_blkdev(major, "ramblock");    del_gendisk(ramblock_disk);    put_disk(ramblock_disk);    blk_cleanup_queue(ramblock_queue);}module_init(ramblock_init);module_exit(ramblock_exit);MODULE_LICENSE("GPL");

加载驱动之后发现:
ramblock:do_ramblock_request 1
然后就卡住了,无法继续操作。说明确实调用了do_ramblock_request这个函数,但由于我们没有对它进行进一步的处理,所以还需要完善。

static void do_ramblock_request(request_queue_t * q){    static int cnt = 0;    struct request *req;    printk("do_ramblock_request %d\n", ++cnt);    while ((req = elv_next_request(q)) != NULL) {        end_request(req, 1);    }}

以电梯调度算法取出下一个请求,然后结束请求并返回1表示成功。实际上我们什么都没有做,但这样就不会卡住了。它会打印出如下结果:
ramblock:do_ramblock_request 1
unknown partition table
之所以提示不识别的分区表,是因为我们在实际上我们在do_ramblock_request里什么都没有做。我们只是简单粗暴的把请求拿出来,然后就简单把它当成完成了。
进一步优化:

static void do_ramblock_request(request_queue_t * q){    static int cnt = 0;    struct request *req;    //printk("do_ramblock_request %d\n", ++cnt);    while ((req = elv_next_request(q)) != NULL) {        /* 数据传输三要素: 源,目的,长度 */        /* 源/目的:当读的时候就是源,写的时候就是目的 */        unsigned long offset = req->sector * 512;//扇区的长度乘以每个扇区大小        /* 目的/源:当读的时候就是目的,写的时候就是源 */        // req->buffer        /* 长度: */               unsigned long len = req->current_nr_sectors * 512;//扇区的长度乘以每个扇区大小        if (rq_data_dir(req) == READ)        {            memcpy(req->buffer, ramblock_buf+offset, len);        }        else        {            memcpy(ramblock_buf+offset, req->buffer, len);        }               end_request(req, 1);    }

其中memcpy(void *dest, const void *src, size_t count)。
测试:
在开发板上:
1. insmod ramblock.ko
2. 格式化: mkdosfs /dev/ramblock
3. 挂接: mount /dev/ramblock /tmp/
4. 读写文件: cd /tmp, 在里面vi文件,比如新建一个1.txt,里面的内容是“hello”
5. cd /; umount /tmp/,卸载设备
6. cat /dev/ramblock > /mnt/ramblock.bin,把文件的内容统统存到/mnt/ramblock.bin,相当于整个磁盘映象。
7. 在PC上查看ramblock.bin
sudo mount -o loop ramblock.bin /mnt,可以把一个普通文件当作块设备文件挂载,输入ls仍然可以看到1.txt。

接下来加入打印语句,可以看到一些有意思的事情:

static void do_ramblock_request(request_queue_t * q){    static int r_cnt = 0;    static int w_cnt = 0;    struct request *req;    //printk("do_ramblock_request %d\n", ++cnt);    while ((req = elv_next_request(q)) != NULL) {        /* 数据传输三要素: 源,目的,长度 */        /* 源/目的: */        unsigned long offset = req->sector * 512;        /* 目的/源: */        // req->buffer        /* 长度: */               unsigned long len = req->current_nr_sectors * 512;        if (rq_data_dir(req) == READ)        {            printk("do_ramblock_request read %d\n", ++r_cnt);            memcpy(req->buffer, ramblock_buf+offset, len);        }        else        {            printk("do_ramblock_request write %d\n", ++w_cnt);            memcpy(ramblock_buf+offset, req->buffer, len);        }               end_request(req, 1);    }}

还是按照上面的步骤测试,当挂载到/tmp之后,我们执行“cp /etc/inittab /tmp”:

do_ramblock_request write 6
do_ramblock_request write 7
do_ramblock_request write 8
再执行sync
do_ramblock_request write 9
do_ramblock_request write 10

其中,打印的时候会有很长的时间间隔,当输入sync时,会完成全部操作。sync是一个系统调用,是同步的意思。

从这里我们可以看出,块设备的读写不会交叉执行,而是会进行优化,放入队列。

接下来进行下一步的优化,实现分区:

static struct block_device_operations ramblock_fops = {    .owner  = THIS_MODULE,    .getgeo = ramblock_getgeo,};static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo){    /* 容量=heads*cylinders*sectors*512 */    geo->heads     = 2;//磁头的个数    geo->cylinders = 32;//柱面的个数    geo->sectors   = RAMBLOCK_SIZE/2/32/512;    return 0;}

要注意的是,实际上很多磁盘已经不是这种结构了,但为了兼容,必须设置虚拟的参数,这样才能借助分区工具。
加载驱动之后输入fdisk /dev/ramblock。然后输入n新建一个分区,输入p表示主分区,然后输入1表示第一个主分区。

Command (m for help): pDisk /dev/ramblock: 1 MB, 1048576 bytes2 heads, 32 sectors/track, 32 cylindersUnits = cylinders of 64 * 512 = 32768 bytes        Device Boot      Start         End      Blocks  Id SystemCommand (m for help): nCommand action   e   extended   p   primary partition (1-4)pPartition number (1-4): 1First cylinder (1-32, default 1): 1Last cylinder or +size or +sizeM or +sizeK (1-32, default 32): 5Command (m for help): wThe partition table has been altered! ramblock: ramblock1

这样第一个分区就好了。剩下的分区也这样操作,最后别忘记输入“w”把分区写到分区表中。最后可以看到:

Command (m for help): pDisk /dev/ramblock: 1 MB, 1048576 bytes2 heads, 32 sectors/track, 32 cylindersUnits = cylinders of 64 * 512 = 32768 bytes        Device Boot      Start         End      Blocks  Id System/dev/ramblock1               1           5         144  83 Linux/dev/ramblock2               6          25         640  83 Linux/dev/ramblock3              26          32         224  83 Linux

如果现在输入”ls /dev/ramblock* -l”,会看到:

brw-rw----    1 0        0        254,   0 Jan  1 00:45 /dev/ramblockbrw-rw----    1 0        0        254,   1 Jan  1 00:45 /dev/ramblock1brw-rw----    1 0        0        254,   2 Jan  1 00:45 /dev/ramblock2brw-rw----    1 0        0        254,   3 Jan  1 00:45 /dev/ramblock3

如果我们想分别格式化的时候就可以直接“fdisk /dev/ramblock2”,还可以直接挂载“mount /dev/ramblock1 /tmp/”。

驱动程序就写到这里,用内存模拟磁盘很简单,可以想象一下,如果块设备也能够像内存一样访问的话,块设备就会非常的简单,但可惜的是,现在还达不到这种水平,硬件的操作太复杂了。但块设备本身的框架并不复杂。

0 0
原创粉丝点击