13-Linux 块设备驱动

13.1 块设备的 I/O 操作特点

维度 字符设备 块设备
访问单位 字节流 扇区(512B)/块(通常 4K)
是否可 seek 通常不可(no_llseek) 必须支持
缓冲 无 页缓存(page cache)
接口 read/write read/write/mmap/mount
内核路径 直接调用 fops VFS → 页缓存 → bio → blk-mq → 驱动
典型设备 串口、按键、LED 硬盘、SSD、U 盘、RAM 盘

块设备的分层:

复制代码
用户 read()/write()
   -> VFS
   -> 页缓存 (page cache) / 直接 I/O (O_DIRECT)
   -> struct bio (描述一次 I/O:哪些页、多大、方向)
   -> blk-mq (多队列、tag、调度)
   -> struct request (被驱动处理的请求)
   -> 驱动 (queue_rq 或 submit_bio)
   -> 硬件

13.2 Linux 块设备驱动结构

13.2.1 block_device_operations 结构体

7.2.5 的定义(include/linux/blkdev.h):

cpp 复制代码
struct block_device_operations {
	void (*submit_bio)(struct bio *bio);
	int (*poll_bio)(struct bio *bio, struct io_comp_batch *iob,
					unsigned int flags);
	int (*open)(struct gendisk *disk, blk_mode_t mode);
	void (*release)(struct gendisk *disk);
	int (*ioctl)(struct block_device *bdev, blk_mode_t mode,
				 unsigned cmd, unsigned long arg);
	int (*compat_ioctl)(struct block_device *bdev, blk_mode_t mode,
						unsigned cmd, unsigned long arg);
	unsigned int (*check_events)(struct gendisk *disk, unsigned int clearing);
	void (*unlock_native_capacity)(struct gendisk *);
	int (*getgeo)(struct gendisk *, struct hd_geometry *);
	int (*set_read_only)(struct block_device *bdev, bool ro);
	void (*free_disk)(struct gendisk *disk);
	void (*swap_slot_free_notify)(struct block_device *, unsigned long);
	int (*report_zones)(struct gendisk *, sector_t sector,
						unsigned int nr_zones,
						struct blk_report_zones_args *args);
	char *(*devnode)(struct gendisk *disk, umode_t *mode);
	int (*get_unique_id)(struct gendisk *disk, u8 id[16],
						 enum blk_unique_id id_type);
	struct module *owner;
	const struct pr_ops *pr_ops;
	int (*alternative_gpt_sector)(struct gendisk *disk, sector_t *sector);
};

与 4.0 的对比(重要):

项 4.0 7.2.5
open 签名 int (*open)(struct block_device *, fmode_t) int (*open)(struct gendisk *, blk_mode_t)
release void (*release)(struct gendisk *, fmode_t) void (*release)(struct gendisk *)
ioctl int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long) blk_mode_t 取代 fmode_t
request_fn 有 已删除
make_request 有 已删除
submit_bio 有(与 make_request 并存) 唯一推荐的直接提交方式
poll_bio 无 有
free_disk 无 有(释放自定义 gendisk)
report_zones 无 有(ZNS 分区设备)

blk_mode_t 是 7.2.5 引入的类型(include/linux/blk_types.h),把 fmode_t 中与块设备相关的位抽出来,取值有 BLK_OPEN_READ/BLK_OPEN_WRITE/BLK_OPEN_EXCL/BLK_OPEN_READ_ONLY 等。

13.2.2 gendisk 结构体

cpp 复制代码
/* include/linux/blkdev.h */
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;
	void *private_data;
	struct bio_set bio_split;
	int flags;
	unsigned long state;
	struct mutex open_mutex;
	unsigned open_partitions;
	struct backing_dev_info *bdi;
	struct kobject queue_kobj;
	...
	u64 disk_capacity;      /* 实际字段为 capacity,用 set_capacity() 设置 */
};

关键字段:

字段 说明
major / first_minor / minors 主次设备号范围
disk_name 出现在 /dev/ 与 /proc/partitions
fops block_device_operations
queue 请求队列(blk_alloc_disk() 自动创建)
private_data 驱动私有数据
flags GENHD_FL_* 标志
bdi 回写信息

关键 API:

cpp 复制代码
struct gendisk *blk_alloc_disk(struct queue_limits *lim, int node);   /* 7.2.5 */
struct gendisk *blk_mq_alloc_disk(struct blk_mq_tag_set *set,
                                  struct queue_limits *lim, void *queuedata);
