块设备以块为单位进行数据传输,通常支持随机访问,如硬盘、U盘等。块设备驱动负责管理这些设备的I/O操作,为内核和用户空间提供统一的接口。这部分通常以理论讲解为主,代码示例较少。
块设备的I/O栈涉及多个层次,从用户空间的系统调用开始,经过VFS(虚拟文件系统)、通用块层,最终到达块设备驱动层。这部分也是理论性较强,直接的代码示例较少。
通用块层是Linux内核中位于VFS和块设备驱动之间的一层,它负责管理块设备的请求队列,对I/O请求进行合并、排序等优化操作,以提高I/O性能。这部分同样以理论为主,直接代码示例较少。
在Linux内核中,块设备驱动需要先注册才能被系统识别和使用,使用完毕后需要注销以释放资源。
#include
#include
#include
#include
// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;
// 模块初始化函数,用于注册块设备
static int __init block_dev_init(void) {
// 分配设备号
// 第一个参数是指向dev_t类型变量的指针,用于存储分配的设备号
// 第二个参数是起始设备号,这里设为0
// 第三个参数是要分配的设备号数量,这里设为1
// 第四个参数是设备名称,用于在sysfs中显示
int ret = alloc_chrdev_region(&dev_num, 0, 1, "my_block_dev");
if (ret) {
// 如果分配设备号失败,打印错误信息
printk(KERN_ERR "Failed to allocate device number\n");
return ret;
}
// 创建一个gendisk结构体实例
// 第一个参数是设备名称
// 第二个参数是该设备包含的分区数量,这里设为0
my_disk = alloc_disk(0);
if (!my_disk) {
// 如果创建gendisk失败,释放已分配的设备号
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "Failed to allocate gendisk\n");
return -ENOMEM;
}
// 设置gendisk的主设备号和次设备号
my_disk->major = MAJOR(dev_num);
my_disk->first_minor = MINOR(dev_num);
// 设置gendisk的名称
sprintf(my_disk->disk_name, "my_disk");
// 设置gendisk的所有者为当前模块
my_disk->owner = THIS_MODULE;
// 设置gendisk的容量,这里假设为1024个扇区,每个扇区512字节
my_disk->capacity = 1024 * 512;
// 注册gendisk
add_disk(my_disk);
printk(KERN_INFO "Block device registered successfully\n");
return 0;
}
// 模块退出函数,用于注销块设备
static void __exit block_dev_exit(void) {
// 从系统移除gendisk
del_gendisk(my_disk);
// 释放gendisk结构体占用的内存
put_disk(my_disk);
// 释放设备号
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "Block device unregistered successfully\n");
}
module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");
request_queue
是块设备请求队列,用于管理I/O请求。bio
(Block I/O)结构体表示一个块设备I/O请求,包含了请求的目标设备、数据缓冲区、操作类型等信息。
#include
#include
#include
#include
#include
#include
// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;
// 定义请求队列
struct request_queue *my_queue;
// 块设备的请求处理函数
static void my_request_fn(struct request_queue *q) {
struct request *req;
// 循环处理请求队列中的请求
while ((req = elv_next_request(q))!= NULL) {
struct bio *bio;
// 遍历请求中的所有bio
for_each_bio(bio, req) {
// 获取bio的扇区偏移
sector_t sector = bio->bi_sector;
// 获取bio的数据缓冲区
char *buffer = bio->bi_io_vec[0].bv_data;
// 获取bio的长度
unsigned int len = bio->bi_io_vec[0].bv_len;
// 这里可以进行实际的I/O操作,例如模拟读取扇区数据
for (unsigned int i = 0; i < len; i++) {
buffer[i] = sector + i;
}
// 标记bio完成
bio_endio(bio, 0);
}
// 标记请求完成
end_request(req, 0);
}
}
// 模块初始化函数,用于注册块设备
static int __init block_dev_init(void) {
int ret;
// 分配设备号
ret = alloc_chrdev_region(&dev_num, 0, 1, "my_block_dev");
if (ret) {
printk(KERN_ERR "Failed to allocate device number\n");
return ret;
}
// 创建请求队列
// 第一个参数是请求处理函数
// 第二个参数是当前模块指针
my_queue = blk_init_queue(my_request_fn, THIS_MODULE);
if (!my_queue) {
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "Failed to create request queue\n");
return -ENOMEM;
}
// 创建一个gendisk结构体实例
my_disk = alloc_disk(0);
if (!my_disk) {
blk_cleanup_queue(my_queue);
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "Failed to allocate gendisk\n");
return -ENOMEM;
}
my_disk->major = MAJOR(dev_num);
my_disk->first_minor = MINOR(dev_num);
sprintf(my_disk->disk_name, "my_disk");
my_disk->owner = THIS_MODULE;
my_disk->capacity = 1024 * 512;
// 设置gendisk的请求队列
my_disk->queue = my_queue;
add_disk(my_disk);
printk(KERN_INFO "Block device registered successfully\n");
return 0;
}
// 模块退出函数,用于注销块设备
static void __exit block_dev_exit(void) {
del_gendisk(my_disk);
put_disk(my_disk);
blk_cleanup_queue(my_queue);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "Block device unregistered successfully\n");
}
module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");
下面是一个完整的块设备驱动实例,结合了前面的注册、注销以及请求处理的内容。
#include
#include
#include
#include
#include
#include
// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;
// 定义请求队列
struct request_queue *my_queue;
// 块设备的请求处理函数
static void my_request_fn(struct request_queue *q) {
struct request *req;
while ((req = elv_next_request(q))!= NULL) {
struct bio *bio;
for_each_bio(bio, req) {
sector_t sector = bio->bi_sector;
char *buffer = bio->bi_io_vec[0].bv_data;
unsigned int len = bio->bi_io_vec[0].bv_len;
// 这里可以进行实际的I/O操作,例如模拟读取扇区数据
for (unsigned int i = 0; i < len; i++) {
buffer[i] = sector + i;
}
bio_endio(bio, 0);
}
end_request(req, 0);
}
}
// 模块初始化函数,用于注册块设备
static int __init block_dev_init(void) {
int ret;
// 分配设备号
ret = alloc_chrdev_region(&dev_num, 0, 1, "my_block_dev");
if (ret) {
printk(KERN_ERR "Failed to allocate device number\n");
return ret;
}
// 创建请求队列
my_queue = blk_init_queue(my_request_fn, THIS_MODULE);
if (!my_queue) {
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "Failed to create request queue\n");
return -ENOMEM;
}
my_disk = alloc_disk(0);
if (!my_disk) {
blk_cleanup_queue(my_queue);
unregister_chrdev_region(dev_num, 1);
printk(KERN_ERR "Failed to allocate gendisk\n");
return -ENOMEM;
}
my_disk->major = MAJOR(dev_num);
my_disk->first_minor = MINOR(dev_num);
sprintf(my_disk->disk_name, "my_disk");
my_disk->owner = THIS_MODULE;
my_disk->capacity = 1024 * 512;
my_disk->queue = my_queue;
add_disk(my_disk);
printk(KERN_INFO "Block device registered successfully\n");
return 0;
}
// 模块退出函数,用于注销块设备
static void __exit block_dev_exit(void) {
del_gendisk(my_disk);
put_disk(my_disk);
blk_cleanup_queue(my_queue);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "Block device unregistered successfully\n");
}
module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");
这个实例展示了一个基本的块设备驱动的完整实现,包括设备的注册与注销、请求队列的创建和请求处理函数的实现。实际应用中,需要根据具体设备的特性对请求处理函数进行更详细的定制,以实现真实的块设备I/O操作。