云游戏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_GPU_FLAG_VIRGL_ENABLED);
    } else {
#if defined(CONFIG_VIRGL)
        VIRTIO_GPU_BASE(g)->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接口到服务器的真实显卡上处理。

 

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值