void put_disk(struct gendisk *disk);
void set_capacity(struct gendisk *disk, sector_t size);
int add_disk(struct gendisk *disk);
int add_disk_no_queue_reg(struct gendisk *disk);
void del_gendisk(struct gendisk *disk);
void blk_cleanup_disk(struct gendisk *disk);

7.2.5 的重要变化:

  • blk_alloc_disk() 现在接受 struct queue_limits * 参数(替代旧的无参形式),在分配时一次性设定队列限制。
  • struct queue_limits 集中描述队列能力:logical_block_size、physical_block_size、max_hw_sectors、max_segments、max_discard_sectors、features 等。
  • alloc_disk()/alloc_disk_node() 已删除。
  • blk_cleanup_disk() 已不再是标准做法,改为 del_gendisk() + put_disk()。

13.2.3 bio、request 和 request_queue

struct bio 描述"一次 I/O 请求":

cpp 复制代码
/* include/linux/blk_types.h */
struct bio {
	struct bio              *bi_next;
	struct block_device     *bi_bdev;
	blk_opf_t               bi_opf;         /* 操作码 + 标志 */
	unsigned short          bi_flags;
	unsigned short          bi_ioprio;
	blk_status_t            bi_status;
	struct bvec_iter        bi_iter;        /* 当前迭代位置 */
	...
	struct bio_vec          *bi_io_vec;
	struct bio_set          *bi_pool;
	...
};

struct bio_vec {
	struct page     *bv_page;
	unsigned int    bv_len;
	unsigned int    bv_offset;
};

常用 bio 操作:

API 作用
bio_data_dir(bio) 判断读/写
bio->bi_iter.bi_sector 起始扇区
bio->bi_iter.bi_size 剩余字节数
bio_for_each_segment(bvl, bio, iter) 遍历段
bio_for_each_segment_all(bvl, bio, i) 遍历全部段
bio_iter_iovec(bio, iter) 取当前段
bio_endio(bio) 完成 bio(成功)
bio_io_error(bio) 完成 bio(失败)
bio_advance_iter() / bio_advance() 推进迭代器

struct request 是 blk-mq 把多个 bio 合并后交给驱动的单位:

cpp 复制代码
struct request {
	struct request_queue *q;
	struct blk_mq_ctx *mq_ctx;
	struct blk_mq_hw_ctx *mq_hctx;
	blk_opf_t cmd_flags;
	blk_status_t status;
	struct bio *bio;
	struct bio *biotail;
	void *special;
	...
	unsigned int __data_len;
	sector_t __sector;
	...
};

request_queue 的现代形态 :7.2.5 中 request_queue 仍是核心结构,但不再有 make_request_fn ;队列由 blk_mq_alloc_disk() 自动创建并绑定 blk-mq。

13.2.4 I/O 调度器

7.2.5 的调度器(block/elevator.c):

调度器 说明 适用
none 无调度,直接下发(多队列默认) NVMe、高速 SSD
mq-deadline 保证延迟上限 通用
kyber 基于目标延迟的轻量调度 高速设备
bfq 预算公平队列,交互性好 桌面、慢速设备

注意 :旧式的 cfq、deadline、noop 已被删除,只剩上面四个。查看与设置:

bash 复制代码
cat /sys/block/sda/queue/scheduler
echo mq-deadline > /sys/block/sda/queue/scheduler
cat /sys/block/sda/queue/nr_requests
cat /sys/block/sda/queue/scheduler

13.3 Linux 块设备驱动的初始化

7.2.5 标准流程(submit_bio 风格,最简单):

cpp 复制代码
static int myblk_init(void)
{
	struct queue_limits lim = {
		.logical_block_size = 512,
		.physical_block_size = 512,
		.max_hw_sectors = 1024,
	};
	struct gendisk *disk;

	disk = blk_alloc_disk(&lim, NUMA_NO_NODE);
	if (IS_ERR(disk))
		return PTR_ERR(disk);

	disk->major = myblk_major;
	disk->first_minor = 0;
	disk->minors = 16;
	disk->fops = &myblk_fops;
	disk->private_data = mydev;
	strscpy(disk->disk_name, "myblk0", DISK_NAME_LEN);
	set_capacity(disk, MYBLK_SIZE_SECTORS);

	add_disk(disk);
	return 0;
}

blk-mq 风格(需要请求队列时):

