From 6bd2390e0870ca2df66e0838564eee4ea815db5a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 16 Oct 2026 18:43:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(explorer):=20S2=20=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=BE=E5=BD=A2=E5=A3=B3=E5=BA=94=E7=94=A8?= =?UTF-8?q?=20explorer=20=E9=AA=A8=E6=9E=B6=EF=BC=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=80=81=E7=BB=8F=20devfs+mmap=20=E7=9B=B4=E6=8E=A5=E5=86=99?= =?UTF-8?q?=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 会把窗口合成整体交出去。 --- Apps/Explorer/linker.ld | 39 +++++++++++++++++++++ Apps/Explorer/main.vp | 71 +++++++++++++++++++++++++++++++++++++++ Apps/Explorer/project.vpj | 30 +++++++++++++++++ VKernel/Kernel/main.vp | 11 ++++++ build.py | 8 +++++ vpsdk/devfs.vp | 57 +++++++++++++++++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 Apps/Explorer/linker.ld create mode 100644 Apps/Explorer/main.vp create mode 100644 Apps/Explorer/project.vpj create mode 100644 vpsdk/devfs.vp diff --git a/Apps/Explorer/linker.ld b/Apps/Explorer/linker.ld new file mode 100644 index 0000000..0d5a4d5 --- /dev/null +++ b/Apps/Explorer/linker.ld @@ -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*) + } +} diff --git a/Apps/Explorer/main.vp b/Apps/Explorer/main.vp new file mode 100644 index 0000000..d1b8215 --- /dev/null +++ b/Apps/Explorer/main.vp @@ -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 diff --git a/Apps/Explorer/project.vpj b/Apps/Explorer/project.vpj new file mode 100644 index 0000000..850465a --- /dev/null +++ b/Apps/Explorer/project.vpj @@ -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 + } +} diff --git a/VKernel/Kernel/main.vp b/VKernel/Kernel/main.vp index 7bf493f..354b3b2 100644 --- a/VKernel/Kernel/main.vp +++ b/VKernel/Kernel/main.vp @@ -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") diff --git a/build.py b/build.py index 0d2f7ef..4f57890 100644 --- a/build.py +++ b/build.py @@ -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") diff --git a/vpsdk/devfs.vp b/vpsdk/devfs.vp new file mode 100644 index 0000000..159b5ee --- /dev/null +++ b/vpsdk/devfs.vp @@ -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))