zoukankan      html  css  js  c++  java
  • Linux块设备驱动_WDS

    推荐书:《Linux内核源代码情景分析》

    1.字符设备驱动和使用中等待某一事件的方法
    ①查询方式
    ②休眠唤醒,但是这种没有超时时间
    ③poll机制,在休眠唤醒基础上加一个超时时间
    ④异步通知,异步通知实际上就是发信号
    ⑤输入子系统,这样比较通用

    2.块设备相对于字符设备驱动逻辑的变化
    ①对于硬盘对读写的优化
    假如要读磁头0的扇区0,然后写磁头1的扇区0,然后读磁头0的扇区1,若像字符设备那样,就会机械山跳转2次,效率低。
    优化:
    先不执行,放入队列,优化后再执行,这里的优化是指调整顺序。

    ②对于flash
    假如要写同一个块的扇区0,然后再写扇区1,若是字符设备的做法,写扇区0时需要先把整块读取出来,然后修改此块中
    扇区0的数据,然后烧写整个这个块。写扇区1时也是需要先把整块读取出来,然后修改此块中扇区1的数据,然后烧写整个这个块。
    优化:
    先不执行,放入队列,优化后再执行,这里的优化是指合并相同块的写请求。

    所以块设备不能向字符设备一样直接提供读写函数,而是需要先放入队列之中,优化后再执行。


    3.块设备驱动程序框架

    App:open, read, write "1.txt"
    ----------------------------------------------- 文件的读写
    文件系统:vfat,tfat, ext2, ext3, yaffs2, jffs2 作用:把文件的读写转换为扇区的读写
    ------------统一的入口:ll_rw_block()---------- 扇区的读写
    1.把“读写”放入队列中(可能这一步就有优化)
    2.调用队列的处理函数(优化/调整顺序/合并)
    块设备驱动程序: 主要是读写块设备函数的实现和属性的提供
    -----------------------------------------------
    硬件:硬盘,flash,eMMC


    4.分析ll_rw_block()

    ll_rw_block //fs/buffer.c, 位于fs下,说明是所有文件系统的一个通用的.c文件
        for (i = 0; i < nr; i++)
        submit_bh(op, op_flags, bh); //fs/buffer.c
            struct bio *bio;
            通用的构造请求,使用bio来构造请求
            submit_bio(bio);
                generic_make_request(bio);
                    struct request_queue *q = bio->bi_disk->queue; //block/blk-core.c 找对队列
                    ret = q->make_request_fn(q, bio); //调用队列的构造请求函数,默认的设置函数是:make_request_fn
                        q->make_request_fn在blk_queue_make_request block/Blk-settings.c中被赋值,
                        blk_queue_make_request在blk_init_allocated_queue block/blk-core.c中被赋值为blk_queue_bio,即make_request_fn=blk_queue_bio
                        blk_queue_bio //默认是这个
                            elv_merge(q, &req, bio) //block/blk-core.c 以电梯调用算法尝试合并这个请求
                            如果合并不成功,调用get_request使用bio构造请求,将请求放入队列中
                                get_request(q, bio->bi_opf, bio, GFP_NOIO);
                                blk_init_request_from_bio(req, bio);
                                blk_flush_plug_list(plug, false);
                                if (q) queue_unplugged(q, depth, from_schedule);                        
                                    __blk_run_queue
                                        q->request_fn(q); //调用队列的处理函数,就是块设备驱动实际的读写函数

    5.写块设备驱动
    1.分配构造struct request_queue,用于提供读写能力
    2.设备描述,提供属性
    ......
    ====>内核指定了一个结构体:gendisk
    驱动框架:
    1.分配gendisk: alloc_disk
    2.设置
    2.1 分配/设置struct request_queue结构,blk_init_queue
    2.2 设置gendisk其它信息
    3.注册gendisk

    6.块设备的操作是以扇区为单位的,内核机制决定的,就算是使用内存模拟的块设备也不例外。

    7.对于一块全0的内存模拟的磁盘,未格式化会报"unknow partation table",因为其分区表为空

    8.测试
    cat /proc/devices 查看设备号
    格式化磁盘:# mkfs /dev/ramblock eg: mkdosfs /dev/ramdisk
    挂载:# mount /dev/ramblock /tmp
    之后就可以通过访问/tmp目录操作磁盘了

    9.创建磁盘映像
    # cat /dev/ramblock > /ramblock.bin 然后再格式化磁盘再echo进去应该也是可以的。
    在Ubuntu里面:# sudo mount -o loop ramblock.bin /mnt     loop选项可以把一个普通文件当作块设备进行挂载。loop把它当作一个回环设备。

    10.把对内存的操作打印出来可以对比App的读写和实际的IO操作发生的时机的区别。cat挂载目录中刚才已经操作过的文件,发现并没有去读,而是使用缓存的。echo到挂载目录的文件中也是没有调用读写函数,而是先刷到缓冲中。

    11.分区
    # ls /dev/ramblock* -l 次设备号是0表示整个磁盘,不是分区。起始是0应该是first_minor=0决定的。
    # fdisk /dev/ramblock m n p 1(此时显示1-32 sylinder就是驱动中在ramblock_getgeo()中配置的) 1 5,再创建一个分区n p 2 6 32 w
    (w表示将配置写到分区表中,分区表就是第一个扇区)
    # ls /dev/ramblock* -l 次设备号是0表示整个磁盘,次设备号是1表示第一个主分区,次设备号是2表示第二个主分区。
    此时也可以分别格式化每一个主分区:
    eg: # mkdosfs /dev/ramblock1
    eg: # mkdosfs /dev/ramblock2
    分别挂载:
    eg: # mount /dev/ramblock1 /mnt
    eg: # mount /dev/ramblock2 /tmp
    使用# fdisk /dev/ramdisk 报错: Unknow value(s) for: Cylinder 不知道柱面数,fdisk是个老工具了,使用它需要驱动告诉它柱面数信
    息(目前很多块设备都不使用这个储存方式了)

    12.内存模拟块设备驱动代码

    /* 
    参考: driverslockz2ram.c 
    
    usage:
        mkfs  Formatted file system
        mount
    
        fdisk  Create partition
    */
    
    #include <linux/major.h>
    #include <linux/vmalloc.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/blkdev.h>
    #include <linux/bitops.h>
    #include <linux/mutex.h>
    #include <linux/slab.h>
    #include <linux/hdreg.h>
    
    #include <asm/setup.h>
    #include <asm/pgtable.h>
    
    static struct gendisk *ramblock_disk;
    static struct request_queue *ramblock_queue;
    
    static int major;
    
    static DEFINE_SPINLOCK(ramblock_lock);
    
    #define RAMBLOCK_SIZE (1024*1024)  /*使用1M内存来模拟磁盘*/
    static unsigned char *ramblock_buf;
    
    /*这些信息在使用fdisk工具分区的时候会用到*/
    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;
    }
    
    
    static struct block_device_operations ramblock_fops = {
        .owner    = THIS_MODULE,
        .getgeo    = ramblock_getgeo,
    };
    
    #if 0
    /*目前内核中elv_next_request已经不存在了*/
    static void do_ramblock_request(struct request_queue * q)
    {
        static int r_cnt = 0;
        static int w_cnt = 0;
        struct request *req;
    
        //printk("do_ramblock_request %d
    ", ++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) /*也就是: req->cmd_flags & 1*/
            {
                printk("do_ramblock_request read %d
    ", ++r_cnt);
                memcpy(req->buffer, ramblock_buf+offset, len); /*这里是使用memcpy来模拟复杂的IO操作*/
            }
            else
            {
                /*加了这个打印,可以看出来当向设备进行写的时候没有立即调用,而是过来一小会才调用的,
                与算法有关,先放到队列中,然后才执行。
    
                # cp /etc/fstab /tmp(挂载目录),发现过来一会也没有调用这个函数
                # sync  立即就打印了
    
                # cp /etc/fstab /tmp  发现没有立即写
                # umount /tmp/ 发现立即打印了
                */
                printk("do_ramblock_request write %d
    ", ++w_cnt);
                memcpy(ramblock_buf+offset, req->buffer, len);
            }
    
            end_request(req, 1);
        }
    }
    #endif
    
    /* for 4.14 kernel */
    static void do_ramblock_request(struct request_queue *q)
    {
        struct request *req;
        char *bio_buffer;
        static int r_cnt = 0;
        static int w_cnt = 0;
    
        req = blk_fetch_request(q);
    
        while (req) {
            unsigned long offset = blk_rq_pos(req) << 9; /*return rq->__sector; the current sector,右移9也即乘以512*/
            unsigned long len  = blk_rq_cur_bytes(req); /*bytes left in the current segment*/
            blk_status_t err = BLK_STS_OK;
    
            if (offset + len > RAMBLOCK_SIZE) {
                printk("do_ramblock_request read execteed
    ");
                len = RAMBLOCK_SIZE-offset;
            }
    
            bio_buffer = bio_data(req->bio); /*拷贝到这里面返回给用户*/
            if (rq_data_dir(req) == READ) {
                printk("do_ramblock_request read %d
    ", ++r_cnt);
                memcpy(bio_buffer, ramblock_buf+offset, len);
            } else {
                printk("do_ramblock_request write %d
    ", ++w_cnt);
                memcpy(ramblock_buf+offset, bio_buffer, len);
            }
    
        if (!__blk_end_request_cur(req, err))   /*must with it,else will blocking when ismod*/
            req = blk_fetch_request(q);
        }
    }
    
    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); /*arg1: 执行实际读写io操作的函数*/
        ramblock_disk->queue = ramblock_queue;
    
        /* 2.2 设置其他属性: 比如容量 */
        major = register_blkdev(0, "ramblock");  /* cat /proc/devices ==> 254 ramblock*/
        ramblock_disk->major       = major;
        ramblock_disk->first_minor = 0; /*修改它试试*/
        sprintf(ramblock_disk->disk_name, "ramblock_name"); /* /dev/ramblock_name */
        ramblock_disk->fops        = &ramblock_fops;
        set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); /*设置磁盘容量,是以扇区为单位的*/
    
        /* 3. 硬件相关操作 */
        ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); /*这个设备全为0,分区表为空,所以在insmod驱动的时候会报"unknow partation table",需要格式化*/
    
        /* 4. 注册 */
        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);
    
        kfree(ramblock_buf);
    }
    
    module_init(ramblock_init);
    module_exit(ramblock_exit);
    
    MODULE_LICENSE("GPL");

    测试ok的。根据do_z2_request(), 就算是这个函数是void类型的也能向外部传递操作执行的状态。

    附:

    1. 给SD卡添加label:

    给SD卡添加label:
    $ sudo fatlabel /dev/sdc1 sfl_sd
    $ df -h
    Filesystem                         Size  Used Avail Use% Mounted on
    /dev/sdc1                          7.3G  4.0K  7.3G   1% /media/ubuntu/sfl_sd





  • 相关阅读:
    【PE/Codecs】YUV文件比较的两种方法
    【Python】删除非空目录的方法shutil.rmtree()以及空目录的方法os.rmdir()
    【SVAC】国家视频编解码标准SVAC的特色和优势
    【SVAC】国标SVAC对飙通行标准,优势何在?
    【SVAC】SVAC推广应用进入关键期和高峰期
    【SVAC】SVAC 2.0安全系统组成
    【SVAC】期待国标视频编解码标准SVAC2.0应用推广!
    【Python】python取整函数和获取文件大小的方法
    【Python】Python中的正则表达式教程
    【Python】 整型数与字符串相互转换
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/10162362.html
Copyright © 2011-2022 走看看