Linux 块设备驱动是最复杂,比字符设备复杂的多。
什么是块设备
块设备是用于存储数据的设备,例如 SD 卡、EMMC、Nand Flash、机械硬盘、固态硬盘等。与字符设备相比,块设备有以下几个主要区别:
1. 数据访问单位不同:
- 块设备:以块(通常是512字节或4KB)为单位进行读写。
- 字符设备:以字节为单位进行读写。
2. 随机访问能力:
- 块设备:支持随机访问,可以按块读写。
- 字符设备:顺序访问,不支持随机读写。
3. 缓冲区使用:
- 块设备:使用缓冲区暂存数据,减少对物理设备的直接操作,提高寿命。
- 字符设备:实时传输,不使用缓冲区。
4. I/O算法不同:
- 块设备:根据设备类型采用不同的 I/O 调度算法,如针对机械硬盘的顺序化处理。
- 字符设备:无复杂的 I/O 调度需求。
块设备驱动框架
1. block_device 结构体
Linux 内核使用 `block_device` 表示一个具体的块设备对象,比如一个硬盘或者分区。关键成员包括:
- `bd_disk`:指向通用磁盘结构 `gendisk`。
2. gendisk 结构体
`gendisk` 描述了整个磁盘设备,其主要成员包括:
- `major`:主设备号。
- `first_minor`:第一个次设备号。
- `minors`:次设备号的数量(即分区数)。
- `part_tbl`:分区表。
- `fops`:块设备操作集。
- `queue`:请求队列,所有对该磁盘设备的请求都放在这个队列中。
常用 API
- 申请 gendisk:
```c
struct gendisk alloc_disk(int minors);
```
- 删除 gendisk:
```c
void del_gendisk(struct gendisk gp);
```
- 添加 gendisk 到内核:
```c
void add_disk(struct gendisk disk);
```
- 设置容量:
```c
void set_capacity(struct gendisk disk, sector_t size);
```
- 调整引用计数:
```c
struct kobject get_disk(struct gendisk disk);
void put_disk(struct gendisk disk);
```
3. block_device_operations 结构体
类似于字符设备的 `file_operations`,块设备的操作集为 `block_device_operations`,包含以下常见函数:
- `open()`:打开块设备。
- `release()`:关闭块设备。
- `rw_page()`:读写指定页。
- `ioctl()`:I/O控制命令。
- `compat_ioctl()`:兼容模式的 I/O 控制命令。
- `getgeo()`:获取磁盘几何信息。
4. 块设备 I/O 请求过程
块设备的读写通过 `request_queue` 实现,其中包含了很多 `request` 结构体,每个 `request` 又包含多个 `bio` 结构体,保存了读写的具体信息。
请求队列 (request_queue)
所有的块设备读写请求都被发送到请求队列中,驱动程序需要处理这些请求。
示例代码:使用 RAM 模拟块设备
以下是一个简单的示例代码,展示如何使用板载 RAM 模拟一个块设备:
```c
include <linux/module.h>
include <linux/fs.h>
include <linux/genhd.h>
include <linux/blkdev.h>
include <linux/blk-mq.h>
include <linux/uaccess.h>
include <linux/vmalloc.h>
define RAMDISK_SIZE (2 1024 1024) // 2MB
define SECTOR_SIZE 512
static unsigned char ram_buffer;
static struct gendisk my_disk;
static struct request_queue my_queue;
static int major;
// make_request 函数
static blk_qc_t ramdisk_make_request(struct request_queue q, struct bio bio) {
sector_t start_sec = bio->bi_iter.bi_sector;
void dsk_ptr = ram_buffer + (start_sec SECTOR_SIZE);
if (bio_data_dir(bio) == READ)
memcpy(bio_kvec_data(bio), dsk_ptr, bio->bi_iter.bi_size);
else
memcpy(dsk_ptr, bio_kvec_data(bio), bio->bi_iter.bi_size);
return BLK_MQ_RQ_DONE;
}
// getgeo 函数
static int ramdisk_getgeo(struct block_device bdev, struct hd_geometry geo) {
geo->heads = 4;
geo->sectors = 16;
geo->cylinders = (RAMDISK_SIZE >> 9) / (4 16);
return 0;
}
// block_device_operations 结构体
static struct block_device_operations ramdisk_fops = {
.owner = THIS_MODULE,
.getgeo = ramdisk_getgeo,
};
// 初始化函数
static int __init ramdisk_init(void) {
ram_buffer = vmalloc(RAMDISK_SIZE);
if (!ram_buffer) return -ENOMEM;
major = register_blkdev(0, "ramdisk");
if (major main_num = major;
my_disk->first_minor = 0;
my_disk->fops = &ramdisk_fops;
my_disk->queue = my_queue;
strcpy(my_disk->disk_name, "ramdisk");
set_capacity(my_disk, RAMDISK_SIZE / SECTOR_SIZE);
add_disk(my_disk);
printk(KERN_INFO "RAM disk loaded: /dev/ramdisk\n");
return 0;
err_cleanup_q:
blk_cleanup_queue(my_queue);
err_unreg:
unregister_blkdev(major, "ramdisk");
err_vfree:
vfree(ram_buffer);
return -ENOMEM;
}
// 卸载函数
static void __exit ramdisk_exit(void) {
del_gendisk(my_disk);
put_disk(my_disk);
unregister_blkdev(major, "ramdisk");
blk_cleanup_queue(my_queue);
vfree(ram_buffer);
}
module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
```
测试步骤
1. 编译并加载模块:
```sh
make
sudo insmod ramdisk.ko
```
2. 查看设备:
```sh
lsblk | grep ramdisk
应该看到 /dev/ramdisk,大小 2M
```
3. 格式化并挂载:
```sh
sudo mkfs.ext4 /dev/ramdisk
sudo mkdir /mnt/ram
sudo mount /dev/ramdisk /mnt/ram
echo "Hello Block!" > /mnt/ram/test.txt
cat /mnt/ram/test.txt
```
4. 卸载并清理:
```sh
sudo umount /mnt/ram
sudo rmmod ramdisk
```
总结
理解块设备驱动的核心在于掌握其结构和流程。从注册块设备号开始,到创建和管理 `gendisk`,再到处理 I/O 请求,这些都是编写高效块设备驱动的关键部分。