feat(desk): S3 底座 —— /dev/desk 共享内存通道 + 用户态协议模块

内核 devfs 新增 shm_device(/dev/desk):同一物理区间映射给所有打开者;main.vp 启动时 mm.malloc 出 DESK_SHM_SIZE(8MB) 并登记,分配失败只打日志跳过(explorer 的 open 会明确失败,不静默兜底)。内核只负责"分配 + 共享映射",区间内布局完全不参与。

vpsdk/desk.vp(新增协议模块):共享区固定布局(DeskHeader / WinSlot / 请求环 / 每窗口事件环 / 像素 arena)、单生产者单消费者环形队列(head/tail,跨进程无锁、全程指针直读写)、explorer 用的 desk_format/desk_arena_alloc 与 app 用的 desk_ready。请求/事件类型常量齐备(CREATE/DESTROY/MOVE/RESIZE/TITLE/FLUSH/ACTIVATE 与 CREATED/CLOSE/KEY/MOUSE/RESIZE)。

vpsdk/devfs.vp 增 DEV_DESK 与 shm_map();Explorer 启动即 open+mmap /dev/desk、desk_format 初始化并打印头信息,验证 IPC 底座可用。
This commit is contained in:
TermiNexus committed 2026-10-16 18:53:27 +08:00
1 parent 6bd2390e08
commit 30708d3250
5 files changed
+317

No files matched your search

