tcler's blog --- 其实我是一个程序员
Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious.

display output is not active while qemu VM start

get “display output is not active” for a while

x86_64 HOST 用 virt-install(qemu-system-loongarch64) 启动不同架构的 linux iso 时发现过了 grub 后,VNC 客户端上 会出现短时间的(根据host性能或多或少) “display output is not active” 然后,OS 启动画面又出现,,然后尝试修改选项 –video=vga 后,这个“现象”就消失了。

why ?

跟 XL 大概聊了一下,怀疑是因为指令模拟效率本身低 + virtio-gpu 模块加载慢导致的,但是如上所述 即使换了性能很好的 host 仍然有这个提示信息,只是时间缩短到1、2秒;

然后尝试跟 DeepSeek 聊,给出的回答/猜测是,–video=vga 模式时,OS 不使用自带的驱动,而是使用中断调用 直接使用 BIOS 里 的 VGA 驱动,这样就不会重新初始化(虚拟)显卡,所以不会出现 VPN 中断~

为了验证 AI 是不是胡乱猜测,又使用 –arch ppc64le 尝试启动 Alma-10 的 ppc64le qcow2 image,果然也出现了类似的情况, 然后在加上 –video=vga 选项后,”display output is not active” 提示就消失了~~ 看来 AI 这次可能是说对了~?

为什么以前没有发现?

以前都是自动化,没有在 VM 刚刚启动那个时间点去连 VPN 看;这次是在一个拆了大内存,只有 16G 内存的 x99 机器上,手工安装 loongarch64 的 iso 镜像,”display output is not active” 持续时间长 所以发现了~


没用的知识又增加了,,其实上学的时候 课本上提过 BIOS 里面的中断处理程序提供外设驱动,没想到 vga 模式,OS(linux) 是 直接复用 BIOS 你的驱动 ~~ ???


按照习惯还是查看代码验证一下吧:

jiyin@max395:~/ws/tools/qemu-master$ grep -i "display output is not active" -r .
./ui/console.c:    static const char placeholder_msg[] = "Display output is not active.";
jiyin@max395:~/ws/tools/qemu-master$ vim ui/console.c

void qemu_console_set_surface(QemuConsole *con,
                             DisplaySurface *surface)
{
    static const char placeholder_msg[] = "Display output is not active.";
    DisplayState *s = con->ds;
    DisplaySurface *old_surface = con->surface;
    DisplaySurface *new_surface = surface;
    DisplayChangeListener *dcl;
    int width;
    int height;

    if (!surface) {
        if (old_surface) {
            width = surface_width(old_surface);
            height = surface_height(old_surface);
        } else {
            width = 640;
            height = 480;
        }

        new_surface = qemu_create_placeholder_surface(width, height, placeholder_msg);
    }

    assert(old_surface != new_surface);

    con->scanout.kind = SCANOUT_SURFACE;
    con->surface = new_surface;
    dpy_gfx_create_texture(con, new_surface);
    QLIST_FOREACH(dcl, &s->listeners, next) {
        if (con != dcl->con) {
            continue;
        }
        displaychangelistener_gfx_switch(dcl, new_surface, surface ? FALSE : TRUE);
    }
    dpy_gfx_destroy_texture(con, old_surface);
    qemu_free_displaysurface(old_surface);
}

jiyin@max395:~/ws/tools/qemu-master$ grep qemu_console_set_surface.*NULL -r .
./hw/display/vhost-user-gpu.c:            qemu_console_set_surface(con, NULL);
./hw/display/virtio-gpu-rutabaga.c:        qemu_console_set_surface(scanout->con, NULL);
./hw/display/virtio-gpu-rutabaga.c:    qemu_console_set_surface(scanout->con, NULL);
./hw/display/virtio-gpu-virgl.c:        qemu_console_set_surface(g->parent_obj.scanout[ss.scanout_id].con, NULL);
./hw/display/virtio-gpu-virgl.c:        qemu_console_set_surface(g->parent_obj.scanout[i].con, NULL);
./hw/display/virtio-gpu.c:    qemu_console_set_surface(scanout->con, NULL);
./hw/display/virtio-gpu.c:        qemu_console_set_surface(g->parent_obj.scanout[i].con, NULL);
jiyin@max395:~/ws/tools/qemu-master$ vim hw/display/virtio-gpu.c

static void virtio_gpu_reset_bh(void *opaque)
{
    VirtIOGPU *g = VIRTIO_GPU(opaque);
    VirtIOGPUClass *vgc = VIRTIO_GPU_GET_CLASS(g);
    struct virtio_gpu_simple_resource *res, *tmp;
    uint32_t resource_id;
    Error *local_err = NULL;
    int i = 0;

    QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) {
        resource_id = res->resource_id;
        vgc->resource_destroy(g, res, &local_err);
        if (local_err) {
            error_append_hint(&local_err, "%s: %s resource_destroy"
                              "for resource_id = %"PRIu32" failed.\n",
                              __func__, object_get_typename(OBJECT(g)),
                              resource_id);
            /* error_report_err frees the error object for us */
            error_report_err(local_err);
            local_err = NULL;
        }
    }

    for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
        qemu_console_set_surface(g->parent_obj.scanout[i].con, NULL);  //<--- 大概就是这里了
    }

    g->reset_finished = true;
    qemu_cond_signal(&g->reset_cond);
}

最后感谢 DeepSeek 帮助梳理 代码调用流程:

OS 加载 virtio-gpu 驱动
    ↓
驱动通过 PCIe 配置空间写命令寄存器 (触发设备复位)
    ↓
QEMU PCI 模拟层捕获写操作
    ↓
调用 VirtIO-PCI 层的 virtio_pci_reset() 或类似函数
    ↓
virtio_gpu_reset()  [hw/display/virtio-gpu.c]
    ↓
调度下半部 virtio_gpu_reset_bh()
    ↓
对每个 scanout 调用 qemu_console_set_surface(con, NULL)
    ↓
qemu_create_placeholder_surface() 生成 "Display output is not active."
    ↓
VNC 客户端显示这条消息

而 VGA(vga-pci.c) 复位时 只是重置寄存器状态,不调用 qemu_console_set_surface(con, NULL); 未初始化时 继续输出最后一次有效画面(或黑屏),不会主动生成占位符。

换句话说,VGA 的设计里没有”通知用户显示暂时不可用”这个环节——它要么显示正确的画面,要么黑屏, 但不会给你看一段提示文字。而 VirtIO-GPU 则通过那个占位符告诉你:”我知道你没画面,我在等驱动给我发数据”。


【更新】关于 vga 模式下出现的 “Guest has not initialized the display (yet).” 应该是真正的初始化 慢 导致的。 因为这个函数貌似大家 都会调用。。

QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head,
                                         const GraphicHwOps *hw_ops,
                                         void *opaque)
{
    static const char noinit[] =
        "Guest has not initialized the display (yet).";
    int width = 640;
    int height = 480;
    QemuConsole *s;
    DisplaySurface *surface;

    s = qemu_graphic_console_lookup_unused();
    if (s) {
        trace_console_gfx_reuse(s->index);
        width = qemu_console_get_width(s, 0);
        height = qemu_console_get_height(s, 0);
    } else {
        trace_console_gfx_new();
        s = (QemuConsole *)object_new(TYPE_QEMU_GRAPHIC_CONSOLE);
    }
    QEMU_GRAPHIC_CONSOLE(s)->head = head;
    qemu_graphic_console_set_hwops(s, hw_ops, opaque);
    if (dev) {
        object_property_set_link(OBJECT(s), "device", OBJECT(dev),
                                 &error_abort);
    }

    surface = qemu_create_placeholder_surface(width, height, noinit);
    qemu_console_set_surface(s, surface);
    s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
                                       console_hw_gl_unblock_timer, s);
    return s;
}

//所以前面 AI 猜测的 vga 场景没有重新初始化 vga 设备,存疑,即便初始化 qemu vga 的代码处理也没有 qemu_console_set_surface(con, NULL); 调用~ 有时间再验证吧