4. Linux内核块设备¶
块设备是Linux内核三大设备类型(字符设备、块设备、网络设备)中最重要的存储类设备,是操作系统数据持久化的核心载体。 不同于字符设备的字节流读写模式,块设备以数据块/扇区为最小读写单位,支持随机访问、缓存缓存、IO调度、请求合并,广泛应用于磁盘、U盘、SD卡、EMMC、RAMDISK等存储硬件。
4.1. 块设备核心概念¶
4.1.1. 块设备定义¶
块设备(Block Device):是指以固定大小的数据块为单位进行数据读写的硬件设备,支持随机寻址访问,内核会为块设备建立页缓存、块缓存,通过IO调度器优化读写请求顺序,减少硬件寻道时间,提升IO吞吐效率。
块设备最小读写单位为扇区(Sector),内核IO操作最小处理单位为逻辑块(Block),标准Linux系统默认扇区大小512Byte,逻辑块大小1024Byte/4KB。
4.1.2. 块设备相关专业名词¶
在深入驱动框架前,需理解块设备子系统中的几个核心抽象概念:
扇区(Sector):块设备硬件寻址的最小单位,传统上为512字节,现代高级格式化磁盘多为4096字节,内核中通常以512字节为逻辑扇区基准。
块(Block):文件系统与内核交互的最小单位,通常是内存页(Page,通常为4KB)的整数倍。
Bio(Block I/O):内核中表示一个I/O操作的基本数据结构。一个Bio包含了一个或多个内存页,以及目标扇区号和操作类型(读/写/丢弃等)。
Request(请求):I/O调度器将多个指向相邻扇区的Bio合并后形成的数据结构,代表一次提交给底层驱动的实际硬件I/O任务。
I/O调度器(I/O Scheduler):位于块层和驱动之间,负责重新排序和合并Request,以优化磁盘磁头移动或提升并发性能。
4.2. 块设备驱动框架¶
现代Linux内核(4.x后期及以后版本)全面采用了blk-mq(Multi-Queue Block Layer)架构,以消除传统单队列块层的锁竞争瓶颈,充分发挥多核CPU和多队列硬件(如NVMe)的性能。
4.2.1. blk-mq多队列机制原理¶
blk-mq多队列架构是内核为解决多核IO瓶颈设计的新型调度模型,采用软件队列+硬件队列双层队列架构,解决全局单队列锁竞争问题。
软件队列(mq_ctx):每一个CPU核独立分配一个私有软件队列,CPU仅操作自身队列,无跨核锁竞争,请求入队无阻塞,极大提升IO入队效率。
硬件队列(hw_ctx):对应硬件设备支持的并发通道,数量可配置(通常等于CPU核数或硬件通道数),负责汇总软件队列请求、下发硬件处理,支持并行IO处理。
4.2.2. 数据流向与框架层次¶
用户空间:发起read/write系统调用。
VFS & 文件系统层:将文件偏移量转换为逻辑块号,生成Bio结构。
块设备层(Block Layer):接收Bio,通过I/O调度器将其合并为Request,并放入硬件队列。
块设备驱动层 (Block Device Driver):通过注册blk_mq_ops中的queue_rq回调函数,从硬件队列中取出Request,直接操作硬件寄存器或内存缓冲区。
硬件层:执行实际的物理读写操作,完成后触发中断。
驱动层:在中断处理或轮询中调用blk_mq_end_request,通知块层该请求已完成,唤醒等待的进程。
4.3. 块设备核心结构体¶
4.3.1. 磁盘设备主体结构体¶
磁盘设备主体结构体(struct gendisk)用于描述一个完整的磁盘设备,包含设备号、设备名称、请求队列、操作集、容量信息等核心属性,是块设备注册的核心结构体。
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 | struct gendisk {
int major; // 主设备号
int first_minor; // 起始次设备号
int minors; // 次设备号数量,决定最大分区数
char disk_name[DISK_NAME_LEN]; // 块设备名称
const struct block_device_operations *fops; // 块设备文件操作回调集
struct request_queue *queue; // 设备IO请求队列
void *private_data; // 驱动自定义私有数据指针
sector_t capacity; // 设备总容量,单位扇区
int flags; // 设备基础状态标志位
unsigned long state; // 设备运行状态位图
/* 设备状态宏定义 */
#define GD_NEED_PART_SCAN 0 // 需要重新扫描分区
#define GD_READ_ONLY 1 // 设备只读模式
#define GD_DEAD 2 // 设备已失效/卸载
#define GD_ADDED 4 // 设备已注册至内核
struct mutex open_mutex; // 设备打开/关闭互斥锁
unsigned open_partitions; // 当前打开的分区数量
struct backing_dev_info *bdi; // 设备后端缓存信息
struct kobject *slave_dir; // 设备sysfs节点目录
int node_id; // 设备所属NUMA节点
struct lockdep_map lockdep_map; // 锁调试检测映射
u64 diskseq; // 设备全局唯一序列号
/* 其他成员省略 */
};
|
4.3.2. 块设备操作集结构体¶
块设备操作集结构体(struct block_device_operations)定义了用户空间或内核其他子系统对该块设备执行特定操作时的回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct block_device_operations {
void (*submit_bio)(struct bio *bio); // 提交bio IO请求
int (*open)(struct block_device *, fmode_t); // 设备打开回调
void (*release)(struct gendisk *, fmode_t); // 设备关闭释放回调
int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long); // ioctl命令
unsigned int (*check_events)(struct gendisk *disk, unsigned int clearing); // 检测设备热插拔/状态事件
int (*getgeo)(struct block_device *, struct hd_geometry *); // 获取磁盘几何参数
int (*set_read_only)(struct block_device *bdev, bool ro); // 设置设备只读/可写模式
char *(*devnode)(struct gendisk *disk, umode_t *mode); // 自定义设备节点属性
struct module *owner; // 模块所有者
/* 其他成员省略 */
};
|
4.3.3. 磁盘几何信息结构体¶
磁盘几何信息结构体(struct hd_geometry)定义模拟传统机械磁盘的物理参数,供内核上层工具识别磁盘规格,固态硬盘/虚拟磁盘可自定义模拟参数。
1 2 3 4 5 6 | struct hd_geometry {
unsigned char heads; // 磁头数量
unsigned char sectors; // 每磁道扇区数
unsigned short cylinders; // 柱面数量
unsigned long start; // 磁盘起始扇区偏移
};
|
4.3.4. Blk-MQ标签集结构体¶
Blk-MQ标签集结构体(struct blk_mq_tag_set)用于管理硬件队列数量、请求深度、IO操作集、私有数据,是Blk-MQ架构初始化的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct blk_mq_tag_set {
struct blk_mq_queue_map map[HCTX_MAX_TYPES];// 硬件队列映射关系
unsigned int nr_maps; // 有效队列映射数量
const struct blk_mq_ops *ops; // Blk-MQ请求操作回调集
unsigned int nr_hw_queues; // 硬件队列数量,通常等于CPU核数
unsigned int queue_depth; // 单队列最大并发请求深度
unsigned int reserved_tags; // 预留标签数量,特殊IO保留
unsigned int cmd_size; // 自定义命令私有数据大小
int numa_node; // 绑定NUMA节点编号
unsigned int timeout; // IO请求超时时间
unsigned int flags; // 队列特性标志位
void *driver_data; // 驱动私有数据指针
/* 其他成员省略 */
};
|
4.3.5. Blk-MQ操作回调集结构体¶
Blk-MQ操作回调集结构体(struct blk_mq_ops)用于多队列IO请求处理核心回调,唯一必需接口为队列请求处理函数,负责解析并处理所有读写IO请求。
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 | struct blk_mq_ops {
// 单请求IO派发核心入口,驱动必须实现
blk_status_t (*queue_rq)(struct blk_mq_hw_ctx *,
const struct blk_mq_queue_data *);
// 批量请求硬件提交收尾,适配批量下发场景
void (*commit_rqs)(struct blk_mq_hw_ctx *);
// 批量IO请求派发,优化高并发IO吞吐
void (*queue_rqs)(struct request **rqlist);
// 请求超时处理,IO异常容错回调
enum blk_eh_timer_return (*timeout)(struct request *);
// 硬件队列初始化,多核队列资源分配
int (*init_hctx)(struct blk_mq_hw_ctx *, void *, unsigned int);
// 硬件队列资源释放
void (*exit_hctx)(struct blk_mq_hw_ctx *, unsigned int);
// 单个请求私有资源初始化
int (*init_request)(struct blk_mq_tag_set *set, struct request *,
unsigned int, unsigned int);
// 单个请求私有资源释放
void (*exit_request)(struct blk_mq_tag_set *set, struct request *,
unsigned int);
/* 其他成员省略 */
};
|
4.3.6. 块设备IO请求结构体¶
块设备IO请求结构体(struct request)用于定义上层下发的IO请求封装体,包含读写方向、扇区位置、数据长度、队列信息,是驱动层处理IO的核心对象。
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 | struct request {
struct request_queue *q; // 所属IO请求队列
struct blk_mq_ctx *mq_ctx; // 软件队列上下文
struct blk_mq_hw_ctx *mq_hctx; // 硬件队列上下文
blk_opf_t cmd_flags; // 请求操作类型与标志
req_flags_t rq_flags; // 请求状态标志位
int tag; // 请求唯一标签ID
unsigned int timeout; // 请求超时时间
unsigned int __data_len; // 请求总数据长度
sector_t __sector; // 请求起始扇区
struct bio *bio; // 请求首个bio片段
struct bio *biotail; // 请求末尾bio片段
union {
struct list_head queuelist; // 队列链表节点
struct request *rq_next; // 下一个请求指针
};
struct block_device *part; // 关联的块设备分区
enum mq_rq_state state; // 请求当前状态
atomic_t ref; // 请求引用计数
unsigned short nr_phys_segments;// 物理聚合段数量
unsigned short ioprio; // IO优先级
rq_end_io_fn *end_io; // IO完成回调函数
void *end_io_data; // IO完成回调私有数据
/* 其他成员省略 */
};
|
4.3.7. 块设备最小IO单元结构体¶
块设备最小IO单元结构体(struct bio)用于描述一段连续/分散的读写数据。一个request可包含多个bio,是驱动层面遍历处理数据的核心载体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | struct bio {
struct bio *bi_next; // 指向下一个bio,构成请求bio链表
struct block_device *bi_bdev; // 关联的目标块设备
blk_opf_t bi_opf; // IO操作类型、读写标志
unsigned short bi_flags; // BIO状态标志位
unsigned short bi_ioprio; // IO优先级
blk_status_t bi_status; // 当前IO处理状态
struct bvec_iter bi_iter; // IO迭代器(扇区偏移、长度)
bio_end_io_t *bi_end_io; // bio单独完成回调
void *bi_private; // 驱动自定义私有数据
unsigned short bi_vcnt; // 当前有效数据段数量
unsigned short bi_max_vecs; // 最大支持数据段数量
atomic_t __bi_cnt; // bio引用计数
struct bio_vec *bi_io_vec; // 分散聚合数据段数组
struct bio_set *bi_pool; // bio内存池
// 内嵌数据段,避免小IO重复内存分配
struct bio_vec bi_inline_vecs[];
/* 其他成员省略 */
};
|
4.4. 块设备核心函数¶
4.4.1. 注册块设备号¶
4.4.1.1. register_blkdev函数¶
register_blkdev函数用于向内核注册块设备主设备号,支持静态指定或动态分配。动态分配传入major=0,内核自动分配空闲主设备号。
函数原型:
1 | int register_blkdev(unsigned int major, const char *name);
|
参数说明:
major:需要注册的主设备号,传0表示动态分配。
name:块设备名称。
返回值:成功返回主设备号;失败返回负数错误码。
4.4.2. 注销块设备号¶
4.4.2.1. unregister_blkdev函数¶
unregister_blkdev函数用于反向注销已注册的块设备主设备号,释放设备号资源,与register_blkdev成对使用。
函数原型:
1 | void unregister_blkdev(unsigned int major, const char *name);
|
参数说明:
major:已注册的主设备号。
name:块设备名称。
4.4.3. 分配MQ标签集资源¶
4.4.3.1. blk_mq_alloc_tag_set函数¶
blk_mq_alloc_tag_set函数用于初始化并分配Blk-MQ多队列标签集核心资源,创建硬件队列、请求标签管理池,是多队列驱动初始化的核心前置函数。
函数原型:
1 | int blk_mq_alloc_tag_set(struct blk_mq_tag_set *set);
|
参数说明:
set:已配置参数的blk_mq_tag_set结构体指针。
返回值:0 成功;负数 失败。
4.4.4. 释放MQ标签集¶
4.4.4.1. blk_mq_free_tag_set函数¶
blk_mq_free_tag_set函数用于释放Blk-MQ标签集占用的内核资源、队列内存、标签池,与blk_mq_alloc_tag_set成对使用。
函数原型:
1 | void blk_mq_free_tag_set(struct blk_mq_tag_set *set);
|
参数说明:
set:需要释放的标签集结构体指针。
4.4.5. 分配gendisk结构体和请求队列¶
4.4.5.1. blk_mq_alloc_disk函数¶
blk_mq_alloc_disk函数是Linux 5.14及以上内核版本新API,一次性分配gendisk磁盘结构体+初始化多队列请求队列,简化驱动初始化流程。
函数原型:
1 | struct gendisk *blk_mq_alloc_disk(struct blk_mq_tag_set *set, void *queuedata);
|
参数说明:
set:已初始化的Blk-MQ标签集。
queuedata:队列私有数据,指向驱动设备结构体。
返回值:成功返回gendisk指针;失败返回错误指针。
4.4.6. 分配gendisk结构体¶
4.4.6.1. alloc_disk函数¶
alloc_disk函数是Linux 5.13及以下旧内核版本API,单独分配gendisk磁盘结构体,需要手动初始化请求队列。
函数原型:
1 | struct gendisk *alloc_disk(int minors);
|
参数说明:
minors:支持的次设备号数量(分区数量)。
返回值:成功返回gendisk指针;失败返回NULL。
4.4.7. 初始化多队列请求队列¶
4.4.7.1. blk_mq_init_queue函数¶
blk_mq_init_queue函数是Linux 5.13及以下旧内核版本API,基于已初始化的Blk-MQ标签集手动创建、初始化多队列请求队列。搭配alloc_disk使用,为gendisk绑定IO队列,是旧版本块设备驱动队列初始化的核心接口。
函数原型:
1 | struct request_queue *blk_mq_init_queue(struct blk_mq_tag_set *set);
|
参数说明:
set:已完成配置与初始化的blk_mq_tag_set标签集结构体。
返回值:成功返回request_queue队列指针;失败返回错误指针(需通过IS_ERR判断)。
4.4.8. 清理销毁请求队列¶
4.4.8.1. blk_cleanup_queue函数¶
blk_cleanup_queue函数用于安全清理、销毁请求队列资源,停止队列调度、释放队列内存,仅用于5.13及以下旧内核。新内核由blk_mq_alloc_disk自动管理队列生命周期,无需手动调用。
函数原型:
1 | void blk_cleanup_queue(struct request_queue *q);
|
参数说明:
q:需要销毁的request_queue队列指针。
4.4.9. 注册磁盘设备到内核¶
4.4.9.1. add_disk函数¶
add_disk函数用于将初始化完成的gendisk设备注册到内核,创建设备节点、sysfs节点,设备正式可用。
函数原型:
1 2 3 4 5 | // 5.15及以上内核
int add_disk(struct gendisk *disk);
// 5.14及以下内核
void add_disk(struct gendisk *disk);
|
参数说明:
disk:已完成参数配置的gendisk结构体。
返回值:5.15+内核版本返回0成功、负数失败;旧版本无返回值。
4.4.10. 从内核移除磁盘设备¶
4.4.10.1. del_gendisk函数¶
del_gendisk函数用于反向注销磁盘设备,删除设备节点、释放内核设备资源,驱动卸载核心接口。
函数原型:
1 | void del_gendisk(struct gendisk *disk);
|
参数说明:
disk:需要注销的gendisk结构体。
4.4.11. 设置设备容量¶
4.4.11.1. set_capacity函数¶
set_capacity函数用于设置块设备总容量,内核自动同步至sysfs和用户态,单位为扇区。
函数原型:
1 | static inline void set_capacity(struct gendisk *disk, sector_t size);
|
参数说明:
disk:目标磁盘设备结构体。
size:设备总扇区数量。
4.4.12. 标记请求开始处理¶
4.4.12.1. blk_mq_start_request函数¶
blk_mq_start_request函数用于标记IO请求开始处理,更新请求状态、计时,防止内核超时判定。
函数原型:
1 | void blk_mq_start_request(struct request *rq);
|
参数说明:
rq:待处理的IO请求结构体。
4.4.13. 结束IO请求¶
4.4.13.1. blk_mq_end_request函数¶
blk_mq_end_request函数用于完成IO请求处理,上报处理状态,释放请求资源,是驱动IO处理的收尾核心接口。
函数原型:
1 | void blk_mq_end_request(struct request *rq, blk_status_t status);
|
参数说明:
rq:处理完成的IO请求。
status:处理状态,BLK_STS_OK成功,其余为错误。
4.4.14. 判断透传请求¶
4.4.14.1. blk_rq_is_passthrough函数¶
blk_rq_is_passthrough函数用于判断当前IO请求是否为硬件透传请求(直通请求)。此类请求绕过内核文件系统与IO调度,多用于SCSI/SD/EMMC硬件指令透传、自定义控制命令,常规磁盘读写业务不会产生该类请求,通用块设备驱动可直接屏蔽。
函数原型:
1 | static inline bool blk_rq_is_passthrough(const struct request *rq);
|
参数说明:
rq:待判断的IO请求结构体。
返回值:true-是硬件透传请求;false-普通读写IO请求。
4.4.15. 设置逻辑块大小¶
4.4.15.1. blk_queue_logical_block_size函数¶
blk_queue_logical_block_size函数用于设置块设备逻辑块大小,是内核文件系统识别的最小读写单位,常规块设备默认配置为512Byte。该参数会上报至用户态,影响文件系统格式化、IO对齐策略。
函数原型:
1 | static inline void blk_queue_logical_block_size(struct request_queue *q, unsigned int size);
|
参数说明:
q:设备对应的请求队列。
size:逻辑块大小,必须为2的幂次(512/1024/2048/4096)。
4.4.16. 设置物理块大小¶
4.4.16.1. blk_queue_physical_block_size函数¶
blk_queue_physical_block_size函数用于设置块设备物理块大小,对应硬件底层最小操作单元,用于内核IO对齐优化,避免非对齐读写造成的性能损耗。机械硬盘、SSD多为4KB,虚拟设备可与逻辑块保持一致。
函数原型:
1 | static inline void blk_queue_physical_block_size(struct request_queue *q, unsigned int size);
|
参数说明:
q:设备对应的请求队列。
size:物理块大小,必须大于等于逻辑块大小,2的幂次。
4.4.17. 设置单次最大IO扇区数¶
4.4.17.1. blk_queue_max_hw_sectors函数¶
blk_queue_max_hw_sectors函数用于限制硬件单次IO请求支持的最大扇区数量,用于约束单次读写数据上限,防止超大IO请求导致硬件溢出、DMA超限。默认常规设备设置1024扇区(512KB)。
函数原型:
1 | static inline void blk_queue_max_hw_sectors(struct request_queue *q, unsigned int max_sectors);
|
参数说明:
q:设备对应的请求队列。
max_sectors:单次IO最大支持扇区数。
4.5. RAM模拟块设备实验¶
本实验驱动基于Linux内核标准blk-mq多队列块设备框架开发,实现纯内存模拟的虚拟RAM磁盘块设备,无需物理存储介质,依靠内核内存空间模拟磁盘读写功能。 驱动默认创建16MB容量的虚拟块设备,兼容Linux 4.19~6.1+内核版本,支持标准块设备打开/释放、IO读写、IOCTL控制、磁盘几何信息查询等功能。
本章的示例代码目录为: linux_driver/38_ram_block
4.5.1. 驱动代码详解¶
核心定义与数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* 定义RAM块设备的名称 */
#define RAMDISK_NAME "ram_block"
/* 定义RAM磁盘的默认大小为16MB */
#define RAMDISK_SIZE_MB 16
/* 定义扇区大小为512字节 */
#define KERNEL_SECTOR_SIZE 512
/* 驱动私有数据结构体 */
struct ramdisk_dev {
u8 *data; /* 指向RAM磁盘数据缓冲区的指针,存储实际的磁盘数据 */
size_t size; /* RAM磁盘的总大小,单位为字节 */
sector_t capacity; /* RAM磁盘的总容量,单位为扇区数 */
struct gendisk *gd; /* 指向关联的gendisk结构体 */
struct blk_mq_tag_set tag_set; /* blk-mq标签集,管理多队列的请求标签和资源 */
spinlock_t lock; /* 自旋锁,保护设备数据缓冲区的并发访问 */
atomic_t refcnt; /* 原子引用计数,跟踪设备的打开次数 */
};
/* 全局设备指针 */
static struct ramdisk_dev *ramdisk_dev;
/* 动态分配的主设备号 */
static int major_num;
|
关键说明:
第2-6行:定义设备名称ram_block,默认RAM磁盘大小为16MB,扇区大小固定为512字节。
第9-17行:驱动私有设备管理结构体,保存单个RAM磁盘运行资源。
第23行:动态分配块设备的主设备号。
块设备打开/释放操作函数
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 | /* 块设备打开操作函数 */
static int ramdisk_open(struct block_device *bdev, fmode_t mode)
{
/* 从块设备的gendisk结构体中获取驱动私有数据 */
struct ramdisk_dev *dev = bdev->bd_disk->private_data;
/* 原子递增设备引用计数 */
atomic_inc(&dev->refcnt);
/* 打印调试信息,显示当前引用计数 */
pr_debug("ramdisk: Device opened, refcnt=%d\n", atomic_read(&dev->refcnt));
return 0;
}
/* 块设备释放操作函数 */
static void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
/* 从gendisk结构体中获取驱动私有数据 */
struct ramdisk_dev *dev = disk->private_data;
/* 原子递减设备引用计数 */
atomic_dec(&dev->refcnt);
/* 打印调试信息,显示当前引用计数 */
pr_debug("ramdisk: Device released, refcnt=%d\n", atomic_read(&dev->refcnt));
}
|
关键说明:
第8、22行:open设备时原子递增引用计数,close设备时原子递减引用计数,保证并发安全。
获取磁盘几何信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* 获取磁盘几何信息函数 */
static int ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 从块设备的gendisk结构体中获取驱动私有数据 */
struct ramdisk_dev *dev = bdev->bd_disk->private_data;
/* 获取设备总扇区数 */
unsigned long capacity = dev->capacity;
/* 模拟磁头数,设置为16 */
geo->heads = 16;
/* 模拟每磁道扇区数,设置为63 */
geo->sectors = 63;
/* 计算并模拟柱面数:总扇区数 / (磁头数 * 每磁道扇区数) */
geo->cylinders = (unsigned short)(capacity / (geo->heads * geo->sectors));
/* 磁盘起始扇区,设置为0 */
geo->start = 0;
return 0;
}
|
关键说明:
第10-16行:模拟老式磁盘磁头/扇区/柱面参数,fdisk等老式分区工具依赖该接口识别磁盘硬件参数。
块设备IOCTL控制操作函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* 块设备IOCTL控制操作函数 */
static int ramdisk_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
/* 从块设备的gendisk结构体中获取驱动私有数据 */
struct ramdisk_dev *dev = bdev->bd_disk->private_data;
/* 根据不同的IOCTL命令进行处理 */
switch (cmd) {
/* BLKGETSIZE命令:获取设备总扇区数 */
case BLKGETSIZE:
/* 将设备容量复制到用户空间 */
return put_user((unsigned long)dev->capacity, (unsigned long __user *)arg);
/* BLKGETSIZE64命令:获取设备总字节数 */
case BLKGETSIZE64:
/* 将设备总字节数复制到用户空间 */
return put_user((u64)dev->capacity << 9, (u64 __user *)arg);
/* 不支持的IOCTL命令 */
default:
/* 返回不支持的命令错误码 */
return -ENOTTY;
}
}
|
关键说明:
第11行:获取设备总扇区数(32位)命令。
第13行:put_user将内核数据拷贝至用户空间,返回32bit扇区总数。
第15行:获取设备总字节数(64位)命令。
第17行:扇区号 << 9 将扇区转换为字节偏移,返回64位磁盘容量。
块设备操作集
1 2 3 4 5 6 7 8 | /* 块设备操作函数集 */
static const struct block_device_operations ramdisk_fops = {
.owner = THIS_MODULE,
.open = ramdisk_open,
.release = ramdisk_release,
.ioctl = ramdisk_ioctl,
.getgeo = ramdisk_getgeo, /* 获取磁盘几何信息函数指针 */
};
|
把上述四个回调函数挂载到内核块设备操作结构体。
blk-mq请求处理函数
ramdisk_queue_rq函数是blk-mq收到读写IO请求的唯一入口,实现内存盘和用户缓冲区的数据拷贝,处理流程为:获取IO请求->校验非直通请求->加自旋锁->遍历IO分散内存段->扇区转字节偏移->边界越界检查->memcpy读写内存->解锁自旋锁->通知内核IO完成。
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 | /* blk-mq请求处理主函数 */
static blk_status_t ramdisk_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
/* 获取当前要处理的请求结构体 */
struct request *rq = bd->rq;
/* 从请求队列的私有数据中获取设备结构体 */
struct ramdisk_dev *dev = rq->q->queuedata;
/* 请求迭代器,用于遍历请求中的所有bio段 */
struct req_iterator iter;
/* bio向量结构体,存储单个数据段的信息 */
struct bio_vec bvec;
/* 当前处理的起始扇区号 */
sector_t sector = blk_rq_pos(rq);
/* 用于保存中断状态的变量 */
unsigned long flags;
/* 请求处理状态,初始化为成功 */
blk_status_t status = BLK_STS_OK;
/* 标记请求开始处理,更新请求状态 */
blk_mq_start_request(rq);
/* 检查是否为透传请求 */
if (blk_rq_is_passthrough(rq)) {
/* 打印警告信息,不支持透传请求 */
pr_warn("ramdisk: Passthrough requests not supported\n");
/* 设置状态为不支持 */
status = BLK_STS_NOTSUPP;
/* 跳转到请求完成处理 */
goto out;
}
/* 获取自旋锁并禁用本地中断,保护数据缓冲区 */
spin_lock_irqsave(&dev->lock, flags);
/* 遍历请求中的所有bio数据段 */
rq_for_each_segment(bvec, rq, iter) {
/* 获取数据段在内存中的虚拟地址 */
void *buf = page_address(bvec.bv_page) + bvec.bv_offset;
/* 获取数据段的长度,单位为字节 */
size_t len = bvec.bv_len;
/* 计算数据段在RAM磁盘中的偏移量 */
loff_t offset = (loff_t)sector << 9;
/* 边界检查:确保IO操作不超出设备范围 */
if (offset + len > dev->size) {
/* 打印错误信息,显示越界的IO参数 */
pr_err("ramdisk: I/O beyond device end (offset=%lld, len=%zu, size=%zu)\n",
offset, len, dev->size);
/* 设置状态为IO错误 */
status = BLK_STS_IOERR;
/* 跳出循环,终止处理 */
break;
}
/* 根据请求方向处理读写操作 */
if (rq_data_dir(rq) == READ) {
/* 读操作:从RAM磁盘复制数据到内核缓冲区 */
memcpy(buf, dev->data + offset, len);
} else {
/* 写操作:从内核缓冲区复制数据到RAM磁盘 */
memcpy(dev->data + offset, buf, len);
}
/* 更新下一个要处理的扇区号 */
sector += len >> 9;
}
/* 释放自旋锁并恢复中断状态 */
spin_unlock_irqrestore(&dev->lock, flags);
out:
/* 完成请求处理,通知blk-mq框架 */
blk_mq_end_request(rq, status);
/* 返回处理状态 */
return BLK_STS_OK;
}
/* blk-mq操作函数集 */
static const struct blk_mq_ops ramdisk_mq_ops = {
/* 请求处理函数指针,blk-mq的入口 */
.queue_rq = ramdisk_queue_rq,
};
|
关键说明:
第21行:标记IO请求开始进入处理流程。
第24-31行:拒绝透传请求,如SCSI命令。
第37行:内核标准遍历宏,一个IO请求可能由多段不连续内存组成,循环逐个处理分段数据。
第43行:sector << 9 即扇区号x512,换算成RAM内存缓冲区的字节偏移地址。
第46-54行:边界检查,防止越界。
第59行:读操作,从虚拟磁盘内存拷贝数据到用户IO缓冲区。
第62行:写操作:用户写入的数据拷贝到内核虚拟磁盘内存。
第74行:完成请求并通知块层,内核回收本次请求资源。
第76行:函数总是返回BLK_STS_OK,实际状态通过blk_mq_end_request传递。
第80-83行:blk-mq操作集,仅提供queue_rq入口,无其他回调。
设备资源初始化函数
模块加载时初始化硬件资源:分配磁盘内存->初始化锁与计数->配置blk-mq多队列->按内核版本差异化创建gendisk->配置块设备参数->注册设备到内核生成/dev/ram_block。
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /* 初始化RAM磁盘设备 */
static int __init ramdisk_init_device(struct ramdisk_dev *dev)
{
/* 函数返回值 */
int ret;
/* 硬件队列数量 */
unsigned int hw_queues;
/* 计算设备总大小:MB转换为字节 */
dev->size = RAMDISK_SIZE_MB * 1024 * 1024;
/* 计算设备总容量:总字节数 / 扇区大小 */
dev->capacity = dev->size / KERNEL_SECTOR_SIZE;
/* 分配RAM磁盘数据缓冲区,使用vzalloc分配大内存并清零 */
dev->data = vzalloc(dev->size);
if (!dev->data) {
/* 打印错误信息,显示分配失败的内存大小 */
pr_err("ramdisk: Failed to allocate %zu bytes of memory\n", dev->size);
/* 返回内存不足错误码 */
return -ENOMEM;
}
/* 初始化保护数据的自旋锁 */
spin_lock_init(&dev->lock);
/* 初始化设备引用计数为0 */
atomic_set(&dev->refcnt, 0);
/* 硬件队列数固定等于系统在线CPU数量 */
hw_queues = num_online_cpus();
/* 打印信息,显示使用的硬件队列数量 */
pr_info("ramdisk: Using %u hardware queues\n", hw_queues);
/* 初始化blk-mq标签集结构体,清零所有字段 */
memset(&dev->tag_set, 0, sizeof(dev->tag_set));
/* 设置blk-mq操作函数集 */
dev->tag_set.ops = &ramdisk_mq_ops;
/* 设置硬件队列数量 */
dev->tag_set.nr_hw_queues = hw_queues;
/* 设置每个硬件队列的最大请求深度 */
dev->tag_set.queue_depth = 128;
/* 设置NUMA节点为无特定节点 */
dev->tag_set.numa_node = NUMA_NO_NODE;
/* 设置blk-mq标志:允许请求合并,提高性能 */
dev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE;
/* 设置标签集的驱动私有数据,指向设备结构体 */
dev->tag_set.driver_data = dev;
/* 分配blk-mq标签集资源 */
ret = blk_mq_alloc_tag_set(&dev->tag_set);
if (ret) {
/* 打印错误信息 */
pr_err("ramdisk: Failed to allocate blk-mq tag set\n");
/* 跳转到错误处理,释放已分配的数据缓冲区 */
goto free_data;
}
/* 内核版本判断:Linux 5.14及以上版本使用新API */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)
/* 使用blk_mq_alloc_disk一步分配gendisk和请求队列 */
dev->gd = blk_mq_alloc_disk(&dev->tag_set, dev);
if (IS_ERR(dev->gd)) {
/* 获取错误码 */
ret = PTR_ERR(dev->gd);
/* 打印错误信息 */
pr_err("ramdisk: Failed to allocate gendisk\n");
/* 跳转到错误处理,释放已分配的标签集 */
goto free_tag_set;
}
/* 内核版本判断:Linux 4.19-5.13版本使用传统API */
#else
/* 先分配gendisk结构体,参数为支持的次设备号数量 */
dev->gd = alloc_disk(1);
if (!dev->gd) {
/* 打印错误信息 */
pr_err("ramdisk: Failed to allocate gendisk\n");
/* 设置错误码为内存不足 */
ret = -ENOMEM;
/* 跳转到错误处理,释放已分配的标签集 */
goto free_tag_set;
}
/* 初始化blk-mq请求队列 */
dev->gd->queue = blk_mq_init_queue(&dev->tag_set);
if (IS_ERR(dev->gd->queue)) {
/* 获取错误码 */
ret = PTR_ERR(dev->gd->queue);
/* 打印错误信息 */
pr_err("ramdisk: Failed to initialize blk-mq queue\n");
/* 跳转到错误处理,释放已分配的gendisk */
goto put_disk;
}
/* 设置请求队列的私有数据,指向设备结构体 */
dev->gd->queue->queuedata = dev;
#endif
/* 设置gendisk的主设备号 */
dev->gd->major = major_num;
/* 设置gendisk的第一个次设备号 */
dev->gd->first_minor = 0;
/* 设置gendisk支持的次设备号数量 */
dev->gd->minors = 1;
/* 设置gendisk的块设备操作函数集 */
dev->gd->fops = &ramdisk_fops;
/* 设置gendisk的私有数据,指向设备结构体 */
dev->gd->private_data = dev;
/* 设置gendisk的设备名称 */
snprintf(dev->gd->disk_name, DISK_NAME_LEN, RAMDISK_NAME);
/* 设置设备容量 */
set_capacity(dev->gd, dev->capacity);
/* 设置队列的逻辑块大小 */
blk_queue_logical_block_size(dev->gd->queue, KERNEL_SECTOR_SIZE);
/* 设置队列的物理块大小 */
blk_queue_physical_block_size(dev->gd->queue, KERNEL_SECTOR_SIZE);
/* 设置队列支持的最大硬件扇区数 */
blk_queue_max_hw_sectors(dev->gd->queue, 1024);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
/* Linux 5.15及以上版本add_disk有返回值 */
ret = add_disk(dev->gd);
if (ret) {
/* 打印错误信息 */
pr_err("ramdisk: Failed to add disk: %d\n", ret);
/* 跳转到错误处理,释放已分配的gendisk */
goto put_disk;
}
#else
/* Linux 4.19-5.14版本add_disk返回void,不会失败 */
add_disk(dev->gd);
/* 设置返回值为成功 */
ret = 0;
#endif
/* 打印设备注册成功信息 */
pr_info("ramdisk: Device registered successfully\n");
/* 打印设备大小信息 */
pr_info("ramdisk: Size: %d MB (%zu bytes, %llu sectors)\n",
RAMDISK_SIZE_MB, dev->size, (unsigned long long)dev->capacity);
return 0;
/* 释放gendisk结构体 */
put_disk:
put_disk(dev->gd);
/* 释放blk-mq标签集 */
free_tag_set:
blk_mq_free_tag_set(&dev->tag_set);
/* 释放RAM数据缓冲区 */
free_data:
vfree(dev->data);
return ret;
}
|
关键说明:
第15行:使用vzalloc分配大块连续虚拟内存并清零。
第29行:硬件IO队列数量等于CPU核心数,实现多队列并行。
第49行:向Linux内核申请blk-mq多队列资源。
第60行:Linux5.14新增API,一步分配gendisk和队列。
第73、84、95行:旧内核分别调用alloc_disk和blk_mq_init_queue分配gendisk和队列,并手动设置queuedata。
第112行:向内核上报磁盘总扇区,lsblk、df等命令通过该参数读取磁盘大小。
第114-118行:设置设备逻辑/物理块大小(512字节)、最大硬件扇区数(1024扇区=512KB)。
第122、131行:add_disk块设备最终注册函数,调用成功后系统自动生成/dev/ram_block设备文件。
设备资源清理函数
模块卸载时逆序释放全部资源,防止内存泄漏。
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 | /* 清理RAM磁盘设备 */
static void ramdisk_cleanup_device(struct ramdisk_dev *dev)
{
/* 检查设备指针是否有效 */
if (!dev)
return;
/* 检查gendisk是否已分配 */
if (dev->gd) {
/* 从系统中删除块设备 */
del_gendisk(dev->gd);
/* 内核版本判断:4.19-5.17版本需要显式清理队列 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
/* 清理blk-mq请求队列 */
blk_cleanup_queue(dev->gd->queue);
#endif
/* 释放gendisk结构体 */
put_disk(dev->gd);
}
/* 释放blk-mq标签集 */
blk_mq_free_tag_set(&dev->tag_set);
/* 检查数据缓冲区是否已分配 */
if (dev->data) {
/* 释放RAM数据缓冲区 */
vfree(dev->data);
/* 将指针置空,防止野指针 */
dev->data = NULL;
}
/* 释放设备私有数据结构体 */
kfree(dev);
}
|
关键说明:
第11行:从内核设备中注销块设备,/dev/ram_block节点消失。
第27行:释放vzalloc申请的磁盘缓冲区内存。
模块初始化函数
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 | /* 模块初始化函数 */
static int __init ramdisk_init(void)
{
/* 函数返回值 */
int ret;
/* 内核版本号的三个组成部分 */
unsigned int major, minor, patch;
/* 从编译时内核版本号中解析主版本号 */
major = (LINUX_VERSION_CODE >> 16) & 0xFF;
/* 解析次版本号 */
minor = (LINUX_VERSION_CODE >> 8) & 0xFF;
/* 解析补丁版本号 */
patch = LINUX_VERSION_CODE & 0xFF;
/* 动态分配块设备主设备号 */
major_num = register_blkdev(0, RAMDISK_NAME);
if (major_num < 0) {
/* 打印错误信息 */
pr_err("ramdisk: Failed to register block device\n");
/* 返回错误码 */
return major_num;
}
/* 分配设备私有数据结构体,使用kzalloc分配并清零 */
ramdisk_dev = kzalloc(sizeof(struct ramdisk_dev), GFP_KERNEL);
if (!ramdisk_dev) {
/* 设置错误码为内存不足 */
ret = -ENOMEM;
/* 跳转到错误处理,注销已分配的主设备号 */
goto unregister_blkdev;
}
/* 初始化RAM磁盘设备 */
ret = ramdisk_init_device(ramdisk_dev);
if (ret) {
/* 跳转到错误处理,释放已分配的设备结构体 */
goto free_dev;
}
/* 打印模块加载成功信息 */
pr_info("ramdisk: Module loaded successfully\n");
return 0;
/* 释放设备私有数据结构体 */
free_dev:
kfree(ramdisk_dev);
/* 注销块设备主设备号 */
unregister_blkdev:
unregister_blkdev(major_num, RAMDISK_NAME);
return ret;
}
|
关键说明:
第10-14行:LINUX_VERSION_CODE版本号位于内核源码/include/generated/uapi/linux/version.h,通过内核源码顶层Makefile编译生成,如#define LINUX_VERSION_CODE 393571,393571转16进制为0x60163,对应0x06 01 63分为高、中、低8位,高8位0x06对应十进制6,中间8位0x01对应十进制1,低8位0x63对应十进制99,即6.1.99内核。
第17行:register_blkdev传入0代表动态随机分配主设备号。
第26行:kzalloc分配设备私有数据结构体,自动清零。
模块退出函数
1 2 3 4 5 6 7 8 9 10 | /* 模块退出函数 */
static void __exit ramdisk_exit(void)
{
/* 清理RAM磁盘设备所有资源 */
ramdisk_cleanup_device(ramdisk_dev);
/* 注销块设备主设备号 */
unregister_blkdev(major_num, RAMDISK_NAME);
/* 打印模块卸载成功信息 */
pr_info("ramdisk: Module unloaded successfully\n");
}
|
关键说明:
第5行:清理设备资源。
第7行:注销主设备号。
4.5.2. Makefile说明¶
本节实验使用的Makefile如下所示,编写该Makefile时,只需要根据实际情况修改变量KERNEL_DIR、CROSS_COMPILE和obj-m即可。
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 | #指定内核路径,可以是相对路径或绝对路径
KERNEL_DIR=../../kernel/
# KERNEL_DIR=/home/guest/LubanCat_Linux_Generic_Full_SDK/kernel-6.1
# KERNEL_DIR=/home/guest/LubanCat_Linux_rk3588_SDK/kernel
#指定目标架构为arm64
ARCH=arm64
#指定交叉编译工具链的前缀
CROSS_COMPILE=aarch64-linux-gnu-
# CROSS_COMPILE=/home/guest/LubanCat_Linux_Generic_Full_SDK/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
#导出为环境变量
export ARCH CROSS_COMPILE
#指定要编译的内核模块目标文件
obj-m := ram_block.o
#all :默认目标,执行时会编译驱动模块
#$(MAKE) :调用make工具
#-C $(KERNEL_DIR) :指定的内核源码目录
#M=$(CURDIR) :模块的源码位于当前目录
#modules :编译模块
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean
#清理编译生成的文件
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
|
4.5.3. 编译驱动¶
在实验目录下输入 make 即可编译驱动和应用程序,编译得到内核模块ram_block.ko。
4.5.4. 程序运行结果¶
如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
4.5.4.1. 实验操作¶
以LubanCat2板卡为例,使用以下命令加载驱动:
1 2 3 4 5 6 7 8 | #加载驱动
sudo insmod ram_block.ko
#信息输出如下
[ 67.274483] ramdisk: Using 4 hardware queues
[ 67.276909] ramdisk: Device registered successfully
[ 67.276968] ramdisk: Size: 16 MB (16777216 bytes, 32768 sectors)
[ 67.276978] ramdisk: Module loaded successfully
|
可以看到驱动使用了4个硬件队列对应RK3568芯片4核CPU,内存磁盘容量初始化正常,16MB总空间,16777216字节,32768个扇区。
使用以下命令查看虚拟块设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #查看系统所有块设备
lsblk
#信息输出如下
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
mmcblk0 179:0 0 7.3G 0 disk
├─mmcblk0p1 179:1 0 8M 0 part
├─mmcblk0p2 179:2 0 128M 0 part /boot
└─mmcblk0p3 179:3 0 7.2G 0 part /
mmcblk0boot0 179:32 0 4M 1 disk
mmcblk0boot1 179:64 0 4M 1 disk
ram_block 252:0 0 16M 0 disk
#查看磁盘详细信息
sudo fdisk -l /dev/ram_block
#信息输出如下
Disk /dev/ram_block: 16 MiB, 16777216 bytes, 32768 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
|
执行lsblk可正常识别出ram_block 16MB虚拟块设备,与板卡原生MMC磁盘并列展示,设备主设备号252、次设备号0。
执行fdisk可查看到磁盘总容量16MiB、总字节数16777216、总扇区数32768,标准扇区大小512字节,无参数异常。
使用以下命令格式化与挂载测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #格式化为ext4文件系统
sudo mkfs.ext4 /dev/ram_block
#信息输出如下
mke2fs 1.44.5 (15-Dec-2018)
Creating filesystem with 16384 1k blocks and 4096 inodes
Filesystem UUID: fdf23f04-32ff-4f05-bd65-da578d27fd64
Superblock backups stored on blocks:
8193
Allocating group tables: done
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done
#挂载虚拟磁盘
sudo mount /dev/ram_block /mnt/
#信息输出如下
[ 718.238154] EXT4-fs (ram_block): mounted filesystem with ordered data mode. Opts: (null)
|
可以看到ram_block虚拟块设备可正常格式化为ext4文件系统,可以被正常挂载。
使用以下命令读写测试:
1 2 3 4 5 6 7 8 9 10 11 | #进入挂载目录
cd /mnt/
#写入测试文件
sudo sh -c "echo RAM Block Device Test > test.txt"
#读取测试文件
cat test.txt
#信息打印如下
RAM Block Device Test
|
可以看到可以正常创建和读取文件。
使用以下命令卸载设备与模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #返回家目录
cd ~
#卸载挂载点
sudo umount /mnt/
#卸载内核模块
sudo rmmod ram_block
#信息打印如下
[ 994.557159] ramdisk: Module unloaded successfully
#查看系统所有块设备
lsblk
#信息打印如下
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
mmcblk0 179:0 0 7.3G 0 disk
├─mmcblk0p1 179:1 0 8M 0 part
├─mmcblk0p2 179:2 0 128M 0 part /boot
└─mmcblk0p3 179:3 0 7.2G 0 part /
mmcblk0boot0 179:32 0 4M 1 disk
mmcblk0boot1 179:64 0 4M 1 disk
|
驱动卸载后ram_block虚拟块设备消失。
4.5.5. 实验注意事项¶
内存分配:16MB属于大块内存,必须使用vzalloc分配虚拟连续内存,禁止使用kmalloc,kmalloc仅支持小块内存分配,否则会分配失败。
扇区大小:Linux系统标准硬件扇区为512字节,修改该宏可能会导致容量错误,磁盘无法识别等。
内核版本兼容:不同内核gendisk、add_disk、队列初始化API不同,删除版本判断宏会导致编译报错或运行崩溃。
数据易失性:本实验设备为内存虚拟磁盘,所有数据存储在RAM中,卸载模块、重启设备后数据全部丢失,不可用于持久化数据存储。