cpp 复制代码
static struct blk_mq_tag_set tag_set = {
	.ops        = &my_mq_ops,
	.nr_hw_queues = 1,
	.queue_depth  = 128,
	.numa_node    = NUMA_NO_NODE,
	.cmd_size     = sizeof(struct my_cmd),
};

static int myblk_mq_init(void)
{
	struct gendisk *disk;
	int ret;

	ret = blk_mq_alloc_tag_set(&tag_set);
	if (ret)
			return ret;

	disk = blk_mq_alloc_disk(&tag_set, &lim, mydev);
	if (IS_ERR(disk)) {
			blk_mq_free_tag_set(&tag_set);
			return PTR_ERR(disk);
	}
	...
	add_disk(disk);
	return 0;
}

13.4 块设备的打开与释放

cpp 复制代码
static int myblk_open(struct gendisk *disk, blk_mode_t mode)
{
	struct myblk_dev *dev = disk->private_data;

	if (mode & BLK_OPEN_WRITE && dev->read_only)
		return -EROFS;

	return 0;
}

static void myblk_release(struct gendisk *disk)
{
	/* 递减打开计数、释放独占资源 */
}

注意:

  • 7.2.5 的 open 第一个参数是 struct gendisk *(不再是 struct block_device *)。
  • open/release 会成对 调用,但 release 在最后一次关闭时才调用。
  • 块设备通常不需要自己维护引用计数,内核的 bd_holder 机制会处理。

13.5 块设备驱动的 ioctl 函数

cpp 复制代码
static int myblk_ioctl(struct block_device *bdev, blk_mode_t mode,
                       unsigned int cmd, unsigned long arg)
{
	struct gendisk *disk = bdev->bd_disk;
	struct myblk_dev *dev = disk->private_data;

	switch (cmd) {
	case BLKFLSBUF:            /* 刷新缓冲 */
		return 0;
	case BLKROSET: {           /* 设置只读 */
		int ro;
		if (get_user(ro, (int __user *)arg))
				return -EFAULT;
		dev->read_only = ro;
		return 0;
	}
	default:
		return -ENOTTY;
	}
}

7.2.5 要点:

  • 很多标准 ioctl 已由块层通用代码处理 (如分区表、BLKGETSIZE64、BLKRRPART)。驱动只需处理设备特有的。
  • 如果驱动不实现 ioctl,块层会返回 -ENOTTY,通用 ioctl 仍可用。
  • 必须用 blkdev_compat_ptr_ioctl 处理 32 位兼容层(如果需要)。
  • 返回 -ENOTTY 而非 -EINVAL 表示"不支持该命令"。

13.6 块设备驱动的 I/O 请求处理

13.6.1 使用请求队列(blk-mq)

cpp 复制代码
static blk_status_t my_queue_rq(struct blk_mq_hw_ctx *hctx,
                                const struct blk_mq_queue_data *bd)
{
	struct request *rq = bd->rq;
	struct myblk_dev *dev = rq->q->queuedata;
	blk_status_t status;

	blk_mq_start_request(rq);          /* 必须调用,标记请求已开始 */

	status = process_request(dev, rq); /* 硬件操作,通常是异步的 */

	if (status != BLK_STS_OK) {
		blk_mq_end_request(rq, status);   /* 失败立即完成 */
		return BLK_STS_OK;
	}

	/* 异步场景:在中断/完成回调中 blk_mq_end_request(rq, BLK_STS_OK) */
	return BLK_STS_OK;
}

static const struct blk_mq_ops my_mq_ops = {
	.queue_rq = my_queue_rq,
	.complete = my_complete,           /* 可选:请求完成回调 */
	.init_request = my_init_request,   /* 可选 */
	.exit_request = my_exit_request,   /* 可选 */
};

关键规则:

  • 必须调用 blk_mq_start_request(),否则超时/统计/错误处理都不正确。
  • 必须调用 blk_mq_end_request() ,否则请求永远不完成,mount/dd 会挂死。
  • queue_rq() 可能在原子上下文被调用,因此内部不能睡眠。
  • 返回 BLK_STS_OK 表示"已接受",返回 BLK_STS_RESOURCE 表示"资源不足,稍后重试"。
  • 7.2.5 新增 .queue_rqs(批量提交),能显著提升性能。

blk_status_t 常见值 (include/linux/blk_types.h):

