写点什么

Linux 下驱动开发 _ 块设备驱动开发 (内存模拟存储)

作者:DS小龙哥
  • 2022-10-19
    重庆
  • 本文字数:4133 字

    阅读完需:约 1 分钟

一、前言

块设备驱动块是 Linux 下 3 大设备驱动框架之一,块设备主要是针对存储类型的设备设计的驱动,配合文件系统完成数据存储。在应用层的 cp、cd、touch、vim、mount 等等可以操作文件,可以操作目录的命令都会通过文件系统,通过块设备驱动完成对底层存储设备的访问,实现数据读取或者写入。


所以大致总结下:块设备驱动的目的是给 Linux 文件系统提供底层接口。

二、编写块设备驱动的思路

既然学到了驱动开发,了解到块设备开发。 那么看这篇文章的小伙伴应该在单片机里裸机方式写过一些 flash 驱动、SD 卡驱动。 对于 flash 存储设备而言,要存取数据,根据芯片的手册我们主要是封装一个写数据函数和读取函数,封装好了这两个函数才方便上层应用的调用。 对于 flash 而言常见的读写单位一般是页、扇区。容量大的 flash 比如 SD 卡,读写最小单位规定为扇区。  扇区一般大小规定为 512 字节,那么底层要封装好的函数就是读扇区,写扇区函数。 这两个函数完成与 flash 空间交互,实现数据存储。


在 Linux 下完成块设备驱动编写,主要是要完成来至文件系统的存储请求,文件系统让你把数据存到那个扇区,你驱动就去存,文件系统让你从那个扇区读取输出来,驱动就去读取。 只要是完美的处理好了文件系统的请求,那么应用层工作就是一切顺利的。 文件系统不需要管你把数据存在什么设备上。是 SD 卡?是 FlashW25Q64 是 eeprom?还是 RAM 内存里?对文件系统而言不关系,它只关心存进去的数据下次可以完美的读取出来便是。


那么为了方便介绍块设备的驱动开发,我这里会先用 malloc 在驱动申请一块内存来当做 FLASH 设备,这样就不需要接任何硬件,降低了难度,纯软件的方式理解驱动框架运作流程。


下面这张图是解释应用层 使用文件目录操作命令操作块设备时,与底层驱动之间的调用大致过程。



块设备与字符设备比较:


(1) 块设备设备节点名称自己定义的,没有标准。


(2)块设备的主设备号可以动态分配,次设备号时通过文件系统对块设备分区时,自动填充。


/dev/sdb fdisk 命令进行分区。 /dev/sdb1 /dev/sdb2


(2)在块设备驱动里可以设置最大支持的分区数量


块设备处理数据的方式



下面是块设备驱动的数据结构:





块设备注册与注销函数
1. 注册函数int register_blkdev(unsigned int major, const char *name)函数功能介绍: 注册一个新的块设备函数参数介绍:@major:块设备的主设备号[1..255]。 如果 major = 0,表示尝试分配未使用的主设备号,返回值就表示分配成功的主设备号。@name:新块设备的名称。 注意: 该名称必须保证在系统中是唯一的。注册示例:int Tiny4412_block_major = register_blkdev(0, "Tiny4412_block");

2. 注销函数void unregister_blkdev(unsigned int major, const char *name)函数功能介绍: 注销已注册的块设备。函数参数介绍:@major: 主设备号@name: 设备名称注销示例:unregister_blkdev(Tiny4412_block_major, "Tiny4412_block");
复制代码

三、块设备的示例代码

3.1 驱动代码

这份代码里存储数据的空间是申请了一段内存来模拟的。没有依赖于硬件,所以:可以在任何 Linux 下编译安装测试,完成块设备驱动的了解学习。


