vhost子系统在SPDK中属于应用层或叫协议层,为虚拟机提供vhost-blk、vhost-scsi和vhost-nvme三种虚拟设备。这里我们以vhost-blk为分析对象,来讨论vhost子系统基本原理。
vhost子系统初始化
vhost子系统的描述如下:
spdk/lib/event/subsystems/vhost/vhost.c:
static struct spdk_subsystem g_spdk_subsystem_vhost = {
.name = "vhost",
.init = spdk_vhost_subsystem_init,
.fini = spdk_vhost_subsystem_fini,
.config = NULL,
.write_config_json = spdk_vhost_config_json,
};
static void
spdk_vhost_subsystem_init(void)
{
int rc = 0;
rc = spdk_vhost_init();
spdk_subsystem_init_next(rc);
}
vhost子系统初始化时,会依次偿试对vhost-scsi、vhost-blk和vhost-nvme进行初始化,如果配置文件中配置了对应类型的设备,那就会完成对应设备的创建并初始化监听socket等待qemu客户端进行连接。
spdk/lib/vhost/vhost.c:
int
spdk_vhost_init(void)
{
int ret;
...
ret = spdk_vhost_scsi_controller_construct();
if (ret != 0) {
SPDK_ERRLOG("Cannot construct vhost controllers\n");
return -1;
}
ret = spdk_vhost_blk_controller_construct();
if (ret != 0) {
SPDK_ERRLOG("Cannot construct vhost block controllers\n");
return -1;
}
ret = spdk_vhost_nvme_controller_construct();
if (ret != 0) {
SPDK_ERRLOG("Cannot construct vhost NVMe controllers\n");
return -1;
}
return 0;
}
vhost-blk初始化
vhost-blk初始化时主要完成了两部分工作:一是vhost设备通用部分,即建立监听socket并拉起监听线程等待客户端连接;另一方面是vhost-blk特有的初始化动作,即打开bdev设备并建立联系:
spdk/lib/vhost/vhost_blk.c:
int
spdk_vhost_blk_construct(const char *name, const char *cpumask, const char *dev_name, bool readonly)
{
struct spdk_vhost_blk_dev *bvdev = NULL;
struct spdk_bdev *bdev;
int ret = 0;
spdk_vhost_lock();
/* 首先通过bdev名称查找对应的bdev对象;bdev子系统在vhost子系统之前先完成初始化,正常情况下这里能找到对应的bdev */
bdev = spdk_bdev_get_by_name(dev_name);
...
bvdev = spdk_dma_zmalloc(sizeof(*bvdev), SPDK_CACHE_LINE_SIZE, NULL);
...
/* 打开对应的bdev,并将句柄记录到bvdev->bdev_desc中 */
ret = spdk_bdev_open(bdev, true, bdev_remove_cb, bvdev, &bvdev->bdev_desc);
...
bvdev->bdev = bdev;
bvdev->readonly = readonly;
/* 完成vhost设备通用部分功能的初始化,并将该vhost设备的backend操作集合设为vhost_blk_device_backend;
说明:不同的vhost类型实现了不同的backend,以完成不同类型特定的一些操作过程。我们在后续分析客户端连接
操作时会深入分析backend的实现 */
ret = spdk_vhost_dev_register(&bvdev->vdev, name, cpumask, &vhost_blk_device_backend);
...
spdk_vhost_unlock();
return ret;
}
vhost设备初始化主要提供了一个可供客户端(如qemu)连接的socket,并遵循vhost协议实现连接服务,这部分功能也是DPDK中已实现的功能,SPDK直接借用了相关代码:
spdk/lib/vhost/vhost.c:
int
spdk_vhost_dev_register(struct spdk_vhost_dev *vdev, const char *name, const char *mask_str,
const struct spdk_vhost_dev_backend *backend)
{
char path[PATH_MAX];
struct stat file_stat;
struct spdk_cpuset *cpumask;
int rc;
/* 将配置文件中读取的mask_str转换成位图记录到cpumask中,代表该vhost设备可以绑定的CPU核范围 */
cpumask = spdk_cpuset_alloc();
...
if (spdk_vhost_parse_core_mask(mask_str, cpumask) != 0) {
...
}
...
/* 生成socket文件路径名,规则是设备路径名(vhost命令启动时-S参数指定)加上vhost对象名称,
例如 “/var/tmp/vhost.2” */
if (snprintf(path, sizeof(path), "%s%s", dev_dirname, name) >= (int)sizeof(path)) {
...
}
...
/* 生成socket监听句柄 */
if (rte_vhost_driver_register(path, 0) != 0) {
...
}
if (rte_vhost_driver_set_features(path, backend->virtio_features) ||
rte_vhost_driver_disable_features(path, backend->disabled_features)) {
...
}
/* 注册socket连接建立后的消息处理notify_op回调 */
if (rte_vhost_driver_callback_register(path, &g_spdk_vhost_ops) != 0) {
...
}
/* 拉起一个监听线程,开始等待客户连接请求 */
if (spdk_call_unaffinitized(_start_rte_driver, path) == NULL) {
...
}
vdev->name = strdup(name);
vdev->path = strdup(path);
vdev->id = ctrlr_num++;
vdev->vid = -1; /* 代表客户端连接对象,在客户端连接过程中生成 */
vdev->lcore = -1; /* 代表当前vhost设备绑定到哪个核上运行,也是在客户端连接后请求处理过程中生成 */
vdev->cpumask = cpumask;
vdev->registered = true;
vdev->backend = backend;
...
TAILQ_INSERT_TAIL(&g_spdk_vhost_devices, vdev, tailq);
return 0;
}
_start_rte_driver会拉起一个监听线程执行fdset_event_dispatch函数,该函数等待客户端的连接请求。当qemu向socket发起连接请求时,监听线程收到该请求并调用vhost_user_server_new_connection建立一个新的连接,然后在新的连接上等待客户端发消息。收到消息时,监听线程会调用vhost_user_read_cb函数处理消息。消息的处理代表了vhost协议的基本原理,我们将在后续独立的博文介绍。
转载请注明:吴斌的博客 » 【SPDK】六、vhost子系统