值 含义
BLK_STS_OK 成功
BLK_STS_NOTSUPP 不支持
BLK_STS_TIMEOUT 超时
BLK_STS_NOSPC 空间不足
BLK_STS_TRANSPORT 传输错误
BLK_STS_TARGET 目标错误
BLK_STS_NEXUS 路径错误
BLK_STS_MEDIUM 介质错误
BLK_STS_PROTECTION 保护信息错误
BLK_STS_RESOURCE 资源不足,可重试
BLK_STS_IOERR 通用 I/O 错误

13.6.2 不使用请求队列(submit_bio)

这是7.2.5 最简单、最推荐的虚拟块设备写法 ,参考 drivers/block/brd.c:

cpp 复制代码
static void myblk_submit_bio(struct bio *bio)
{
	struct myblk_dev *dev = bio->bi_bdev->bd_disk->private_data;
	struct bvec_iter iter;
	struct bio_vec bvec;
	sector_t sector = bio->bi_iter.bi_sector;

	bio_for_each_segment(bvec, bio, iter) {
		unsigned int len = bvec.bv_len;
		void *iovec = page_address(bvec.bv_page) + bvec.bv_offset;

		if (op_is_write(bio_op(bio)))
				memcpy(dev->data + (sector << SECTOR_SHIFT), iovec, len);
		else
				memcpy(iovec, dev->data + (sector << SECTOR_SHIFT), len);

		sector += len >> SECTOR_SHIFT;
	}

	bio_endio(bio);          /* 完成,无错误 */
}

brd.c 的完整初始化流程(7.2.5):

cpp 复制代码
static int brd_alloc(int i)
{
	struct brd_device *brd;
	struct gendisk *disk;
	struct queue_limits lim = {
		.physical_block_size    = PAGE_SIZE,
		.max_hw_discard_sectors = UINT_MAX,
		.max_discard_segments   = 1,
		.discard_granularity    = PAGE_SIZE,
		.features               = BLK_FEAT_SYNCHRONOUS | BLK_FEAT_NOWAIT,
	};

	brd = brd_find_or_alloc_device(i);
	if (IS_ERR(brd))
		return PTR_ERR(brd);

	disk = brd->brd_disk = blk_alloc_disk(&lim, NUMA_NO_NODE);
	if (IS_ERR(disk))
		goto out_free_dev;

	disk->major          = RAMDISK_MAJOR;
	disk->first_minor    = i * max_part;
	disk->minors         = max_part;
	disk->fops           = &brd_fops;
	disk->private_data   = brd;
	strscpy(disk->disk_name, buf, DISK_NAME_LEN);
	set_capacity(disk, rd_size * 2);

	err = add_disk(disk);
	...
}

卸载流程:

cpp 复制代码
static void brd_free_disk(struct gendisk *disk)
{
	struct brd_device *brd = disk->private_data;

	del_gendisk(disk);
	put_disk(disk);
	...
}

13.7 实例:vmem_disk 驱动

13.7.1 vmem_disk 的硬件原理

原书的 vmem_disk 用 vmalloc 分配的内存 模拟一个块设备(而非 brd 的页数组)。7.2.5 版本改用 submit_bio 实现,代码更短更清晰。

13.7.2 vmem_disk 驱动模块的加载与卸载

cpp 复制代码
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>

#define VMLINUX_BLK_SIZE   (1024 * 1024)     /* 1MB */
#define VMLINUX_SECTORS    (VMLINUX_BLK_SIZE >> SECTOR_SHIFT)

static int vmem_major;
static struct gendisk *vmem_disk;
static unsigned char *vmem_data;
static struct queue_limits vmem_lim = {
	.logical_block_size  = 512,
	.physical_block_size = 512,
	.max_hw_sectors      = 256,
};

static int vmem_open(struct gendisk *disk, blk_mode_t mode)
{
	pr_info("vmem_disk: open\n");
	return 0;
}

static void vmem_release(struct gendisk *disk)
{
	pr_info("vmem_disk: release\n");
}

static void vmem_submit_bio(struct bio *bio)
{
	struct bvec_iter iter;
	struct bio_vec bvec;
	sector_t sector = bio->bi_iter.bi_sector;
	unsigned long offset = sector << SECTOR_SHIFT;

	bio_for_each_segment(bvec, bio, iter) {
		void *buf = page_address(bvec.bv_page) + bvec.bv_offset;
		unsigned int len = bvec.bv_len;

		if (offset + len > VMLINUX_BLK_SIZE) {
			bio_io_error(bio);
			return;
		}

		if (op_is_write(bio_op(bio)))
			memcpy(vmem_data + offset, buf, len);
		else
			memcpy(buf, vmem_data + offset, len);

		offset += len;
	}

	bio_endio(bio);
}

