L19. 剖析驱动开发,从零实现块设备驱动

1. 概述

块设备主要指的是用来存储数据的设备,类似于SD卡、U盘、Nor Flash、Nand Flash、机械硬盘和固态硬盘等。块设备驱动就是用来访问这些存储设备的,其与字符设备驱动不同的是:

  • 块设备只能以块为基本单位实现读写,块是 linux 虚拟文件系统(VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。

  • 块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中;字符设备是按照字节进行读写访问的。不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。

2. 代码框架

在记录块设备驱动的基本框架之前,先大致了解一下块设备驱动要实现的工作:在Linux驱动编程中,每一类驱动都会有一个对应的结构体。具体场景应用时,上层应用代码经过一系列虚拟文件系统API后最终会调用到驱动的这个结构体。应用所有对硬件的操作,都是通过调用此结构体的成员功能函数实现的。

对应设备驱动结构体定义于:include/linux/genhd.h

struct gendisk {
    /* major, first_minor and minors are input parameters only,
     * don't use directly.  Use disk_devt() and disk_max_parts().
     */
    int major;          /* major number of driver */
    int first_minor;
    int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

    char disk_name[DISK_NAME_LEN];  /* name of major driver */
    char *(*devnode)(struct gendisk *gd, umode_t *mode);

    unsigned int events;        /* supported events */
    unsigned int async_events;  /* async events, subset of all */

    struct disk_part_tbl __rcu *part_tbl;
    struct hd_struct part0;

    const struct block_device_operations *fops;
    struct request_queue *queue;
    void *private_data;

    int flags;
    struct kobject *slave_dir;

    struct timer_rand_state *random;
    atomic_t sync_io;       /* RAID */
    struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
    struct kobject integrity_kobj;
#endif  /* CONFIG_BLK_DEV_INTEGRITY */
    int node_id;
    struct badblocks *bb;
};

在设备驱动中,主要的工作就是在入口中实现对gendisk结构体成员的填充,并注册到系统中去,供上层调用。


在了解到块设备驱动需要做的大致工作后,就要在块设备驱动基础框架上实现这些工作。块设备驱动代码主要分为一下几个部分:

声明入口、出口函数

module_init();
module_exit();

入口函数

在入口函数中,实现的功能比较多:
① 申请数据缓存区

ramdisk.block_buf = kzalloc(RAMDISK_SIZE, GFP_KERNEL)

② 向文件系统注册块设备

ramdisk.major = register_blkdev(0, DEVICE_NAME);

③ 初始化请求队列

ramdisk.queue = blk_init_queue(ramdisk_request, &ramdisk.lock);

④ 申请gendisk结构体,实例成员,并注册到系统中

ramdisk.gendisk = alloc_disk(3);
    ramdisk.gendisk->major = ramdisk.major;
    ramdisk.gendisk->first_minor = 0;
    ramdisk.gendisk->fops = &ramdisk_fops;
    ramdisk.gendisk->queue = ramdisk.queue;
    sprintf(ramdisk.gendisk->disk_name, "dx_ramdisk");
    set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);
    add_disk(ramdisk.gendisk);

出口函数

注销在入口函数中申请的结构体空间以及释放获取的动态内存。

3. 主要功能实现

内存操作

既然涉及到数据的读取与存储,必然需要实现对存储设备内存的操作。由于内存数据的读写都是以块为单位,故读写操作放在队列中实现。内存操作的接口ramdisk_request放在blk_init_queue初始化队列中,开发人员只需要实现ramdisk_request函数的功能即可。

这里简单地用内存来模拟磁盘,故用memcpy来实现数据读写功能。

static void ramdisk_transfer(struct request *req)
{   
    unsigned long start = blk_rq_pos(req) << 9;     
    unsigned long len  = blk_rq_cur_bytes(req);     

    void *buffer = bio_data(req->bio);      
    
    if(rq_data_dir(req) == READ)        
        memcpy(buffer, ramdisk.block_buf + start, len);
    else if(rq_data_dir(req) == WRITE)  
        memcpy(ramdisk.block_buf + start, buffer, len);

}

void ramdisk_request(struct request_queue *q)
{
    int err = 0;
    struct request *req;

    req = blk_fetch_request(q);
    while(req != NULL) {
        ramdisk_transfer(req);
        if (!__blk_end_request_cur(req, err))
            req = blk_fetch_request(q);
    }
}

至于其他存储设备,就需要在ramdisk_request中实现对该存储设备的块读写操作。

4. 测试

① 注册驱动: insmod ramdisk.ko

注册.png

② 查询磁盘状态: fdisk -l

磁盘状态.png

③ 格式化磁盘: mkfs.vfat /dev/dx_ramdisk

格式化磁盘.png

④挂载磁盘: mount /dev/dx_ramdisk /dx_tmp1

磁盘挂载.png

由第④步即可看到,磁盘已经挂载到创建的dx_tmp1空文件夹上了。表明本次测试成功,系统就可以直接使用此磁盘来存储文件数据,

5. 总结

到这里,一个简单的块设备驱动就完成了。总结一下:在块设备驱动编程时,与字符设备驱动类似,需要实例操作系统提供的设备结构体成员,然后再将实例后的结构体注册到系统中,以供上层应用定向调用。
需要注意的是,本篇实例是通过内存来模拟的块设备驱动,所以在实现存储区读写操作就比较简单。如果是针对具体的SPI FLASH、Nor FLASH、EEPROM等存储设备,还需要打通硬件读写功能。

参考: 《【正点原子】I.MX6U嵌入式Linux驱动开发指南.pdf》

后记:

源码:https://github.com/LinuxTaoist/Linux_drivers/blob/master/block_driver/ramdisk.c

如需技术交流,可关注公众号“开源519”

开源519.jpg

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拂去尘世尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值