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. 模块化设计: 渲染器可独立启用/禁用
相关推荐
2035去旅行17 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希12318 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡19 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.1 天前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道1 天前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.1 天前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落1 天前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨1 天前
WSL2的安装与使用
linux·wsl
微小的鱼1 天前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng1 天前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器