From e41701e2017f8be3e2afafc66633d4af941f460a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 21:37:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(fb0):=20=E6=96=B0=E5=A2=9E=20FB=5FWAIT=5FV?= =?UTF-8?q?BLANK=20ioctl=EF=BC=8C=E5=90=88=E6=88=90=E5=89=8D=E7=AD=89?= =?UTF-8?q?=E5=9E=82=E7=9B=B4=E5=9B=9E=E6=89=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit explorer 直接写 framebuffer 会与显示器扫描并发,造成撕裂/闪烁;而它在 Ring3 没有 IO 端口权限,不能像历史内核桌面那样自己 inb 0x3DA。所以在 /dev/fb0 上加 FB_WAIT_VBLANK(0x4601)由内核代等垂直回扫(带超时,无 VGA 时序时不死等),vpsdk 暴露 fb_wait_vblank(),desk_server.render() 写屏前调用一次。 --- Apps/Explorer/desk_server.vp | 7 ++++++- Apps/Explorer/main.vp | 2 +- VKernel/Kernel/drivers/devfs/devfs.vp | 10 ++++++++++ vpsdk/devfs.vp | 10 ++++++---- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 85ca7cc..f216db4 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -62,6 +62,7 @@ _CURSOR_ARROW: [256]uint8 = [ class DeskServer: # 屏幕 fb: uint32* # /dev/fb0 映射后的线性地址 + fb_fd: int # /dev/fb0 的 fd(等 vblank 用) fb_w: int fb_h: int pitch_px: int @@ -97,8 +98,9 @@ class DeskServer: return (uint32(255) << 24) | (b << 16) | (g << 8) | r def init(self, fb: uint32*, w: int, h: int, pitch: int, fmt: uint32, - shm: uint64, kfd: int, mfd: int): + shm: uint64, kfd: int, mfd: int, fb_fd: int): self.fb = fb + self.fb_fd = fb_fd self.fb_w = w self.fb_h = h self.pitch_px = pitch / 4 @@ -355,6 +357,9 @@ class DeskServer: def render(self): if self.dirty_any == 0: return + # 写屏前等垂直回扫:直接写 framebuffer 会与显示器扫描并发 ⇒ 撕裂/闪烁。 + # 用户态没有 IO 权限(不能 inb 0x3DA),由内核代等。 + devfs.fb_wait_vblank(self.fb_fd) y: int = 0 while y < self.fb_h: if self.dirty[y] == 0: diff --git a/Apps/Explorer/main.vp b/Apps/Explorer/main.vp index bbc2fc4..372c13e 100644 --- a/Apps/Explorer/main.vp +++ b/Apps/Explorer/main.vp @@ -87,7 +87,7 @@ export def _start() -> int: # 6) 进入窗口服务端主循环(永不返回):请求环 + 输入 + 合成。 srv: desk_server.DeskServer = desk_server.DeskServer() - srv.init(fb, int(info.width), int(info.height), int(info.pitch), info.format, shm, kfd, mfd) + srv.init(fb, int(info.width), int(info.height), int(info.pitch), info.format, shm, kfd, mfd, fb_fd) _dbg("[explorer] desk server running\n") srv.serve() return 0 diff --git a/VKernel/Kernel/drivers/devfs/devfs.vp b/VKernel/Kernel/drivers/devfs/devfs.vp index 31ae09d..1daa36f 100644 --- a/VKernel/Kernel/drivers/devfs/devfs.vp +++ b/VKernel/Kernel/drivers/devfs/devfs.vp @@ -1,5 +1,6 @@ import drivers.serial.uart.serial as serial import string +import asm define MAX_FILE_DESCRIPTORS = 64 @@ -487,6 +488,8 @@ def devfs_register_mouse() -> int: # 本节点只记录 BootInfo 里的物理地址与几何,不自己持有 framebuffer 映射。 # ============================================================================ define FB_GET_INFO = 0x4600 +# 等一次垂直回扫:用户态(explorer)没有 IO 端口权限,不能自己 inb 0x3DA,由内核代做。 +define FB_WAIT_VBLANK = 0x4601 @__attribute__(packed) class FbInfo: @@ -518,6 +521,13 @@ virtual class framebuffer_device(devfs_file): return -1 def ioctl(self, fd: int, request: int, arg: rawptr) -> int: + if request == FB_WAIT_VBLANK: + # 等进入垂直回扫期(0x3DA bit3)。带超时:当前若无 VGA 时序不至于死等。 + t: int = 100000 + while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: t -= 1 + t = 100000 + while (asm.inb(0x3DA) & 0x08) == 0 and t > 0: t -= 1 + return 0 if request != FB_GET_INFO or not arg: return -1 info: FbInfo* = FbInfo*(uint64(arg)) diff --git a/vpsdk/devfs.vp b/vpsdk/devfs.vp index a3b1949..2693844 100644 --- a/vpsdk/devfs.vp +++ b/vpsdk/devfs.vp @@ -22,6 +22,7 @@ define DEV_DESK = "/dev/desk" # ioctl 命令与返回结构:必须与 VKernel/Kernel/drivers/devfs/devfs.vp 的 # FB_GET_INFO / FbInfo 逐字段一致(packed;4×u32 + 2×u64 = 32 字节)。 define FB_GET_INFO = 0x4600 +define FB_WAIT_VBLANK = 0x4601 @__attribute__(packed) class FbInfo: @@ -50,11 +51,12 @@ def fb_get_info(fd: int, out: FbInfo*) -> int: string.memset(out, 0, FbInfo.__sizeof__()) return fs.ioctl(fd, uint32(FB_GET_INFO), out) -def fb_map(fd: int, size: uint64) -> uint64: - """把 framebuffer 映射进本进程,返回线性起始地址(0 = 失败),之后可直接写像素。 +def fb_wait_vblank(fd: int) -> int: + """等一次垂直回扫(合成前调用,减轻 tearing)。用户态无 IO 权限,由内核代做。""" + return fs.ioctl(fd, uint32(FB_WAIT_VBLANK), None) - 走通用 mmap(fd = 该设备 fd):内核按设备的 mmap_info 把物理区间按页映射进来。 - """ +def fb_map(fd: int, size: uint64) -> uint64: + """把 framebuffer 映射进本进程,返回线性地址(0 = 失败)。""" return syscall.mmap(0, size, uint64(syscall.PROT_READ | syscall.PROT_WRITE), uint64(syscall.MAP_SHARED), uint64(fd), 0)