云游戏GPU虚拟化技术分析 您所在的位置:网站首页 手机kvm虚拟化 云游戏GPU虚拟化技术分析

云游戏GPU虚拟化技术分析

2024-07-09 09:33| 来源: 网络整理| 查看: 265

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 实验室设备网 版权所有