蓝色残留根因:main.vp 里 VGA_drv.Print 在 __init__ 尾部执行,而它晚于 app spawn —— explorer 已经把桌面画上去了,内核随后又往 framebuffer 打一行测试文本/清屏,于是永远盖在桌面上(整屏刷一次也救不了,因为之后又被画)。S3e 之后屏幕由 explorer 独占合成,直接删除内核侧 VGA 驱动创建与 Print。另:handle_request 的 CREATE 分支加一条诊断打印(style/w/h),用于确认 style 是否真的透传到 explorer —— 5 条日志,定位后撤。
411 lines
18 KiB
Plaintext
411 lines
18 KiB
Plaintext
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")
|
||
|
||
# PS/2 鼠标:这里是唯一的初始化入口(注册 IRQ12 + 设采样率 + 使能数据上报)。
|
||
# services/mouse.vp 只负责 USB 鼠标,所以这行被注释掉时 /dev/input/mouse 能 open
|
||
# 却永远没有事件 —— explorer 的鼠标通路整条哑掉(现场:桌面有键盘无鼠标)。
|
||
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()
|
||
# /dev/fb0:把 framebuffer 的物理区间与几何登记成设备节点,供用户态图形壳
|
||
# explorer 经 open + ioctl(FB_GET_INFO) + mmap 接管屏幕绘制(S1)。
|
||
# 【重要】这里**直接从 BootInfo 重取几何**,不复用 L116-120 的长寿命局部量:
|
||
# 现场证据——[fb0] ioctl req=0x4600 但 w=0 h=0,即注册时传进去的 width/height 已是 0
|
||
# (本文件顶部注释记录的"__init__ 的 fb/width/height/pitch 局部量被偶发清零"同类问题:
|
||
# 长函数里这些局部量在 L121 之后、L304 之前被写坏,而 L366+ 的 _CheckFrameLocals 又不报,
|
||
# 属于时点相关而非全程损坏)。BootInfo 是全局,随时读取都拿到真值。
|
||
_CheckFrameLocals("pre_devfs_fb0", fb, width, height, pitch)
|
||
devfs.devfs_register_fb0(fb,
|
||
int(BootInfo.framebuffer_width),
|
||
int(BootInfo.framebuffer_height),
|
||
int(BootInfo.framebuffer_pitch),
|
||
int(BootInfo.framebuffer_format),
|
||
BootInfo.framebuffer_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")
|
||
|
||
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")
|
||
|
||
# S3e 之后屏幕由用户态 explorer 独占合成:内核不再创建 VGA 驱动、不再往 framebuffer
|
||
# 画任何东西。原先这里会 Print 一行测试文本,而它执行时间在 explorer 启动之后 ——
|
||
# 等于把内核测试内容盖在桌面上(也是屏幕上蓝色/文本残留的来源)。
|
||
_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
|