想要写出一个驱动,最好的办法是先从一个已经可以正常运行的驱动看看别人是怎么写的,所以这篇文章记录以下Android内核里目前挂载data分区的设备驱动,virtio_blk驱动究竟是怎样运作起来的。
若无特殊标注,代码可能位于:
common/drivers/block/virtio_blk.c
如果不在此路径,请搜索。
首先找到驱动的入口函数,也就是__init标记的初始化函数:
static int __init init(void)
{
int error;
virtblk_wq = alloc_workqueue("virtio-blk", 0, 0);
if (!virtblk_wq)
return -ENOMEM;
major = register_blkdev(0, "virtblk");
if (major < 0) {
error = major;
goto out_destroy_workqueue;
}
error = register_virtio_driver(&virtio_blk);
if (error)
goto out_unregister_blkdev;
return 0;
out_unregister_blkdev:
unregister_blkdev(major, "virtblk");
out_destroy_workqueue:
destroy_workqueue(virtblk_wq);
return error;
}
这个函数做三件事,第一件事,创建了这个驱动的单独的工作队列,第二件事,注册了一个块设备,第三件事,注册了这个驱动。非常清晰的初始化过程。我们逐件分析。
工作队列
在内核代码中,经常会遇到不能或不合适去马上调用某个处理过程,此时希望将该工作推送给某个内核线程执行,这样做的原因有很多,比如:
1.中断触发了某个过程的执行条件,而该过程执行时间较长或者会调用导致睡眠的函数,则该过程不应该在中断上下文中立即被调用。
2.类似于中断,一些紧急性的任务不希望执行比较耗时的非关键过程,则需要把该过程提交到低优先级线程执行。比如一个轮询的通信接收线程,它需要快速完成检测和接收数据,而对数据的解析则应该交由低优先级线程慢慢处理。
3.有时希望将一些工作集中起来以获取批处理的性能;或则合并缩减一些执行线程,减少资源消耗。
基于以上需求,人们开发出了工作队列这一机制。工作队列不光在操作系统内核中会用到,一些应用程序或协议栈也会实现自己的工作队列。
概念
工作队列 ( workqueue )是将操作(或回调)延期异步执行的一种机制。工作队列可以把工作推后,交由一个内核线程去执行,并且工作队列是执行在线程上下文中,因此工作执行过程中可以被重新调度、抢占、睡眠。
工作项(work item)是工作队列中的元素,是一个回调函数和多个回调函数输入参数的集合,有时也会有额外的属性成员,总之通过一个结构体即可记录和描述一个工作项。
特性
通过工作队列来执行一个工作相比于直接执行,会有一下特性:
1.异步,工作不是在本中断或线程中执行,而是交由其他线程执行。
2.延期,交由低优先级线程执行,执行前可能被抢占,也可能有其他工作排在前面执行,所以从提交工作队列到工作真正被执行之间会延时不定长时间。
3.排队,FIFO 模式先到先执行。也肯会有多个优先级的工作队列,低优先级的工作队列要等到高优先级的工作队列全部执行完成后才能执行。但同一个工作队列内部的各项都是按时间先后顺序执行的额,不会进行钱赞重排。
4. 缓存,既然是队列它就能缓存多个项,需要异步执行丢进去就行,队列会逐个执行。虽然工作队列能缓存多个项,但也是有上限的当队列已满时,新的入队项就会被丢弃,丢弃的个数会被统计下来。
实现
队列本身可以通过数组加索引实现也可以通过链表来实现,但鉴于工作队列不存在抢占重排序的问题,则通过数组加索引的方式会更高效简洁。
工作项可以通过一个结构体来定义,则工作队列的主体就是一个结构体数组。同时还有一些数据用来描述该结构体,如:队列深度,入队指针,出队指针,丢弃统计,错误统计等。
工作队列实现的关键操作:
创建和删除工作队列
清空队列
入队,或添加工作
出队,或执行工作
统计显示队列状态,包括丢弃个数
删除或取消已入队但还未执行的工作
采用工作队列机制,就是说一件事我们不需要自己去完成,而是往里塞,CPU大哥有空的时候就会去完成,塞进去之后咱也就不管了。
第二件事是注册了一个virtblk的块设备,暂时还揣测不出意图是什么,第三件事就是完成了驱动virtblk的注册,我们来看看注册了个什么玩意:
static struct virtio_driver virtio_blk = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.feature_table_legacy = features_legacy,
.feature_table_size_legacy = ARRAY_SIZE(features_legacy),
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtblk_probe,
.remove = virtblk_remove,
.config_changed = virtblk_config_changed,
#ifdef CONFIG_PM_SLEEP
.freeze = virtblk_freeze,
.restore = virtblk_restore,
#endif
};
就是完成了自己定义的驱动的注册过程,这个结构里面规定了这个驱动的功能表、功能表大小、驱动名、owner、设备探测时调用的probe方法、设备移除时调用的remove方法,设备配置发生变更时的config_changed方法、电源进入待机或休眠时调用的freeze方法,恢复时的restore方法等,驱动注册也就是告诉操作系统,咱们执行什么动作时需要调用什么函数就好了。
但是我们也要观察到virtio_driver并不是驱动模型中的device_driver,它应该是经历了一次封装,我们找到该结构定义的地方:
//common/include/linux/virtio.h
struct virtio_driver {
struct device_driver driver;
const struct virtio_device_id *id_table;
const unsigned int *feature_table;
unsigned int feature_table_size;
const unsigned int *feature_table_legacy;
unsigned int feature_table_size_legacy;
int (*validate)(struct virtio_device *dev);
int (*probe)(struct virtio_device *dev);
void (*scan)(struct virtio_device *dev);
void (*remove)(struct virtio_device *dev);
void (*config_changed)(struct virtio_device *dev);
#ifdef CONFIG_PM
int (*freeze)(struct virtio_device *dev);
int (*restore)(struct virtio_device *dev);
#endif
};
可以发现virtio_driver里面确实包含了一个device_driver结构。目前我们只完成了对这个device_driver的name和owner的设置,我们可以猜测在register_virtio_driver这个virtio驱动注册函数里面,可能包含了对这个device_driver结构的一些其他的初始化,如果是,那么这一块就是一个通用逻辑,并不需要我们处理了:
///common/drivers/virtio/virtio.c
int register_virtio_driver(struct virtio_driver *driver)
{
/* Catch this early. */
BUG_ON(driver->feature_table_size && !driver->feature_table);
driver->driver.bus = &virtio_bus;
return driver_register(&driver->driver);
}
也就是将那个驱动挂到了virtio_bus上,仅此而已,我们不妨也再分析一下driver_register这个函数看看它做了些什么:
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
if (!drv->bus->p) {
pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
drv->name, drv->bus->name);
return -EINVAL;
}
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
pr_warn("Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus);
if (other) {
pr_err("Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv);
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
从这个函数我们知道,在一个bus上是通过name来判断唯一性的。
因此,分析到这里,我们可以知道:
对于virtio_blk这样的virtio驱动,其内部的device_driver我们仅需要完成name和owner的赋值就好。
紧接着,我们继续分析它各个方法的实现。
probe方法:
probe方法会在完成匹配后被调用,通常用于设备的初始化:
probe-part1
static int virtblk_probe(struct virtio_device *vdev)
{
struct virtio_blk *vblk;
struct request_queue *q;
int err, index;
u32 v, blk_size, max_size, sg_elems, opt_io_size;
u16 min_io_size;
u8 physical_block_exp, alignment_offset;
unsigned int queue_depth;
函数首先对要用到的变量进行声明,在内核中使用的C标准要求对变量的声明必须要放在函数的最前面。
probe-part2
if (!vdev->config->get) {
dev_err(&vdev->dev, "%s failure: config access disabled\n",
__func__);
return -EINVAL;
}
紧接着判断了被绑定设备的config中的get方法,这个方法是干嘛的呢,首先看virtio_device设备的定义:
//common/include/linux/virtio.h
struct virtio_device {
int index; // 在virtio_bus上的唯一位置
bool failed; // restore操作会使用到
bool config_enabled; // 配置改变报告的开关
bool config_change_pending; // 配置改变已经报告的标志位
spinlock_t config_lock; // 配置改变报告时使用的锁
spinlock_t vqs_list_lock; /* Protects VQs list access */
struct device dev; // virtio_device内部封装的device
struct virtio_device_id id; // id用来match,前面的推送有分析过
const struct virtio_config_ops *config; // 该设备的配置操作
const struct vringh_config_ops *vringh_config;
struct list_head vqs; // 这个设备对应的virtqueue链表
u64 features; // 设备和驱动同时支持的功能
void *priv; // driver可能用到的私有指针
};
所以我们这里是去调用了配置操作里面的get操作。那这些配置操作都有些啥呢,我们又进入到virtio_config_ops结构中:
//common/include/linux/virtio_config.h
struct virtio_config_ops {
void (*enable_cbs)(struct virtio_device *vdev);
void (*get)(struct virtio_device *vdev, unsigned offset,
void *buf, unsigned len);
void (*set)(struct virtio_device *vdev, unsigned offset,
const void *buf, unsigned len);
u32 (*generation)(struct virtio_device *vdev);
u8 (*get_status)(struct virtio_device *vdev);
void (*set_status)(struct virtio_device *vdev, u8 status);
void (*reset)(struct virtio_device *vdev);
int (*find_vqs)(struct virtio_device *, unsigned nvqs,
struct virtqueue *vqs[], vq_callback_t *callbacks[],
const char * const names[], const bool *ctx,
struct irq_affinity *desc);
void (*del_vqs)(struct virtio_device *);
u64 (*get_features)(struct virtio_device *vdev);
int (*finalize_features)(struct virtio_device *vdev);
const char *(*bus_name)(struct virtio_device *vdev);
int (*set_vq_affinity)(struct virtqueue *vq,
const struct cpumask *cpu_mask);
const struct cpumask *(*get_vq_affinity)(struct virtio_device *vdev,
int index);
bool (*get_shm_region)(struct virtio_device *vdev,
struct virtio_shm_region *region, u8 id);
};
在这个操作表上面,有一段注释是这么说的:不要假设传入所有的函数实现都是在简单是获取或者设置一个值,也就是对于上面的get、set等操作来说在一个原子化的上下文中它也可能是不安全的。
@enable_cbs:启用回调
vdev 该virtio_device
@get:读取一个配置域的值
vdev 该virtio_device
offset 在配置域中的偏移
buf 将值写入buf
len buf区长度
@set:设置一个配置域的值
vdev 该virtio_device
offset 在配置域中的偏移
buf 从buf中读入
len buf区长度
@generation: 配置生成计数器(可选)
vdev 该virtio_device
返回值 返回配置生成计数
@get_status:读取状态码字节
vdev 该virtio_device
返回值 状态码字节
@set_status:设置状态码字节
vdev 该virtio_device
返回值 状态码字节
@reset:重置设备
vdev 该virtio_device
该操作过后,status和feature协商需要重新完成,
设备不能在它的virtqueue和config的回调函数中被重置,
也不能与添加或移除操作并行地调用
@find_vqs:发现并实例化virtqueues
@del_vqs:释放掉使用find_vqs找到的virtqueue
@get_features:获取该设备的功能比特数组
vdev 该virtio_device
返回值 the first 64 feature bits (all we currently need)
@finalize_features:确认我们将会使用的功能
vdev 该virtio_device
这个功能将会发送给设备驱动支持的功能比特位,
设备如果想,可以修改dev->feature,尽管说是确认,但是该函数可以被反复调用
@bus_name:返回关联设备的总线名
其余略
因此,probe-part2中的函数确认了获取设备配置域的方法已经被成功配置,以确保接下来的操作可以被正确进行。
probe-part3
err = ida_simple_get(&vd_index_ida, 0, minor_to_index(1 << MINORBITS),
GFP_KERNEL);
if (err < 0)
goto out;
index = err;
这是一个id生成器的操作,它将顺序产生一个序列,作为index。
probe-part4
/* We need to know how many segments before we allocate. */
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_SEG_MAX,
struct virtio_blk_config, seg_max,
&sg_elems);
这个函数是一个宏函数:
/* Conditional config space accessors. */
#define virtio_cread_feature(vdev, fbit, structname, member, ptr) \
({ \
int _r = 0; \
if (!virtio_has_feature(vdev, fbit)) \
_r = -ENOENT; \
else \
virtio_cread((vdev), structname, member, ptr); \
_r; \
})
宏函数中最后一个表达式的值作为宏函数的返回值,在上面这个函数中返回值是_r。首先检查是否该vdev支持这个功能,也就是检验某一位bit,如果支持,则调用virtio_cread宏函数,他返回负数表示该功能不支持:
#define virtio_cread(vdev, structname, member, ptr) \
do { \
typeof(((structname*)0)->member) virtio_cread_v; \
\
might_sleep(); \
/* Sanity check: must match the member's type */ \
typecheck(typeof(virtio_to_cpu((vdev), virtio_cread_v)), *(ptr)); \
\
switch (sizeof(virtio_cread_v)) { \
case 1: \
case 2: \
case 4: \
vdev->config->get((vdev), \
offsetof(structname, member), \
&virtio_cread_v, \
sizeof(virtio_cread_v)); \
break; \
default: \
__virtio_cread_many((vdev), \
offsetof(structname, member), \
&virtio_cread_v, \
1, \
sizeof(virtio_cread_v)); \
break; \
} \
*(ptr) = virtio_to_cpu(vdev, virtio_cread_v); \
} while(0)
这里有个typeof函数,第三行这一句话的意思其实是定义了一个structname的member成员的数据类型,作为之后调用get函数的buf区域。由于以上都是宏函数,本质上还是在原来的地方调用,不存在形式参数的复制之类的问题,第26行将virtio_cread_v的值存到了ptr指针区域,也就是sg_elems中。
probe-part5
/* We need at least one SG element, whatever they say. */
if (err || !sg_elems)
sg_elems = 1;
/* Prevent integer overflows and honor max vq size */
sg_elems = min_t(u32, sg_elems, VIRTIO_BLK_MAX_SG_ELEMS - 2);
/* We need extra sg elements at head and tail. */
sg_elems += 2;
紧接着3、4行是对sg_elems和err的判断,err不为0表示VIRTIO_BLK_F_SEG_MAX这个功能不受支持,sg_elems则是从设备配置空间中读取的,如果读出来是0,也要置成1。
probe-part6
vdev->priv = vblk = kmalloc(sizeof(*vblk), GFP_KERNEL);
if (!vblk) {
err = -ENOMEM;
goto out_free_index;
}
/* This reference is dropped in virtblk_remove(). */
refcount_set(&vblk->refs, 1); // 引用计数
mutex_init(&vblk->vdev_mutex); // 初始化锁
vblk->vdev = vdev;
vblk->sg_elems = sg_elems;
INIT_WORK(&vblk->config_work, virtblk_config_changed_work);
第2行把这个vdev设备的私有指针绑定到virtio_blk结构,第15行绑定了工作队列机制里面的一个work的回调函数。
probe-part7
err = init_vq(vblk);
if (err)
goto out_free_vblk;
/* Default queue sizing is to fill the ring. */
if (!virtblk_queue_depth) {
queue_depth = vblk->vqs[0].vq->num_free;
/* ... but without indirect descs, we use 2 descs per req */
if (!virtio_has_feature(vdev, VIRTIO_RING_F_INDIRECT_DESC))
queue_depth /= 2;
} else {
queue_depth = virtblk_queue_depth;
}
初始化virtqueue,并设置好深度。接着对virtio_blk的相应成员进行了配置。
probe-part8
memset(&vblk->tag_set, 0, sizeof(vblk->tag_set));
vblk->tag_set.ops = &virtio_mq_ops;
vblk->tag_set.queue_depth = queue_depth;
vblk->tag_set.numa_node = NUMA_NO_NODE;
vblk->tag_set.flags = BLK_MQ_F_SHOULD_MERGE;
vblk->tag_set.cmd_size =
sizeof(struct virtblk_req) +
sizeof(struct scatterlist) * sg_elems;
vblk->tag_set.driver_data = vblk;
vblk->tag_set.nr_hw_queues = vblk->num_vqs;
err = blk_mq_alloc_tag_set(&vblk->tag_set);
if (err)
goto out_free_vq;
virtio_blk结构是这样的:
struct virtio_blk {
/*
* This mutex must be held by anything that may run after
* virtblk_remove() sets vblk->vdev to NULL.
*
* blk-mq, virtqueue processing, and sysfs attribute code paths are
* shut down before vblk->vdev is set to NULL and therefore do not need
* to hold this mutex.
*/
struct mutex vdev_mutex; // 锁
struct virtio_device *vdev; // 绑定的vdev
/* The disk structure for the kernel. */
struct gendisk *disk; // 绑定的通用磁盘结构
/* Block layer tags. */
struct blk_mq_tag_set tag_set; // block层的tag,多队列的一些配置项目
/* Process context for config space updates */
struct work_struct config_work; // 工作项,工作队列机制
/*
* Tracks references from block_device_operations open/release and
* virtio_driver probe/remove so this object can be freed once no
* longer in use.
*/
refcount_t refs; // 引用计数
/* What host tells us, plus 2 for header & tailer. */
unsigned int sg_elems; // 段数目,无论如何头部和尾部都有段
/* Ida index - used to track minor number allocations. */
int index;
/* num of vqs */
int num_vqs; // virtqueue 数目
struct virtio_blk_vq *vqs; // 指向virtqueue的指针
};
紧接着初始化通用磁盘:
probe-part9
vblk->disk = blk_mq_alloc_disk(&vblk->tag_set, vblk);
if (IS_ERR(vblk->disk)) {
err = PTR_ERR(vblk->disk);
goto out_free_tags;
}
q = vblk->disk->queue;
virtblk_name_format("vd", index, vblk->disk->disk_name, DISK_NAME_LEN);
vblk->disk->major = major;
vblk->disk->first_minor = index_to_minor(index);
vblk->disk->minors = 1 << PART_BITS;
vblk->disk->private_data = vblk;
vblk->disk->fops = &virtblk_fops;
vblk->disk->flags |= GENHD_FL_EXT_DEVT;
vblk->index = index;
blk_mq_alloc_disk里面利用了vblk->tag_set中的一部分信息,创建了一个磁盘节点(通用磁盘+请求队列),并且格式化了名字为vd*,/data分区使用的vdc磁盘,就是在这里被格式化命名。所以,创建磁盘有关的代码其实已经包装得比较完善了。
probe-part10
/* configure queue flush support */
virtblk_update_cache_mode(vdev);
/* If disk is read-only in the host, the guest should obey */
if (virtio_has_feature(vdev, VIRTIO_BLK_F_RO))
set_disk_ro(vblk->disk, 1);
/* We can handle whatever the host told us to handle. */
blk_queue_max_segments(q, vblk->sg_elems-2);
/* No real sector limit. */
blk_queue_max_hw_sectors(q, -1U);
max_size = virtio_max_dma_size(vdev);
/* Host can optionally specify maximum segment size and number of
* segments. */
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_SIZE_MAX,
struct virtio_blk_config, size_max, &v);
if (!err)
max_size = min(max_size, v);
blk_queue_max_segment_size(q, max_size);
/* Host can optionally specify the block size of the device */
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_BLK_SIZE,
struct virtio_blk_config, blk_size,
&blk_size);
if (!err) {
err = blk_validate_block_size(blk_size);
if (err) {
dev_err(&vdev->dev,
"virtio_blk: invalid block size: 0x%x\n",
blk_size);
goto out_cleanup_disk;
}
blk_queue_logical_block_size(q, blk_size);
} else
blk_size = queue_logical_block_size(q);
/* Use topology information if available */
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_TOPOLOGY,
struct virtio_blk_config, physical_block_exp,
&physical_block_exp);
if (!err && physical_block_exp)
blk_queue_physical_block_size(q,
blk_size * (1 << physical_block_exp));
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_TOPOLOGY,
struct virtio_blk_config, alignment_offset,
&alignment_offset);
if (!err && alignment_offset)
blk_queue_alignment_offset(q, blk_size * alignment_offset);
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_TOPOLOGY,
struct virtio_blk_config, min_io_size,
&min_io_size);
if (!err && min_io_size)
blk_queue_io_min(q, blk_size * min_io_size);
err = virtio_cread_feature(vdev, VIRTIO_BLK_F_TOPOLOGY,
struct virtio_blk_config, opt_io_size,
&opt_io_size);
if (!err && opt_io_size)
blk_queue_io_opt(q, blk_size * opt_io_size);
if (virtio_has_feature(vdev, VIRTIO_BLK_F_DISCARD)) {
q->limits.discard_granularity = blk_size;
virtio_cread(vdev, struct virtio_blk_config,
discard_sector_alignment, &v);
q->limits.discard_alignment = v ? v << SECTOR_SHIFT : 0;
virtio_cread(vdev, struct virtio_blk_config,
max_discard_sectors, &v);
blk_queue_max_discard_sectors(q, v ? v : UINT_MAX);
virtio_cread(vdev, struct virtio_blk_config, max_discard_seg,
&v);
/*
* max_discard_seg == 0 is out of spec but we always
* handled it.
*/
if (!v)
v = sg_elems - 2;
blk_queue_max_discard_segments(q,
min(v, MAX_DISCARD_SEGMENTS));
blk_queue_flag_set(QUEUE_FLAG_DISCARD, q);
}
if (virtio_has_feature(vdev, VIRTIO_BLK_F_WRITE_ZEROES)) {
virtio_cread(vdev, struct virtio_blk_config,
max_write_zeroes_sectors, &v);
blk_queue_max_write_zeroes_sectors(q, v ? v : UINT_MAX);
}
这一部分是通过读取配置空间支持的功能配置,来初始化以支持各个功能。
probe-part11
virtblk_update_capacity(vblk, false);
virtio_device_ready(vdev);
err = device_add_disk(&vdev->dev, vblk->disk, virtblk_attr_groups);
if (err)
goto out_cleanup_disk;
return 0;
out_cleanup_disk:
blk_cleanup_disk(vblk->disk);
out_free_tags:
blk_mq_free_tag_set(&vblk->tag_set);
out_free_vq:
vdev->config->del_vqs(vdev);
kfree(vblk->vqs);
out_free_vblk:
kfree(vblk);
out_free_index:
ida_simple_remove(&vd_index_ida, index);
out:
return err;
}
在所有功能都已经配置好,在最后probe函数会报告设备已经准备好、把磁盘添加到系统,并向调用者(virtio总线)返回0,表示该设备已经绑定了virtio_blk驱动。
remove方法:
拆楼容易建楼难,相比于probe方法,remove方法的代码就简洁许多,主要是释放申请的空间,删除对应的逻辑结构:
static void virtblk_remove(struct virtio_device *vdev)
{
struct virtio_blk *vblk = vdev->priv;
/* Make sure no work handler is accessing the device. */
flush_work(&vblk->config_work);
del_gendisk(vblk->disk); // 删除通用磁盘
blk_cleanup_disk(vblk->disk); // 清理磁盘,主要是清理队列和释放磁盘的引用计数
blk_mq_free_tag_set(&vblk->tag_set); // 释放tag_set结构
mutex_lock(&vblk->vdev_mutex);
/* Stop all the virtqueues. */
vdev->config->reset(vdev); // 调用reset方法来resetvdev
/* Virtqueues are stopped, nothing can use vblk->vdev anymore. */
vblk->vdev = NULL; // 清除绑定关系
vdev->config->del_vqs(vdev); // 调用del_vqs方法来删除virtqueue
kfree(vblk->vqs); // 释放申请空间
mutex_unlock(&vblk->vdev_mutex);
virtblk_put(vblk); // 清除引用计数
}
config_changed方法和电源管理相关方法:
config_changed方法和电源管理相关的方法似乎是另外一些个比较复杂的机制,暂时先不去了解它。
然后以上的方法都实现了以后,将驱动提交注册后,就能为所绑定的设备提供支持,驱动也就能正常运行了。
不过不要忘记了,我们这个驱动是一个磁盘驱动,理论上它应该会有对磁盘的IO操作,那这些操作在哪呢,这些操作是由队列提供的函数完成的,它在probe-part8中被设置:
static const struct blk_mq_ops virtio_mq_ops = {
.queue_rq = virtio_queue_rq,
.commit_rqs = virtio_commit_rqs,
.complete = virtblk_request_done,
.init_request = virtblk_init_request,
.map_queues = virtblk_map_queues,
};
关于这个的细节因为还没有做到这一步,重要但是现在暂时还不需要了解。
完成对virtio_blk驱动的分析之后,对自己实现一个驱动,就有了大致的理解。同时,想要写好驱动,还至少需要去了解virtio的实现逻辑、block层io的逻辑和电源管理的相关知识等,依旧道阻且长哇。