Files
ViperOS/VKernel/Kernel/main.vp
T
TermiNexus 14e69f62f5 fix(vkernel): 不再 unmount 根文件系统,app 运行期才能读盘
- app 是 spawn 之后异步运行的,运行期仍要 fat32.open(load_so / 资产文件);原来在自动启动块后面立刻 fat32.unmount(),导致 app 第一次 LOAD_SO 就 "[so] failed to open file"

- 该 unmount 原本只为防“破坏性 FAT32 测试”改坏 disk.img,而那些测试已删除,留着只剩切断 app 文件访问的副作用

- 卸载能力保留给非根挂载点(U 盘/多分区),将来配挂载点表 + 引用计数
2026-10-14 18:42:24 +08:00

446 lines
20 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.
from stdint import *
import bootinfo
import intr
import platform.pch as pch
import platform.pch.timer as timer
import platform.pch.rtc as rtc
import drivers.serial.uart.su8250 as su8250
import drivers.serial.uart.serial as serial
import drivers.devfs.devfs as devfs
import paging.paging as paging
import drivers.video.vesafb.vga as vga
import drivers.video.vesafb.gfx as gfx
import drivers.storage.ide.ide as ide
import drivers.fs.fat32.fat32 as fat32
import drivers.fs.fat32.fat32_types as fat32_types
import mm.mm as mm
import string
import sched.sched as sched
import sched.process as proc
import drivers.input.keyboard.keyboard as keyboard
import drivers.input.mouse.mouse as mouse
import drivers.usb.usb as usb
import drivers.core.cpu.cpu as cpu
import drivers.core.cpu.apic as apic
import viperlib
import services.keyboard as kbd_svc
import services.mouse as mse_svc
import services.desktop as desktop
import intr.syscall as syscall
import execrunner.elf as elf
import asm
kbd_tid: int = -1
mse_tid: int = -1
# 影子副本 + 校验:__init__ 的 fb/width/height/pitch 局部量曾被偶发写坏(被清零,
# 导致 desktop.start 拿到 fb=0 → 刷新把后备缓冲写到物理 0 起,覆盖内核自身)。
# 这里保存模块级影子并在关键节点比对,命中即 fail-fast 暴露写坏源,不再让损坏值被静默使用。
_ShadowFb: uint64 = 0
_ShadowW: int = 0
_ShadowH: int = 0
_ShadowPitch: int = 0
def _SetFrameShadow(fb: rawptr, w: int, h: int, pitch: int):
global _ShadowFb, _ShadowW, _ShadowH, _ShadowPitch
_ShadowFb = uint64(fb)
_ShadowW = w
_ShadowH = h
_ShadowPitch = pitch
def _CheckFrameLocals(tag: const str, fb: rawptr, w: int, h: int, pitch: int):
if uint64(fb) != _ShadowFb or w != _ShadowW or h != _ShadowH or pitch != _ShadowPitch:
b: [176]char
viperlib.snprintf(&b, 176,
"[FATAL] %s: frame locals corrupted fb=0x%lx w=%d h=%d pitch=%d (expect 0x%lx %d %d %d)\n",
tag, uint64(fb), w, h, pitch, _ShadowFb, _ShadowW, _ShadowH, _ShadowPitch)
serial.puts(&b)
while True:
sched.Scheduler._yield()
BootInfo: bootinfo.bootinfo*
class ViperKernel:
BootInfo: bootinfo.bootinfo*
VGA: vga._VGAScreenDriver*
def __init__(self):
global BootInfo
self.BootInfo = BootInfo
cpu.enable_sse()
serial.init()
serial.puts("===== ViperOS VKernel Test\n =====")
serial.puts("V2.3.8\n")
intr.gdt.init()
cpu.msr_write(cpu.IA32_KERNEL_GS_BASE, uint64(&cpu._per_cpu))
serial.puts("[main] gdt init done\n")
intr.idt.init()
syscall.init()
serial.puts("[main] syscall init done\n")
sched.Scheduler()
serial.puts("[main] sched init done\n")
timer.timer_init()
serial.puts("[main] timer init done\n")
#mouse.init()
#serial.puts("[main] mouse init done\n")
# 必须初始化 APIC:QEMU/OVMF(UEFI)下本地 APIC 已启用,PIC 外部中断
# 须经 LINT0 送达 CPU。apic.init() 将 LINT0 配为 EXTINT(取消屏蔽),
# 否则定时器中断被 APIC 吞掉 → sti 后 msleep 永远等不到 ticks 挂死。
apic.early_init()
serial.puts("[main] apic init done\n")
# [DBG] 综合诊断:判定 LAPIC MMIO 是否真正可访问/可写(用后移除)
_dbg_base: uint64 = apic.get_base()
_dbg_ver: uint32 = apic.read(0x030) # LAPIC_VERSION:真 LAPIC 应为 0x50014
_dbg_svr: uint32 = apic.read(0x0F0) # LAPIC_SVR:init 已写 0x1FF
apic.write(0x350, 0x700) # 再次写 LINT0,验证写粘性
_dbg_lint0: uint32 = apic.read(0x350)
_dbg_feat: uint32 = cpu.detect_features_static()
_dbg_buf: [160]char
viperlib.snprintf(&_dbg_buf, 160,
"[DBG] base=0x%lx ver=0x%x svr=0x%x lint0=0x%x feat=0x%x apic=%d\n",
_dbg_base, _dbg_ver, _dbg_svr, _dbg_lint0, _dbg_feat,
1 if (_dbg_feat & cpu.CPU_FEATURE_APIC) else 0)
serial.puts(&_dbg_buf)
# [DBG] 读回 IDT 门 32,验证 isr32 分发地址是否正确安装(用后移除)
_g32: uint64 = (uint64(intr.idt.idt[32].base_low) | (uint64(intr.idt.idt[32].base_middle) << 16) | (uint64(intr.idt.idt[32].base_high) << 32))
_g32buf: [96]char
viperlib.snprintf(&_g32buf, 96, "[DBG] idt32 base=0x%lx sel=0x%x type=0x%x\n", _g32, intr.idt.idt[32].selector, intr.idt.idt[32].type_attr)
serial.puts(&_g32buf)
asm.sti()
serial.puts("[main] sti done\n")
# [DBG] 软件触发 int 0x20:门 32 有效则分发到 isr32→timer_handler 打印 H 并返回;
# 门无效则 #GP(isrCommonhandler 打印 [GP] err=0x20),一次性区分分发路径(用后移除)
serial.puts("[DBG] before swint\n")
__asm__("int 0x20", op=[ASM_DESCR.CLOBBER_MEMORY])
serial.puts("[DBG] after swint\n")
# [DBG] 读 PIC1 ISR:bit0 置位说明 PIT IRQ0 已到达 PIC 并被响应(用后移除)
asm.outb(0x20, 0x0B) # OCW3 读 ISR
asm.io_wait()
_pic_isr: uint8 = asm.inb(0x20)
_isrbuf: [48]char
viperlib.snprintf(&_isrbuf, 48, "[DBG] pic_isr=0x%x\n", _pic_isr)
serial.puts(&_isrbuf)
timer.timer_msleep(100)
serial.puts("[main] msleep done\n")
rtc.rtc_init()
serial.puts("[main] rtc init done\n")
ncpu: uint32 = apic.get_cpu_count()
ncpu_cr: [32]char
viperlib.snprintf(&ncpu_cr, 32, "[main] cpu_count=%u\n", ncpu)
serial.puts(ncpu_cr)
if ncpu > 1:
apic.start_aps()
serial.puts("[main] kernel init done\n")
fb: rawptr = rawptr(BootInfo.framebuffer_addr)
width: int = int(BootInfo.framebuffer_width)
height: int = int(BootInfo.framebuffer_height)
pitch: int = int(BootInfo.framebuffer_pitch)
fb_format: int = int(BootInfo.framebuffer_format)
fb_size: uint64 = BootInfo.framebuffer_size
_SetFrameShadow(fb, width, height, pitch)
# [DBG] 打印 BootInfo 指针与 memmap 原始字段,定位全 0 根因(用后移除)
_dbbuf: [140]char
viperlib.snprintf(&_dbbuf, 140, "[main] BootInfo=0x%lx mmap=0x%lx mmsz=%lu mmd=%lu fb=0x%lx\n",
uint64(BootInfo), BootInfo.MemmapAddr, BootInfo.MemmapSize,
BootInfo.MemmapDescSize, BootInfo.framebuffer_addr)
serial.puts(&_dbbuf)
# [DBG] 转储 BootInfo 地址处原始 88 字节(用后移除)
_dbraw: [16]char
for _dbi in range(88):
if (_dbi % 16) == 0:
viperlib.snprintf(&_dbraw, 16, "\n[DB] %04x:", _dbi)
serial.puts(&_dbraw)
_dbv: uint8 = *(uint8*(uint64(BootInfo) + uint64(_dbi)))
viperlib.snprintf(&_dbraw, 16, " %02x", _dbv)
serial.puts(&_dbraw)
serial.puts("\n[DB] dump end\n")
# 内核镜像末端(含 .bss;__bss_end 见 VKernel/linker.ld):传给 mm.init 使其在挑选
# 堆 arena 时排除整块内核镜像 —— .bss 不占文件大小,bootloader 只按文件大小预留页,
# 否则 .bss 尾部(ticks/_perf_* 等全局量)会落在 memmap 空闲区里被堆占用。
_kern_end: uint64 = 0
__asm__(f"lea {asmout(_kern_end, ASM_DESCR.OUTPUT_REG)}, [rip + __bss_end]",
op=[ASM_DESCR.CLOBBER_MEMORY])
if BootInfo is not None:
paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize)
fb_pages: uint64 = (fb_size + uint64(0xFFF)) >> uint64(12)
for fpi in range(fb_pages):
fva: uint64 = uint64(fb) + fpi * uint64(0x1000)
# 帧缓冲改用回写(去掉 PTE_PCD):此前按非缓存映射,每次写屏都直通设备,
# 全屏/大块刷新极慢。回写在 QEMU 下显示正确(TCG 不模拟缓存不一致);
# 真机若要严格正确应改为 write-combining(走 PAT)。
paging.MapPage(fva, fva, paging.PTE_WRITABLE)
mm.init(BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr, fb_size, BootInfo.kernel_PhysAddr, _kern_end)
pt_pool_buf: rawptr = mm.malloc(uint64(513) * uint64(4096))
paging.init_pool(pt_pool_buf, uint64(513) * uint64(4096))
else:
paging.init(0, 0)
mm.init(0, 0, 0, 0, 0)
pt_pool_buf2: rawptr = mm.malloc(uint64(513) * uint64(4096))
paging.init_pool(pt_pool_buf2, uint64(513) * uint64(4096))
serial.puts("[main] paging and mm init done\n")
# 把 .rodata(字符串常量/vtable)映射只读 + CR0.WP=1:
# 越界写立即 #PF 暴露 cr2/rip(此前静默破坏格式串/路径/符号名 → P2/P3/P4/P5 连锁)
paging.ProtectKernelRO()
# [PG] 页表诊断:确认 .rodata 是否真正只读(2MB 大页会令 MakeRangeRO 失效)
paging.DumpMapping(0x100000, "kern")
paging.DumpMapping(0x60000, "bstk")
paging.DumpMapping(0x80000000, "fb")
proc.ProcessManager()
serial.puts("[main] process manager init done\n")
ide.init()
serial.puts("[main] ide init done\n")
serial.puts("[main] initializing FAT32 filesystem\n")
scan_res: int = fat32.scan_drives()
ndrives: int = fat32.get_drive_count()
ndrives_cr: [64]char
viperlib.snprintf(&ndrives_cr, 64, "[fat32] scan_res=%d found %d drives\n", scan_res, ndrives)
serial.puts(&ndrives_cr)
if ndrives > 0:
mount_res: int = fat32.mount()
if mount_res == 0:
dp: fat32_types.dirobj
res: int = fat32.opendir("/", &dp)
if res == 0:
serial.puts("[fat32] listing 0:/\n")
for count in range(20):
info: fat32_types.fileinfo
res2: int = fat32.readdir(&dp, &info)
if res2 != 0: break
if info.fname[0] == 0: break
is_dir: int = 1 if (info.attr & fat32_types.AM_DIR) else 0
entry_cr: [300]char
viperlib.snprintf(&entry_cr, 300, " %s size=%lu dir=%d\n", &info.fname[0], info.file_size, is_dir)
serial.puts(&entry_cr)
fat32.closedir(&dp)
serial.puts("[fat32] directory listing done\n")
else:
serial.puts("[fat32] failed to open root directory\n")
# Load ELF apps BEFORE running destructive FAT32 tests
serial.puts("[main] loading ELF apps...\n")
dp_apps: fat32_types.dirobj
apps_res: int = fat32.opendir("/APPS", &dp_apps)
if apps_res == 0:
serial.puts("[main] APPS dir listing:\n")
for ac in range(20):
ai: fat32_types.fileinfo
ar: int = fat32.readdir(&dp_apps, &ai)
if ar != 0: break
if ai.fname[0] == 0: break
ae: [300]char
viperlib.snprintf(&ae, 300, " %s size=%lu\n", &ai.fname[0], ai.file_size)
serial.puts(&ae)
fat32.closedir(&dp_apps)
else:
serial.puts("[main] APPS dir not found\n")
# 还原 elf 自启动(初版 6813947 的 main.py,改写为新语法):sched 暂停 →
# 逐个 spawn .elf(hello / terminal / gargantua / scene3d)→ 加载 slog.so 做一次
# log_info 冒烟调用 → sched 恢复。spawn 要在 FAT32 破坏性测试之前完成。
sched.Scheduler.disable()
hello_pid: int = elf.spawn_elf("/APPS/hello.elf")
if hello_pid < 0:
serial.puts("[main] Hello spawn failed\n")
else:
hlog: [64]char
viperlib.snprintf(&hlog, 64, "[main] Hello spawned as pid=%d\n", hello_pid)
serial.puts(&hlog)
term_pid: int = elf.spawn_elf("/APPS/terminal.elf")
if term_pid < 0:
serial.puts("[main] Terminal spawn failed\n")
else:
tlog: [64]char
viperlib.snprintf(&tlog, 64, "[main] Terminal spawned as pid=%d\n", term_pid)
serial.puts(&tlog)
garg_pid: int = elf.spawn_elf("/APPS/gargantua.elf")
if garg_pid < 0:
serial.puts("[main] Gargantua spawn failed\n")
else:
glog: [64]char
viperlib.snprintf(&glog, 64, "[main] Gargantua spawned as pid=%d\n", garg_pid)
serial.puts(&glog)
s3d_pid: int = elf.spawn_elf("/APPS/scene3d.elf")
if s3d_pid < 0:
serial.puts("[main] Scene3D spawn failed\n")
else:
s3d_log: [64]char
viperlib.snprintf(&s3d_log, 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid)
serial.puts(&s3d_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")
log_info_fn: rawptr = elf.get_so_symbol(so_base, "log_info", "/LIBS/SLOG.SO")
if log_info_fn is not None:
msg: const str = "Hello from shared library!"
# 与 intr/syscall.vp 的 SO_CALL1 同款:按 SysV 把第一个实参放 rdi,
# 再 call 符号地址(日志函数不需要返回值)。
__asm__(f"""mov rdi, {asminp(msg, ASM_DESCR.REG_ANY)}
call {asminp(log_info_fn, ASM_DESCR.REG_ANY)}""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
else:
serial.puts("[main] log_info symbol not found\n")
else:
serial.puts("[main] failed to load slog.so\n")
sched.Scheduler.enable()
# 破坏性 FAT32 测试已删除(write/rename/delete/mkdir/rmdir 会永久改坏 disk.img)。
# **不 unmount 根文件系统**:app 是 spawn 之后异步运行的,运行期还要读盘
# (load_so / 资产文件等),一旦卸载根 FS,它们的 fat32.open 会直接失败
# (现场:app 第一次 LOAD_SO 得到 "[so] failed to open file")。
# 卸载只应服务于非根挂载点(U 盘/多分区),并配挂载点表 + 引用计数。
else:
serial.puts("[fat32] mount failed\n")
else:
serial.puts("[fat32] no drives found\n")
devfs.devfs_init()
devfs.devfs_register_serial0()
devfs.devfs_register_serial1()
devfs.devfs_register_keyboard()
devfs.devfs_register_mouse()
serial.puts("[main] sched started\n")
fd: int = devfs.devfs_open("/dev/serial0", 0)
buf: [64]char
string.memset(&buf, 0, 64)
devfs.devfs_write(fd, "hello from devfs!", 17)
devfs.devfs_read(fd, &buf, 64)
devfs.devfs_close(fd)
cr: [30]char
string.memset(&cr, 0, 30)
viperlib.snprintf(&cr, 30, "hello %d %.2f", 42, float(0.24))
serial.puts(cr)
serial.puts("\n")
bootinfo_cr: [256]char
string.memset(&bootinfo_cr, 0, 256)
viperlib.snprintf(&bootinfo_cr, 256, "Bootinfo 0x%016lx %lu %lu %lu %lu %lu map 0x%lx+%lu descsize=%lu",
BootInfo.framebuffer_addr,
BootInfo.framebuffer_format,
BootInfo.framebuffer_width,
BootInfo.framebuffer_height,
BootInfo.framebuffer_pitch,
BootInfo.framebuffer_size,
BootInfo.MemmapAddr,
BootInfo.MemmapSize,
BootInfo.MemmapDescSize)
serial.puts(bootinfo_cr)
serial.puts("\n")
a: const str = "Hello"
serial.puts(&a[4]) # o
serial.puts("\n")
d: const int = 42
b: const int* = &d
string.memset(&cr, 0, 4)
viperlib.snprintf(&cr, 30, "%d", b[0]) # 42
serial.puts(cr)
serial.puts("\n")
# paging and mm already initialized before FAT32
usb.init()
_CheckFrameLocals("after_usb", fb, width, height, pitch)
serial.puts("[main] starting keyboard service\n")
kbd_svc.start()
serial.puts("[main] starting mouse service\n")
mse_svc.start()
serial.puts("[main] services started\n")
VGA_drv = vga._VGAScreenDriver(fb, width, height, pitch, fb_format)
self.VGA = &VGA_drv
self.VGA.Print(100, 100, "ViperOS VKernel Test")
_CheckFrameLocals("after_vga", fb, width, height, pitch)
serial.puts("你好\n")
serial.puts("[main] starting desktop\n")
_CheckFrameLocals("pre_desktop", fb, width, height, pitch)
desktop.start(fb, width, height, pitch)
serial.puts("[main] desktop started\n")
# ELF apps already loaded before FAT32 tests
serial.puts("[main] entering main loop\n")
# sched.sched_dump()
last_sec: uint32 = 0
loop_cnt: uint32 = 0
while True:
cur_sec: uint32 = timer.timer_get_seconds()
if cur_sec != last_sec:
last_sec = cur_sec
#if cur_sec % 5 == 0:
# sched.sched_dump()
loop_cnt += 1
sched.Scheduler._yield()
#while True:
# cube.RenderScene(back_fb, cube.SCREEN_W, cube.SCREEN_H, time)
# string.memcpy(fb, back_fb, width * height * 4)
# serial.puts("time")
# time += float(0.1)
# circle(width / 2, height / 2, 180, fb)
# circle(640, 400, 180, 0xFFFFFFFF)
# X 字符串遍历
# c.Cast 更换
# 时钟
# PCI
# SHA1 它者文件更新
# assets
# X attributes语法和谐
# 合成式类型转换
VKP: ViperKernel*
# stackrealign:_start 由 bootloader jmp 进入(rsp≡0 mod 16),llc 按 call 约定
# 假设入口 rsp≡8 → 帧偏 8 → 变参函数 movaps #GP。动态重对齐栈修复。
@__attribute__(section(".text.startup"), aligned(16), stackrealign)
def _start() -> int:
global VKP, BootInfo
saved_rdi: uint64
__asm__(f"mov {asmout(saved_rdi, ASM_DESCR.OUTPUT_REG)}, rdi",
op = [ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RDI])
asm.BSSClean()
BootInfo = bootinfo*(saved_rdi) # 转为 bootinfo*
VKP = &ViperKernel()
while True:
asm.sti()
asm.hlt()
sched.Scheduler._yield()
return 0