云游戏GPU虚拟化技术分析 | 您所在的位置:网站首页 › 手机kvm虚拟化 › 云游戏GPU虚拟化技术分析 |
GPU全称是Graphic Processing Unit--图形处理器 1. 常规gpu调用逻辑接收cpu的指令,并行的进行渲染操作 2. GPU虚拟化三种分类方式:虚拟显卡,显卡透传,vgpu虚拟化 2.1 端游gpu端游主要是指在pc(windows)上运行的游戏; 实现:GPU透传 2.1.1 一对一透传 2.2.2 一对多透传 操作比较简单了,技术比较成熟了,具体操作见文档参考资料。 2.2 手游gpu手游主要是指在安卓手机上运行的游戏; 实现:通过virtio-gpu模块,实现显卡的一对多虚拟化 3. 部分代码解读NOTE:以手游v-gpu为例 virtio-gpu工作流程图 3.1 前端内核驱动层 内核中是以virtio-gpu.ko.xz 模块存在的,加载方式 modprobe virtio-gpu virtio工作流程图 内核模块结构体 static struct virtio_driver virtio_gpu_driver = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .probe = virtio_gpu_probe, //初始化设备,系统启动加载 .remove = virtio_gpu_remove, .config_changed = virtio_gpu_config_changed };设备对应的操作接口,供用户层调用 static const struct file_operations virtio_gpu_driver_fops = { .owner = THIS_MODULE, .open = drm_open, .mmap = virtio_gpu_mmap, .poll = drm_poll, .read = drm_read, .unlocked_ioctl = drm_ioctl, .release = drm_release, .compat_ioctl = drm_compat_ioctl, .llseek = noop_llseek, };初始化设备 static int virtio_gpu_probe(struct virtio_device *vdev) { struct drm_device *dev; int ret; if (vgacon_text_force() && virtio_gpu_modeset == -1) return -EINVAL; if (virtio_gpu_modeset == 0) return -EINVAL; dev = drm_dev_alloc(&driver, &vdev->dev); if (IS_ERR(dev)) return PTR_ERR(dev); vdev->priv = dev; if (!strcmp(vdev->dev.parent->bus->name, "pci")) { ret = virtio_gpu_pci_quirk(dev, vdev); if (ret) goto err_free; } ret = virtio_gpu_init(dev); //初始化设备,前后端通信消息队列 if (ret) goto err_free; ret = drm_dev_register(dev, 0); if (ret) goto err_free; drm_fbdev_generic_setup(vdev->priv, 32); return 0; err_free: drm_dev_put(dev); return ret; }用于图像处理和光标操作,及相关的回调函数 virtio_gpu_init_vq(&vgdev->ctrlq, virtio_gpu_dequeue_ctrl_func); virtio_gpu_init_vq(&vgdev->cursorq, virtio_gpu_dequeue_cursor_func);3.2 后端qemu层 后端设备初始化 static void virtio_gpu_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); VirtIOGPUBaseClass *vgc = VIRTIO_GPU_BASE_CLASS(klass); vgc->gl_flushed = virtio_gpu_gl_flushed; vdc->realize = virtio_gpu_device_realize; //主要逻辑入口 vdc->reset = virtio_gpu_reset; vdc->get_config = virtio_gpu_get_config; vdc->set_config = virtio_gpu_set_config; dc->vmsd = &vmstate_virtio_gpu; device_class_set_props(dc, virtio_gpu_properties); }主要操作入口 static void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(qdev); VirtIOGPU *g = VIRTIO_GPU(qdev); bool have_virgl; #if !defined(CONFIG_VIRGL) || defined(HOST_WORDS_BIGENDIAN) have_virgl = false; #else have_virgl = display_opengl; #endif if (!have_virgl) { g->parent_obj.conf.flags &= ~(1 virtio_config.num_capsets = virtio_gpu_virgl_get_num_capsets(g); #endif } //初始化图像/光标处理队列,注册对应的回调函数,并将队列加入任务队列 // virtio_gpu_handle_ctrl_cb回调将g->ctrl_bh 加入任务队列,qemu_bh_schedule(g->ctrl_bh); if (!virtio_gpu_base_device_realize(qdev, virtio_gpu_handle_ctrl_cb, virtio_gpu_handle_cursor_cb, errp)) { return; } g->ctrl_vq = virtio_get_queue(vdev, 0); g->cursor_vq = virtio_get_queue(vdev, 1); g->ctrl_bh = qemu_bh_new(virtio_gpu_ctrl_bh, g); //回调处理函数virtio_gpu_ctrl_bh g->cursor_bh = qemu_bh_new(virtio_gpu_cursor_bh, g); QTAILQ_INIT(&g->reslist); QTAILQ_INIT(&g->cmdq); QTAILQ_INIT(&g->fenceq); }初始化图像/光标处理队列,注册对应的回调函数,并将队列加入任务队列 virtio_gpu_base_device_realize(DeviceState *qdev, VirtIOHandleOutput ctrl_cb, VirtIOHandleOutput cursor_cb, Error **errp) { …… if (virtio_gpu_virgl_enabled(g->conf)) { /* use larger control queue in 3d mode */ virtio_add_queue(vdev, 256, ctrl_cb); //初始化消息队列vq virtio_add_queue(vdev, 16, cursor_cb); } else { virtio_add_queue(vdev, 64, ctrl_cb); virtio_add_queue(vdev, 16, cursor_cb); } }初始化virtioqueue VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size, VirtIOHandleOutput handle_output) { int i; for (i = 0; i < VIRTIO_QUEUE_MAX; i++) { if (vdev->vq[i].vring.num == 0) break; } if (i == VIRTIO_QUEUE_MAX || queue_size > VIRTQUEUE_MAX_SIZE) abort(); vdev->vq[i].vring.num = queue_size; vdev->vq[i].vring.num_default = queue_size; vdev->vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN; vdev->vq[i].handle_output = handle_output; //真正的回调处理函数 vdev->vq[i].handle_aio_output = NULL; vdev->vq[i].used_elems = g_malloc0(sizeof(VirtQueueElement) * queue_size); return &vdev->vq[i]; }到这里qemu准备好了接收前端虚拟机的消息回调函数(handle_output)了 如何接收前端消息? 首先这个gpu模块是基于virtio-pci来实现的,自然就继承了virtio-pci的一切特性,首先virtio初始化相应的IO操作函数 memory_region_init_io(&proxy->bar, OBJECT(proxy), &virtio_pci_config_ops, //读写操作结构体 proxy, "virtio-pci", size); static const MemoryRegionOps virtio_pci_config_ops = { .read = virtio_pci_config_read, //读操作 .write = virtio_pci_config_write, //写操作 .impl = { .min_access_size = 1, .max_access_size = 4, }, .endianness = DEVICE_LITTLE_ENDIAN, };以写操作为例 static void virtio_ioport_write(void *opaque, uint32_t addr, uint32_t val) { …… case VIRTIO_PCI_QUEUE_NOTIFY: // VIRTIO_PCI_QUEUE_NOTIFY虚拟机内核virtio-gpu驱动发过来的消息指令 if (val < VIRTIO_QUEUE_MAX) { virtio_queue_notify(vdev, val); } break; …… } void virtio_queue_notify(VirtIODevice *vdev, int n) { VirtQueue *vq = &vdev->vq[n]; if (unlikely(!vq->vring.desc || vdev->broken)) { return; } trace_virtio_queue_notify(vdev, vq - vdev->vq, vq); if (vq->host_notifier_enabled) { event_notifier_set(&vq->host_notifier); } else if (vq->handle_output) { vq->handle_output(vdev, vq); //处理回调 if (unlikely(vdev->start_on_kick)) { virtio_set_started(vdev, true); } } }handle_output对应的回调函数 static void virtio_gpu_ctrl_bh(void *opaque) { VirtIOGPU *g = opaque; virtio_gpu_handle_ctrl(&g->parent_obj.parent_obj, g->ctrl_vq); //处理函数 }处理函数 static void virtio_gpu_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) { VirtIOGPU *g = VIRTIO_GPU(vdev); struct virtio_gpu_ctrl_command *cmd; if (!virtio_queue_ready(vq)) { return; } #ifdef CONFIG_VIRGL if (!g->renderer_inited && g->parent_obj.use_virgl_renderer) { virtio_gpu_virgl_init(g); g->renderer_inited = true; } #endif cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command)); //获取队列中的指令 while (cmd) { cmd->vq = vq; cmd->error = 0; cmd->finished = false; QTAILQ_INSERT_TAIL(&g->cmdq, cmd, next); //获取队列中的所有指令并加入到g-cmdq链表中 cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command)); } virtio_gpu_process_cmdq(g); //处理cmd指令 #ifdef CONFIG_VIRGL if (g->parent_obj.use_virgl_renderer) { virtio_gpu_virgl_fence_poll(g); } #endif }处理cmd指令 void virtio_gpu_process_cmdq(VirtIOGPU *g) { struct virtio_gpu_ctrl_command *cmd; if (g->processing_cmdq) { return; } g->processing_cmdq = true; while (!QTAILQ_EMPTY(&g->cmdq)) { //循环处理指令 cmd = QTAILQ_FIRST(&g->cmdq); if (g->parent_obj.renderer_blocked) { break; } /* process command */ VIRGL(g, virtio_gpu_virgl_process_cmd, virtio_gpu_simple_process_cmd, g, cmd); //以3d为例,这里会调用virtio_gpu_virgl_process_cmd QTAILQ_REMOVE(&g->cmdq, cmd, next); if (virtio_gpu_stats_enabled(g->parent_obj.conf)) { g->stats.requests++; } if (!cmd->finished) { QTAILQ_INSERT_TAIL(&g->fenceq, cmd, next); g->inflight++; if (virtio_gpu_stats_enabled(g->parent_obj.conf)) { if (g->stats.max_inflight < g->inflight) { g->stats.max_inflight = g->inflight; } fprintf(stderr, "inflight: %3d (+)\r", g->inflight); } } else { g_free(cmd); } } g->processing_cmdq = false; }具体的每个操作指令 void virtio_gpu_virgl_process_cmd(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) { VIRTIO_GPU_FILL_CMD(cmd->cmd_hdr); virgl_renderer_force_ctx_0(); switch (cmd->cmd_hdr.type) { //详细对应的操作指令,调用到virgl,mesa接口 case VIRTIO_GPU_CMD_CTX_CREATE: virgl_cmd_context_create(g, cmd); break; case VIRTIO_GPU_CMD_CTX_DESTROY: virgl_cmd_context_destroy(g, cmd); break; case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: virgl_cmd_create_resource_2d(g, cmd); break; case VIRTIO_GPU_CMD_RESOURCE_CREATE_3D: virgl_cmd_create_resource_3d(g, cmd); break; case VIRTIO_GPU_CMD_SUBMIT_3D: virgl_cmd_submit_3d(g, cmd); break; case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: virgl_cmd_transfer_to_host_2d(g, cmd); break; case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D: virgl_cmd_transfer_to_host_3d(g, cmd); break; case VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D: virgl_cmd_transfer_from_host_3d(g, cmd); break; case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: virgl_resource_attach_backing(g, cmd); break; case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING: virgl_resource_detach_backing(g, cmd); break; case VIRTIO_GPU_CMD_SET_SCANOUT: virgl_cmd_set_scanout(g, cmd); break; case VIRTIO_GPU_CMD_RESOURCE_FLUSH: virgl_cmd_resource_flush(g, cmd); break; case VIRTIO_GPU_CMD_RESOURCE_UNREF: virgl_cmd_resource_unref(g, cmd); break; case VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE: /* TODO add security */ virgl_cmd_ctx_attach_resource(g, cmd); break; case VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE: /* TODO add security */ virgl_cmd_ctx_detach_resource(g, cmd); break; case VIRTIO_GPU_CMD_GET_CAPSET_INFO: virgl_cmd_get_capset_info(g, cmd); break; case VIRTIO_GPU_CMD_GET_CAPSET: virgl_cmd_get_capset(g, cmd); break; case VIRTIO_GPU_CMD_GET_DISPLAY_INFO: virtio_gpu_get_display_info(g, cmd); break; case VIRTIO_GPU_CMD_GET_EDID: virtio_gpu_get_edid(g, cmd); break; default: cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; break; } if (cmd->finished) { return; } if (cmd->error) { fprintf(stderr, "%s: ctrl 0x%x, error 0x%x\n", __func__, cmd->cmd_hdr.type, cmd->error); virtio_gpu_ctrl_response_nodata(g, cmd, cmd->error); return; } if (!(cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE)) { virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA); return; } trace_virtio_gpu_fence_ctrl(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type); virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type); } 4. 总结Virtio-gpu分为前后端架构,前后端的消息传递通过virtio架构实现的,前端通过ioevent通知后端,后端通过irq通知前端;前端virtio-gpu驱动捕捉到显卡操作指令/数据放到共享队列上,并通知后端,后端从共享队列上得到数据/指令,然后调用virgl,mesa接口到服务器的真实显卡上处理。
|
CopyRight 2018-2019 实验室设备网 版权所有 |