块设备驱动
一、块设备基础
- 按固定大小"块"访问的设备(磁盘、Flash、SD 卡)
- 与字符设备区别:可随机访问、有缓冲区
- 内核用
struct block_device/struct gendisk表示
二、关键数据结构
1. bio(Block I/O)
struct bio {
sector_t bi_sector; // 起始扇区
struct bio_vec *bi_io_vec; // 段数组
unsigned int bi_vcnt; // 段数
unsigned int bi_idx; // 当前段
unsigned int bi_size;
struct block_device *bi_bdev;
// ...
};
struct bio_vec {
struct page *bv_page;
unsigned int bv_offset;
unsigned int bv_len;
};
- 一个 bio 可包含多个 bio_vec(多段)
- 由高层构造,传递给驱动
2. request / request_queue
struct request {
struct list_head queuelist;
sector_t sector;
unsigned long nr_sectors;
struct bio *bio;
// ...
};
struct request_queue {
struct elevator_queue *elevator;
// ...
};
- 老接口:bio 被合并成 request,IO 调度器排序
- 新接口(blk-mq):bio 直接提交
3. gendisk
struct gendisk {
int major;
int first_minor;
int minors; // 次设备数量
char disk_name[DISK_NAME_LEN];
struct block_device_operations *fops;
struct request_queue *queue;
// ...
};
三、传统方式(blk_init_queue)
static struct request_queue *queue;
static struct gendisk *gd;
static DEFINE_SPINLOCK(lock);
static void my_request(struct request_queue *q)
{
struct request *rq;
while ((rq = blk_fetch_request(q)) != NULL) {
struct bio *bio;
sector_t sector = blk_rq_pos(rq);
unsigned int nr = blk_rq_cur_sectors(rq);
// 遍历 bio
__rq_for_each_bio(bio, rq) {
// 处理 bio(map、transfer、copy)
}
if (!__blk_end_request_cur(rq, 0))
continue;
}
}
static int my_init(void)
{
queue = blk_init_queue(my_request, &lock);
blk_queue_logical_block_size(queue, 512);
gd = alloc_disk(1);
gd->major = MY_MAJOR;
gd->first_minor = 0;
gd->fops = &my_fops;
gd->queue = queue;
snprintf(gd->disk_name, 32, "myblk");
set_capacity(gd, NR_SECTORS);
add_disk(gd);
return 0;
}
四、现代方式(blk-mq,推荐)
static int my_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *rq = bd->rq;
blk_mq_start_request(rq);
// 异步处理
// 完成时:
blk_mq_end_request(rq, 0);
return BLK_MQ_RQ_QUEUE_OK;
}
static struct blk_mq_ops my_mq_ops = {
.queue_rq = my_queue_rq,
};
static const struct block_device_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
};
static int my_init(void)
{
struct blk_mq_tag_set tag_set = {
.ops = &my_mq_ops,
.nr_hw_queues = 1,
.nr_maps = 1,
.queue_depth = 128,
};
blk_mq_alloc_tag_set(&tag_set);
queue = blk_mq_init_queue(&tag_set);
blk_queue_logical_block_size(queue, 512);
gd = blk_mq_alloc_disk(&tag_set, NULL);
gd->fops = &my_fops;
set_capacity(gd, NR_SECTORS);
add_disk(gd);
return 0;
}
五、提交 bio(绕过 IO 调度)
// 直接提交 bio(如 NVMe 驱动)
void submit_bio(struct bio *bio);
六、bio 处理
static int my_xfer_bio(struct bio *bio)
{
struct bio_vec bvec;
struct bvec_iter iter;
bio_for_each_segment(bvec, bio, iter) {
void *buf = kmap(bvec.bv_page) + bvec.bv_offset;
sector_t sector = iter.bi_sector;
unsigned int len = bvec.bv_len;
// 读 / 写 buf
kunmap(bvec.bv_page);
}
return 0;
}
七、块设备操作
static const struct block_device_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.ioctl = my_ioctl,
.getgeo = my_getgeo,
.revalidate_disk = my_revalidate,
};
八、嵌入式应用
- MTD(Memory Technology Device):NAND/NOR
- MMC(MultiMediaCard):SD/eMMC
- NVMe:PCIe SSD
- UBI:NAND 抽象层
- ramdisk / loop:内存/文件当块设备
嵌入式开发一般不直接写块设备驱动,但理解原理能帮调试。
九、调试
lsblk
cat /proc/partitions
cat /sys/block/sda/queue/scheduler # io scheduler
iostat -x 1
cat /sys/block/sda/stat
cat /proc/diskstats
blktrace / blkparse