Files
ViperOS/vpsdk/desk.vp
T
TermiNexus 9f372ce814 refactor(desk): ring_push/ring_pop 改整块 memcpy,避免协议加字段漏拷
原来两个环函数逐字段拷贝(type/win/pid/a..d/title),DeskMsg 新增 style 时漏同步,导致 style 在入队时被丢弃。改为按 DeskMsg.__sizeof__() 整块 memcpy:结构体以后再加字段自动生效,不会再出现忘记同步某一处的问题。
2026-10-17 11:26:59 +08:00

240 lines
9.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import vpsdk.devfs as devfs
import vpsdk.fs as fs
import string
from stdint import *
# ============================================================================
# /dev/desk 共享内存协议(S3):图形壳 explorer 与各 app 之间唯一的通信通道。
#
# 内核只提供"同一物理区间映射给所有打开者"(devfs 的 /dev/desk + mmap),
# 区间内部的布局与语义全部由本模块约定 —— 用户态协商,内核不参与。
#
# 区间布局(偏移均相对共享区首,全部 8 字节对齐):
# +0x000000 DeskHeader 全局头(含 magic/几何/窗口数/arena 指针)
# +0x000100 [DESK_MAX_WIN]WinSlot 窗口槽表(explorer 是唯一写者)
# +0x002000 RingCtrl + slots 请求环(app → explorer)
# +0x004000 [DESK_MAX_WIN] RingCtrl+slots 每窗口事件环(explorer → app),每窗 0x1000
# +0x200000 像素 arena 各窗口后备缓冲从这里切(bump 分配)
#
# 约定:
# · explorer 启动后把 header.ready 置 1 —— app 见到 0 表示"图形壳还没起来";
# · 窗口槽由 explorer 分配/回收;app 只通过请求环申请与更新,绝不直接改槽表;
# · 像素缓冲由 app 直接写(arena 里属于自己窗口的那块),写完压一条 FLUSH 请求
# 让 explorer 合成;这是"共享内存 + 消息"而不是"拷贝像素"的关键。
# ============================================================================
# 必须与内核 VKernel/Kernel/drivers/devfs/devfs.vp 的 DESK_SHM_SIZE 一致
# (mmap 时用户必须先给出长度,所以这里不能靠"问内核")。
define DESK_SHM_SIZE = 8388608
define DESK_MAGIC = 0x4B534544 # 'DESK'
define DESK_VERSION = 1
define DESK_MAX_WIN = 8
define DESK_TITLE_MAX = 32
define DESK_RING_SLOTS = 64
# 固定偏移
define DESK_OFF_HEADER = 0x000000
define DESK_OFF_SLOTS = 0x000100
define DESK_OFF_REQ = 0x002000
define DESK_OFF_EVENTS = 0x004000
define DESK_OFF_ARENA = 0x200000
# 消息类型:请求(app → explorer)
define DESK_REQ_CREATE = 1 # a=x b=y c=w d=h,title 在数据区;explorer 回 DESK_EVT_CREATED
define DESK_REQ_DESTROY = 2
define DESK_REQ_MOVE = 3 # a=x b=y
define DESK_REQ_RESIZE = 4 # a=w b=h
define DESK_REQ_TITLE = 5 # title 在数据区
define DESK_REQ_FLUSH = 6 # 像素已写,请求合成该窗口
define DESK_REQ_ACTIVATE = 7 # 置顶并聚焦
# 消息类型:事件(explorer → app)
define DESK_EVT_CREATED = 101 # a=win_id(失败为 -1)
define DESK_EVT_CLOSE = 102 # 用户点了关闭按钮,app 应当退出
define DESK_EVT_KEY = 103 # a=ascii b=keycode c=state
define DESK_EVT_MOUSE = 104 # a=x b=y c=buttons d=type
define DESK_EVT_RESIZE = 105 # a=w b=h
# 鼠标事件的细分(DESK_EVT_MOUSE 的 a 字段);vpsdk/window.vp 把它映射成 app 的 EVENT_TYPE_*
define DESK_MOUSE_PRESS = 1
define DESK_MOUSE_RELEASE = 2
define DESK_MOUSE_HOVER = 3
@__attribute__(packed)
class DeskHeader:
magic: uint32
version: uint32
ready: int # explorer 置 1 表示图形壳已接管
win_count: int
fb_w: uint32
fb_h: uint32
fb_pitch: uint32
_pad0: uint32
arena_off: uint32
arena_used: uint32
_pad1: uint32
@__attribute__(packed)
class WinSlot:
used: int
pid: int
active: int
minimized: int
z_order: int
ux: int
uy: int
w: int
h: int
style: int
cursor: int
fb_off: uint32
fb_w: uint32
fb_h: uint32
_pad2: uint32
title: [DESK_TITLE_MAX]char
@__attribute__(packed)
class DeskMsg:
type: uint32
win: int # 请求:目标窗口;CREATE 时为 0(窗口还没诞生)
pid: int # 事件的接收方 pid(请求时由 app 填自己的 pid)
a: int
b: int
c: int
d: int
style: uint32 # CREATE:窗口样式(取值同 vpsdk/window.vp 的 STYLE_*)
# 标题随消息内联(CREATE / REQ_TITLE 用)—— 消息有固定上限,标题最多 DESK_TITLE_MAX-1。
title: [DESK_TITLE_MAX]char
@__attribute__(packed)
class RingCtrl:
head: uint32 # 消费者下一次取的位置
tail: uint32 # 生产者下一次放的位置
size: uint32 # 槽数
_pad: uint32
def desk_slot_size() -> uint32:
return uint32(WinSlot.__sizeof__())
def desk_ring_ctrl_size() -> uint32:
return uint32(RingCtrl.__sizeof__())
def desk_msg_size() -> uint32:
return uint32(DeskMsg.__sizeof__())
# ---------------------------------------------------------------------------
# 环形队列:控制块在给定偏移处,槽紧随其后。单生产者单消费者,无锁 ——
# head == tail 空,(tail+1)%size == head 满。跨进程共享内存下靠"每次读写都落到
# 内存"的顺序保证:这里不缓存跨调用的状态,全部经指针读写。
# ---------------------------------------------------------------------------
def ring_init(ctrl_off: uint64, size: uint32):
c: RingCtrl* = RingCtrl*(ctrl_off)
c.head = uint32(0)
c.tail = uint32(0)
c.size = size
c._pad = uint32(0)
for i in range(size):
m: DeskMsg* = DeskMsg*(ctrl_off + uint64(desk_ring_ctrl_size()) + uint64(i) * uint64(desk_msg_size()))
string.memset(m, 0, desk_msg_size())
def ring_push(ctrl_off: uint64, msg: DeskMsg) -> int:
"""生产者入队。返回 0 = 成功,-1 = 满。"""
c: RingCtrl* = RingCtrl*(ctrl_off)
nxt: uint32 = (c.tail + uint32(1)) % c.size
if nxt == c.head:
return -1
slot: DeskMsg* = DeskMsg*(ctrl_off + uint64(desk_ring_ctrl_size()) + uint64(c.tail) * uint64(desk_msg_size()))
# 整块拷贝:DeskMsg 将来再加字段不必同步改这里(逐字段拷贝曾漏掉 style)
string.memcpy(rawptr(slot), rawptr(&msg), uint64(desk_msg_size()))
c.tail = nxt # 最后写 tail:读者以 tail 为"有货"的标志
return 0
def ring_pop(ctrl_off: uint64, out: DeskMsg*) -> int:
"""消费者出队。返回 1 = 取到一条,0 = 空。"""
c: RingCtrl* = RingCtrl*(ctrl_off)
if c.head == c.tail:
return 0
slot: DeskMsg* = DeskMsg*(ctrl_off + uint64(desk_ring_ctrl_size()) + uint64(c.head) * uint64(desk_msg_size()))
# 整块拷贝(同 ring_push)
string.memcpy(rawptr(out), rawptr(slot), uint64(desk_msg_size()))
c.head = (c.head + uint32(1)) % c.size
return 1
# ---------------------------------------------------------------------------
# 各固定结构的共享区偏移
# ---------------------------------------------------------------------------
def desk_header_off() -> uint64:
return uint64(DESK_OFF_HEADER)
def desk_slot_off(win_id: int) -> uint64:
return uint64(DESK_OFF_SLOTS) + uint64(win_id) * uint64(desk_slot_size())
def desk_req_ring_off() -> uint64:
return uint64(DESK_OFF_REQ)
def desk_evt_ring_off(pid: int) -> uint64:
"""事件环按**接收方 pid** 分环(不是按 win_id,也不是单环)。
· 不能按 win_id:建窗答复 CREATED 必须在 app 知道 win_id 之前送达;
· 不能只用一个共享单环:环是单消费者结构,多个 app 各自 pop 会互相抢事件。
⇒ 按 pid 索引(进程数 = MAX_PROCESSES = 8,与 DESK_MAX_WIN 同量级),
app 用自己的 pid 找到属于自己的那条环,天然隔离。
"""
return uint64(DESK_OFF_EVENTS) + uint64(pid) * uint64(0x1000)
# ---------------------------------------------------------------------------
# 挂载:open /dev/desk + mmap 整个共享区,返回区首线性地址(0 = 失败)。
# explorer 负责格式化(magic/version/几何/各环 init/ready=1);app 只校验。
# ---------------------------------------------------------------------------
def desk_open() -> int:
return fs.open(devfs.DEV_DESK, uint32(0))
def desk_map(fd: int) -> uint64:
return devfs.shm_map(fd, uint64(DESK_SHM_SIZE))
def desk_format(base: uint64, fb_w: uint32, fb_h: uint32, fb_pitch: uint32):
"""explorer 专用:初始化共享区头与各环。"""
h: DeskHeader* = DeskHeader*(base + desk_header_off())
string.memset(h, 0, DeskHeader.__sizeof__())
h.magic = uint32(DESK_MAGIC)
h.version = uint32(DESK_VERSION)
h.fb_w = fb_w
h.fb_h = fb_h
h.fb_pitch = fb_pitch
h.arena_off = uint32(DESK_OFF_ARENA)
h.arena_used = uint32(0)
h.win_count = 0
h.ready = 0
ring_init(base + desk_req_ring_off(), uint32(DESK_RING_SLOTS))
for pi in range(DESK_MAX_WIN):
ring_init(base + desk_evt_ring_off(pi), uint32(DESK_RING_SLOTS))
# 区首清零后 magic 必须最后写,避免 app 读到半初始化状态
h.magic = uint32(DESK_MAGIC)
h.ready = 1
def desk_ready(base: uint64) -> int:
"""app 专用:判断图形壳是否已接管并可通信。"""
h: DeskHeader* = DeskHeader*(base + desk_header_off())
if h.magic != uint32(DESK_MAGIC):
return 0
return h.ready
def desk_arena_alloc(base: uint64, size: uint32) -> uint32:
"""explorer 专用:从像素 arena 切一块,返回相对区首的偏移(0 = 空间不足)。"""
h: DeskHeader* = DeskHeader*(base + desk_header_off())
need: uint32 = (size + uint32(15)) & ~uint32(15)
used: uint32 = h.arena_used
if uint32(DESK_OFF_ARENA) + used + need > uint32(DESK_SHM_SIZE):
return uint32(0)
h.arena_used = used + need
return uint32(DESK_OFF_ARENA) + used