#include <linux/module.h> #include <linux/blkdev.h> #include <linux/hdreg.h> #include <linux/version.h>#include <linux/vmalloc.h>
/** insmod tiny4412_blkdev.ko * # or insmod tiny4412_blkdev.ko size=numK/M/G/T * fdisk /dev/tiny4412_blkdev # create 2 patitions * mkfs.ext2 /dev/tiny4412_blkdev1 * mkfs.ext2 /dev/tiny4412_blkdev2 * mount /dev/tiny4412_blkdev1 /mnt/temp1/ * mount /dev/tiny4412_blkdev2 /mnt/temp2/ * # play in /mnt/temp1/ and /mnt/temp2/ * umount /mnt/temp1/ * umount /mnt/temp2/ * rmmod tiny4412_blkdev.ko * */static int Tiny4412_block_major=0;static struct request_queue *tiny4412_blkdev_queue; static struct gendisk *tiny4412_blkdev_disk;
#define TINY4412_BLK_DEV_BYTES (1024*1024*50) /*设置块设备的大小*/static unsigned char *sizeof_p;
/** Handle an I/O request.* 实现扇区的读写
unsigned long sector: 当前扇区位置unsigned long nsect : 扇区读写数量char *buffer : 读写的缓冲区指针int write : 是读还是写*/static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write){ /*块设备最小单位是一个扇区,一个扇区的字节数是512字节*/ unsigned long offset = sector; /*写入数据的位置*/ unsigned long nbytes = nsect; /*写入的长度*/ if((offset + nbytes)>TINY4412_BLK_DEV_BYTES) { printk("写超出范围,强制结束(%ld %ld)\n", offset, nbytes); return; } if(write) /*为真,表示是写*/ memcpy(sizeof_p + offset, buffer, nbytes); else /*读操作*/ memcpy(buffer,sizeof_p + offset, nbytes);}
/*处理请求*/static int tiny4412_blkdev_make_request(struct request_queue *q, struct bio *bio) { int dir; unsigned long long dsk_offset; struct bio_vec *bvec; int i; void *iovec_mem; /*判断读写方向*/ if(bio_data_dir(bio) == WRITE) dir = 1; else dir = 0; dsk_offset = bio->bi_sector << 9; bio_for_each_segment(bvec, bio, i) { iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; //起始位置,长度,源数据,方向 Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir); kunmap(bvec->bv_page); dsk_offset += bvec->bv_len; } bio_endio(bio, 0); return 0;}

struct block_device_operations tiny4412_blkdev_fops = { .owner= THIS_MODULE, };

static int __init tiny4412_blkdev_init(void) { sizeof_p=vmalloc(TINY4412_BLK_DEV_BYTES); if(sizeof_p==NULL) { printk("空间申请失败!\n"); return 0; } /*动态分配请求队列*/ tiny4412_blkdev_queue = blk_alloc_queue(GFP_KERNEL); /*绑定请求队列*/ blk_queue_make_request(tiny4412_blkdev_queue,tiny4412_blkdev_make_request); /*动态分配次设备号结构*/ /*每一个磁盘(分区)都是使用一个gendisk结构保存*/ tiny4412_blkdev_disk = alloc_disk(64); /*磁盘名称赋值*/ strcpy(tiny4412_blkdev_disk->disk_name, "tiny4412_blkdev");
/*注册一个块设备,自动分配主设备号*/ Tiny4412_block_major = register_blkdev(0,"Tiny4412_block"); printk("Tiny4412_block_major=%d\n",Tiny4412_block_major); tiny4412_blkdev_disk->major=Tiny4412_block_major; /*主设备号*/ tiny4412_blkdev_disk->first_minor = 0; /*次设备号*/ tiny4412_blkdev_disk->fops = &tiny4412_blkdev_fops; /*文件操作结合*/ tiny4412_blkdev_disk->queue = tiny4412_blkdev_queue; /*处理数据请求的队列*/ /*设置磁盘结构 capacity 的容量*/ /*注意: 块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。 cat /sys/block/xxxx/size 可以查看到设置的大小 把字节为单位的大小转换为以扇区为单位时,我们需要除以512,或者右移9位 */ set_capacity(tiny4412_blkdev_disk,TINY4412_BLK_DEV_BYTES>>9); //添加磁盘信息到内核 add_disk(tiny4412_blkdev_disk); return 0;}
static void __exit tiny4412_blkdev_exit(void) { //删除磁盘 del_gendisk(tiny4412_blkdev_disk); put_disk(tiny4412_blkdev_disk); //清除队列 blk_cleanup_queue(tiny4412_blkdev_queue); /*注销块设备*/ unregister_blkdev(Tiny4412_block_major, "Tiny4412_block"); vfree(sizeof_p);}
module_init(tiny4412_blkdev_init); module_exit(tiny4412_blkdev_exit);MODULE_LICENSE("GPL");
复制代码

3.2 安装测试

[root@wbyq code]#insmod tiny4412_block_device.ko
[ 6920.590000] tiny4412_sda: unknown partition table
[ 6920.590000] 块设备注册成功!
[root@wbyq code]#ls /dev/tiny4412_sda -l
brw-rw---- 1 root root 253, 0 Nov 14 2018 /dev/tiny4412_sda
[root@wbyq code]#mkfs.ext2 /dev/tiny4412_sda //格式化块设备
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
2560 inodes, 10240 blocks
512 blocks (5%) reserved for the super user
First data block=1
Maximum filesystem blocks=262144
2 block groups
8192 blocks per group, 8192 fragments per group
1280 inodes per group
Superblock backups stored on blocks:
8193
[root@wbyq code]#mount /dev/tiny4412_sda /mnt/ //挂载块设备
[root@wbyq code]#cd /mnt/
[root@wbyq mnt]#ls
lost+found
[root@wbyq mnt]#mkdir 123
[root@wbyq mnt]#ls
123 lost+found
[root@wbyq mnt]#touch 123.c
[root@wbyq mnt]#ls
123 123.c lost+found
[root@wbyq mnt]#cd /
[root@wbyq ]#umount /mnt/






[root@wbyq ]#cat /sys/class/block/tiny4412_sda/size
20480



[root@wbyq ]#mount /dev/tiny4412_sda /mnt/
[root@wbyq ]#df -h
Filesystem Size Used Available Use% Mounted on
192.168.10.11:/work/rootfs/
46.8G 15.5G 28.9G 35% /
/dev/tiny4412_sda 9.7M 14.0K 9.2M 0% /mnt
​
​ 作业:
1.​ 看懂块设备框架,使用的模拟的内存。
2.​ 加入SD卡的驱动,配合块设备框架,完成完整的块设备驱动编写。
复制代码

3.3 分区之后的情况

[root@wbyq code]#ls /dev/tiny4412_sda
tiny4412_sda tiny4412_sda1
[root@wbyq code]#ls /dev/tiny4412_sda1
[root@wbyq code]#ls /dev/tiny4412_sda* -l
brw-rw---- 1 root root 253, 0 Nov 14 2018 /dev/tiny4412_sda
brw-rw---- 1 root root 253, 1 Nov 14 2018 /dev/tiny4412_sda1
复制代码


发布于: 刚刚阅读数: 3
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022-01-06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux下驱动开发_块设备驱动开发(内存模拟存储)_10月月更_DS小龙哥_InfoQ写作社区