feat(explorer): S2 —— 新增图形壳应用 explorer 骨架,用户态经 devfs+mmap 直接写屏

Apps/Explorer:复制标准 app 骨架(main.vp / linker.ld / project.vpj,链接地址 0x400000)。启动流程:open /dev/fb0 → ioctl(FB_GET_INFO) 打几何 → mmap 拿到线性地址 → 等约 2 秒(等内核桌面首屏全量重绘过去)→ 画一条渐变横条验证像素真上屏 → open /dev/input/keyboard|mouse 验证输入设备可达 → 常驻不退出。

vpsdk/devfs.vp(新增 SDK 设备层):设备路径常量(与内核注册名逐字一致)、FB_GET_INFO 与 FbInfo 镜像(packed,与内核同布局)、fb_open/fb_get_info/fb_map/kbd_open/mouse_open。

build.py:把 Explorer 加进 required_apps 清单与完整构建分支;内核 main.vp 在四个 app 之后 spawn /APPS/explorer.elf。内核 desktop 与 explorer 并存属迁移中间态,S3 会把窗口合成整体交出去。
This commit is contained in:
TermiNexus committed 2026-10-16 18:43:13 +08:00
1 parent 7d5380148b
commit 6bd2390e08
6 files changed
+216

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
ENTRY(__entry)
SECTIONS {
. = 0x400000;
.text : {
__entry = .;
*(.text.startup)
*(.text)
*(.text.*)
}
.rodata : {
*(.rodata)
*(.rodata.*)
}
.data : {
*(.data)
*(.data.*)
}
.bss : {
__bss_start = .;
*(.bss)
*(.bss.*)
__bss_end = .;
}
/DISCARD/ : {
*(.comment)
*(.note)
*(.eh_frame)
*(.eh_frame_hdr)
*(.reloc)
*(.rela)
*(.debug*)
}
}
+71
View File
@@ -0,0 +1,71 @@
import vpsdk.devfs as devfs
import vpsdk.process as process
import vpsdk.syscall as syscall
from stdint import *
import viperlib
def _dbg(msg: const str):
syscall._syscall1(uint64(syscall.Syscall.SERIAL_PUTS), uint64(msg))
@__attribute__(section(".text.startup"), aligned(16))
export def _start() -> int:
_dbg("[explorer] start\n")
# 1) 打开 framebuffer 设备,问几何,再映射成线性地址
fb_fd: int = devfs.fb_open()
if fb_fd < 0:
_dbg("[explorer] open /dev/fb0 FAILED\n")
process.exit(1)
return 0
info: devfs.FbInfo = devfs.fb_get_info(fb_fd)
buf: [160]char
viperlib.snprintf(&buf, 160,
"[explorer] fb %ux%u pitch=%u fmt=%u size=%lu phys=0x%lx\n",
info.width, info.height, info.pitch, info.format, info.size, info.phys)
_dbg(&buf)
fb_va: uint64 = devfs.fb_map(fb_fd, info.size)
if fb_va == 0:
_dbg("[explorer] mmap /dev/fb0 FAILED\n")
process.exit(1)
return 0
buf2: [96]char
viperlib.snprintf(&buf2, 96, "[explorer] fb mapped at 0x%lx\n", fb_va)
_dbg(&buf2)
# 2) 等桌面把首屏全量重绘做完(否则刚写的像素会被 _mark_all_dirty 的首次重绘盖掉)。
# 这里没有时钟可用,就空转 yield:yield_cpu 约每 10ms 真让出一次,200 次约 2 秒。
for _i in range(200):
process.yield_cpu()
# 3) 画一条横贯屏幕的渐变条 —— 证明"用户态直接写 framebuffer 真的上屏"。
w: int = int(info.width)
h: int = int(info.height)
pitch_px: int = int(info.pitch) / 4
pixels: uint32* = uint32*(fb_va)
y0: int = h / 3
y1: int = y0 + h / 6
for y in range(y0, y1):
row: int = y * pitch_px
for x in range(w):
rr: int = (x * 255) / w
bb: int = 255 - rr
pixels[row + x] = uint32((rr << 16) | (0x78 << 8) | bb)
_dbg("[explorer] painted gradient band\n")
# 4) 输入设备可达性:能 open 到就说明 /dev/* 通路已打通
# (真正读事件、接管窗口合成放到 S3 —— 内核桌面此刻还在抢同一个 PS/2 口)。
kfd: int = devfs.kbd_open()
mfd: int = devfs.mouse_open()
buf3: [96]char
viperlib.snprintf(&buf3, 96, "[explorer] input: kbd=%d mouse=%d\n", kfd, mfd)
_dbg(&buf3)
# 图形壳暂时只是占位,常驻不退出(app 的 _start 绝不能再 return:栈顶下一格是 0)。
while True:
process.yield_cpu()
return 0
+30
View File
@@ -0,0 +1,30 @@
{
"name": "Explorer",
"version": "1.0.0",
"source_dir": "./",
"build_dir": "./__vpcbuild__",
"compiler": {
"cmd": "llc",
"flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"]
},
"linker": {
"cmd": "ld.lld.exe",
"flags": [
"-m", "elf_x86_64",
"-T", "linker.ld",
"--oformat", "elf64-x86-64",
"--strip-all"
],
"output": "explorer.elf"
},
"includes": ["../..", "../../../../includes"],
"target": {
"triple": "x86_64-none-elf",
"datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
},
"options": {
"slice_level": 3,
"target": "llvm",
"strict_mode": true
}
}
+11
View File
@@ -247,6 +247,17 @@ class ViperKernel:
viperlib.snprintf(&s3d_log, 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid)
serial.puts(&s3d_log)
# 图形壳 explorer(S2 骨架):经 devfs 的 /dev/fb0 + mmap 在用户态直接写屏。
# 内核侧 desktop(内核线程)此刻仍在画桌面,两者并存属于迁移中间态;
# S3 会把窗口合成整体交给它、并删掉内核 UI 那批 syscall。
exp_pid: int = elf.spawn_elf("/APPS/explorer.elf")
if exp_pid < 0:
serial.puts("[main] Explorer spawn failed\n")
else:
exp_log: [64]char
viperlib.snprintf(&exp_log, 64, "[main] Explorer spawned as pid=%d\n", exp_pid)
serial.puts(&exp_log)
so_base: rawptr = elf.load_so("/LIBS/SLOG.SO")
if so_base is not None:
serial.puts("[main] slog.so loaded, calling log_info...\n")
+8
View File
@@ -52,6 +52,7 @@ def BuildApps():
("Apps/Terminal/__vpcbuild__/output/terminal.elf", "./disk/apps/terminal.elf"),
("Apps/Gargantua/__vpcbuild__/output/gargantua.elf", "./disk/apps/gargantua.elf"),
("Apps/Scene3D/__vpcbuild__/output/scene3d.elf", "./disk/apps/scene3d.elf"),
("Apps/Explorer/__vpcbuild__/output/explorer.elf", "./disk/apps/explorer.elf"),
]
os.makedirs("./disk/apps", exist_ok=True)
os.makedirs("./disk/libs", exist_ok=True)
@@ -106,6 +107,13 @@ def BuildApps():
shutil.copy("Apps/Scene3D/__vpcbuild__/output/scene3d.elf", "./disk/apps/scene3d.elf")
exp_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Explorer/project.vpj --clear-cache'
if clean:
exp_cmd += ' --clean'
os.system(exp_cmd)
shutil.copy("Apps/Explorer/__vpcbuild__/output/explorer.elf", "./disk/apps/explorer.elf")
os.makedirs("./disk/libs", exist_ok=True)
so_path = os.path.abspath("Libs/SerialLogger/__vpcbuild__/output/slog.so")
vpui_path = os.path.abspath("Libs/VPUI64/__vpcbuild__/output/vpui64.so")
+57
View File
@@ -0,0 +1,57 @@
import vpsdk.fs as fs
import vpsdk.syscall as syscall
import string
from stdint import *
# ============================================================================
# devfs 设备层:设备节点路径 + 各设备的专用读写/控制助手。
#
# 设备节点由内核在启动时注册(见 VKernel/Kernel/main.vp 的 devfs_register_*),
# 用户态用普通 open/read/write/ioctl 访问 —— 不再有"设备专用 syscall"。
# ============================================================================
# 路径必须与内核注册名逐字一致
define DEV_SERIAL0 = "/dev/serial0"
define DEV_SERIAL1 = "/dev/serial1"
define DEV_FB0 = "/dev/fb0"
define DEV_KBD = "/dev/input/keyboard"
define DEV_MOUSE = "/dev/input/mouse"
# ioctl 命令与返回结构:必须与 VKernel/Kernel/drivers/devfs/devfs.vp 的
# FB_GET_INFO / FbInfo 逐字段一致(packed;4×u32 + 2×u64 = 32 字节)。
define FB_GET_INFO = 0x4600
@__attribute__(packed)
class FbInfo:
width: uint32
height: uint32
pitch: uint32
format: uint32
size: uint64
phys: uint64
def fb_open() -> int:
return fs.open(DEV_FB0, uint32(0))
def fb_get_info(fd: int) -> FbInfo:
"""查 framebuffer 几何 / 格式 / 物理区间。失败时返回全 0(size == 0 即可判失败)。"""
info: FbInfo
string.memset(&info, 0, FbInfo.__sizeof__())
fs.ioctl(fd, uint32(FB_GET_INFO), &info)
return info
def fb_map(fd: int, size: uint64) -> uint64:
"""把 framebuffer 映射进本进程,返回线性起始地址(0 = 失败),之后可直接写像素。
走通用 mmap(fd = 该设备 fd):内核按设备的 mmap_info 把物理区间按页映射进来。
"""
return syscall.mmap(0, size, uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_SHARED), uint64(fd), 0)
def kbd_open() -> int:
return fs.open(DEV_KBD, uint32(0))
def mouse_open() -> int:
return fs.open(DEV_MOUSE, uint32(0))