Guest → QEMU → Virglrenderer 调用逻辑分析

1. 概述

本文分析虚拟机图形加速的完整调用链路:从 Guest OS 中的应用程序发起图形请求,经过 virtio-gpu 驱动、QEMU 虚拟机管理器,最终到达 virglrenderer 库执行实际渲染的全过程。

1.1 整体架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                         Guest OS                            │
│  ┌─────────────┐                                            │
│  │ Application │ (OpenGL/Vulkan API)                        │
│  └──────┬──────┘                                            │
│         │                                                    │
│  ┌──────▼──────────────────┐                                │
│  │  Mesa / Vulkan Driver   │                                │
│  │  (virgl, venus)         │                                │
│  └──────┬──────────────────┘                                │
│         │ ioctl()                                           │
│  ┌──────▼──────────────────┐                                │
│  │  virtio-gpu Kernel      │                                │
│  │  Driver (DRM/KMS)       │                                │
│  └──────┬──────────────────┘                                │
└─────────┼──────────────────────────────────────────────────┘
          │ virtio ring (virtqueue)
          │ 
┌─────────▼──────────────────────────────────────────────────┐
│                         QEMU                               │
│  ┌──────────────────────────────┐                          │
│  │  virtio-gpu PCI Device       │                          │
│  │  (hw/display/virtio-gpu.c)   │                          │
│  └──────┬───────────────────────┘                          │
│         │ virgl_renderer_*()                               │
│  ┌──────▼───────────────────────┐                          │
│  │  virglrenderer 库接口层      │                           │
└──┼─────────────────────────────┼───────────────────────────┘
   │                             │
   │ libvirglrenderer.so         │
   │                             │
┌──▼──────────────────────────────▼───────────────────────────┐
│              Virglrenderer Library                          │
│  ┌──────────────────┬──────────────────┬─────────────────┐  │
│  │  vrend (OpenGL)  │ venus (Vulkan)   │ drm             │  │
│  └────────┬─────────┴────────┬─────────┴────────┬────────┘  │
└───────────┼──────────────────┼──────────────────┼───────────┘
            │                  │                  │
    ┌───────▼──────┐   ┌───────▼──────┐   ┌──────▼──────┐
    │  Host OpenGL │   │ Host Vulkan  │   │  Host DRM   │
    │  Driver      │   │ Driver       │   │             │
    └──────────────┘   └──────────────┘   └─────────────┘

2. 核心调用流程详解

2.1 初始化阶段

阶段 1: QEMU 启动 virglrenderer

当 QEMU 启动虚拟机时,如果配置了 virtio-gpu 设备,会执行以下初始化:

c 复制代码
// QEMU 侧伪代码
virgl_renderer_callbacks cbs = {
    .version = 4,
    .write_fence = qemu_write_fence,              // fence 回调
    .write_context_fence = qemu_write_ctx_fence,  // 上下文 fence
    .create_gl_context = qemu_create_gl_context,  // GL 上下文创建
    .destroy_gl_context = qemu_destroy_gl_context,
    .make_current = qemu_make_current,
    .get_drm_fd = qemu_get_drm_fd,               // 获取 DRM fd
    .get_server_fd = qemu_get_server_fd,         // Venus 专用
    .get_egl_display = qemu_get_egl_display      // EGL 显示
};

int flags = VIRGL_RENDERER_USE_EGL | 
            VIRGL_RENDERER_THREAD_SYNC |
            VIRGL_RENDERER_ASYNC_FENCE_CB;

// 初始化 virglrenderer
virgl_renderer_init(qemu_cookie, flags, &cbs);
阶段 2: virglrenderer 内部初始化

virgl_renderer_init() 函数执行以下步骤(代码:src/virglrenderer.c:):

c 复制代码
int virgl_renderer_init(void *cookie, int flags, 
                        struct virgl_renderer_callbacks *cbs)
{
    // 1. 保存回调和状态
    state.cookie = cookie;
    state.flags = flags;
    state.cbs = cbs;
    
    // 2. 初始化资源表(全局资源管理)
    virgl_resource_table_init(pipe_cbs);
    state.resource_initialized = true;
    
    // 3. 初始化上下文表(全局上下文管理)
    virgl_context_table_init();
    state.context_initialized = true;
    