+21
View File
@@ -1,4 +1,5 @@
import vpsdk.devfs as devfs
import vpsdk.desk as desk
import vpsdk.process as process
import vpsdk.syscall as syscall
from stdint import *
@@ -65,6 +66,26 @@ export def _start() -> int:
viperlib.snprintf(&buf3, 96, "[explorer] input: kbd=%d mouse=%d\n", kfd, mfd)
_dbg(&buf3)
# 5) 挂上 /dev/desk 共享区并格式化 —— explorer 是图形壳,共享区由它初始化,
# app 侧只 desk_ready() 校验。这里先把头打出来证明 IPC 底座可用。
desk_fd: int = devfs.desk_open()
if desk_fd < 0:
_dbg("[explorer] open /dev/desk FAILED\n")
process.exit(1)
return 0
shm: uint64 = devfs.desk_map(desk_fd)
if shm == 0:
_dbg("[explorer] mmap /dev/desk FAILED\n")
process.exit(1)
return 0
desk.desk_format(shm, info.width, info.height, info.pitch)
hdr: desk.DeskHeader* = desk.DeskHeader*(shm + desk.desk_header_off())
buf4: [144]char
viperlib.snprintf(&buf4, 144,
"[explorer] desk shm=0x%lx ready=%d slot=%lu arena=%u\n",
shm, hdr.ready, uint64(desk.desk_slot_size()), hdr.arena_off)
_dbg(&buf4)
# 图形壳暂时只是占位,常驻不退出(app 的 _start 绝不能再 return:栈顶下一格是 0)。
while True:
process.yield_cpu()
+49
View File
@@ -527,3 +527,52 @@ def devfs_register_fb0(fb: rawptr, width: int, height: int, pitch: int, format:
fb0_file.fb_pitch = uint32(pitch)
fb0_file.fb_format = uint32(format)
return devfs_create_file("/dev/fb0", fb0_file, None)
# ============================================================================
# /dev/desk:图形壳 explorer 与各 app 之间的**共享内存通道**。
#
# 内核只做两件事:启动时分配一块物理内存、让每个 open 者 mmap 到**同一**物理区间。
# 区间内部的布局(窗口表 / 各窗口像素缓冲 / 请求与事件环形队列)完全由用户态协议
# 约定,见 vpsdk/desk.vp —— 内核不解析其内容,也不参与窗口语义。
#
# 为什么用 devfs 而不是新 syscall:syscall 表已对齐 Linux 且不留私有段;
# "共享一块内存给多方"正好能用 设备 + mmap 表达(Linux 用 shm/memfd + MAP_SHARED,
# 这里用 devfs 设备 + mmap 达到同样效果)。
# ============================================================================
# 共享区大小:vpsdk/desk.vp 里有一份同名同值的镜像(用户在 mmap 时必须先知道长度)。
define DESK_SHM_SIZE = 8388608
virtual class shm_device(devfs_file):
shm_phys: uint64
shm_size: uint64
def open(self, path: const str, flags: int) -> int:
return 0
def close(self, fd: int) -> int:
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
return -1
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
return -1
def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int:
if out_phys: *out_phys = self.shm_phys
if out_size: *out_size = self.shm_size
return 0
desk_file: shm_device* = shm_device()
def devfs_register_desk(shm: rawptr, size: uint64) -> int:
"""登记 /dev/desk。shm 是内核启动时 mm.malloc 出来的共享区;所有 open 者映射同一区间。"""
global desk_file
if not shm or size == 0:
serial.puts("[devfs] desk shm register skipped (no memory)\n")
return -1
desk_file.__init__()
desk_file.shm_phys = uint64(shm)
desk_file.shm_size = size
return devfs_create_file("/dev/desk", desk_file, None)
+4
View File
@@ -298,6 +298,10 @@ class ViperKernel:
# /dev/fb0:把 framebuffer 的物理区间与几何登记成设备节点,供用户态图形壳
# explorer 经 open + ioctl(FB_GET_INFO) + mmap 接管屏幕绘制(S1)。
devfs.devfs_register_fb0(fb, width, height, pitch, fb_format, fb_size)
# /dev/desk:explorer 与各 app 的共享内存通道(S3)。分配失败就让登记跳过 ——
# explorer 的 open 会明确失败并打日志,不做静默兜底。
desk_shm: rawptr = mm.malloc(uint64(devfs.DESK_SHM_SIZE))
devfs.devfs_register_desk(desk_shm, uint64(devfs.DESK_SHM_SIZE))
serial.puts("[main] sched started\n")
+233
View File
@@ -0,0 +1,233 @@
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_MSG_SIZE = 40 # DeskMsg 的字节数(见下)
# 固定偏移
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
@__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
a: int
b: int
c: int
d: int
_pad: uint32
@__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()))
slot.type = msg.type
slot.win = msg.win
slot.a = msg.a
slot.b = msg.b
slot.c = msg.c
slot.d = msg.d
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()))
out.type = slot.type
out.win = slot.win
out.a = slot.a
out.b = slot.b
out.c = slot.c
out.d = slot.d
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(win_id: int) -> uint64:
return uint64(DESK_OFF_EVENTS) + uint64(win_id) * 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 i in range(DESK_MAX_WIN):
ring_init(base + desk_evt_ring_off(i), 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
+10
View File
@@ -17,6 +17,7 @@ define DEV_SERIAL1 = "/dev/serial1"
define DEV_FB0 = "/dev/fb0"
define DEV_KBD = "/dev/input/keyboard"
define DEV_MOUSE = "/dev/input/mouse"
define DEV_DESK = "/dev/desk"
# ioctl 命令与返回结构:必须与 VKernel/Kernel/drivers/devfs/devfs.vp 的
# FB_GET_INFO / FbInfo 逐字段一致(packed;4×u32 + 2×u64 = 32 字节)。
@@ -55,3 +56,12 @@ def kbd_open() -> int:
def mouse_open() -> int:
return fs.open(DEV_MOUSE, uint32(0))
def shm_map(fd: int, size: uint64) -> uint64:
"""把共享内存设备映射进本进程,返回线性起始地址(0 = 失败)。
与 fb_map 同一机制(设备 mmap_info 给出的物理区间);不同点在于 /dev/desk
的区间对**所有**打开者都是同一块物理内存,因此天然可跨进程共享。
"""
return syscall.mmap(0, size, uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_SHARED), uint64(fd), 0)