基于i.MX6ULL的RAM Disk驱动开发
基于i.MX6ULL的RAM Disk驱动开发
本文详细分析基于i.MX6ULL平台的RAM Disk块设备驱动实现,结合代码和理论知识,深入讲解Linux块设备驱动的核心概念和实现方法。
1. 源码仓库
本文档所涉及的源代码位于:https://gitee.com/dream-cometrue/linux_driver_imx6ull
2. 驱动概述
本驱动实现了一个基于内存的虚拟块设备(RAM Disk),它将系统内存的一部分模拟成块设备,使得上层应用程序可以像操作物理磁盘一样对其进行读写操作。该驱动位于26_ramdisk_makerequest
目录下,主要包含两个文件:
ramdisk.c
:核心驱动代码Makefile
:编译脚本
3. 理论基础
3.1 Linux块设备驱动架构
Linux块设备驱动的核心组件包括:
struct gendisk
:通用磁盘结构体,代表一个块设备struct request_queue
:请求队列,管理I/O请求struct block_device_operations
:块设备操作函数集- BIO(Block I/O)层:处理块I/O请求的基本单位
3.2 请求处理机制
块设备驱动有两种主要的请求处理方式:
- Request-based:传统的请求队列处理方式,使用
struct request
结构 - Make-request-based:直接处理BIO请求的方式,更高效
本驱动采用Make-request-based方式,通过blk_queue_make_request()
函数设置请求处理函数。
4. 核心数据结构
struct ramdisk_dev {int major; // 主设备号u8 *ramdiskbuf; // RAM磁盘缓冲区struct gendisk *gendisk; // 通用磁盘结构struct request_queue *queue; // 请求队列spinlock_t lock; // 自旋锁
};
该结构体定义了RAM Disk设备的核心数据成员,使用全局变量ramdisk
实例化。
5. 驱动初始化
5.1 模块入口函数
static int __init ramdisk_init(void)
驱动的初始化函数,执行以下关键步骤:
5.1.1 内存分配
ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
使用kzalloc()
分配2MB的连续内存空间作为RAM磁盘的存储区域,GFP_KERNEL
标志表示在常规内核内存区分配。
5.1.2 注册块设备
ramdisk.major = register_blkdev(0, RMADISK_NAME);
动态注册块设备,主设备号由系统分配(传入0)。
5.1.3 初始化自旋锁
spin_lock_init(&ramdisk.lock);
初始化自旋锁,用于多处理器环境下的同步。
5.1.4 分配请求队列
ramdisk.queue = blk_alloc_queue(GFP_KERNEL);
使用现代APIblk_alloc_queue()
分配请求队列,替代了已废弃的blk_init_queue()
。
5.1.5 分配通用磁盘
ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
分配gendisk
结构体,RAMDISK_MINOR
定义了次设备号的数量。
5.1.6 设置请求处理函数
blk_queue_make_request(ramdisk.queue, ramdisk_make_request);
将ramdisk_make_request
函数注册为请求处理函数,这是make-request模式的核心。
5.1.7 配置gendisk
ramdisk.gendisk->private_data = &ramdisk;
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, "ramdisk");
set_capacity(ramdisk.gendisk, RAMDISK_SIZE / 512);
配置gendisk
结构体的各项参数,包括私有数据指针、主次设备号、操作函数集、请求队列、设备名称和容量(以512字节扇区为单位)。
5.1.8 注册磁盘
add_disk(ramdisk.gendisk);
将配置好的gendisk
添加到系统中,此时设备对用户空间可见。
6. 请求处理函数
6.1 make_request函数
static void ramdisk_make_request(struct request_queue *queue, struct bio *bio)
这是驱动的核心函数,直接处理BIO请求,相比传统的request模式更高效。
6.1.1 计算偏移量
offset = bio->bi_iter.bi_sector << 9;
将起始扇区号转换为字节偏移量(左移9位相当于乘以512)。
6.1.2 遍历BIO向量
bio_for_each_segment(bvec, bio, iter)
使用bio_for_each_segment
宏遍历BIO的所有段(segment),因为一个BIO可能包含非连续的内存页。
6.1.3 数据传输
char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;
len = bvec.bv_len;if (bio_data_dir(bio) == READ)memcpy(ptr, ramdisk.ramdiskbuf + offset, len);
elsememcpy(ramdisk.ramdiskbuf + offset, ptr, len);
根据I/O方向(读或写),使用memcpy
在RAM磁盘缓冲区和用户缓冲区之间复制数据。
6.1.4 完成BIO请求
set_bit(BIO_UPTODATE, &bio->bi_flags);
bio_endio(bio, 0);
标记BIO为"up-to-date"状态,并调用bio_endio()
完成请求,通知上层。
7. 块设备操作函数
static const struct block_device_operations ramdisk_fops = {.owner = THIS_MODULE,.open = ramdisk_open,.release = ramdisk_release,.getgeo = ramdisk_getgeo,
};
7.1 打开函数
static int ramdisk_open(struct block_device *bdev, fmode_t mode)
简单的存根函数,始终返回成功,因为RAM磁盘无需复杂的打开操作。
7.2 释放函数
static void ramdisk_release(struct gendisk *disk, fmode_t mode) {}
空的释放函数,与open
函数对应。
7.3 获取几何信息
static int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
提供磁盘的几何信息,用于兼容传统工具。虽然现代Linux主要使用LBA寻址,但此函数仍有必要实现。
8. 驱动卸载
8.1 模块退出函数
static void __exit ramdisk_exit(void)
执行以下清理操作:
del_gendisk()
:从系统中删除磁盘blk_cleanup_queue()
:清理请求队列unregister_blkdev()
:注销块设备kfree()
:释放RAM磁盘缓冲区内存
9. Makefile分析
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := ramdisk.o
build : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
9.1 变量定义
KERNERDIR
:指向内核源码树根目录CURRENTDIR
:当前工作目录obj-m
:指定要编译为模块的目标文件
9.2 编译规则
使用内核构建系统进行编译,M=$(CURRENTDIR)
参数告诉内核构建系统在指定目录下查找模块源码。
11. 编译与测试
11.1 编译驱动
make
在26_ramdisk_makerequest
目录下执行make命令,生成ramdisk.ko
模块文件。
11.2 加载驱动
insmod ramdisk.ko
加载模块后,系统会分配主设备号,并创建/dev/ramdisk
设备节点。
11.3 创建文件系统
mkfs.ext4 /dev/ramdisk
在RAM磁盘上创建ext4文件系统。
11.4 挂载使用
mkdir /mnt/ramdisk
mount /dev/ramdisk /mnt/ramdisk
创建挂载点并挂载RAM磁盘。
11.5 测试性能
# 写测试
dd if=/dev/zero of=/mnt/ramdisk/testfile bs=1M count=100
# 读测试
dd if=/mnt/ramdisk/testfile of=/dev/null bs=1M
由于RAM磁盘基于内存,其读写速度远超物理磁盘。