From 82e951ee84ceeba44b7421173d4d3da1bb204e1f Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 15:20:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20=E5=BB=BA=E7=AA=97=E4=B8=8E?= =?UTF-8?q?=E7=BD=AE=E8=84=8F=E6=A0=87=E8=AE=B0=E5=8A=A0=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=8C=BA=EF=BC=8C=E6=B6=88=E9=99=A4=E5=B9=B6=E5=8F=91=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=E5=85=B1=E6=A7=BD/=E5=90=8C=20z=5Forder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit create() 由各 App 线程经 syscall 并发调用(开机多 App 同时建窗最易命中),但 _win_count/_next_z 的读-改-写无互斥:抢占后读到同一 _win_count ⇒ 两个 App 共用同一 AppWindow 槽,标题/坐标/z_order/fb 互相覆盖;读到同一 _next_z ⇒ z_order 相同,渲染排序退化成槽位下标顺序 —— 均表现为更下方窗口的 title 突然显示到最上方。 改为:关键区①只原子分配槽位,结构体填充留在区外(避免长时间关中断);关键区②把 active/z_order/焦点一次性发布(渲染线程只在 active==1 后读该槽)。_bring_to_front 的 _next_z 自增同加关键区;_mark_rows_dirty/_mark_all_dirty/_clear_dirty_flags 同加关键区,避免与 App 线程并发时丢置脏标记(丢标记会让该行永不重绘,旧合成滞留在屏幕上)。 --- VKernel/Kernel/services/desktop.vp | 38 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/VKernel/Kernel/services/desktop.vp b/VKernel/Kernel/services/desktop.vp index 4308628..3777a63 100644 --- a/VKernel/Kernel/services/desktop.vp +++ b/VKernel/Kernel/services/desktop.vp @@ -396,6 +396,11 @@ def _get_title_height(style: int) -> int: def _mark_rows_dirty(start_y: int, count: int): global _dirty_count, _dirty_y_min, _dirty_y_max + # 关键区:本函数会被 App 线程(draw_text/flush/set_title → syscall)与桌面线程并发调用, + # 对 _dirty_rows/_dirty_count/_dirty_y_min/_dirty_y_max 的读-改-写不加锁会丢置脏标记 + # ⇒ 该行永远不重绘,旧合成(例如下方窗口的 title 压在其它窗口上)会留在屏幕上, + # 直到别的操作再次置脏才"弹回去"——正是"更下方的 title 突然跑到最上方、不易复现"。 + asm.cli() for i in range(start_y, start_y + count): if i >= 0 and i < _screen_h: if not _dirty_rows[i]: @@ -405,23 +410,29 @@ def _mark_rows_dirty(start_y: int, count: int): _dirty_y_min = i if i > _dirty_y_max or _dirty_count == 1: _dirty_y_max = i + asm.sti() def _mark_all_dirty(): global _dirty_count, _dirty_y_min, _dirty_y_max + asm.cli() string.memset(_dirty_rows, 1, _screen_h) _dirty_count = _screen_h _dirty_y_min = 0 _dirty_y_max = _screen_h - 1 + asm.sti() def _has_dirty_rows() -> int: return _dirty_count def _clear_dirty_flags(): global _dirty_count, _dirty_y_min, _dirty_y_max + # 关键区:清除与(App 线程的)置脏并发时,会把刚置上的行清掉 ⇒ 同样丢一帧更新 + asm.cli() string.memset(_dirty_rows, 0, _screen_h) _dirty_count = 0 _dirty_y_min = _screen_h _dirty_y_max = -1 + asm.sti() def _wait_vblank(): global _vblank_ok @@ -573,8 +584,11 @@ def _bring_to_front(idx: int): if idx < 0 or idx >= _win_count: return aw: AppWindow* = &_wins[idx] if not aw.active or aw.minimized: return + # 与 create() 共用 _next_z,同样必须原子自增,否则可能出现两个窗口 z_order 相同 + asm.cli() _next_z += 1 aw.z_order = _next_z + asm.sti() def _hit_test_window(mx: int, my: int) -> int: best: int = -1 @@ -2558,18 +2572,24 @@ def _draw_app_window(back: uint32*, win: AppWindow*): def create(x: int, y: int, w: int, h: int, title: const str, style: int) -> int: global _win_count, _next_z, _focused_win - if _win_count >= MAX_APP_WINDOWS: return -1 + # 关键区①:槽位分配必须原子。create() 由各 App 线程经 syscall 并发调用(开机时多个 + # App 同时建窗,最易命中)。若被定时器抢占后读到同一个 _win_count,两个 App 会共用 + # 同一个 AppWindow 槽 ⇒ 标题/坐标/z_order/fb 被互相覆盖 ⇒ "更下方的 title 突然跑到 + # 最上方"(现场)。这里只做"占位",结构体填充放在关键区外,避免长时间关中断。 + asm.cli() + if _win_count >= MAX_APP_WINDOWS: + asm.sti() + return -1 idx: int = _win_count _win_count += 1 + asm.sti() aw: AppWindow* = &_wins[idx] - aw.active = 1 + aw.active = 0 aw.x = x aw.y = y aw.w = w aw.h = h aw.content_h = h - _get_title_height(style) - 2 - _next_z += 1 - aw.z_order = _next_z aw.style = style aw.close_hover = 0 aw.min_hover = 0 @@ -2608,8 +2628,16 @@ def create(x: int, y: int, w: int, h: int, title: const str, style: int) -> int: fb_size: int = w * aw.content_h for fi in range(fb_size): aw.fb[fi] = bg_col - _mark_rows_dirty(y, h) + # 关键区②:发布窗口 —— active/z_order/焦点一次性生效。渲染线程只在 active==1 之后 + # 才会读这个槽,所以上面的字段填充必须先完成;且 _next_z 与 _bring_to_front 共用, + # 必须原子自增,否则两个窗口会拿到相同 z_order ⇒ 排序退化成槽位下标顺序。 + asm.cli() + _next_z += 1 + aw.z_order = _next_z + aw.active = 1 _focused_win = idx + asm.sti() + _mark_rows_dirty(y, h) return idx def draw_text(win_id: int, x: int, y: int, text: const str, color: uint32) -> int: