嵌入式Linux RAMDisk驱动开发
1. 概述
本章将详细介绍嵌入式Linux系统中RAMDisk块设备驱动的开发。RAMDisk是一种基于内存的块设备,它将系统内存模拟成块设备使用。本章以i.MX6ULL开发板为例,详细分析RAMDisk驱动的实现原理和代码结构。
2. RAMDisk驱动原理
2.1 块设备基础
在Linux系统中,设备分为字符设备、块设备和网络设备三大类。块设备的特点是:
- 以固定大小的数据块为单位进行读写
- 支持随机访问
- 数据传输通过请求队列(request queue)进行管理
- 使用缓冲区缓存机制提高性能
块设备驱动的核心数据结构包括:
struct gendisk
: 通用磁盘结构,描述磁盘的基本信息struct request_queue
: 请求队列,管理I/O请求struct block_device_operations
: 块设备操作函数集
2.2 RAMDisk工作原理
RAMDisk驱动的工作原理是将一段内存区域模拟成块设备。当上层应用对RAMDisk进行读写操作时,驱动程序将数据直接从内存中读取或写入内存,从而实现类似磁盘的存储功能。
主要特点:
- 访问速度快:直接操作内存,无需物理磁盘I/O
- 断电数据丢失:内存中的数据在系统断电后会丢失
- 容量有限:受限于系统可用内存大小
- 可重复使用:可以像普通磁盘一样格式化、分区和挂载
3. 驱动代码分析
3.1 头文件包含
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/input/mt.h>
#include <linux/delay.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
这些头文件提供了驱动开发所需的基本功能:
linux/module.h
: 模块相关定义linux/kernel.h
: 内核常用宏和函数linux/init.h
: 初始化相关宏linux/fs.h
: 文件系统相关定义linux/slab.h
: 内存分配函数linux/blkdev.h
: 块设备相关定义linux/hdreg.h
: 硬盘寄存器相关定义
3.2 宏定义
c
#define RAMDISK_SIZE (2 * 1024 * 1024)
#define RMADISK_NAME "ramdisk"
#define RAMDISK_MINOR 3
RAMDISK_SIZE
: 定义RAMDisk的大小为2MBRMADISK_NAME
: 设备名称RAMDISK_MINOR
: 次设备号数量
3.3 设备结构体
c
struct ramdisk_dev
{
int major;
u8 *ramdiskbuf;
struct gendisk *gendisk;
struct request_queue *queue;
spinlock_t lock;
};
struct ramdisk_dev ramdisk;
ramdisk_dev
结构体用于管理RAMDisk设备的各个组件:
major
: 主设备号ramdiskbuf
: 指向分配的内存缓冲区gendisk
: 通用磁盘结构指针queue
: 请求队列指针lock
: 自旋锁,用于同步访问
3.4 数据传输函数
c
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.ramdiskbuf + start, len);
else
memcpy(ramdisk.ramdiskbuf + start, buffer, len);
}
ramdisk_transfer
函数负责实际的数据传输:
blk_rq_pos(req)
: 获取请求的起始扇区号<< 9
: 将扇区号转换为字节偏移(512字节/扇区)blk_rq_cur_bytes(req)
: 获取当前请求的数据长度bio_data(req->bio)
: 获取数据缓冲区地址- 根据请求方向(读/写)执行相应的内存拷贝操作
3.5 请求处理函数
c
static void ramdisk_request_fn(struct request_queue *q)
{
int err = 0;
struct request *req;
req = blk_fetch_request(q);
while (req)
{
ramdisk_transfer(req);
if (!__blk_end_request_cur(req, err))
{
req = blk_fetch_request(q);
}
}
}
ramdisk_request_fn
是请求队列的处理函数:
- 从请求队列中获取一个请求
- 调用
ramdisk_transfer
进行数据传输 - 使用
__blk_end_request_cur
结束当前请求 - 如果还有剩余请求,继续处理下一个
3.6 块设备操作函数
c
static int ramdisk_open(struct block_device *bdev, fmode_t mode)
{
return 0;
}
static void ramdisk_release(struct gendisk *disk, fmode_t mode) {}
static int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
geo->heads = 2;
geo->cylinders = 32;
geo->sectors = RAMDISK_SIZE / (2 * 32 * 512);
return 0;
}
static const struct block_device_operations ramdisk_fops = {
.owner = THIS_MODULE,
.open = ramdisk_open,
.release = ramdisk_release,
.getgeo = ramdisk_getgeo,
};
块设备操作函数集定义了设备的基本操作:
open
: 设备打开函数,返回0表示成功release
: 设备释放函数,空实现getgeo
: 获取设备几何信息,用于兼容传统磁盘操作
3.7 模块初始化函数
c
static int __init ramdisk_init(void)
{
int ret = 0;
ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
if (ramdisk.ramdiskbuf == NULL)
{
ret = -EINVAL;
goto fail_kzalloc;
}
ramdisk.major = register_blkdev(0, RMADISK_NAME);
if (ramdisk.major < 0)
{
ret = -EINVAL;
goto fail_register_blkdev;
}
printk("Driver: ramdisk major is %#x\r\n", ramdisk.major);
spin_lock_init(&ramdisk.lock);
ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock);
if (ramdisk.queue == NULL)
{
ret = -EINVAL;
goto fail_blk_init_queue;
}
ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
if (ramdisk.gendisk == NULL)
{
ret = -EINVAL;
goto fail_alloc_disk;
}
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);
add_disk(ramdisk.gendisk);
return 0;
fail_alloc_disk:
blk_cleanup_queue(ramdisk.queue);
printk("Driver: fail_alloc_disk\r\n");
fail_blk_init_queue:
unregister_blkdev(ramdisk.major, RMADISK_NAME);
printk("Driver: fail_blk_init_queue\r\n");
fail_register_blkdev:
kfree(ramdisk.ramdiskbuf);
printk("Driver: fail_register_blkdev\r\n");
fail_kzalloc:
printk("Driver: fail_kzalloc\r\n");
return ret;
}
ramdisk_init
函数完成了驱动的初始化工作:
- 分配内存缓冲区
- 注册块设备,获取主设备号
- 初始化自旋锁
- 创建请求队列
- 分配通用磁盘结构
- 配置磁盘参数
- 添加磁盘到系统
错误处理采用goto模式,确保资源正确释放。
3.8 模块退出函数
c
static void __exit ramdisk_exit(void)
{
del_gendisk(ramdisk.gendisk);
blk_cleanup_queue(ramdisk.queue);
unregister_blkdev(ramdisk.major, RMADISK_NAME);
kfree(ramdisk.ramdiskbuf);
}
ramdisk_exit
函数负责清理资源:
- 从系统中删除磁盘
- 清理请求队列
- 注销块设备
- 释放内存缓冲区
4. 编译配置
4.1 Makefile分析
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_modules
kernel_modules:
$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modules
clean:
$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
Makefile关键点:
KERNERDIR
: 内核源码路径CURRENTDIR
: 当前目录路径obj-m
: 指定生成ramdisk.ko模块- 编译命令使用内核构建系统进行模块编译
5. 驱动加载与测试
5.1 编译驱动
bash
make
5.2 加载驱动
bash
insmod ramdisk.ko
5.3 查看设备信息
bash
dmesg | tail
应该能看到类似输出:
Driver: ramdisk major is 0xXX
5.4 格式化和挂载
bash
# 格式化为ext4文件系统
mkfs.ext4 /dev/ramdisk
# 创建挂载点
mkdir /mnt/ramdisk
# 挂载设备
mount /dev/ramdisk /mnt/ramdisk
# 测试读写
echo "Hello RAMDisk" > /mnt/ramdisk/test.txt
cat /mnt/ramdisk/test.txt
5.5 卸载和卸载驱动
bash
# 卸载文件系统
umount /mnt/ramdisk
# 卸载驱动模块
rmmod ramdisk
6. 性能特点与应用场景
6.1 性能特点
-
优点:
- 极高的读写速度
- 低延迟
- 无机械磨损
- 支持频繁的读写操作
-
缺点:
- 断电数据丢失
- 容量受限于内存
- 占用系统内存资源
6.2 应用场景
- 临时文件存储
- 高频读写缓存
- 系统启动临时空间
- 嵌入式系统中的快速存储需求
- 测试和调试环境
7. 设备树配置
虽然RAMDisk是纯软件实现的虚拟设备,不需要硬件资源,但可以从设备树中获取一些系统信息。在imx6ull-alientek-emmc.dts
中,我们可以看到系统内存配置:
dts
memory {
reg = <0x80000000 0x20000000>;
};
这表示系统内存从0x80000000开始,大小为512MB。RAMDisk分配的内存来自这片区域。
源码仓库位置: https://gitee.com/dream-cometrue/linux_driver_imx6ull