static const struct block_device_operations vmem_fops = {
	.owner       = THIS_MODULE,
	.open        = vmem_open,
	.release     = vmem_release,
	.submit_bio  = vmem_submit_bio,
};

加载与卸载:

cpp 复制代码
static int __init vmem_init(void)
{
	int ret;

	vmem_data = vzalloc(VMLINUX_BLK_SIZE);
	if (!vmem_data)
		return -ENOMEM;

	ret = register_blkdev(0, "vmemdisk");
	if (ret < 0)
		goto err_free;
	vmem_major = ret;

	vmem_disk = blk_alloc_disk(&vmem_lim, NUMA_NO_NODE);
	if (IS_ERR(vmem_disk)) {
		ret = PTR_ERR(vmem_disk);
		goto err_unreg;
	}

	vmem_disk->major       = vmem_major;
	vmem_disk->first_minor = 0;
	vmem_disk->minors      = 1;
	vmem_disk->fops        = &vmem_fops;
	strscpy(vmem_disk->disk_name, "vmemdisk", DISK_NAME_LEN);
	set_capacity(vmem_disk, VMLINUX_SECTORS);

	ret = add_disk(vmem_disk);
	if (ret)
		goto err_put;

	pr_info("vmem_disk: loaded, major=%d\n", vmem_major);
	return 0;

err_put:
	put_disk(vmem_disk);
err_unreg:
	unregister_blkdev(vmem_major, "vmemdisk");
err_free:
	vfree(vmem_data);
	return ret;
}

static void __exit vmem_exit(void)
{
	del_gendisk(vmem_disk);
	put_disk(vmem_disk);
	unregister_blkdev(vmem_major, "vmemdisk");
	vfree(vmem_data);
	pr_info("vmem_disk: unloaded\n");
}

module_init(vmem_init);
module_exit(vmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("vmem_disk: virtual block device, Linux 7.2.5");

13.7.3 vmem_disk 设备驱动的 block_device_operations

见上面的 vmem_fops。要点:

  • .owner = THIS_MODULE 让块层自动维护模块引用计数。
  • .open/.release 可选,但建议实现以便观察生命周期。
  • .submit_bio 是 7.2.5 最简单的 I/O 入口。

13.7.4 vmem_disk 的 I/O 请求处理

vmem_submit_bio() 的实现要点:

  1. 用 bio_for_each_segment() 遍历,不要假设 bio 只有一个段。
  2. 用 op_is_write(bio_op(bio)) 判断方向 ,不要直接比较 bi_opf。
  3. 越界必须 bio_io_error(bio) 并返回,不能继续拷贝。
  4. 完成后必须 bio_endio(bio)。
  5. 段内偏移是 bvec.bv_offset,不要忘记。

QEMU 验证:

bash 复制代码
insmod vmemdisk.ko
lsblk
cat /proc/partitions
dd if=/dev/zero of=/dev/vmemdisk bs=1k count=8
dd if=/dev/vmemdisk of=/dev/null bs=1k count=8
mkfs.ext2 /dev/vmemdisk       # 需要 CONFIG_EXT2_FS
mkdir /mnt/vmem && mount /dev/vmemdisk /mnt/vmem
echo hello > /mnt/vmem/test.txt
cat /mnt/vmem/test.txt
umount /mnt/vmem
dmesg | tail -10
rmmod vmemdisk

观察点:

  • lsblk 显示 vmemdisk 1M;
  • dd 读写成功且无错误;
  • mkfs/mount 证明块设备语义正确(这是块设备驱动的"及格线");
  • rmmod 后 dmesg 无 UAF。

13.8 Linux MMC 子系统

MMC 子系统是块设备驱动的分层范例:

复制代码
用户空间 (/dev/mmcblk0)
   -> block layer
   -> mmc_blk (drivers/mmc/core/block.c)   ------ 块设备例化
   -> mmc core (drivers/mmc/core/)          ------ 核心层:命令、卡识别
   -> mmc host (drivers/mmc/host/)          ------ 主机控制器驱动
   -> 硬件

关键结构:

结构 头文件 作用
struct mmc_host include/linux/mmc/host.h 主机控制器
struct mmc_host_ops 同上 主机操作:request、set_ios、get_ro
struct mmc_card include/linux/mmc/card.h 卡
struct mmc_request / mmc_command / mmc_data include/linux/mmc/core.h 请求/命令/数据
struct mmc_driver include/linux/mmc/driver.h 卡驱动

关键 API:

cpp 复制代码
struct mmc_host *mmc_alloc_host(int extra, struct device *dev);
int mmc_add_host(struct mmc_host *host);
void mmc_remove_host(struct mmc_host *host);
void mmc_free_host(struct mmc_host *host);
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq);