    // 4. 初始化窗口系统(EGL/GLX)
    if (flags & VIRGL_RENDERER_USE_EGL) {
        int drm_fd = cbs->get_drm_fd(cookie);
        vrend_winsys_init(flags, drm_fd);
        state.winsys_initialized = true;
    }
    
    // 5. 初始化 OpenGL 渲染器
    if (!(flags & VIRGL_RENDERER_NO_VIRGL)) {
        vrend_renderer_init(&vrend_cbs, renderer_flags);
        state.vrend_initialized = true;
    }
    
    // 6. 初始化 Vulkan 渲染器 (Venus)
    if (flags & VIRGL_RENDERER_RENDER_SERVER) {
        proxy_renderer_init(&proxy_cbs, flags);
        state.proxy_initialized = true;
    }
    
    // 7. 初始化 DRM 原生渲染器
    if (flags & VIRGL_RENDERER_DRM) {
        drm_renderer_init(drm_fd);
        state.drm_initialized = true;
    }
    
    // 8. 初始化 fence 同步表
    virgl_fence_table_init();
    state.fence_initialized = true;
    
    return 0;
}

关键数据结构

c 复制代码
struct global_state {
    void *cookie;                    // QEMU 传入的上下文指针
    const struct virgl_renderer_callbacks *cbs;  // 回调函数表
    
    // 各子系统初始化标志
    bool vrend_initialized;          // OpenGL 渲染器
    bool proxy_initialized;          // Vulkan/Venus
    bool drm_initialized;            // DRM 原生
    bool resource_initialized;       // 资源管理
    bool context_initialized;        // 上下文管理
    bool fence_initialized;          // fence 同步
};

2.2 上下文创建与管理

