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 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") asm.sti() serial.puts("[main] sti done\n") 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) _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) # 内核镜像末端(含 .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 自启动:sched 暂停 → 逐个 spawn .elf(hello / terminal / gargantua / # scene3d / explorer)→ 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) # 图形壳 explorer(图形壳应用):经 devfs 的 /dev/fb0 + mmap 在用户态直接写屏。 # S3e 之后内核侧已无任何窗口/合成语义(services/desktop.vp 已删除), # 屏幕由 explorer 独占绘制。 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) 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() # 【诊断·临时】devfs 注册结果:计数 + files[] 里实际存下的名字 + 直接 find 一次。 # 判读:counts=0 或名字为空 ⇒ 注册没落表;名字正常但 find_in_dir 返回 None ⇒ 名字比较 # 路径坏了;两者都正常而下面 devfs_open 仍 -1 ⇒ 封装层(路径切分/返回值)问题。 _dfi: [160]char viperlib.snprintf(&_dfi, 160, "[main] devfs reg dirs=%d files=%d\n", devfs.directory_count, devfs.file_count) serial.puts(&_dfi) for _k in range(devfs.file_count): _dfn: [160]char viperlib.snprintf(&_dfn, 160, "[main] devfs files[%d] name='%s' priv=0x%lx parent=0x%lx\n", _k, devfs.files[_k].name, uint64(devfs.files[_k].private_data), uint64(devfs.files[_k].parent_dir)) serial.puts(&_dfn) _df_hit: devfs.devfs_file* = devfs.devfs_file.find_in_dir("serial0", devfs.files[0].parent_dir) _dfh: [128]char viperlib.snprintf(&_dfh, 128, "[main] devfs find_in_dir('serial0', files[0].parent)=0x%lx\n", uint64(_df_hit)) serial.puts(&_dfh) # 【诊断·临时】在注册 fb0/desk **之前**先查一次 serial0:与后面那次对比, # 即可判定是不是本轮新增的两个设备注册把 devfs 表搞坏(而不是别的原因)。 _fd0: int = devfs.devfs_open("/dev/serial0", 0) _fd0_cr: [64]char viperlib.snprintf(&_fd0_cr, 64, "[main] devfs serial0(before fb/desk) fd=%d\n", _fd0) serial.puts(&_fd0_cr) devfs.devfs_close(_fd0) # /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)) # 【诊断·临时】量出 desk 共享区地址与 devfs 注册表地址:若两者区间重叠,说明 # mm 把堆分到了内核镜像/.bss 上(devfs 注册表被踩 ⇒ devfs_open 查不到设备 ⇒ fd=-1)。 _desk_cr: [128]char viperlib.snprintf(&_desk_cr, 128, "[main] desk shm=0x%lx size=%lu devfs_files=0x%lx devfs_dirs=0x%lx\n", uint64(desk_shm), uint64(devfs.DESK_SHM_SIZE), uint64(&devfs.files[0]), uint64(&devfs.directories[0])) serial.puts(&_desk_cr) devfs.devfs_register_desk(desk_shm, uint64(devfs.DESK_SHM_SIZE)) serial.puts("[main] sched started\n") fd: int = devfs.devfs_open("/dev/serial0", 0) # 【诊断·临时】devfs 设备查找是否成功:fd < 0 表示 lookup 失败(serial0_open 不会被调、 # 所以 "[serial0] open:" 也不会出现);用户态写串口(explorer 的首条日志)也走这条路。 _devfs_cr: [64]char viperlib.snprintf(&_devfs_cr, 64, "[main] devfs serial0 fd=%d\n", fd) serial.puts(&_devfs_cr) 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") # S3e:内核不再启动 desktop 线程 —— 桌面/窗口/合成/输入分发整体下沉到用户态 # 图形壳 explorer(它经 /dev/fb0 + /dev/input/* + /dev/desk 接管)。 # 这里保留一次 fb 局部量一致性校验,确认进用户态图形壳之前帧缓冲参数没被写坏。 _CheckFrameLocals("pre_explorer", fb, width, height, pitch) # 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