• Linux块设备驱动详解


    1.块设备驱动

    块设备驱动是用来操作硬盘类的存储设备的

    image-20220520113053720

    1.1什么是块设备驱动

    1. 块设备驱动的概念:系统中能够随机访问固定大小(1block =512byte)数据片的设备被称之为块设备。块设备文件一般都是以安装文件系统的方式使用,这也是块设备通常的访问方式。块设备的访问方式是随机的。
    2. 块设备中最小的可寻址单位是扇区,扇区大小一般是2的整数倍。最常见的大小是512字节。是文件系统的一种抽象,只能基于块来访问文件系统。物理磁盘寻址是按照扇区的级别进行的,内核访问的所有磁盘操作又都是按照块进行的。扇区是设备的最小可寻址单位,所以块不能比扇区还小,只能数倍于扇区大小。
    3. 内核对块大小的要求是:必须是扇区大小的整数倍,并且小于页面的大小,所以块的大小通常是512字节、1K或者4K。

    1.2块设备驱动和字符设备驱动的对比

    1. 块设备接口相对复杂,不如字符设备明晰易用
    2. 块设备驱动程序对整个系统的性能影响较大,速度和效率是设计块设备驱动程要重点考虑的问题
    3. 系统中使用缓冲区与访问请求的优化管理(合并与重新排序)来提高系统性能

    image-20220520115038053

    1.3块设备驱动的相关知识简介

    磁头:一个磁盘有多少个面就有多少个磁头

    磁道:在一个磁头上可以有很多环,这些环就叫做磁道

    扇区:磁道上访问数据的最小的单位就是扇区,一个扇区的大小就是512字节

    1block = 512字节 1024字节 2048字节 4096字节

    1扇区 = 512字节

    块设备的能存储的数据=磁头*磁道*扇区*512

    取数据的逻辑和电梯调度的逻辑一样

    image-20220520113853510

    1.4 块设备驱动的框架图

    image-20220520120038401

    1. 虚拟文件系统(VFS):隐藏了各种硬件的具体细节,为用户操作不同的硬件提供了一个统一的接口。其基于不同的文件系统格式,比如EXT,FAT等。用户程序对设备的操作都通过VFS来完成,在VFS上面就是诸如open、close、write和read的函数API。
    2. Disk Cache:硬盘的高速缓存,用户缓存最近访问的文件数据,如果能在高速缓存中找到,就不必去访问硬盘,毕竟硬盘的访问速度慢很多。
    3. 映射层(mapping layer):这一层主要用于确定文件系统的block size,然后计算所请求的数据包含多少个block。同时调用具体文件系统函数来访问文件的inode,确定所请求的数据在磁盘上面的逻辑地址。
    4. Generic Block Layer:通用块层,Linux内核把块设备看做是由若干个扇区组成的数据空间,上层的读写请求在通用块层被构造成一个或多个bio结构
    5. I/O Scheduler Layer :I/O调度层,负责将通用块层的块I/O操作进行(电梯调度算法)调度、插入、暂存、排序、合并、分发等操作,对磁盘的操作更为高效。负责将通用块层的块I/O操作进行
    6. 块设备驱动层:在块系统架构的最底层,由块设备驱动根据排序好的请求,对硬件进行数据访问。
    user:
    	open     read    write    close
    -------------------(io请求)-----------------------------------
    kernel	|中间层: (block_device)
    	    |	将用户的io请求转化成BIO(block,input ,output),
    	    |	在物理内存上连续的bio会被合成request,这个request
    	    |	会被放到内核的一个队列上。
    	    |---------------------------------------------------------
    	    |driver:gendisk
    	    |	1.分配对象
    	    |	2.对象初始化
    	    |	3.初始化一个队列  head----request(read)----request(write)---...
    	    |	//4.硬盘设备的初始化
    	    |	5.注册、注销
    ------------------------------------------------------------------		
    haredware :   分配的内存(模拟真实的设备)(1M)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1.5块设备驱动的API

    1.gendisk的结构体对象
        struct gendisk {   
            int major;   //块设备的主设备号
            int first_minor; //起始的次设备号
            int minors; // 最大的次设备数,如果不能分区,则为1
            char disk_name[DISK_NAME_LEN]; //磁盘的名字
            struct disk_part_tbl  *part_tbl;
            //磁盘的分区表的首地址
            struct hd_struct part0;
            //part0分区的描述
            const struct block_device_operations *fops;
            //块设备的操作方法结构体
    
            struct request_queue *queue;
            //队列(重要)
    
            void *private_data;
            //私有数据
        };
    
        分区的结构体
        struct hd_struct {
            sector_t start_sect; //起始的扇区号
            sector_t nr_sects;   //扇区的个数                                                                                             
            int  partno;        //分区号
        };
    
        //块设备的操作方法结构体
        struct block_device_operations {
            int (*open) (struct block_device *, fmode_t);
            int (*release) (struct gendisk *, fmode_t);
            int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
            int (*getgeo)(struct block_device *, struct hd_geometry *);	
            //设置磁盘的磁头,磁道,扇区的个数的。hd_geometry
        }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    image-20220520135311721
    2.结构体对象的初始化
        struct gendisk *mydisk;
    
        struct gendisk *alloc_disk(int minors)
        //void put_disk(struct gendisk *disk)
        //归还引用计数
        功能:分配gendisk的内存,然后完成必要的初始化
        参数:
            @minors:分区的个数
        返回值:成功返回分配到的内存的首地址,失败返回NULL
    
    
        int register_blkdev(unsigned int major, const char *name)
        //void unregister_blkdev(unsigned int major, const char *name)
        功能:申请设备设备驱动的主设备号
        参数:
            @major : 0:自动申请
                      >0 :静态指定
            @name  :名字  cat /proc/devices
        返回值:	
                major=0 ;成功返回主设备号,失败返回错误码
                major>0 :成功返回0 ,失败返回错误码
    
        void set_capacity(struct gendisk *disk, sector_t size)
        功能:设置磁盘的容量
    
        struct request_queue *blk_mq_init_sq_queue(struct blk_mq_tag_set *set,const struct blk_mq_ops 
        *ops,unsigned int queue_depth,unsigned int set_flags)
        //void blk_cleanup_queue(struct request_queue *q)
         功能:用于在给定队列深度的情况下使用mq ops设置队列的助手,以及通过mq ops标志传递的助手
         参数:
         @被初始化的tag对象,tag被上层使用,里面包含硬件队列的个数,队列的操作方法结构体,标志位等
         @放入到tag中的操作方法结构体
         @ tag中指定支持的队列深度
         @将tag中队列的处理标志位,例如BLK_MQ_F_SHOULD_MERGE, BLK_MQ_F_BLOCKING等
         返回值:成功返回队列指针,失败返回错误码指针 
    
    3.注册、注销
        void add_disk(struct gendisk *disk)
        //注册
        void del_gendisk(struct gendisk *disk)
        //注销
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1.6块设备驱动的实例1

    #include 
    #include 
    #include 
    #include 
    #include 
    #define BLKSIZE (1 * 1024 * 1024) // 1M
    #define BLKNAME "mydisk"
    struct gendisk* disk;
    int major;
    char *addr;
    int mydisk_open(struct block_device* blkdev, fmode_t mode)
    {
        printk("%s:%d\n", __func__, __LINE__);
        return 0;
    }
    void mydisk_close(struct gendisk* dk, fmode_t mode)
    {
        printk("%s:%d\n", __func__, __LINE__);
    }
    int mydisk_getgeo(struct block_device* blkdev, struct hd_geometry* hd)
    {
        printk("%s:%d\n", __func__, __LINE__);
        // struct hd_geometry {
        //   unsigned char heads; //磁头个数
        //   unsigned char sectors; //扇区个数
        //   unsigned short cylinders; //磁道个数
        // };
        hd->heads = 4;
        hd->cylinders = 16;
        hd->sectors = BLKSIZE / hd->heads / hd->cylinders / 512;
        return 0;
    }
    const struct block_device_operations fops = {
        .open = mydisk_open,
        .release = mydisk_close,
        .getgeo = mydisk_getgeo,
    };
    static int __init mydisk_init(void)
    {
        // 1.分配对象
        disk = alloc_disk(4);
        if (disk == NULL) {
            printk("alloc gendisk memory error\n");
            return -ENOMEM;
        }
        // 2.初始化对象
        major = register_blkdev(0, BLKNAME);
        if (major < 0) {
            printk("get blkdev number error\n");
            return major;
        }
        disk->major = major;
        disk->first_minor = 0;
        strcpy(disk->disk_name, BLKNAME);
        set_capacity(disk, BLKSIZE >> 9);
        disk->fops = &fops;
    
        // disk->queue = 
    
        //3.申请1M内存当成硬盘使用
        addr = vmalloc(BLKSIZE);
    
        //4.注册
        add_disk(disk);
    
        return 0;
    }
    static void __exit mydisk_exit(void)
    {
        del_gendisk(disk);
        vfree(addr);
        unregister_blkdev(major,BLKNAME);
        put_disk(disk);
    }
    module_init(mydisk_init);
    module_exit(mydisk_exit);
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    1.7队列处理相关的结构体、关系、相关函数

    struct  request_queue 
    {
        /*双向链表数据结构,将所有加入到队列的IO请求组建成一个双向链表*/
        struct  list_head  queue_head; 
        struct list_head    requeue_list; //request队列
        spinlock_t      requeue_lock;     //队列自旋锁
        unsigned long     nr_requests;     /* 最大的请求数量 */
        unsigned long     queue_flags;/*当前请求队列的状QUEUE_FLAG_STOPPED*/};
    struct  request
    {
        struct list_head queuelist;/* 请求对象中的链表元素*/
        struct request_queue *q;	/* 指向存放当前请求的请求队列*/
        unsigned int __data_len;	/* 当前请求要求数据传输的总的数据量 */
        sector_t __sector;         /* 当前请求要求数据传输的块设备的起始扇区 */
        struct bio *bio;		/* bio对象所携带的信息转存至请求对象中*/
        struct bio *biotail;	/* bio链表*/};
    通常一个request请求可以包含多个bio,一个bio对应一个I/O请求  
     struct bio {		
        struct bio *bi_next;	 /* 指向当前bio的下一个对象*/	
        unsigned long  bi_flags; 	 /* 状态、命令等 */	
        unsigned long bi_rw; 	 /* 表示READ/WRITE*/	
        struct block_device	*bi_bdev;    /* 与请求相关联的块设备对象指针*/	
        unsigned short bi_vcnt;	 /* bi_io_vec数组中元素个数 */	
        unsigned short bi_idx;	 /* 当前处理的bi_io_vec数组元素索引 */
        unsigned int bi_size;	 /* 本次传输需要传输的数据总量,byte(扇区大小整数倍) */	
        struct bio_vec *bi_io_vec;/* 指向一个IO向量的数组,数组中的内各元素对应一个物理页的page对象 */
      };
    struct bio_vec {		
        struct page  *bv_page; //指向用于数据传输的页面所对应的struct page对象
        unsigned int bv_len;   //表示当前要传输的数据大小		
        unsigned int bv_offset;//表示数据在页面内的偏移量	
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    reque_queue和request及bio的关系

    image-20220520154603655

    块设备驱动

    队列处理相关的函数(参考块设备驱动代码文件夹)

    blk_mq_start_request(rq); //开始处理队列
    blk_mq_end_request(rq, BLK_STS_OK); //结束队列处理
    rq_for_each_segment(bvec, rq, iter) //从request->bio_vec
    void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset; //将页地址转换为线性地址(内地址)
    rq_data_dir(rq))   //从request获取本次读写的方向  WRITE 1   READ 0
    dev_addr+(rq->__sector *512) //磁盘设备的地址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.8虚拟块设备驱动的实例2

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define BLKSIZE (1 * 1024 * 1024) // 1M
    #define BLKNAME "mydisk"
    struct gendisk* disk;
    int major;
    struct request_queue* queue;
    struct blk_mq_tag_set tag;
    char* devaddr = NULL;
    int mydisk_open(struct block_device* blkdev, fmode_t mode)
    {
        printk("%s:%d\n", __func__, __LINE__);
        return 0;
    }
    void mydisk_close(struct gendisk* dk, fmode_t mode)
    {
        printk("%s:%d\n", __func__, __LINE__);
    }
    int mydisk_getgeo(struct block_device* blkdev, struct hd_geometry* hd)
    {
        printk("%s:%d\n", __func__, __LINE__);
        // struct hd_geometry {
        //   unsigned char heads; //磁头个数
        //   unsigned char sectors; //扇区个数
        //   unsigned short cylinders; //磁道个数
        // };
        hd->heads = 4;
        hd->cylinders = 16;
        hd->sectors = BLKSIZE / hd->heads / hd->cylinders / 512;
        return 0;
    }
    const struct block_device_operations fops = {
        .open = mydisk_open,
        .release = mydisk_close,
        .getgeo = mydisk_getgeo,
    };
    blk_status_t queue_handle(struct blk_mq_hw_ctx* hctx,
        const struct blk_mq_queue_data* bd)
    {
        blk_status_t status = BLK_STS_OK;
        struct request* rq = bd->rq;
        struct bio_vec bvec;
        struct req_iterator iter;
        loff_t pos = blk_rq_pos(rq) << SECTOR_SHIFT;//宏为9 相当于除以2^9(512)
        // 1.开始处理队列
        blk_mq_start_request(rq);
    
        // 2.队列处理过程
    
        rq_for_each_segment(bvec, rq, iter)
        {
            unsigned long b_len = bvec.bv_len;
    
            void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset;
    
            if ((pos + b_len) > BLKSIZE)
                b_len = (unsigned long)(BLKSIZE - pos);
    
            if (rq_data_dir(rq)) // WRITE
                //devaddr + pos:磁盘的地址
                //b_buf        :用户的地址
                //b_len        :本次写的长度
                memcpy(devaddr + pos, b_buf, b_len);
            else // READ
                //b_buf        :用户的地址
                 //devaddr + pos:磁盘的地址
                //b_len        :本次写的长度
                memcpy(b_buf, devaddr + pos, b_len);
    
            pos += b_len;
        }
    
        // 3.队列处理完成
        blk_mq_end_request(rq, status);
    
        return status; // always return ok
    }
    
    const struct blk_mq_ops rq_ops = {
        .queue_rq = queue_handle,
    };
    static int __init mydisk_init(void)
    {
        // 1.分配对象
        disk = alloc_disk(4);
        if (disk == NULL) {
            printk("alloc gendisk memory error\n");
            return -ENOMEM;
        }
        // 2.初始化对象
        major = register_blkdev(0, BLKNAME);
        if (major < 0) {
            printk("get blkdev number error\n");
            return major;
        }
        queue = blk_mq_init_sq_queue(&tag, &rq_ops, 2, BLK_MQ_F_SHOULD_MERGE);
        if (IS_ERR(queue)) {
            printk("init queue error\n");
            return PTR_ERR(queue);
        }
        disk->major = major;
        disk->first_minor = 0;
        strcpy(disk->disk_name, BLKNAME);
        set_capacity(disk, BLKSIZE >> 9);
        disk->fops = &fops;
        disk->queue = queue;
    
        // 3.申请1M内存当成硬盘使用
        devaddr = vmalloc(BLKSIZE);
        if (devaddr == NULL) {
            printk("vmalloc memory error\n");
            return -ENOMEM;
        }
        // 4.注册
        add_disk(disk);
    
        return 0;
    }
    static void __exit mydisk_exit(void)
    {
        del_gendisk(disk);
        vfree(devaddr);
        blk_cleanup_queue(queue);
        unregister_blkdev(major, BLKNAME);
        put_disk(disk);
    }
    module_init(mydisk_init);
    module_exit(mydisk_exit);
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134

    1.9块设备驱动测试方法

    1.安装块设备驱动
        sudo insmod mydisk.ko
    2.查看设备节点
       	ls -l /dev/mydisk
        brw-rw---- 1 root disk 252, 0 622 14:14 /dev/mydisk
    3.查看磁盘相关的命令
        sudo  fdisk -l
        Disk /dev/mydisk:1 MiB,1048576 字节,2048 个扇区
        单元:扇区 / 1 * 512 = 512 字节
        扇区大小(逻辑/物理)512 字节 / 512 字节
        I/O 大小(最小/最佳)512 字节 / 512 字节  
    4.分区
        sudo fdisk /dev/mydisk
        m 获取帮助
        d   删除分区
        p   打印分区表
        n   添加新分区
        w   将分区表写入磁盘并退出
        q   退出而不保存更改
    
        在dev下产生/dev/mydisk1节点
    5.分区格式化
        sudo mkfs.ext2 /dev/mydisk1
            
    6.挂载硬盘到某个目录下
         sudo mount -t ext2 /dev/mydisk1  ~/udisk
            
    7.向磁盘中存放文件
         sudo cp ~/work/day10/02mydisk/mydisk.c .
    8.取消挂载
         cd
         sudo umount udisk
    9.将块设备驱动的数据读取到ram.bin文件中
      	sudo cat /dev/mydisk1 > ram.bin
            
    10.重新挂载查看
        sudo mount -o loop ram.bin ~/udisk
        去udisk目录下查看是否有刚才拷贝的文件,如果有就说明成功了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    【Jmeter】安装配置:Jmeter 下载 MySQL JDBC 驱动
    Spring基础:注解进行自动注入
    [iOS界面切换- Present And Push]
    逐鹿澳洲市场 宁德时代储能全场景解决方案亮相澳大利亚全能源展
    微信开发者工具使用SVN
    老站长带你全面认识基站和天线
    英文字母表
    外卖项目09---Redis了解
    【前端小tip】深拷贝不能处理函数的解决方法,文末包含所有深拷贝常见问题的解决方法
    SQL当前查询条件数据需要调用其他数据时创建临时表实现
  • 原文地址:https://blog.csdn.net/l2020429/article/details/127707323