上下文创建流程 (代码:src/virglrenderer.c:216
c 复制代码
int virgl_renderer_context_create_with_flags(
    uint32_t ctx_id,
    uint32_t ctx_flags,
    uint32_t nlen,
    const char *name)
{
    // 1. 提取 capset_id(决定渲染器类型)
    uint32_t capset_id = ctx_flags & 
                         VIRGL_RENDERER_CONTEXT_FLAG_CAPSET_ID_MASK;
    
    struct virgl_context *ctx;
    
    // 2. 根据 capset_id 选择渲染器
    switch (capset_id) {
    case VIRTGPU_DRM_CAPSET_VIRGL:
    case VIRTGPU_DRM_CAPSET_VIRGL2:
        // OpenGL 渲染器
        ctx = vrend_renderer_context_create(ctx_id, nlen, name);
        break;
        
    case VIRTGPU_DRM_CAPSET_VENUS:
        // Vulkan 渲染器
        ctx = proxy_context_create(ctx_id, ctx_flags, nlen, name);
        break;
        
    case VIRTGPU_DRM_CAPSET_DRM:
        // DRM 原生渲染器
        ctx = drm_renderer_create(nlen, name);
        break;
    
    // 3. 设置上下文属性
    ctx->ctx_id = ctx_id;
    ctx->in_fence_fd = -1;
    ctx->capset_id = capset_id;
    ctx->fence_retire = per_context_fence_retire;  // Fence 回调
    
    // 4. 注册到全局上下文表
    virgl_context_add(ctx);
    
    return 0;
}

上下文结构src/virgl_context.h):

c 复制代码
struct virgl_context {
    uint32_t ctx_id;
    uint32_t capset_id;             // 渲染器类型标识
    int in_fence_fd;                // 输入同步 fence
    
    // 虚函数表(多态)
    void (*destroy)(struct virgl_context *ctx);
    void (*attach_resource)(struct virgl_context *ctx, 
                           struct virgl_resource *res);
    void (*detach_resource)(struct virgl_context *ctx, 
                           struct virgl_resource *res);
    
    int (*submit_cmd)(struct virgl_context *ctx,
                     const void *buffer, size_t size);
    int (*submit_fence)(struct virgl_context *ctx,
                       uint32_t flags, uint32_t ring_idx, 
                       uint64_t fence_id);
    void (*retire_fences)(struct virgl_context *ctx);
    
    int (*get_blob)(struct virgl_context *ctx, ...);
    int (*transfer_3d)(struct virgl_context *ctx, ...);
    
    // Fence 回调
    virgl_context_fence_retire fence_retire;
};

2.3 资源创建流程

流程 1: Guest 创建纹理/缓冲区
复制代码
Guest Application
    │
    ├─► glGenTextures() / glCreateBuffers()
    │
    ▼
Mesa Driver (virgl_dri.so)
    │
    ├─► virgl_resource_create()
    │
    ▼
Kernel virtio-gpu Driver
    │
    ├─► VIRTIO_GPU_CMD_RESOURCE_CREATE_3D
    │   (通过 virtqueue 发送命令)
    │
    ▼
QEMU virtio-gpu Device
    │
    ├─► virgl_cmd_resource_create_3d()
    │
    ▼
Virglrenderer
流程 2: virglrenderer 资源创建

传统资源创建 (代码:src/virglrenderer.c):

c 复制代码
int virgl_renderer_resource_create(
    struct virgl_renderer_resource_create_args *args,
    struct iovec *iov,
    uint32_t num_iovs)
{
    // 1. 参数验证
    if (args->handle == 0) return EINVAL;
    if (virgl_resource_lookup(args->handle)) return -EINVAL;
    
    // 2. 转换参数为 vrend 格式
    struct vrend_renderer_resource_create_args vrend_args = {
        .target = args->target,      // GL_TEXTURE_2D, GL_ARRAY_BUFFER 等
        .format = args->format,      // PIPE_FORMAT_*
        .bind = args->bind,          // VIRGL_RES_BIND_RENDER_TARGET 等
        .width = args->width,
        .height = args->height,
        .depth = args->depth,
        .array_size = args->array_size,
        .nr_samples = args->nr_samples,
        .last_level = args->last_level  // mipmap 层级
    };
    
    // 3. 调用 vrend 创建实际 GL 资源
    pipe_res = vrend_renderer_resource_create(&vrend_args, NULL);
    
    // 4. 创建 virgl_resource 包装器
    res = virgl_resource_create_from_pipe(args->handle, pipe_res, 
                                          iov, num_iovs);
    
    // 5. 获取映射信息
    res->map_info = vrend_renderer_resource_get_map_info(pipe_res);
    
    return 0;
}

Blob 资源创建 (现代方式,代码:src/virglrenderer.c):

c 复制代码
int virgl_renderer_resource_create_blob(
    const struct virgl_renderer_resource_create_blob_args *args)
{
    // 1. 确定存储位置
    switch (args->blob_mem) {
    case VIRGL_RENDERER_BLOB_MEM_GUEST:
        has_host_storage = false;    // 仅 Guest 内存
        has_guest_storage = true;
        break;
    case VIRGL_RENDERER_BLOB_MEM_HOST3D:
        has_host_storage = true;     // 仅 Host GPU 内存
        has_guest_storage = false;
        break;
    case VIRGL_RENDERER_BLOB_MEM_HOST3D_GUEST:
        has_host_storage = true;     // 两者都有(共享)
        has_guest_storage = true;
        break;
    }
    
    // 2. 如果无 Host 存储,直接从 iov 创建
    if (!has_host_storage) {
        res = virgl_resource_create_from_iov(args->res_handle, 
                                             args->iovecs, args->num_iovs);
        res->map_info = VIRGL_RENDERER_MAP_CACHE_CACHED;
        return 0;
    }
    
    // 3. 通过上下文创建 blob(Vulkan/DRM)
    ctx = virgl_context_lookup(args->ctx_id);
    ret = ctx->get_blob(ctx, args->res_handle, args->blob_id, 
                        args->size, args->blob_flags, &blob);
    
    // 4. 根据 blob 类型创建资源
    switch (blob.type) {
    case VIRGL_RESOURCE_FD_DMABUF:
    case VIRGL_RESOURCE_FD_OPAQUE:
        res = virgl_resource_create_from_fd(args->res_handle, 
                                            blob.type, blob.u.fd, ...);
        break;
    case VIRGL_RESOURCE_OPAQUE_HANDLE:
        res = virgl_resource_create_from_opaque_handle(ctx, 
                                args->res_handle, blob.u.opaque_handle);
        break;
    case VIRGL_RESOURCE_VA_HANDLE:
        res = virgl_resource_create_from_va_handle(args->res_handle, 
                                                    blob.u.va_handle);
        break;
    default:
        res = virgl_resource_create_from_pipe(args->res_handle,
                                              blob.u.pipe_resource, ...);
    }
    
    res->map_info = blob.map_info;
    res->map_size = args->size;
    
    return 0;
}

2.4 命令提交流程

完整命令提交路径
复制代码
Guest: glDrawArrays()
    │
    ▼
Mesa: virgl_encode_draw_vbo()
    │ (编码为 VIRGL_CCMD_DRAW_VBO 命令)
    ▼
Kernel: virtio_gpu_execbuffer_ioctl()
    │
    ▼
virtqueue: 发送命令缓冲区
    │
    ▼
QEMU: virtio_gpu_cmd_submit_3d()
    │
    ▼
virglrenderer: virgl_renderer_submit_cmd()
virglrenderer 命令提交 (代码:src/virglrenderer.c
c 复制代码
int virgl_renderer_submit_cmd(void *buffer, int ctx_id, int ndw)
{
    // 1. 查找上下文
    struct virgl_context *ctx = virgl_context_lookup(ctx_id);
    if (!ctx) return EINVAL;
    
    // 2. 参数验证
    if (ndw < 0 || (unsigned)ndw > UINT32_MAX / sizeof(uint32_t))
        return EINVAL;
    if (((uintptr_t)buffer & 3) != 0)  // 必须 4 字节对齐
        return EFAULT;
    
    // 3. 调用上下文的虚函数提交命令
    // 对于 OpenGL 上下文,这会调用 vrend_decode_ctx_submit_cmd()
    // 对于 Vulkan 上下文,这会调用 proxy_context_submit_cmd()
    return ctx->submit_cmd(ctx, buffer, (uint32_t)ndw * sizeof(uint32_t));
}
OpenGL 命令解码与执行(vrend 渲染器)
c 复制代码
// vrend_decode_ctx_submit_cmd() 伪代码
int vrend_decode_ctx_submit_cmd(ctx, buffer, size)
{
    uint32_t *cmd = (uint32_t *)buffer;
    
    while (size > 0) {
        uint32_t header = cmd[0];
        uint32_t cmd_id = header & 0xFF;
        uint32_t cmd_len = (header >> 16) & 0xFFFF;
        
        switch (cmd_id) {
        case VIRGL_CCMD_CLEAR:
            vrend_decode_clear(ctx, cmd, cmd_len);
            break;
        case VIRGL_CCMD_DRAW_VBO:
            vrend_decode_draw_vbo(ctx, cmd, cmd_len);
            break;
        case VIRGL_CCMD_BIND_SHADER:
            vrend_decode_bind_shader(ctx, cmd, cmd_len);
            break;
        case VIRGL_CCMD_SET_VIEWPORT_STATE:
            vrend_decode_set_viewport(ctx, cmd, cmd_len);
            break;
        // ... 60+ 个命令类型
        }
        
        cmd += cmd_len;
        size -= cmd_len * 4;
    }
}

// 示例:绘制命令解码
void vrend_decode_draw_vbo(ctx, cmd, len)
{
    uint32_t start = cmd[1];
    uint32_t count = cmd[2];
    uint32_t mode = cmd[3];          // GL_TRIANGLES 等
    uint32_t indexed = cmd[4];
    uint32_t instance_count = cmd[5];
    
    // 设置 GL 状态
    vrend_set_pipeline_state(ctx);
    
    // 执行 OpenGL 绘制
    if (indexed)
        glDrawElementsInstanced(mode, count, ...);
    else
        glDrawArraysInstanced(mode, start, count, instance_count);
}

2.5 Fence 同步机制

Fence 创建与通知
c 复制代码
// Guest 侧发起 fence
int virgl_renderer_context_create_fence(
    uint32_t ctx_id,
    uint32_t flags,
    uint32_t ring_idx,
    uint64_t fence_id)
{
    struct virgl_context *ctx = virgl_context_lookup(ctx_id);
    
    // 调用上下文的 submit_fence
    return ctx->submit_fence(ctx, flags, ring_idx, fence_id);
}

// Fence 完成后的回调路径
static void per_context_fence_retire(
    struct virgl_context *ctx,
    uint32_t ring_idx,
    uint64_t fence_id)
{
    // 通知 QEMU fence 已完成
    state.cbs->write_context_fence(state.cookie,
                                   ctx->ctx_id,
                                   ring_idx,
                                   fence_id);
}
QEMU 侧处理
c 复制代码
// QEMU 实现 write_context_fence 回调
void qemu_write_context_fence(void *cookie, 
                              uint32_t ctx_id,
                              uint32_t ring_idx,
                              uint64_t fence_id)
{
    // 1. 通过 virtqueue 发送 fence 完成通知给 Guest
    virtio_gpu_ctrl_response(vgpu, cmd, 
                            VIRTIO_GPU_RESP_OK_NODATA);
    
    // 2. 触发 Guest 中断
    virtio_notify_irq(vgpu);
}

// Guest 内核收到中断后
void virtio_gpu_fence_event_process(vgpu)
{
    // 唤醒等待的用户空间进程
    dma_fence_signal(fence);
}

2.6 数据传输流程

Host ← Guest 传输(Upload)
c 复制代码
int virgl_renderer_transfer_write_iov(
    uint32_t handle,
    uint32_t ctx_id,
    int level,              // mipmap 层级
    uint32_t stride,        // 行跨度
    uint32_t layer_stride,  // 层跨度
    struct virgl_box *box,  // 传输区域
    uint64_t offset,
    struct iovec *iovec,    // Guest 内存
    unsigned int iovec_cnt)
{
    struct virgl_resource *res = virgl_resource_lookup(handle);
    
    struct vrend_transfer_info transfer_info = {
        .level = level,
        .stride = stride,
        .layer_stride = layer_stride,
        .box = (struct pipe_box *)box,
        .offset = offset,
        .iovec = iovec,
        .iovec_cnt = iovec_cnt,
        .synchronized = false
    };
    
    if (ctx_id) {
        // 通过上下文传输(可能有额外处理)
        struct virgl_context *ctx = virgl_context_lookup(ctx_id);
        return ctx->transfer_3d(ctx, res, &transfer_info,
                               VIRGL_TRANSFER_TO_HOST);
    } else {
        // 直接传输到 GL 资源
        return vrend_renderer_transfer_pipe(res->pipe_resource, 
                                           &transfer_info,
                                           VIRGL_TRANSFER_TO_HOST);
    }
}
数据传输实现(vrend)
c 复制代码
// vrend_renderer_transfer_pipe() 简化流程
int vrend_renderer_transfer_pipe(pipe_res, info, direction)
{
    // 1. 绑定纹理/缓冲区
    if (pipe_res->target == GL_TEXTURE_2D) {
        glBindTexture(GL_TEXTURE_2D, pipe_res->gl_id);
    } else {
        glBindBuffer(GL_ARRAY_BUFFER, pipe_res->gl_id);
    }
    
    // 2. 从 iovec 拷贝数据到 GL
    if (direction == VIRGL_TRANSFER_TO_HOST) {
        if (pipe_res->target == GL_TEXTURE_2D) {
            // 纹理上传
            glTexSubImage2D(GL_TEXTURE_2D, info->level,
                           info->box->x, info->box->y,
                           info->box->width, info->box->height,
                           format, type, iov_data);
        } else {
            // 缓冲区上传
            glBufferSubData(GL_ARRAY_BUFFER, info->offset,
                           size, iov_data);
        }
    } else {
        // VIRGL_TRANSFER_FROM_HOST (下载)
        glGetTexImage(...);  // 或 glGetBufferSubData
    }
    
    return 0;
}

3. 多渲染器架构

3.1 渲染器选择机制

c 复制代码
// 能力集 (Capset) 决定渲染器类型
enum capset_id {
    VIRTGPU_DRM_CAPSET_VIRGL = 1,    // OpenGL (Legacy)
    VIRTGPU_DRM_CAPSET_VIRGL2 = 2,   // OpenGL (Modern)
    VIRTGPU_DRM_CAPSET_VENUS = 3,    // Vulkan
    VIRTGPU_DRM_CAPSET_DRM = 4,      // DRM Native
};

// Guest 查询能力集
virgl_renderer_get_cap_set(capset_id, &max_ver, &max_size);
virgl_renderer_fill_caps(capset_id, version, caps);

// Guest 创建上下文时指定 capset_id
ctx_flags = VIRTGPU_DRM_CAPSET_VENUS;
virgl_renderer_context_create_with_flags(ctx_id, ctx_flags, ...);

3.2 各渲染器特点

渲染器 Capset ID 命令协议 典型用途
vrend VIRGL/VIRGL2 VIRGL_CCMD_* OpenGL 应用
venus VENUS Vulkan 原生命令流 Vulkan 应用
drm DRM DRM ioctl 封装 硬件特定优化

4. 关键数据结构总结

4.1 全局状态

c 复制代码
static struct global_state {
    void *cookie;                  // QEMU 上下文
    const struct virgl_renderer_callbacks *cbs;
    
    // 子系统初始化标志
    bool vrend_initialized;
    bool proxy_initialized;
    bool drm_initialized;
} state;

4.2 回调函数表

c 复制代码
struct virgl_renderer_callbacks {
    int version;
    void (*write_fence)(void *cookie, uint32_t fence);
    void (*write_context_fence)(void *cookie, uint32_t ctx_id, 
                               uint32_t ring_idx, uint64_t fence_id);
    virgl_renderer_gl_context (*create_gl_context)(...);
    int (*get_drm_fd)(void *cookie);
    int (*get_server_fd)(void *cookie, uint32_t version);
    void *(*get_egl_display)(void *cookie);
};

4.3 资源对象

c 复制代码
struct virgl_resource {
    uint32_t res_id;
    struct pipe_resource *pipe_resource;  // GL/Gallium 资源
    enum virgl_resource_fd_type fd_type;
    int fd;                               // dmabuf/shm fd
    const struct iovec *iov;              // Guest 内存映射
    uint32_t map_info;
    uint64_t map_size;
    void *mapped;
};

4.4 上下文对象

c 复制代码
struct virgl_context {
    uint32_t ctx_id;
    uint32_t capset_id;
    
    // 虚函数(多态实现)
    int (*submit_cmd)(struct virgl_context *ctx, ...);
    int (*submit_fence)(struct virgl_context *ctx, ...);
    int (*get_blob)(struct virgl_context *ctx, ...);
    void (*retire_fences)(struct virgl_context *ctx);
};

5. 性能优化要点

5.1 零拷贝路径

  • Blob 资源 + dmabuf: Guest/Host 共享物理页
  • virgl_resource_map(): 直接映射 Host GPU 内存到 Guest
  • GBM allocation: 使用硬件优化的分配器

5.2 异步执行

  • VIRGL_RENDERER_THREAD_SYNC: Fence 轮询在独立线程
  • VIRGL_RENDERER_ASYNC_FENCE_CB: Fence 回调异步触发
  • virtqueue: 批量命令提交,减少上下文切换

5.3 命令批处理

  • Guest 累积多个 GL 命令后一次性提交
  • QEMU 批量读取 virtqueue 请求
  • virglrenderer 批量解码命令流

6. 总结

调用链总览

复制代码
Application (glClear, glDrawArrays, ...)
               ↓
Mesa Driver (编码 VIRGL 命令)
               ↓
Kernel virtio-gpu (virtqueue 传输)
               ↓
QEMU virtio-gpu Device (virgl_renderer_* API)
               ↓
Virglrenderer Library (路由到具体渲染器)
               ↓
┌──────────┬──────────┬──────────┐
│  vrend   │  venus   │   drm    │ 
│ (OpenGL) │ (Vulkan) │ (Native) │
└──────────┴──────────┴──────────┘
      ↓          ↓          ↓    
   Host GPU Driver (实际硬件执行)

关键特性

  1. 多渲染器支持: 单一接口支持 OpenGL/Vulkan/DRM
  2. 异步架构: Fence 机制实现高效同步
  3. 灵活资源管理: 支持传统资源和 Blob 资源
  4. 零拷贝优化: dmabuf 共享减少内存拷贝
  5. 模块化设计: 渲染器可独立启用/禁用
相关推荐
chenzhou__1 小时前
LinuxC语言并发程序笔记(第二十天)
linux·c语言·笔记·学习
会飞的土拨鼠呀2 小时前
运维工程师需要具备哪些技能
linux·运维·ubuntu
虎头金猫3 小时前
随时随地处理图片文档!Reubah 加cpolar的实用体验
linux·运维·人工智能·python·docker·开源·visual studio
NiKo_W4 小时前
Linux 数据链路层
linux·服务器·网络·内网穿透·nat·数据链路层
dessler4 小时前
MYSQL-物理备份(xtrabackup)使用指南
linux·数据库·mysql
郝学胜-神的一滴4 小时前
Effective Python 第52条:用subprocess模块优雅管理子进程
linux·服务器·开发语言·python
晨枫阳4 小时前
不同语言数组详解
linux·服务器·windows
q***75185 小时前
Linux(CentOS)安装 MySQL
linux·mysql·centos
CS_浮鱼6 小时前
【Linux】线程
linux·c++·算法