QEMU 验证:

bash 复制代码
# QEMU 自带 SD 卡控制器
qemu-system-x86_64 ... -device sdhci-pci -drive file=sd.img,if=sd,format=raw
# guest 中
ls /sys/class/mmc_host/
ls /dev/mmcblk*
cat /sys/block/mmcblk0/size

7.2.5 要点:

  • mmc_host_ops.request 是唯一的同步入口,由核心层串行化。
  • 必须正确实现 set_ios()(时钟、总线宽度、电压)。
  • 7.2.5 支持 mmc_cqe_ops(命令队列)、HS400、UHS-II 等高速模式。
  • 与 SDIO 的区别:SDIO 走 mmc_io 接口而非块设备(见 drivers/mmc/core/sdio*.c)。

13.9 常见错误

错误 现象 修正
open 签名仍是 block_device*, fmode_t 编译错误 改成 gendisk*, blk_mode_t
release 仍有两个参数 编译错误 改成 gendisk* 单参数
make_request/request_fn 编译失败(已删除) 改用 submit_bio 或 queue_rq
忘记 blk_mq_start_request() 超时/统计异常 必须调用
忘记 blk_mq_end_request() I/O 永久挂起 成功/失败都要完成
bio_for_each_segment 中忘记偏移 数据错位 用 bvec.bv_offset
越界未 bio_io_error 堆破坏 检查范围
用 alloc_disk() 编译失败 改用 blk_alloc_disk(&lim, node)
卸载只 put_disk() 不 del_gendisk() 设备仍可见 先 del_gendisk()
set_capacity() 单位错误 容量错误 单位是 512B 扇区
忘记 register_blkdev() 设备号冲突 动态分配主设备号

13.10 本章实验清单

实验 目的 关键观察
vmem_disk(submit_bio) 学习最简单的块设备 lsblk 可见,dd 成功
mkfs + mount 验证块设备语义 文件系统可挂载
blk-mq 版本 学习请求队列 queue_rq 被调用
对比 brd 学习内核参考实现 modprobe brd rd_nr=2 rd_size=1024
调度器切换 理解 I/O 调度 /sys/block/*/queue/scheduler
virtio-blk 观察 学习真实驱动 blktrace/iostat
MMC/SD 实验 学习分层子系统 /dev/mmcblk0 出现

13.11 总结

  1. 块设备的核心路径是 VFS → 页缓存 → bio → blk-mq → 驱动。
  2. 7.2.5 中 make_request 与 request_fn 已删除;新驱动用 submit_bio(简单)或 queue_rq(需要队列时)。
  3. blk_alloc_disk(&lim, node) 取代 alloc_disk(),队列限制通过 struct queue_limits 描述。
  4. open/release/ioctl 的参数类型已改为 gendisk/blk_mode_t。
  5. blk_mq_start_request() 与 blk_mq_end_request() 必须成对出现。
  6. I/O 调度器只剩 none/mq-deadline/kyber/bfq。
  7. MMC 子系统是"块设备 + 主机驱动/核心层分离"的最佳范例。
  8. QEMU 中用内存模拟块设备(vmem_disk/brd)即可完整验证原书的全部知识点。
相关推荐
Snasph4 小时前
15- Linux I2C 核心、总线与设备驱动
linux 驱动学习
Snasph4 小时前
附录-API 速查与术语表
linux 驱动学习
Snasph4 小时前
11-内存与 I/O 访问
linux 驱动学习
Snasph6 小时前
14-Linux 网络设备驱动
linux 驱动学习
Snasph7 小时前
18-ARM Linux 设备树
linux 驱动学习
Snasph7 小时前
20-Linux 芯片级移植及底层驱动
linux 驱动学习
Snasph8 小时前
12- Linux 设备驱动的软件架构思想
linux 驱动学习
Snasph9 小时前
23-从 Linux 4.0 到 7.2.5 的迁移
linux 驱动学习
Snasph11 天前
01-设备驱动概述与开发环境构建
linux 驱动学习