feat: Linux ABI P0-P6, BMIDE DMA, ImageSee, 4KiB page pool and Explorer hardening

This commit is contained in:
TermiNexus committed 2026-10-29 00:42:21 +08:00
1 parent ebd43ef9da
commit 6f856992da
41 files changed
+8697 -2471

No files matched your search

+5 -1
View File
@@ -97,4 +97,8 @@ _UNCADK/
# 编译
**/output/
**/temp/
**/__vpcbuild__/
**/__vpcbuild__/
# 本地大文件 / 临时文件
Vmware/
NotoSansSC/
1.py
+236 -47
View File
@@ -5,6 +5,9 @@ import vpsdk.process as process
import vpsdk.syscall as syscall
import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表
import vpsdk.fonttools.fonttools as fonttools
import vpsdk.imagesee.imagesee as imagesee
import vpsdk.imagesee.image as image
import vpsdk.stdlib as stdlib
from stdint import *
import string
@@ -55,6 +58,10 @@ define MIN_WIN_W = 160
define MIN_WIN_H = 100
# 窗口阴影(0 = 关闭)
define SHADOW_ENABLED = 1
# 画面同步开关:
# 1 = 每帧合成前等待垂直回扫(减少撕裂,VMware 下可能受 0x3DA 可用性影响)
# 0 = 不等回扫,最低输入延迟(拖动最跟手,但可能出现撕裂)
define SCREEN_SYNC = 1
define SHADOW_LEFT = 8
define SHADOW_RIGHT = 12
define SHADOW_TOP = 4
@@ -262,6 +269,35 @@ def _corner_alpha(px: int, py: int, cx: int, cy: int, r: int) -> int:
return 255
# ---------------------------------------------------------------------------
# 把已合成的一整段像素提交到 framebuffer。
#
# 用 non-temporal store(movnti)+ sfence:即使 framebuffer 是回写映射,
# 也绕过数据缓存直接写到内存,扫描输出能立刻看到;同时 8 字节顺序写会合并
# 成写合并缓冲,比逐字节/逐像素写快得多。QEMU TCG 下等价于普通写,不受影响。
# ---------------------------------------------------------------------------
def _fb_copy(dst: uint64, src: uint64, nbytes: uint64):
if nbytes == 0: return
if (nbytes & uint64(7)) == 0:
qwords: uint64 = nbytes >> 3
__asm__(f"""mov rdi, {asminp(dst, ASM_DESCR.REG_ANY)}
mov rsi, {asminp(src, ASM_DESCR.REG_ANY)}
mov rcx, {asminp(qwords, ASM_DESCR.REG_ANY)}
Lfbcopy:
mov rax, [rsi]
movnti qword ptr [rdi], rax
add rsi, 8
add rdi, 8
dec rcx
jnz Lfbcopy
sfence""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_RSI])
else:
string.memcpy(rawptr(dst), rawptr(src), nbytes)
class DeskServer:
# 屏幕
fb: uint32* # /dev/fb0 映射后的线性地址
@@ -271,6 +307,7 @@ class DeskServer:
pitch_px: int
fmt: uint32 # 0 = RGBA, 1 = BGRA
bg_row: uint32* # 一行背景的预构造副本
bg_full: uint32* # 壁纸(整屏);None = 使用 bg_row 纯色背景
back: uint32* # 后备缓冲:绘制先落这里,整帧后逐行拷到 fb
# 共享区
shm: uint64
@@ -310,6 +347,14 @@ class DeskServer:
resize_oh: int
resize_sx: int
resize_sy: int
# 内容双缓冲:vis_* 是当前正在合成的内容;s.fb_off/fb_w/fb_h 是应用正在
# 绘制的新内容。应用 FLUSH 后才交换,避免 resize 过程中按新步长读旧内容。
vis_off: [desk.DESK_MAX_WIN]uint32
vis_cap: [desk.DESK_MAX_WIN]uint32
vis_w: [desk.DESK_MAX_WIN]int
vis_h: [desk.DESK_MAX_WIN]int
spare_off: [desk.DESK_MAX_WIN]uint32
spare_cap: [desk.DESK_MAX_WIN]uint32
# 标题栏按钮的 hover 状态(用于变色和刷新)
hover_btn_win: int
hover_btn_kind: int
@@ -323,6 +368,79 @@ class DeskServer:
return (uint32(255) << 24) | (r << 16) | (g << 8) | b
return (uint32(255) << 24) | (b << 16) | (g << 8) | r
# 桌面壁纸:/sys/images/screen/background.jpg,等比“ cover”裁切铺满屏幕。
# 加载失败时保持 init() 里预构造的纯色 bg_row,不影响启动。
def _load_background(self):
img: image.Image* = imagesee.load("/sys/images/screen/background.jpg")
if img is None:
# FAT32 is shared with other booting apps; retry once before
# falling back to the solid color.
process.yield_cpu()
img = imagesee.load("/sys/images/screen/background.jpg")
if img is None:
devfs.serial_puts("[explorer] background: not found (solid color)\n")
return
w: int = self.fb_w
h: int = self.fb_h
if w <= 0 or h <= 0: return
buf: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4)))
if buf is None: return
iw: int = img.width
ih: int = img.height
if iw <= 0 or ih <= 0: return
sw: int = iw
sh: int = ih
ox: int = 0
oy: int = 0
if iw * h > ih * w:
# 图片更宽:按高度铺满,左右裁切
sw = (ih * w) / h
if sw < 1: sw = 1
ox = (iw - sw) / 2
else:
sh = (iw * h) / w
if sh < 1: sh = 1
oy = (ih - sh) / 2
xoff: int32* = int32*(stdlib.malloc(uint64(w * 4)))
if xoff is None: return
x: int = 0
while x < w:
sx: int = ox + (x * sw) / w
if sx < 0: sx = 0
if sx >= iw: sx = iw - 1
xoff[x] = int32(sx)
x = x + 1
y: int = 0
while y < h:
sy: int = oy + (y * sh) / h
if sy < 0: sy = 0
if sy >= ih: sy = ih - 1
srow: int = sy * iw
drow: int = y * w
if self.fmt == uint32(1):
# ImageSee 的 0xAARRGGBB 与 BGRA framebuffer 内存布局一致
x = 0
while x < w:
buf[drow + x] = img.pixels[srow + int(xoff[x])] | uint32(0xFF000000)
x = x + 1
else:
x = 0
while x < w:
c: uint32 = img.pixels[srow + int(xoff[x])]
r: uint32 = (c >> 16) & uint32(0xFF)
g: uint32 = (c >> 8) & uint32(0xFF)
b: uint32 = c & uint32(0xFF)
buf[drow + x] = (uint32(0xFF) << 24) | (b << 16) | (g << 8) | r
x = x + 1
y = y + 1
self.bg_full = buf
devfs.serial_puts("[explorer] background: image loaded\n")
def bg_src(self, y: int) -> uint32*:
if self.bg_full is not None:
return uint32*(uint64(self.bg_full) + uint64(y * self.fb_w) * uint64(4))
return self.bg_row
# ---- 按 alpha/255 把 fg 混到 bg 上(RGBA/BGRA 同为 R/B@0,16、G@8,可共用 SWAR)----
def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32:
ia: int = 255 - a
@@ -370,6 +488,14 @@ class DeskServer:
self.resize_oh = 0
self.resize_sx = 0
self.resize_sy = 0
i0: int = 0
for i0 in range(desk.DESK_MAX_WIN):
self.vis_off[i0] = uint32(0)
self.vis_cap[i0] = uint32(0)
self.vis_w[i0] = 0
self.vis_h[i0] = 0
self.spare_off[i0] = uint32(0)
self.spare_cap[i0] = uint32(0)
self.hover_btn_win = -1
self.hover_btn_kind = 0
# 预构造一行背景,渲染时整行 memcpy
@@ -380,6 +506,9 @@ class DeskServer:
col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35))
for i in range(w):
self.bg_row[i] = col
# 可选壁纸:整屏缓冲,失败则继续用上面的纯色行
self.bg_full = None
self._load_background()
# 后备缓冲:整屏像素,行距 = fb_w(与 framebuffer 的 pitch 无关)
back_va: uint64 = syscall.mmap(0, uint64(w) * uint64(h) * uint64(4),
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
@@ -517,24 +646,41 @@ class DeskServer:
# 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配,
# 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。
def _set_window_rect(self, s: desk.WinSlot*, x: int, y: int, w: int, h: int) -> int:
def _set_window_rect(self, s: desk.WinSlot*, win: int, x: int, y: int, w: int, h: int) -> int:
ch: int = self._content_h(h)
need: int = w * ch
if w <= 0 or need <= 0: return 0
if need > int(s.fb_cap):
# 交互缩放时按 1.5 倍增长,避免拖拽过程中每加几像素就切一块新 arena;
# 最大化的一次性大跨度仍会直接按 need 分配。
alloc_px: int = need
grow: int = int(s.fb_cap) + int(s.fb_cap) / 2
if grow > alloc_px: alloc_px = grow
off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4))
if off == uint32(0): return 0
s.fb_off = off
s.fb_cap = uint32(alloc_px)
b: uint32* = uint32*(self.shm + uint64(off))
col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
for k in range(alloc_px):
b[k] = col
# 纯移动:内容尺寸没变,不切缓冲
if w == int(s.fb_w) and ch == int(s.fb_h):
s.ux = x
s.uy = y
s.w = w
s.h = h
return 1
target_off: uint32 = s.fb_off
target_cap: uint32 = s.fb_cap
# 当前绘制目标若正在显示(上次已 FLUSH),或容量不足,就换到备用/新缓冲。
# 这样 resize 期间合成器仍读旧 vis 缓冲,绝不会按新步长读旧内容。
if target_off == self.vis_off[win] or need > int(target_cap):
if self.spare_off[win] != uint32(0) and need <= int(self.spare_cap[win]):
target_off = self.spare_off[win]
target_cap = self.spare_cap[win]
self.spare_off[win] = uint32(0)
self.spare_cap[win] = uint32(0)
else:
alloc_px: int = need
grow: int = int(target_cap) + int(target_cap) / 2
if grow > alloc_px: alloc_px = grow
off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4))
if off == uint32(0): return 0
# 旧 target 还没显示过,可留作下一次 resize 的备用
if s.fb_off != self.vis_off[win] and s.fb_off != uint32(0):
self.spare_off[win] = s.fb_off
self.spare_cap[win] = s.fb_cap
target_off = off
target_cap = uint32(alloc_px)
s.fb_off = target_off
s.fb_cap = target_cap
s.ux = x
s.uy = y
s.w = w
@@ -577,7 +723,7 @@ class DeskServer:
if ny + nh > self.fb_h: nh = self.fb_h - ny
if nx == s.ux and ny == s.uy and nw == s.w and nh == s.h: return
self.mark_win(s.uy, s.h)
if self._set_window_rect(s, nx, ny, nw, nh) != 0:
if self._set_window_rect(s, self.resize_win, nx, ny, nw, nh) != 0:
self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE),
int(s.fb_w), int(s.fb_h), 0, 0)
self.mark_win(s.uy, s.h)
@@ -635,7 +781,7 @@ class DeskServer:
mw: int = self.fb_w
mh: int = self.fb_h - TASKBAR_H
if mh < TITLE_H + TITLE_GAP + 1: return
if self._set_window_rect(s, 0, 0, mw, mh) == 0: return
if self._set_window_rect(s, win, 0, 0, mw, mh) == 0: return
s.restore_x = ox
s.restore_y = oy
s.restore_w = ow
@@ -652,7 +798,7 @@ class DeskServer:
if s.used == 0 or s.maximized == 0: return 0
oy: int = s.uy
oh: int = s.h
if self._set_window_rect(s, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0:
if self._set_window_rect(s, win, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0:
return 0
s.maximized = 0
self.mark_win(oy, oh)
@@ -727,6 +873,12 @@ class DeskServer:
s.fb_cap = uint32(cap_px)
s.fb_w = uint32(w)
s.fb_h = uint32(content_h)
self.vis_off[i] = buf_off
self.vis_cap[i] = uint32(cap_px)
self.vis_w[i] = w
self.vis_h[i] = content_h
self.spare_off[i] = uint32(0)
self.spare_cap[i] = uint32(0)
string.memset(&s.title, 0, desk.DESK_TITLE_MAX)
# 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾
buf: uint32* = uint32*(self.shm + uint64(buf_off))
@@ -986,8 +1138,13 @@ class DeskServer:
content_row: int = yy - content_y0
if row_is_title != 0 and title_alpha < 255:
self._blend_row(wx + 1, yy, ww - 2, row_col, title_alpha)
elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, ww - 2)
elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh and buf is not None:
blit_w: int = ww - 2
if blit_w > fw - 2: blit_w = fw - 2
if blit_w > 0:
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, blit_w)
if blit_w < ww - 2:
self.row_fill(wx + 1 + blit_w, yy, (ww - 2) - blit_w, row_col)
else:
self.row_fill(wx, yy, ww, row_col)
if rel == 0 or rel == wh - 1:
@@ -1005,7 +1162,7 @@ class DeskServer:
wh: int = s.h
content_row: int = yy - content_y0
do_blit: int = 0
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh and buf is not None:
do_blit = 1
cyc: int = r
if rel >= r: cyc = wh - r - 1
@@ -1026,7 +1183,7 @@ class DeskServer:
if _is_in_corner(rx, rel, cxc, cyc, r) != 0:
on_border: int = _is_on_border(rx, rel, ww, wh, r)
idx: int = yy * self.fb_w + px
if on_border == 0 and do_blit != 0:
if on_border == 0 and do_blit != 0 and rx < fw:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
@@ -1052,7 +1209,12 @@ class DeskServer:
if rel == 0 or rel == wh - 1:
self.row_fill(mid0, yy, midw, outline_col)
elif do_blit != 0:
self.row_blit(mid0, yy, buf, content_row * fw + r, midw)
mid_blit_w: int = midw
if mid_blit_w > fw - r: mid_blit_w = fw - r
if mid_blit_w > 0:
self.row_blit(mid0, yy, buf, content_row * fw + r, mid_blit_w)
if mid_blit_w < midw:
self.row_fill(mid0 + mid_blit_w, yy, midw - mid_blit_w, row_col)
elif row_is_title != 0 and title_alpha < 255:
self._blend_row(mid0, yy, midw, row_col, title_alpha)
else:
@@ -1069,7 +1231,7 @@ class DeskServer:
if _is_in_corner(rx, rel, cxr, cyc, r) != 0:
on_border: int = _is_on_border(rx, rel, ww, wh, r)
idx: int = yy * self.fb_w + px
if on_border == 0 and do_blit != 0:
if on_border == 0 and do_blit != 0 and rx < fw:
self.back[idx] = buf[content_row * fw + rx]
px += 1
continue
@@ -1446,7 +1608,7 @@ class DeskServer:
w: int = order[i]
if yy < sy[w] or yy >= sy[w] + sh[w]: continue
if sx[w] > xx:
self.row_blit(xx, yy, self.bg_row, xx, sx[w] - xx)
self.row_blit(xx, yy, self.bg_src(yy), xx, sx[w] - xx)
ex: int = sx[w] + sw[w]
if ex > xx: xx = ex
if smode[w] != 0:
@@ -1457,9 +1619,9 @@ class DeskServer:
elif rel < WIN_RADIUS or rel >= sh[w] - WIN_RADIUS:
need = 1
if need != 0:
self.row_blit(sx[w], yy, self.bg_row, sx[w], sw[w])
self.row_blit(sx[w], yy, self.bg_src(yy), sx[w], sw[w])
if xx < self.fb_w:
self.row_blit(xx, yy, self.bg_row, xx, self.fb_w - xx)
self.row_blit(xx, yy, self.bg_src(yy), xx, self.fb_w - xx)
yy += 1
# 2) 窗口
for i in range(cnt):
@@ -1546,13 +1708,22 @@ class DeskServer:
self._draw_shadow(s, y0, y1, shadow_r)
if wy + wh <= y0 or wy >= y1: return
buf: uint32* = uint32*(self.shm + uint64(s.fb_off))
fw: int = int(s.fb_w)
fh: int = int(s.fb_h)
# 槽表被写坏时宁可跳过该窗口,也不要按越界 fb_off 读像素把整个合成器带崩。
need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4)
if s.fb_off < uint32(desk.DESK_OFF_ARENA) or uint64(s.fb_off) + need_bytes > uint64(desk.DESK_SHM_SIZE):
return
# 合成器只读“最后一次 FLUSH 的可见缓冲”。resize 期间 s.fb_* 已经是新尺寸,
# 但应用还没画完,绝不能拿新步长去读旧缓冲(否则就是截图里的乱码)。
v_off: uint32 = self.vis_off[win]
fw: int = self.vis_w[win]
fh: int = self.vis_h[win]
buf: uint32* = None
if v_off >= uint32(desk.DESK_OFF_ARENA) and fw > 0 and fh > 0:
need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4)
if uint64(v_off) + need_bytes <= uint64(desk.DESK_SHM_SIZE):
buf = uint32*(self.shm + uint64(v_off))
else:
fw = 0
fh = 0
else:
fw = 0
fh = 0
content_y0: int = wy + th + TITLE_GAP
r: int = WIN_RADIUS
yy: int = y0
@@ -1667,10 +1838,13 @@ class DeskServer:
def render(self):
if self.dirty_any == 0: return
devfs.fb_wait_vblank(self.fb_fd)
# 逐脏段渲染并立刻提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交(4MB memcpy),帧率直接崩。
# 段内先全画进 back 再逐行提交,保证每一行提交出去的都是完整一帧的内容。
if SCREEN_SYNC != 0:
# 可选:等垂直回扫,减少撕裂。内核侧是有界轮询(2048 次),
# 不会像旧版 2×100000 次端口读那样拖慢一帧。
devfs.fb_wait_vblank(self.fb_fd)
# 逐脏段渲染并提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交,帧率直接崩。
# 段内先全画进 back 再整段提交,保证提交出去的每一行都是完整一帧的内容。
y: int = 0
while y < self.fb_h:
if self.dirty[y] == 0:
@@ -1681,14 +1855,19 @@ class DeskServer:
y += 1
y1: int = y
self.render_range(y0, y1)
yy: int = y0
while yy < y1:
if self.dirty[yy] != 0:
# fb 行距 = pitch_px,back 行距 = fb_w
string.memcpy(rawptr(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4)),
rawptr(uint64(self.back) + uint64(yy * self.fb_w) * uint64(4)),
uint64(self.fb_w) * uint64(4))
yy += 1
# back 与 fb 行距一致时整段一次 memcpy:大范围拖动时比逐行 1080 次
# rep movsb 少很多固定开销;行距不同才退回逐行。
if self.pitch_px == self.fb_w:
_fb_copy(uint64(self.fb) + uint64(y0 * self.pitch_px) * uint64(4),
uint64(self.back) + uint64(y0 * self.fb_w) * uint64(4),
uint64((y1 - y0) * self.fb_w) * uint64(4))
else:
yy: int = y0
while yy < y1:
_fb_copy(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4),
uint64(self.back) + uint64(yy * self.fb_w) * uint64(4),
uint64(self.fb_w) * uint64(4))
yy += 1
self.clear_dirty()
# ---------------- 事件投递 ----------------
@@ -1806,7 +1985,7 @@ class DeskServer:
self.mark_win(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_RESIZE):
self.mark_win(s.uy, s.h)
if self._set_window_rect(s, s.ux, s.uy, m.a, m.b) != 0:
if self._set_window_rect(s, win, s.ux, s.uy, m.a, m.b) != 0:
s.maximized = 0
self.mark_win(s.uy, s.h)
elif ty == uint32(desk.DESK_REQ_TITLE):
@@ -1819,6 +1998,16 @@ class DeskServer:
if self.hit_test(self.cx, self.cy) == win:
self._mark_cursor_band()
elif ty == uint32(desk.DESK_REQ_FLUSH):
# 应用已经按 s.fb_w/fb_h 画完一帧:此刻才把"正在显示的内容"切到
# 新缓冲。旧的 vis 缓冲留作下一次 resize 的备用。
old_off: uint32 = self.vis_off[win]
if old_off != s.fb_off:
self.spare_off[win] = old_off
self.spare_cap[win] = self.vis_cap[win]
self.vis_off[win] = s.fb_off
self.vis_cap[win] = s.fb_cap
self.vis_w[win] = int(s.fb_w)
self.vis_h[win] = int(s.fb_h)
# 最小化窗口仍在后台绘制/FLUSH:不要置脏,否则合成器每帧都要重画背景。
if s.minimized == 0:
self.mark(s.uy, s.h)
+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*)
}
}
+146
View File
@@ -0,0 +1,146 @@
# ImageSee demo: decode PNG and JPEG with vpsdk.imagesee and show them.
import vpsdk.vpui as vpui
import vpsdk.window as window
import vpsdk.process as process
import vpsdk.stdlib as stdlib
import vpsdk.imagesee.imagesee as imagesee
import vpsdk.imagesee.image as im
import vpsdk.stdio as stdio
import string
from stdint import *
define WIN_X = 40
define WIN_Y = 36
define WIN_W = 1120
define WIN_H = 616
define CANVAS_W = 536
define CANVAS_H = 520
define BG = 0xFF14141E
define PANEL = 0xFF20202C
define BORDER = 0xFF3A3A50
# Fill dst[0..w*h) with a dark checkerboard so transparent parts are visible.
def _checker(dst: uint32*, w: int, h: int):
y: int = 0
while y < h:
x: int = 0
while x < w:
c: uint32 = uint32(PANEL)
if ((x >> 4) + (y >> 4)) & 1:
c = uint32(BG)
dst[y * w + x] = c
x = x + 1
y = y + 1
def _draw_fit(dst: uint32*, dw: int, dh: int, img: im.Image*,
ox: int, oy: int, bw: int, bh: int):
if img is None: return
tw: int = bw
th: int = (tw * img.height) / img.width
if th > bh:
th = bh
tw = (th * img.width) / img.height
if tw < 1 or th < 1: return
x0: int = ox + (bw - tw) / 2
y0: int = oy + (bh - th) / 2
y: int = 0
while y < th:
sy: int = (y * img.height) / th
if sy >= img.height: sy = img.height - 1
x: int = 0
while x < tw:
sx: int = (x * img.width) / tw
if sx >= img.width: sx = img.width - 1
ty: int = y0 + y
tx: int = x0 + x
if tx >= 0 and ty >= 0 and tx < dw and ty < dh:
dst[ty * dw + tx] = img.pixels[sy * img.width + sx]
x = x + 1
y = y + 1
def _border(dst: uint32*, dw: int, dh: int, x: int, y: int, w: int, h: int, c: uint32):
i: int = 0
while i < w:
if x + i >= 0 and x + i < dw:
if y >= 0 and y < dh: dst[y * dw + x + i] = c
if y + h - 1 >= 0 and y + h - 1 < dh: dst[(y + h - 1) * dw + x + i] = c
i = i + 1
i = 0
while i < h:
if y + i >= 0 and y + i < dh:
if x >= 0 and x < dw: dst[(y + i) * dw + x] = c
if x + w - 1 >= 0 and x + w - 1 < dw: dst[(y + i) * dw + x + w - 1] = c
i = i + 1
def _report(path: const str) -> im.Image*:
img: im.Image* = imagesee.load(path)
if img is None:
stdio.printf("[imagesee] %s: FAILED stage=%d\n", path, imagesee.last_error())
return None
stdio.printf("[imagesee] %s: %s %dx%d sum=%x\n",
path, imagesee.format_name(img), img.width, img.height,
imagesee.checksum(img))
return img
def _panel_failed(dst: uint32*, w: int, h: int):
y: int = 0
while y < h:
x: int = 0
while x < w:
c: uint32 = uint32(0xFF300018)
if ((x >> 5) + (y >> 5)) & 1:
c = uint32(0xFF001830)
dst[y * w + x] = c
x = x + 1
y = y + 1
@__attribute__(section(".text.startup"), aligned(16))
export def _start() -> int:
stdio.printf("[imagesee] app start\n")
png_img: im.Image* = _report("/IMAGES/SAMPLE.PNG")
jpg_img: im.Image* = _report("/IMAGES/PHOTO.JPG")
_report("/IMAGES/GRAY.JPG")
_report("/IMAGES/PAL.PNG")
_report("/IMAGES/INTER.PNG")
root: vpui.Tk = vpui.Tk()
root.title("ImageSee - PNG | JPEG")
root.set_style(window.STYLE_WIN11)
root.geometry(WIN_X, WIN_Y, WIN_W, WIN_H)
root.bg_color = uint32(BG)
master: vpui.Tk* = &root
title: vpui.Label = vpui.Label(master, "ImageSee left: PNG right: JPEG", uint32(0xFFE0E0F0))
root.pack(0, vpui.PACK_TOP, vpui.FILL_X, 0, 12, 6)
fb1: uint32* = uint32*(stdlib.malloc(uint64(CANVAS_W * CANVAS_H * 4)))
fb2: uint32* = uint32*(stdlib.malloc(uint64(CANVAS_W * CANVAS_H * 4)))
if fb1 is None or fb2 is None:
process.exit(1)
return 0
_checker(fb1, CANVAS_W, CANVAS_H)
_checker(fb2, CANVAS_W, CANVAS_H)
if png_img is None:
_panel_failed(fb1, CANVAS_W, CANVAS_H)
else:
_draw_fit(fb1, CANVAS_W, CANVAS_H, png_img, 8, 8, CANVAS_W - 16, CANVAS_H - 16)
if jpg_img is None:
_panel_failed(fb2, CANVAS_W, CANVAS_H)
else:
_draw_fit(fb2, CANVAS_W, CANVAS_H, jpg_img, 8, 8, CANVAS_W - 16, CANVAS_H - 16)
_border(fb1, CANVAS_W, CANVAS_H, 0, 0, CANVAS_W, CANVAS_H, uint32(BORDER))
_border(fb2, CANVAS_W, CANVAS_H, 0, 0, CANVAS_W, CANVAS_H, uint32(BORDER))
c1: vpui.FBCanvas = vpui.FBCanvas(master, CANVAS_W, CANVAS_H)
c1.set_fb(fb1, CANVAS_W, CANVAS_H)
root.pack(1, vpui.PACK_LEFT, 0, 1, 10, 8)
c2: vpui.FBCanvas = vpui.FBCanvas(master, CANVAS_W, CANVAS_H)
c2.set_fb(fb2, CANVAS_W, CANVAS_H)
root.pack(2, vpui.PACK_LEFT, 0, 1, 10, 8)
stdio.printf("[imagesee] window ready\n")
root.mainloop()
process.exit(0)
return 0
+31
View File
@@ -0,0 +1,31 @@
{
"name": "ImageSee",
"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": "imagesee.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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+8 -4
View File
@@ -20,9 +20,11 @@ A bare-metal 64-bit operating system written in the [Viper](https://git.gvsds.co
- **VKernel** —— 64 位微内核(x86_64-none-elf 目标)
- **UEFI 引导器** —— `boot/boot.c`(GNU-EFI 编译为 `disk/EFI/BOOT/bootx64.efi`)
- **FAT32 驱动** —— 自研实现,支持挂载、目录、文件操作
- **4 个用户应用** —— HelloWorld / Terminal / Gargantua / Scene3D
- **7 个用户应用** —— HelloWorld / Terminal / Gargantua / Scene3D / Explorer / FontDemo / ImageSee
- **1 个动态库** —— SerialLogger
- **ViperPOS SDK** —— 用户态开发库(系统调用、窗口、文件系统、进程)
- **ViperPOS SDK** —— 用户态开发库(系统调用、窗口、文件系统、进程、图像解码)
- **syscall ABI 计划** —— 与 Linux x86-64 对齐的路线见 [`docs/syscall.md`](docs/syscall.md)
- **ImageSee** —— PNG/JPEG 解码库与示例应用见 [`docs/imagesee.md`](docs/imagesee.md)
### 目录结构
@@ -185,9 +187,11 @@ Main components:
- **VKernel** — 64-bit microkernel (target `x86_64-none-elf`)
- **UEFI bootloader** — `boot/boot.c` (compiled by GNU-EFI to `disk/EFI/BOOT/bootx64.efi`)
- **FAT32 driver** — self-implemented, supports mount/dir/file operations
- **4 user applications** — HelloWorld / Terminal / Gargantua / Scene3D
- **7 user applications** — HelloWorld / Terminal / Gargantua / Scene3D / Explorer / FontDemo / ImageSee
- **1 dynamic library** — SerialLogger
- **ViperPOS SDK** — user-space development kit (syscall, window, fs, process)
- **ViperPOS SDK** — user-space development kit (syscall, window, fs, process, image decoding)
- **Syscall ABI plan** — Linux x86-64 alignment roadmap in [`docs/syscall.md`](docs/syscall.md)
- **ImageSee** — PNG/JPEG decoder library and demo app, see [`docs/imagesee.md`](docs/imagesee.md)
### Directory layout
+79
View File
@@ -88,3 +88,82 @@ Write-Host "Unmounting disk image..."
imdisk -D -m $MOUNT_LETTER
Write-Host "Disk image created: $DISK_IMG" -ForegroundColor Green
# ==========================================================
# Refresh the VMware disk next to the .vmx
# ==========================================================
# Vmware/ViperOS.vmdk is a monolithicFlat descriptor; the data lives in
# ViperOS-flat.vmdk. Both are regenerated from disk.img so "Open VM" always
# sees the image that was just packed. The .vmx already points at
# ide0:0.fileName = "ViperOS.vmdk"; it uses EFI firmware for bootx64.efi.
$VMWARE_DIR = Join-Path $PROJECT_DIR "Vmware"
$VMWARE_VMDK = Join-Path $VMWARE_DIR "ViperOS.vmdk"
$VMWARE_FLAT = Join-Path $VMWARE_DIR "ViperOS-flat.vmdk"
$VMWARE_VMX = Join-Path $VMWARE_DIR "Viper OS.vmx"
if (Test-Path $VMWARE_DIR) {
Write-Host "=== Updating VMware disk ===" -ForegroundColor Cyan
$vmRunning = Get-Process -Name "vmware-vmx" -ErrorAction SilentlyContinue
if ($vmRunning) {
Write-Host "Warning: a VMware VM is running; close it and re-run to refresh the VMDK." -ForegroundColor Yellow
} else {
# A leftover .lck from a crash keeps VMware from opening the VM.
$vmLock = "$VMWARE_VMX.lck"
if (Test-Path $vmLock) {
Write-Host "Removing stale VMware lock: $vmLock" -ForegroundColor Yellow
Remove-Item -Recurse -Force $vmLock -ErrorAction SilentlyContinue
}
$size = (Get-Item $DISK_IMG).Length
$sectors = [int64]($size / 512)
$cyl = [int64][math]::Floor($sectors / (16 * 63))
# Keep the existing CID/UUID so VMware treats it as the same disk.
$cid = "fffffffe"
$uuid = "60 00 C2 96 88 c0 09 6d-c8 30 f7 71 57 ce 47 0d"
$longid = "6af48e7def7fae6bfa57a4affffffffe"
if (Test-Path $VMWARE_VMDK) {
foreach ($l in (Get-Content $VMWARE_VMDK -ErrorAction SilentlyContinue)) {
if ($l -match '^CID=([0-9a-fA-F]+)') { $cid = $Matches[1] }
if ($l -match '^ddb\.uuid\s*=\s*"(.+)"') { $uuid = $Matches[1] }
if ($l -match '^ddb\.longContentID\s*=\s*"(.+)"') { $longid = $Matches[1] }
}
}
$descriptor = @"
# Disk DescriptorFile
version=1
encoding="GBK"
CID=$cid
parentCID=ffffffff
createType="monolithicFlat"
# Extent description
RW $sectors FLAT "ViperOS-flat.vmdk" 0
# The Disk Data Base
#DDB
ddb.adapterType = "ide"
ddb.geometry.cylinders = "$cyl"
ddb.geometry.heads = "16"
ddb.geometry.sectors = "63"
ddb.longContentID = "$longid"
ddb.uuid = "$uuid"
ddb.virtualHWVersion = "21"
"@
try {
if (Test-Path $VMWARE_FLAT) { Remove-Item -Force $VMWARE_FLAT }
Copy-Item -Force $DISK_IMG $VMWARE_FLAT
Set-Content -Path $VMWARE_VMDK -Value $descriptor -Encoding ASCII
if (-not (Select-String -Path $VMWARE_VMX -Pattern 'ide0:0\.fileName\s*=\s*"ViperOS\.vmdk"' -Quiet)) {
Write-Host "Note: $VMWARE_VMX does not reference ViperOS.vmdk; check ide0:0.fileName." -ForegroundColor Yellow
}
Write-Host "VMDK updated: $VMWARE_VMDK ($sectors sectors, $([math]::Round($size / 1MB)) MiB)" -ForegroundColor Green
} catch {
Write-Host "Warning: failed to refresh the VMware disk: $($_.Exception.Message)" -ForegroundColor Yellow
}
}
} else {
Write-Host "Note: Vmware directory not found; skipping VMDK update." -ForegroundColor DarkGray
}
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""Generate the sample images shipped with the ImageSee demo.
Run from the ViperOS root:
python Scripts/gen_imagesee_assets.py
The generated files live in Assets/images/ and are copied to /IMAGES on the
ViperOS disk by build.py. Requires Pillow (only for generating the assets;
the OS itself does not need it).
"""
import os
import struct
import zlib
from PIL import Image, ImageDraw, ImageFilter
W, H = 320, 220
OUT = os.path.join("Assets", "images")
BG_OUT = os.path.join("Assets", "background.jpg")
def test_pattern():
img = Image.new("RGBA", (W, H), (0, 0, 0, 255))
px = img.load()
for y in range(H):
for x in range(W):
r = (x * 255) // (W - 1)
g = (y * 255) // (H - 1)
b = 255 - ((x + y) * 255) // (W + H - 2)
px[x, y] = (r, g, b, 255)
d = ImageDraw.Draw(img)
d.rectangle([12, 12, 92, 72], fill=(255, 255, 255, 255))
d.rectangle([22, 22, 82, 62], fill=(0, 0, 0, 255))
d.rectangle([108, 12, 188, 72], fill=(230, 40, 40, 255))
d.rectangle([198, 12, 278, 72], fill=(40, 200, 80, 255))
d.rectangle([288, 12, 308, 72], fill=(40, 80, 230, 255))
d.ellipse([40, 100, 160, 210], fill=(255, 220, 40, 255))
d.ellipse([150, 90, 250, 190], outline=(255, 255, 255, 255), width=5)
d.line([0, H - 1, W - 1, 80], fill=(255, 0, 255, 255), width=3)
d.line([0, 80, W - 1, H - 1], fill=(0, 255, 255, 255), width=3)
for i in range(0, 40):
d.rectangle([280 - i, 160 + i, 286 - i, 166 + i], fill=(i * 6, i * 6, i * 6, 255))
return img
def png_chunk(typ: bytes, data: bytes) -> bytes:
c = struct.pack(">I", len(data)) + typ + data
c += struct.pack(">I", zlib.crc32(typ + data) & 0xFFFFFFFF)
return c
def write_interlaced_png(path, img):
"""Adam7 interlaced RGBA PNG (filter 0), to exercise the pass decoder."""
w, h = img.size
px = img.load()
passes = [(0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4),
(0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2)]
raw = bytearray()
for x0, y0, dx, dy in passes:
pw = (w - x0 + dx - 1) // dx if w > x0 else 0
ph = (h - y0 + dy - 1) // dy if h > y0 else 0
if pw <= 0 or ph <= 0:
continue
for ry in range(ph):
raw.append(0) # filter: None
y = y0 + ry * dy
for rx in range(pw):
x = x0 + rx * dx
r, g, b, a = px[x, y]
raw += bytes((r, g, b, a))
ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 1)
out = b"\x89PNG\r\n\x1a\n" + png_chunk(b"IHDR", ihdr)
out += png_chunk(b"IDAT", zlib.compress(bytes(raw), 9))
out += png_chunk(b"IEND", b"")
with open(path, "wb") as f:
f.write(out)
def make_background():
"""960x540 desktop wallpaper shipped as /sys/images/screen/background.jpg."""
w, h = 960, 540
base = Image.new("RGB", (w, h))
px = base.load()
for y in range(h):
t = y / (h - 1)
r = int(12 + 34 * t)
g = int(16 + 20 * t)
b = int(34 + 44 * t)
for x in range(w):
# subtle diagonal tint
u = (x / (w - 1) + t) * 0.5
px[x, y] = (min(255, r + int(10 * u)),
min(255, g + int(6 * u)),
min(255, b + int(18 * u)))
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
d = ImageDraw.Draw(overlay)
blobs = [
(140, 420, 210, (30, 120, 190, 120)),
(330, 140, 170, (150, 40, 180, 110)),
(700, 360, 260, (20, 90, 160, 120)),
(820, 110, 150, (220, 60, 130, 90)),
(520, 470, 190, (40, 170, 170, 90)),
]
for cx, cy, rr, col in blobs:
d.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=col)
overlay = overlay.filter(ImageFilter.GaussianBlur(30))
base = Image.alpha_composite(base.convert("RGBA"), overlay).convert("RGB")
# vignette keeps the corners dark so white window chrome stays readable
mask = Image.new("L", (w, h))
mp = mask.load()
for y in range(h):
for x in range(w):
dx = (x - w / 2) / (w / 2)
dy = (y - h / 2) / (h / 2)
d2 = dx * dx + dy * dy
v = 255 - int(min(150, d2 * 110))
mp[x, y] = max(0, v)
base = Image.composite(base, Image.new("RGB", (w, h), (0, 0, 0)), mask)
base.save(BG_OUT, "JPEG", quality=88, subsampling=2, optimize=False)
print("[assets] wrote", BG_OUT)
def main():
os.makedirs(OUT, exist_ok=True)
img = test_pattern()
# Opaque RGBA PNG (exercises color type 6, non-interlaced).
img.save(os.path.join(OUT, "sample.png"), "PNG")
# Palette PNG with one transparent color (color type 3 + tRNS).
pal = img.convert("RGB").quantize(colors=64, method=Image.MEDIANCUT)
pal.info["transparency"] = 0
pal.save(os.path.join(OUT, "pal.png"), "PNG", transparency=0)
# Baseline 4:2:0 JPEG.
img.convert("RGB").save(os.path.join(OUT, "photo.jpg"), "JPEG",
quality=90, subsampling=2, optimize=False)
# Grayscale JPEG (single component path).
img.convert("L").save(os.path.join(OUT, "gray.jpg"), "JPEG", quality=90)
# Interlaced RGBA PNG with a vertical alpha ramp (Adam7 + alpha).
inter = img.copy()
ip = inter.load()
for y in range(H):
for x in range(W):
r, g, b, a = ip[x, y]
ip[x, y] = (r, g, b, (x * 255) // (W - 1))
write_interlaced_png(os.path.join(OUT, "inter.png"), inter)
make_background()
# Reference samples for the app's serial output.
print("[assets] reference samples (PNG/JPEG decode with PIL):")
for name in ("sample.png", "photo.jpg", "gray.jpg",
"pal.png", "inter.png"):
path = os.path.join(OUT, name)
ref = Image.open(path).convert("RGBA")
pts = [(ref.width // 4, ref.height // 3),
(ref.width // 2, ref.height // 2),
((ref.width * 3) // 4, (ref.height * 2) // 3)]
vals = [ref.getpixel(p) for p in pts]
print(f"[assets] {name:12s} {ref.width}x{ref.height} " +
" ".join(f"{p[0]},{p[1]},{p[2]}" for p in vals))
print("[assets] wrote", OUT)
if __name__ == "__main__":
main()
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""Canonical ViperOS syscall table.
The kernel and vpsdk keep separate copies (vpsdk must not depend on VKernel),
so both files are generated from this list. Never edit the generated block
by hand:
# BEGIN GENERATED SYSCALL TABLE
...
# END GENERATED SYSCALL TABLE
Usage:
python Scripts/syscall_table.py --write # regenerate both files
python Scripts/syscall_table.py --check # fail if out of sync
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TARGETS = [
ROOT / "VKernel" / "Kernel" / "intr" / "syscall.vp",
ROOT / "vpsdk" / "syscall.vp",
]
BEGIN = "# BEGIN GENERATED SYSCALL TABLE"
END = "# END GENERATED SYSCALL TABLE"
# (kind, payload...) kind: "section" | "entry"
# Entry numbers follow Linux x86-64; Linux calls we do not implement yet are
# simply absent (the dispatcher returns -ENOSYS for unknown numbers).
TABLE = [
("section", "Linux 对齐段(号 = Linux x86-64 的号)"),
("entry", "READ", 0, "read(fd, buf, count)"),
("entry", "WRITE", 1, "write(fd, buf, count)"),
("entry", "OPEN", 2, "open(path, flags) - Linux O_*"),
("entry", "CLOSE", 3, "close(fd)"),
("entry", "STAT", 4, "stat(path, fileinfo*) - legacy struct"),
("entry", "FSTAT", 5, "fstat(fd, struct stat*)"),
("entry", "POLL", 7, "poll(fds, nfds, timeout) - stub 0"),
("entry", "SEEK", 8, "lseek(fd, offset, whence) -> new offset"),
("entry", "MMAP", 9, "mmap(addr, len, prot, flags, fd, off)"),
("entry", "MPROTECT", 10, "mprotect(addr, len, prot)"),
("entry", "MUNMAP", 11, "munmap(addr, len)"),
("entry", "BRK", 12, "brk(addr) -> new break"),
("entry", "RT_SIGACTION", 13, "rt_sigaction() - bookkeeping"),
("entry", "RT_SIGPROCMASK", 14, "rt_sigprocmask() - bookkeeping"),
("entry", "RT_SIGRETURN", 15, "rt_sigreturn()"),
("entry", "IOCTL", 16, "ioctl(fd, request, arg)"),
("entry", "PREAD64", 17, "pread64(fd, buf, count, offset)"),
("entry", "PWRITE64", 18, "pwrite64(fd, buf, count, offset)"),
("entry", "READV", 19, "readv(fd, iov, iovcnt)"),
("entry", "WRITEV", 20, "writev(fd, iov, iovcnt)"),
("entry", "ACCESS", 21, "access(path, mode)"),
("entry", "PIPE", 22, "pipe(int[2])"),
("entry", "YIELD", 24, "sched_yield()"),
("entry", "MREMAP", 25, "mremap(old, oldsz, newsz, flags, newaddr)"),
("entry", "MADVISE", 28, "madvise(addr, len, advice)"),
("entry", "DUP", 32, "dup(fd)"),
("entry", "DUP2", 33, "dup2(oldfd, newfd)"),
("entry", "NANOSLEEP", 35, "nanosleep(req, rem)"),
("entry", "GETPID", 39, "getpid()"),
("entry", "CLONE", 56, "clone(flags, stack, ptid, ctid, tls)"),
("entry", "FORK", 57, "fork() - copy address space"),
("entry", "SPAWN", 59, "execve(path, argv, envp)"),
("entry", "THREAD_EXIT", 60, "exit(): retire current thread"),
("entry", "WAIT4", 61, "wait4(pid, status, options, rusage)"),
("entry", "KILL", 62, "kill(pid, sig) - bookkeeping"),
("entry", "UNAME", 63, "uname(utsname*)"),
("entry", "FCNTL", 72, "fcntl(fd, cmd, arg)"),
("entry", "GETCWD", 79, "getcwd(buf, size)"),
("entry", "CHDIR", 80, "chdir(path)"),
("entry", "RENAME", 82, "rename(old, new)"),
("entry", "MKDIR", 83, "mkdir(path)"),
("entry", "RMDIR", 84, "rmdir(path)"),
("entry", "REMOVE", 87, "unlink(path)"),
("entry", "READLINK", 89, "readlink(path, buf, size) - no symlinks"),
("entry", "GETUID", 102, "getuid()"),
("entry", "GETGID", 104, "getgid()"),
("entry", "GETEUID", 107, "geteuid()"),
("entry", "GETEGID", 108, "getegid()"),
("entry", "GETTIMEOFDAY", 96, "gettimeofday(tv, tz)"),
("entry", "GETPPID", 110, "getppid()"),
("entry", "ARCH_PRCTL", 158, "arch_prctl(code, addr)"),
("entry", "REBOOT_LINUX", 169, "reboot(magic1, magic2, cmd, arg)"),
("entry", "GETTID", 186, "gettid()"),
("entry", "FUTEX", 202, "futex(uaddr, op, val, timeout)"),
("entry", "GETDENTS64", 217, "getdents64(fd, buf, count)"),
("entry", "SET_TID_ADDRESS", 218, "set_tid_address(tidptr)"),
("entry", "CLOCK_GETTIME", 228, "clock_gettime(clockid, timespec*)"),
("entry", "CLOCK_GETRES", 229, "clock_getres(clockid, timespec*)"),
("entry", "EXIT", 231, "exit_group(): terminate all threads"),
("entry", "OPENAT", 257, "openat(dirfd, path, flags, mode)"),
("entry", "NEWFSTATAT", 262, "newfstatat(dirfd, path, stat*, flags)"),
("entry", "UNLINKAT", 263, "unlinkat(dirfd, path, flags)"),
("entry", "READLINKAT", 267, "readlinkat(dirfd, path, buf, size)"),
("entry", "FACCESSAT", 269, "faccessat(dirfd, path, mode, flags)"),
("entry", "SET_ROBUST_LIST", 273, "set_robust_list(head, len)"),
("entry", "DUP3", 292, "dup3(oldfd, newfd, flags)"),
("entry", "PIPE2", 293, "pipe2(int[2], flags)"),
("entry", "PRLIMIT64", 302, "prlimit64(pid, resource, new, old)"),
("entry", "GETRANDOM", 318, "getrandom(buf, len, flags)"),
("entry", "STATX", 332, "statx(...) - -ENOSYS, glibc falls back"),
("entry", "RSEQ", 334, "rseq(rseq, len, flags) - -ENOSYS"),
("section", "临时私有段(0x1000+):Linux 无对应,逐条迁出"),
("entry", "LOAD_SO", 0x1006, "TODO(userspace): dynamic library loading"),
("entry", "SO_CALL1", 0x1007, "TODO(userspace): dynamic library call"),
("entry", "REBOOT", 0x1008, "TODO(linux): reboot(169)"),
("entry", "SHUTDOWN", 0x1009, "TODO(linux): reboot(RB_POWER_OFF)"),
("entry", "SPAWN_PROC", 0x100A, "TODO(userspace): Terminal run helper"),
("entry", "THREAD_CREATE_OLD", 0x100B, "TODO(userspace): ViperOS thread_create"),
]
def render() -> str:
out = ["enum Syscall:"]
for item in TABLE:
if item[0] == "section":
out.append(f" # ---- {item[1]} ----")
continue
_, name, num, comment = item
value = f"0x{num:04X}" if num >= 0x1000 else f"{num}"
out.append(f" {name:<18}= {value:<7}# {comment}")
return "\n".join(out) + "\n"
def split_lines(text: str):
"""Split keeping line endings (the tree mixes CRLF and a few LF lines)."""
return text.splitlines(keepends=True)
def replace_block(text: str) -> str:
lines = split_lines(text)
b = e = -1
for i, line in enumerate(lines):
stripped = line.strip()
if stripped == BEGIN:
b = i
elif stripped == END:
e = i
break
if b < 0 or e < 0 or e <= b:
raise SystemExit(f"markers not found: {BEGIN} / {END}")
newline = "\r\n" if lines[b].endswith("\r\n") else "\n"
block = [ln + newline for ln in render().splitlines()]
return "".join(lines[: b + 1] + block + lines[e:])
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--check", action="store_true")
ap.add_argument("--write", action="store_true")
args = ap.parse_args()
if not args.check and not args.write:
ap.error("pass --check or --write")
stale = []
for path in TARGETS:
if not path.exists():
print(f"[syscall] missing {path}")
return 1
with open(path, encoding="utf-8", newline="") as fh:
old = fh.read()
new = replace_block(old)
if new != old:
stale.append(path)
if args.write:
with open(path, "w", encoding="utf-8", newline="") as fh:
fh.write(new)
print(f"[syscall] updated {path.relative_to(ROOT)}")
else:
print(f"[syscall] ok {path.relative_to(ROOT)}")
if args.check and stale:
print("[syscall] out of sync; run: python Scripts/syscall_table.py --write")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+24 -5
View File
@@ -111,6 +111,10 @@ virtual class devfs_file:
# mmap 支持:默认不可映射(返回 -1)。可映射设备(/dev/fb0)覆写它,
# 把自己的物理区间交给内核 mmap 去映射进用户地址空间。
def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int: return -1
# 1 = 映射该设备的用户页要按 MMIO 处理(PTE_PCD,禁用缓存)。
# framebuffer 必须返回 1:VMware/真机上写回缓存的 MMIO 写入可能不会立刻
# 反映到扫描输出,表现为系统还在跑但屏幕停在旧画面。共享内存(/dev/desk)返回 0。
def mmap_uncached(self) -> int: return 0
class _DevFSObject:
@@ -288,6 +292,12 @@ class _DevFSObject:
file_ptr: devfs_file* = fd_table[fd].file
return file_ptr.mmap_info(fd, out_phys, out_size)
def get_mmap_uncached(self, fd: int) -> int:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return 0
file_ptr: devfs_file* = fd_table[fd].file
return file_ptr.mmap_uncached()
def list_directory(self, path: const str,
buffer: str,
size: uint32) -> int:
@@ -332,6 +342,9 @@ def devfs_ioctl(fd: int, cmd: uint32, arg: rawptr) -> int:
def devfs_get_mmap_info(fd: int, out_phys: uint64*, out_size: uint64*) -> int:
return DevFSObject.get_mmap_info(fd, out_phys, out_size)
def devfs_mmap_uncached(fd: int) -> int:
return DevFSObject.get_mmap_uncached(fd)
def devfs_list_directory(path: const str, buffer: str, size: uint32) -> int:
return DevFSObject.list_directory(path, buffer, size)
@@ -527,11 +540,15 @@ virtual class framebuffer_device(devfs_file):
if request == FB_WAIT_VBLANK:
# 等进入垂直回扫期(0x3DA bit3)。返回等待消耗的迭代数:到上限即超时
#(当前没有 VGA 回扫信号),调用方可据此判断同步是否真的生效。
t: int = 100000
while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: t -= 1
t2: int = 100000
while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0: t2 -= 1
return 100000 - t2
t: int = 2048
while (asm.inb(0x3DA) & 0x08) != 0 and t > 0:
t -= 1
asm.pause()
t2: int = 2048
while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0:
t2 -= 1
asm.pause()
return 2048 - t2
if request != FB_GET_INFO or not arg:
return -1
info: FbInfo* = FbInfo*(uint64(arg))
@@ -548,6 +565,8 @@ virtual class framebuffer_device(devfs_file):
if out_size: *out_size = self.fb_size
return 0
def mmap_uncached(self) -> int: return 1
fb0_file: framebuffer_device* = framebuffer_device()
def devfs_register_fb0(fb: rawptr, width: int, height: int, pitch: int, format: int, size: uint64) -> int:
@@ -31,12 +31,10 @@ def read(pdrv: uint8, buff: uint8*, sector: uint32, count: uint32) -> types.DRES
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY
drive_lock.lock()
i: uint32
for i in range(count):
rd_res: int = ide.readSector(sector + i, buff + (i * 512))
if rd_res != 0:
drive_lock.unlock()
return types.DRESULT.RES_ERROR
rd_res: int = ide.readSectors(sector, buff, count)
if rd_res != 0:
drive_lock.unlock()
return types.DRESULT.RES_ERROR
drive_lock.unlock()
return types.DRESULT.RES_OK
@@ -6,6 +6,10 @@ import fat32_time as ftime
import viperlib
import drivers.serial.uart.serial as serial
# Max sectors per coalesced file read (64 KiB); the IDE layer further caps DMA
# commands at 128 sectors.
define DMA_READ_CLUSTER_CAP = 128
def _read32(buf: uint8*, off: uint32) -> uint32:
return uint32(buf[off]) | (uint32(buf[off + 1]) << 8) | (uint32(buf[off + 2]) << 16) | (uint32(buf[off + 3]) << 24)
@@ -1525,6 +1529,39 @@ class fileobj:
if self.file_pos >= self.file_size: break
if self.first_cluster < 2: break
if self.current_cluster < 2 or vol.is_eoc(self.current_cluster): break
# Fast path: at a cluster boundary, coalesce contiguous clusters and
# issue one multi-sector read (BMIDE DMA) instead of one PIO command
# per 512-byte sector. Capped at 64 KiB per call; the loop continues.
if (self.file_pos % vol.cluster_size) == 0 and remaining >= vol.cluster_size:
max_bytes: uint32 = remaining
fleft: uint32 = self.file_size - self.file_pos
if max_bytes > fleft: max_bytes = fleft
max_clusters: uint32 = max_bytes / vol.cluster_size
cap_clusters: uint32 = (uint32(DMA_READ_CLUSTER_CAP) * uint32(512)) / vol.cluster_size
if max_clusters > cap_clusters: max_clusters = cap_clusters
run: uint32 = 0
c: uint32 = self.current_cluster
next_c: uint32 = 0
while run < max_clusters:
if c < 2 or vol.is_eoc(c): break
run = run + 1
next_c = vol.cluster_get_next(c)
if next_c != c + 1: break
c = next_c
if run >= 2:
sectors: uint32 = run * uint32(vol.cluster_sectors)
abs_sect: uint64 = vol.cluster_to_sector(self.current_cluster) + uint64(vol.hidden_sectors)
vol.lock.lock()
res_run: DRESULT = diskio.read(vol.pdrv, buf_ptr, uint32(abs_sect), sectors)
vol.lock.unlock()
if res_run != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR
nbytes: uint32 = sectors * uint32(512)
buf_ptr = buf_ptr + nbytes
self.file_pos = self.file_pos + nbytes
total_read = total_read + nbytes
remaining = remaining - nbytes
self.current_cluster = next_c
continue
cluster_offset: uint32 = self.file_pos % vol.cluster_size
sector_in_cluster: uint32 = cluster_offset / 512
offset_in_sector: uint32 = cluster_offset % 512
+272 -2
View File
@@ -1,5 +1,9 @@
import asm
import drivers.usb.pci as pci
import drivers.serial.uart.serial as serial
import paging.paging as paging
import viperlib
import string
enum IDError:
@@ -21,6 +25,119 @@ define SECTOR_SIZE = 512
_ide_initialized: int = 0
# ---- Bus-master IDE (BMIDE) ----
# One 64 KiB PRD entry per command; the DMA buffer is 64 KiB aligned in BSS so
# a single entry never crosses a 64 KiB boundary.
define DMA_MAX_SECTORS = 16 # 16 * 512 = 8 KiB per DMA command
define DMA_AREA_SIZE = 131072
define DMA_SCRATCH_LBA = 0x7FFF0 # near the end of the 256 MiB image; safe to restore
define DMA_AREA_SIZE = 131072
_bm_base: uint16 = 0
_bm_ok: int = 0
_dma_area: [DMA_AREA_SIZE]uint8
_dma_buf: uint64 = 0
_dma_buf_pa: uint64 = 0
_prdt_area: [64]uint64
_prdt: uint64 = 0
_prdt_pa: uint64 = 0
_dma_logged: int = 0
_dma_tried: int = 0
_dma_dir: uint8 = 0x08
_pci_cmd: uint16 = 0
def _mfence():
__asm__("mfence", op=[ASM_DESCR.CLOBBER_MEMORY])
def _clflush_range(addr: uint64, nbytes: uint64):
a: uint64 = addr & ~uint64(63)
end: uint64 = addr + nbytes
while a < end:
__asm__(f"clflush [{asminp(a, ASM_DESCR.REG_ANY)}]",
op=[ASM_DESCR.CLOBBER_MEMORY])
a = a + uint64(64)
def _ide_reset():
asm.outb(0x3F6, 0x04)
for _ in range(10000): asm.nop()
asm.outb(0x3F6, 0x00)
for _ in range(10000): asm.nop()
# Ask the drive to switch to a DMA transfer mode. After SRST the drive is in
# its default PIO mode; READ DMA (0xC8) may be aborted until SET FEATURES runs.
# use_mdma=0: Ultra DMA (word 88), use_mdma=1: multiword DMA (word 63).
def _set_dma_mode(use_mdma: int) -> int:
err: IDError = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return -1
asm.outb(IDE_baseport_PRIMARY + 6, uint8(0xE0))
asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xEC)) # IDENTIFY
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return -1
ident: [256]uint16
i: int = 0
while i < 256:
ident[i] = asm.inw(IDE_baseport_PRIMARY)
i = i + 1
mode: int = -1
value: int = 0
if use_mdma != 0:
# word 63 bits 0..7: multiword DMA modes 0..7 (we use 0..2)
m: int = 2
while m >= 0:
if ident[63] & uint16(1 << m):
mode = m
break
m = m - 1
if mode < 0: return -1
value = 0x20 | mode
else:
# word 88: Ultra DMA modes supported (bits 0..6). Pick the highest mode
# the drive advertises: the firmware/controller timing is usually already
# programmed for it.
m = 6
while m >= 0:
if ident[88] & uint16(1 << m):
mode = m
break
m = m - 1
if mode < 0: return -1
value = 0x40 | mode
err = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return -1
asm.outb(IDE_baseport_PRIMARY + 1, uint8(0x03)) # SET FEATURES: transfer mode
asm.outb(IDE_baseport_PRIMARY + 2, uint8(value))
asm.outb(IDE_baseport_PRIMARY + 6, uint8(0xE0))
asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xEF))
err = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return -1
st: uint8 = asm.inb(IDE_baseport_PRIMARY + 7)
if st & uint8(0x01): return -1
return mode
# Read a scratch sector with DMA and compare against the PIO copy. A wrong
# bus-master direction bit may write the scratch sector; restore it via PIO.
def _dma_selftest(dr: uint8) -> int:
global _dma_dir
_dma_dir = dr
ref: [512]uint8
rr: int = readSector(uint32(DMA_SCRATCH_LBA), &ref[0])
if rr != IDError.IDE_ERR_NONE: return -1
r: int = _dma_read(uint32(DMA_SCRATCH_LBA), 1)
ok: int = 0
if r == IDError.IDE_ERR_NONE:
if string.memcmp(rawptr(_dma_buf), rawptr(&ref[0]), 512) == 0: ok = 1
if ok == 0:
writeSector(uint32(DMA_SCRATCH_LBA), &ref[0])
return -1
return 0
# Reset, set the requested DMA class, and run the direction selftest.
def _try_dma(use_mdma: int, dr: uint8) -> int:
_ide_reset()
m: int = _set_dma_mode(use_mdma)
if m < 0: return -1
if _dma_selftest(dr) == 0: return m
return -1
def waitReady(baseport: uint16) -> static IDError:
timeout: int = 100000
for _ in range(timeout, 0, -1):
@@ -64,7 +181,8 @@ def flushStaleData():
p = p + 1
def init() -> int:
global _ide_initialized
global _ide_initialized, _bm_base, _bm_ok, _dma_buf, _prdt, _pci_cmd
global _dma_buf_pa, _prdt_pa
if _ide_initialized: return IDError.IDE_ERR_NONE
bus: uint8
dev_n: uint8
@@ -75,14 +193,56 @@ def init() -> int:
vendor: uint16 = pci.pci_read16(bus, dev_n, func, 0x00)
if vendor == 0xFFFF: continue
cls: uint32 = pci.pci_get_class(bus, dev_n, func)
if cls == 0x010180 or cls == 0x0101:
# class/subclass = 0x0101 (IDE); progIF bit7 = bus-master capable.
# VMware's PIIX4 reports progIF 0x8A (not exactly 0x80), so do
# not compare the whole 24-bit class/progIF word.
cls_sub: uint32 = cls & uint32(0xFFFF00)
proto: uint32 = cls & uint32(0xFF)
if cls_sub == uint32(0x010100) and (proto & uint32(0x80)) != 0:
bar4: uint32 = pci.pci_read32(bus, dev_n, func, 0x20)
bar0: uint32 = pci.pci_read32(bus, dev_n, func, 0x10)
lb0: [128]char
viperlib.snprintf(&lb0, 128, "[ide] pci %d:%d.%d cls=0x%x bar0=0x%x bar4=0x%x\n",
uint32(bus), uint32(dev_n), uint32(func),
cls, bar0, bar4)
serial.puts(&lb0)
cmd: uint16 = pci.pci_read16(bus, dev_n, func, 0x04)
pci.pci_write32(bus, dev_n, func, 0x04, uint32(cmd | 0x0007))
if _bm_base == 0 and (bar4 & uint32(1)) != 0:
_bm_base = uint16(bar4 & uint32(0xFFFC))
_pci_cmd = pci.pci_read16(bus, dev_n, func, 0x04)
asm.outb(0x3F6, 0x04)
for _ in range(10000): asm.nop()
asm.outb(0x3F6, 0x00)
for _ in range(10000): asm.nop()
status: uint8 = asm.inb(0x1F7)
if _bm_base != 0:
_dma_buf = (uint64(&_dma_area[0]) + uint64(0xFFFF)) & ~uint64(0xFFFF)
_prdt = (uint64(&_prdt_area[0]) + uint64(15)) & ~uint64(15)
_dma_buf_pa = paging.GetPagePhys(_dma_buf)
if _dma_buf_pa != 0: _dma_buf_pa = _dma_buf_pa + (_dma_buf & uint64(0xFFF))
_prdt_pa = paging.GetPagePhys(_prdt)
if _prdt_pa != 0: _prdt_pa = _prdt_pa + (_prdt & uint64(0xFFF))
lb: [160]char
viperlib.snprintf(&lb, 160, "[ide] BMIDE base=0x%x pci_cmd=0x%x dma_va=0x%x dma_pa=0x%x prdt_va=0x%x prdt_pa=0x%x\n",
uint32(_bm_base), uint32(_pci_cmd), uint32(_dma_buf), uint32(_dma_buf_pa),
uint32(_prdt), uint32(_prdt_pa))
serial.puts(&lb)
_bm_ok = 1
dm: int = _try_dma(0, uint8(0x08))
if dm < 0: dm = _try_dma(0, uint8(0x00))
if dm < 0: dm = _try_dma(1, uint8(0x08))
if dm < 0: dm = _try_dma(1, uint8(0x00))
if dm >= 0:
mb: [96]char
viperlib.snprintf(&mb, 96, "[ide] DMA selftest ok mode=%d dir=0x%x\n",
dm, uint32(_dma_dir))
serial.puts(&mb)
else:
_bm_ok = 0
serial.puts("[ide] DMA unavailable, using PIO\n")
else:
serial.puts("[ide] BMIDE unavailable, using PIO\n")
_ide_initialized = 1
return IDError.IDE_ERR_NONE
@@ -130,6 +290,116 @@ def readSector(lba: uint32, buffer: rawptr) -> int:
# serial.puts(dbg)
return IDError.IDE_ERR_NONE
# One bus-master DMA command (<= 64 KiB) into the internal aligned buffer.
def _dma_read(lba: uint32, sectors: uint32) -> int:
global _bm_ok, _dma_logged, _dma_tried, _dma_dir
if _bm_ok == 0 or _dma_buf == 0 or _prdt == 0:
return IDError.IDE_ERR_DEVICE_NOT_READY
if sectors == 0 or sectors > uint32(DMA_MAX_SECTORS):
return IDError.IDE_ERR_SECTOR_COUNT
err: IDError = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE:
return err
# If a previous PIO command left data in the drive buffer (DRQ=1), command
# block writes are ignored. Drain it before starting the DMA command.
flushStaleData()
# Stop the engine, set direction = device -> memory.
asm.outb(_bm_base, uint8(0x00))
bytes: uint32 = sectors * uint32(512)
buf_pa: uint64 = _dma_buf_pa
if buf_pa == 0: buf_pa = _dma_buf
prdt_pa: uint64 = _prdt_pa
if prdt_pa == 0: prdt_pa = _prdt
prd: uint32* = uint32*(_prdt)
prd[0] = uint32(buf_pa)
# count == 0 means 64 KiB; bit15 is EOT.
prd[1] = (bytes & uint32(0xFFFF)) | uint32(0x8000)
if _dma_tried == 0:
_dma_tried = 1
dbg: [160]char
viperlib.snprintf(&dbg, 160, "[ide] dma try lba=0x%x n=%u buf=0x%x prdt=0x%x prd=%x,%x\n",
lba, sectors, uint32(buf_pa), uint32(prdt_pa), prd[0], prd[1])
serial.puts(&dbg)
_clflush_range(_prdt, uint64(8))
_clflush_range(_dma_buf, uint64(bytes))
_mfence()
asm.outl(_bm_base + uint16(4), uint32(prdt_pa))
asm.outb(_bm_base + uint16(2), uint8(0x06)) # clear error/IRQ
# Start the bus-master engine first; the PIIX4 waits for the drive's DMA
# request. QEMU tolerates either order.
asm.outb(_bm_base, uint8(_dma_dir | uint8(0x01))) # start + direction
# ATA READ DMA (LBA28, 0xC8)
asm.outb(IDE_baseport_PRIMARY + 2, uint8(sectors & uint32(0xFF)))
asm.outb(IDE_baseport_PRIMARY + 3, uint8(lba & uint32(0xFF)))
asm.outb(IDE_baseport_PRIMARY + 4, uint8((lba >> 8) & uint32(0xFF)))
asm.outb(IDE_baseport_PRIMARY + 5, uint8((lba >> 16) & uint32(0xFF)))
asm.outb(IDE_baseport_PRIMARY + 6, uint8(uint32(0xE0) | ((lba >> 24) & uint32(0x0F))))
asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xC8))
ok: int = 0
t: int = 2000000
st: uint8 = 0
while t > 0:
st = asm.inb(_bm_base + uint16(2))
if st & uint8(0x02): break # error
if st & uint8(0x04): ok = 1; break # interrupt/completion
t = t - 1
asm.pause()
asm.outb(_bm_base, uint8(0x00)) # stop
asm.outb(_bm_base + uint16(2), uint8(0x06))
ata: uint8 = asm.inb(IDE_baseport_PRIMARY + 7)
if ok == 0:
fb: [96]char
viperlib.snprintf(&fb, 96, "[ide] DMA timeout st=0x%x ata=0x%x\n", uint32(st), uint32(ata))
serial.puts(&fb)
return IDError.IDE_ERR_TIMEOUT
if ata & uint8(0x01):
fb2: [96]char
viperlib.snprintf(&fb2, 96, "[ide] DMA ata error st=0x%x ata=0x%x\n", uint32(st), uint32(ata))
serial.puts(&fb2)
return IDError.IDE_ERR_DEVICE_ERROR
_clflush_range(_dma_buf, uint64(bytes))
_mfence()
if _dma_logged == 0:
_dma_logged = 1
serial.puts("[ide] DMA read ok\n")
return IDError.IDE_ERR_NONE
# Multi-sector read: DMA for >=2 sectors, PIO for a single sector or after a
# DMA failure. Caller must hold the drive lock.
def readSectors(lba: uint32, buffer: rawptr, count: uint32) -> int:
global _bm_ok
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
if count == 0: return IDError.IDE_ERR_SECTOR_COUNT
if _bm_ok == 0 or count < 2:
i: uint32 = 0
while i < count:
r: int = readSector(lba + i, rawptr(uint64(buffer) + uint64(i) * uint64(512)))
if r != IDError.IDE_ERR_NONE: return r
i = i + 1
return IDError.IDE_ERR_NONE
remaining: uint32 = count
cur: uint32 = lba
dst: uint64 = uint64(buffer)
while remaining > 0:
chunk: uint32 = remaining
if chunk > uint32(DMA_MAX_SECTORS): chunk = uint32(DMA_MAX_SECTORS)
r = _dma_read(cur, chunk)
if r != IDError.IDE_ERR_NONE:
serial.puts("[ide] DMA failed, falling back to PIO\n")
_bm_ok = 0
_ide_reset()
i = 0
while i < remaining:
rr: int = readSector(cur + i, rawptr(dst + uint64(i) * uint64(512)))
if rr != IDError.IDE_ERR_NONE: return rr
i = i + 1
return IDError.IDE_ERR_NONE
string.memcpy(rawptr(dst), rawptr(_dma_buf), uint64(chunk) * uint64(512))
dst = dst + uint64(chunk) * uint64(512)
cur = cur + chunk
remaining = remaining - chunk
return IDError.IDE_ERR_NONE
def writeSector(lba: uint32, buffer: const rawptr) -> int:
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
err: IDError = waitReady(IDE_baseport_PRIMARY)
+234 -32
View File
@@ -32,6 +32,7 @@ define ET_DYN = 3
define EM_X86_64 = 62
define PT_LOAD = 1
define PT_DYNAMIC = 2
define PT_INTERP = 3
define PF_X = 1
define PF_W = 2
define PF_R = 4
@@ -120,7 +121,7 @@ define MAX_SHDRS = 64
define MAX_DYNS = 64
define MAX_RELAS = 256
def load_elf(path: const str, do_map: int = 1) -> rawptr:
def load_elf(path: const str, do_map: int = 1, base: uint64 = 0) -> rawptr:
fp: fat32_types.fileobj* = fat32.open(path, fat32_types.FA_READ)
if fp is None:
err: uint32 = fat32.last_error()
@@ -180,6 +181,10 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
serial.puts("[elf] not ET_EXEC or ET_DYN\n")
fat32.close(fp)
return None
if ehdr.e_type == ET_EXEC and base != 0:
serial.puts("[elf] ET_EXEC cannot be relocated\n")
fat32.close(fp)
return None
if ehdr.e_machine != EM_X86_64:
serial.puts("[elf] not x86_64\n")
@@ -246,7 +251,12 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
viperlib.snprintf(&cr2, 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages)
serial.puts(cr2)
raw_alloc: rawptr = mm.malloc(total_pages * 0x1000 + 0x1000)
# Keep the image offset of the first LOAD segment: the final page table maps
# whole pages, so the physical buffer must start at a page boundary that
# corresponds to `virt_page_start` below.
low_off: uint64 = lowest_vaddr & uint64(0xFFF)
alloc_sz: uint64 = total_pages * uint64(0x1000) + uint64(0x2000)
raw_alloc: rawptr = mm.malloc(alloc_sz)
_db_ra: [80]char
viperlib.snprintf(&_db_ra, 80, "[elf] malloc ret raw_alloc=0x%lx\n", uint64(raw_alloc))
serial.puts(_db_ra)
@@ -255,7 +265,8 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
fat32.close(fp)
return None
phys_base_aligned: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF)
phys_page_start_u: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF)
phys_base_aligned: uint64 = phys_page_start_u + low_off
phys_base: rawptr = rawptr(phys_base_aligned)
string.memset(phys_base, 0, total_pages * 0x1000)
@@ -280,9 +291,9 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
fat32.close(fp)
virt_page_start: uint64 = lowest_vaddr & ~uint64(0xFFF)
phys_page_start: uint64 = uint64(phys_base) & ~uint64(0xFFF)
map_end: uint64 = (highest_vaddr_end + 0xFFF) & ~uint64(0xFFF)
virt_page_start: uint64 = (base + lowest_vaddr) & ~uint64(0xFFF)
phys_page_start: uint64 = phys_page_start_u
map_end: uint64 = (base + highest_vaddr_end + uint64(0xFFF)) & ~uint64(0xFFF)
num_map_pages: uint64 = (map_end - virt_page_start) // 0x1000
# Store mapping info for spawn_elf (used when do_map=0)
@@ -298,7 +309,7 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
page_flags: uint64 = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER
paging.MapPage(virt_addr, phys_addr, page_flags)
entry_addr: rawptr = rawptr(entry_point)
entry_addr: rawptr = rawptr(base + entry_point)
return entry_addr
@@ -319,7 +330,7 @@ def run_elf(path: const str) -> int:
serial.puts("[elf] returned from entry\n")
return 0
define MAX_SPAWNED = 8
define MAX_SPAWNED = 32
_elf_entries: [MAX_SPAWNED]rawptr
# User stack virtual address: top of 48-bit user space, 64KB below
@@ -336,6 +347,153 @@ _last_elf_phys_page_start: uint64 = 0
_last_elf_virt_page_start: uint64 = 0
_last_elf_num_map_pages: uint64 = 0
# ============================================================================
# Linux process startup stack: argc/argv/envp/auxv
# ============================================================================
define AT_NULL = 0
define AT_PHDR = 3
define AT_PHENT = 4
define AT_PHNUM = 5
define AT_PAGESZ = 6
define AT_BASE = 7
define AT_FLAGS = 8
define AT_ENTRY = 9
define AT_UID = 11
define AT_EUID = 12
define AT_GID = 13
define AT_EGID = 14
define AT_PLATFORM = 15
define AT_HWCAP = 16
define AT_CLKTCK = 17
define AT_SECURE = 23
define AT_RANDOM = 25
define AT_EXECFN = 31
_rng_stack_state: uint64 = 0x243F6A8885A308D3
def _stack_rand() -> uint8:
global _rng_stack_state
_rng_stack_state = _rng_stack_state * uint64(6364136223846793005) + uint64(1442695040888963407)
return uint8((_rng_stack_state >> 33) & uint64(0xFF))
def _stk_u64(buf: uint8*, u: uint64*, v: uint64):
if u[0] < uint64(8): return
u[0] = u[0] - uint64(8)
q: uint64* = uint64*(uint64(buf) + u[0])
q[0] = v
def _stk_str(buf: uint8*, u: uint64*, s: const str, base_va: uint64) -> uint64:
n: uint64 = uint64(int(string.strlen(s)) + 1)
if n > uint64(256): n = uint64(256)
if u[0] < n: return uint64(0)
u[0] = u[0] - n
dst: uint8* = uint8*(uint64(buf) + u[0])
i: int = 0
while uint64(i) < n:
dst[i] = uint8(s[i])
i += 1
dst[n - uint64(1)] = uint8(0)
return base_va + u[0]
# Build the stack image in a 4KB scratch buffer, then copy it to the top of the
# process' 64KB user stack. Pointers are virtual addresses (USER_STACK_VTOP-based).
def _build_argv_stack(p: proc.Process*, path: const str, argv: rawptr, envp: rawptr,
phdr_va: uint64, entry_va: uint64, phent: uint64, phnum: uint64,
interp_base: uint64) -> int:
buf: [4096]uint8
string.memset(&buf[0], 0, 4096)
base_va: uint64 = USER_STACK_VTOP - uint64(4096)
u: uint64 = uint64(4096)
path_va: uint64 = _stk_str(&buf[0], &u, path, base_va)
argv_va: [17]uint64
env_va: [17]uint64
argc: int = 0
envc: int = 0
if argv is not None:
av: str* = str*(argv)
while argc < 16:
sp: str = av[argc]
if sp is None: break
argv_va[argc] = _stk_str(&buf[0], &u, sp, base_va)
argc += 1
if argc == 0:
argv_va[0] = path_va
argc = 1
if envp is not None:
ev: str* = str*(envp)
while envc < 16:
sp2: str = ev[envc]
if sp2 is None: break
env_va[envc] = _stk_str(&buf[0], &u, sp2, base_va)
envc += 1
plat_va: uint64 = _stk_str(&buf[0], &u, "x86_64", base_va)
u = u - uint64(16)
ri: int = 0
while ri < 16:
rp: uint8* = uint8*(uint64(&buf[0]) + u + uint64(ri))
rp[0] = _stack_rand()
ri += 1
random_va: uint64 = base_va + u
# keep every pushed word 8-byte aligned; the gap below the strings is unused
u = u & ~uint64(7)
aux_t: [24]uint64
aux_v: [24]uint64
naux: int = 0
aux_t[naux] = uint64(AT_PHDR); aux_v[naux] = phdr_va; naux += 1
aux_t[naux] = uint64(AT_PHENT); aux_v[naux] = phent; naux += 1
aux_t[naux] = uint64(AT_PHNUM); aux_v[naux] = phnum; naux += 1
aux_t[naux] = uint64(AT_PAGESZ); aux_v[naux] = uint64(4096); naux += 1
aux_t[naux] = uint64(AT_BASE); aux_v[naux] = interp_base; naux += 1
aux_t[naux] = uint64(AT_FLAGS); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_ENTRY); aux_v[naux] = entry_va; naux += 1
aux_t[naux] = uint64(AT_UID); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_EUID); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_GID); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_EGID); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_PLATFORM); aux_v[naux] = plat_va; naux += 1
aux_t[naux] = uint64(AT_HWCAP); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_CLKTCK); aux_v[naux] = uint64(100); naux += 1
aux_t[naux] = uint64(AT_SECURE); aux_v[naux] = uint64(0); naux += 1
aux_t[naux] = uint64(AT_RANDOM); aux_v[naux] = random_va; naux += 1
aux_t[naux] = uint64(AT_EXECFN); aux_v[naux] = path_va; naux += 1
# memory (low -> high): argc, argv[], NULL, envp[], NULL, auxv[], AT_NULL
# stack grows down, so push AT_NULL first and the rest in reverse.
_stk_u64(&buf[0], &u, uint64(0))
_stk_u64(&buf[0], &u, uint64(0))
ai: int = naux - 1
while ai >= 0:
_stk_u64(&buf[0], &u, aux_v[ai])
_stk_u64(&buf[0], &u, aux_t[ai])
ai -= 1
_stk_u64(&buf[0], &u, uint64(0))
ai = envc - 1
while ai >= 0:
_stk_u64(&buf[0], &u, env_va[ai])
ai -= 1
_stk_u64(&buf[0], &u, uint64(0))
ai = argc - 1
while ai >= 0:
_stk_u64(&buf[0], &u, argv_va[ai])
ai -= 1
_stk_u64(&buf[0], &u, uint64(argc))
used: uint64 = uint64(4096) - u
if used == 0 or used > uint64(4096): return -1
dst: uint8* = uint8*(p.user_stack - used)
src: uint8* = uint8*(uint64(&buf[0]) + u)
string.memcpy(dst, src, used)
p.entry_rsp = USER_STACK_VTOP - used
return 0
# Map the most recent load_elf() result into the *active* (cloned) PML4.
def _map_last_elf():
mi: uint64 = 0
while mi < _last_elf_num_map_pages:
paging.MapPage(_last_elf_virt_page_start + mi * uint64(0x1000),
_last_elf_phys_page_start + mi * uint64(0x1000),
paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
mi += 1
def _elf_entry_wrapper(arg: rawptr) -> int:
pid: int = int(uint64(arg))
_db1: [64]char
@@ -376,13 +534,24 @@ def _elf_entry_wrapper(arg: rawptr) -> int:
# will set RFLAGS.IF from the saved frame (0x202) for user mode.
# This prevents timer interrupt preemption between TSS setup and iretq.
# Drop to Ring 3 user mode via iretq
proc.drop_to_user_mode(entry_fn, user_stack_vtop, cur_p.kern_stack)
user_rsp: uint64 = USER_STACK_VTOP
if cur_p.entry_rsp != 0:
user_rsp = cur_p.entry_rsp
proc.drop_to_user_mode(entry_fn, user_rsp, cur_p.kern_stack)
cur: proc.Process* = proc.ProcessManager.current()
cur.exit()
return 0
def spawn_elf(path: const str) -> int:
def spawn_elf(path: const str, argv: rawptr = None, envp: rawptr = None) -> int:
if ELF_LOAD == 0: return -1 # [临时] 见文件头 ELF_LOAD 开关说明
phdr_va: uint64 = 0
phent_u: uint64 = 0
phnum_u: uint64 = 0
main_base: uint64 = 0
main_entry: uint64 = 0
interp_len: int = 0
interp_path: [256]char
string.memset(&interp_path[0], 0, 256)
fat32.lock()
name_start: int = 0
si: int = 0
@@ -408,17 +577,35 @@ def spawn_elf(path: const str) -> int:
fat32.read(fp_scan, &ehdr_buf_s, 64)
ehdr_s: Elf64_Ehdr* = &ehdr_buf_s
if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN:
if ehdr_s.e_type == ET_DYN:
main_base = uint64(0x60000000)
main_entry = main_base + ehdr_s.e_entry
phoff_s: uint64 = ehdr_s.e_phoff
phentsize_s: uint16 = ehdr_s.e_phentsize
phnum_s: uint16 = ehdr_s.e_phnum
if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS:
phdr_bufs_s: [MAX_PHDRS]Elf64_Phdr
string.memset(&phdr_bufs_s, 0, MAX_PHDRS * 56)
phent_u = uint64(phentsize_s)
phnum_u = uint64(phnum_s)
found_load_s: int = 0
for pi_s in range(phnum_s):
fat32.seek(fp_scan, uint32(phoff_s + uint64(pi_s) * uint64(phentsize_s)))
fat32.read(fp_scan, &phdr_bufs_s[pi_s], 56)
if phdr_bufs_s[pi_s].p_type == PT_INTERP:
ilen: uint64 = uint64(phdr_bufs_s[pi_s].p_filesz)
if ilen > uint64(255): ilen = uint64(255)
if ilen > 0:
fat32.seek(fp_scan, uint32(phdr_bufs_s[pi_s].p_offset))
ir: int = int(fat32.read(fp_scan, &interp_path[0], uint32(ilen)))
if ir > 0:
interp_len = ir
interp_path[interp_len] = 0
if phdr_bufs_s[pi_s].p_type == PT_LOAD:
ph_off_s: uint64 = uint64(phdr_bufs_s[pi_s].p_offset)
ph_fsz_s: uint64 = uint64(phdr_bufs_s[pi_s].p_filesz)
if ehdr_s.e_phoff >= ph_off_s and ehdr_s.e_phoff < ph_off_s + ph_fsz_s:
phdr_va = main_base + uint64(phdr_bufs_s[pi_s].p_vaddr) + (uint64(ehdr_s.e_phoff) - ph_off_s)
seg_end_s: uint64 = phdr_bufs_s[pi_s].p_vaddr + phdr_bufs_s[pi_s].p_memsz
if found_load_s == 0:
lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr
@@ -431,20 +618,10 @@ def spawn_elf(path: const str) -> int:
highest_vaddr_end = seg_end_s
fat32.close(fp_scan)
isolate_va: uint64 = lowest_vaddr
isolate_va: uint64 = main_base + lowest_vaddr
if isolate_va == 0:
isolate_va = 0x400000
# Load ELF to physical memory WITHOUT mapping in kernel PML4.
# do_map=0: load_elf stores mapping info in globals for spawn_elf to use.
# ROOT CAUSE FIX: previously load_elf mapped ELF vaddr (e.g. 0x400000) into
# kernel PML4, overwriting identity mapping of buddy memory_block headers.
entry: rawptr = load_elf(path, 0)
if entry is None:
serial.puts("[elf] spawn: load failed\n")
fat32.unlock()
return -1
# Clone kernel PML4 (identity mapping intact, no ELF mapping yet)
proc_pml4: uint64 = paging.clone_for_process(isolate_va)
slog1: [80]char
@@ -455,21 +632,40 @@ def spawn_elf(path: const str) -> int:
fat32.unlock()
return -1
# Map ELF pages in the CLONED PML4 (not kernel's).
# This prevents overwriting kernel identity mapping at 0x400000 which
# would corrupt buddy system memory_block headers.
# Must disable interrupts: @pml4 global is used by MapPage and a timer
# interrupt mid-section would call switch_pid, corrupting @pml4.
# Load the main image and (if present) the interpreter, mapping each into
# the cloned PML4 immediately so the next load can reuse the globals.
# Interrupts stay off: @pml4 is global.
asm.cli()
_kernel_cr3: uint64 = 0
__asm__(f"""mov {asmout(_kernel_cr3, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY])
__asm__(f"mov cr3, {asminp(proc_pml4, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(proc_pml4)
mi: uint64
for mi in range(_last_elf_num_map_pages):
_va: uint64 = _last_elf_virt_page_start + mi * 0x1000
_pa: uint64 = _last_elf_phys_page_start + mi * 0x1000
paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
entry: rawptr = load_elf(path, 0, main_base)
if entry is None:
__asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(_kernel_cr3)
asm.sti()
serial.puts("[elf] spawn: load failed\n")
fat32.unlock()
return -1
_map_last_elf()
interp_entry: rawptr = None
interp_base: uint64 = uint64(0)
if interp_len > 0:
interp_base = uint64(0x70000000)
interp_entry = load_elf(&interp_path[0], 0, interp_base)
if interp_entry is None:
__asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(_kernel_cr3)
asm.sti()
serial.puts("[elf] spawn: interpreter load failed\n")
fat32.unlock()
return -1
_map_last_elf()
entry = interp_entry
__asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(_kernel_cr3)
asm.sti()
@@ -482,8 +678,14 @@ def spawn_elf(path: const str) -> int:
serial.puts("[elf] spawn: create_process failed\n")
fat32.unlock()
return -2
p.elf_base = uint64(entry)
p.elf_base = main_base
p.pml4_root = proc_pml4
p.parent_pid = proc.current()
if _build_argv_stack(p, path, argv, envp, phdr_va, main_entry, phent_u, phnum_u, interp_base) != 0:
serial.puts("[elf] spawn: startup stack build failed\n")
p.exit()
fat32.unlock()
return -4
if p.pid >= 0 and p.pid < MAX_SPAWNED:
_elf_entries[p.pid] = entry
File diff suppressed because it is too large. Load diff
+27 -4
View File
@@ -111,13 +111,25 @@ class ViperKernel:
op=[ASM_DESCR.CLOBBER_MEMORY])
if BootInfo is not None:
paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize)
# PAT: entry 4 = write-combining. The framebuffer is mapped with
# PTE_PAT so large compositor blits stay fast on VMware/real HW
# (WB is fast but can be incoherent for scanout; UC is slow).
feats: uint32 = cpu.detect_features_static()
if (feats & uint32(cpu.CPU_FEATURE_PAT)) != 0:
pat_v: uint64 = cpu.msr_read(uint32(0x277))
pat_v = (pat_v & ~(uint64(7) << uint64(32))) | (uint64(1) << uint64(32))
cpu.msr_write(uint32(0x277), pat_v)
paging.set_pat_wc()
serial.puts("[main] PAT write-combining enabled\n")
fb_flags: uint64 = paging.PTE_WRITABLE
if paging.has_pat_wc() != 0:
fb_flags = fb_flags | paging.PTE_PAT
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)
# 帧缓冲是 MMIO:优先 PAT WC(快且对扫描输出可见);CPU 不支持
# PAT 时保持回写,避免用 UC 把整帧提交拖慢。
paging.MapPage(fva, fva, fb_flags)
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))
@@ -240,6 +252,12 @@ class ViperKernel:
viperlib.snprintf(&font_log, 64, "[main] FontDemo spawned as pid=%d\n", font_pid)
serial.puts(&font_log)
img_pid: int = elf.spawn_elf("/APPS/imagesee.elf")
if img_pid >= 0:
img_log: [64]char
viperlib.snprintf(&img_log, 64, "[main] ImageSee spawned as pid=%d\n", img_pid)
serial.puts(&img_log)
sched.Scheduler.enable()
# 破坏性 FAT32 测试已删除(write/rename/delete/mkdir/rmdir 会永久改坏 disk.img)。
@@ -263,7 +281,12 @@ class ViperKernel:
# /dev/desk:explorer 与各 app 的共享内存通道(S3)。分配失败就让登记跳过 ——
# explorer 的 open 会明确失败并打日志,不做静默兜底。
desk_shm: rawptr = mm.malloc(uint64(devfs.DESK_SHM_SIZE))
sl: [160]char
viperlib.snprintf(&sl, 160, "[main] desk shm=0x%lx size=0x%lx\n",
uint64(desk_shm), uint64(devfs.DESK_SHM_SIZE))
serial.puts(&sl)
devfs.devfs_register_desk(desk_shm, uint64(devfs.DESK_SHM_SIZE))
mm.check_lists(1)
serial.puts("[main] sched started\n")
+192 -6
View File
@@ -56,6 +56,58 @@ class memory_block:
# 块头魔数("BLK!")
define BLOCK_MAGIC = 0x424C4B21
# [DIAG] 监视某个空闲块头是否被越界写清零;配合 _last_user 定位"谁写到了这里"。
static _watch_addr: uint64 = 0
static _last_user: uint64 = 0
static _last_size: uint64 = 0
static _last_kind: int = 0
def _watch_check(where: int):
global _watch_addr
if _watch_addr == 0: return
if uint64((memory_block*(_watch_addr)).magic) == uint64(BLOCK_MAGIC): return
_wl: [192]char
viperlib.snprintf(&_wl, 192, "[mm] WATCH0 at %d: watch=0x%lx magic=0x%lx last=0x%lx size=0x%lx kind=%d\n",
where, _watch_addr, uint64(uint32((memory_block*(_watch_addr)).magic)),
_last_user, _last_size, _last_kind)
serial.puts(&_wl)
_watch_addr = 0
# ---- 真正的 4KiB 匿名页分配器 ----
# mmap 的匿名/文件页不再走 malloc(4096)(那会占 8KiB:4KiB 头页 + 4KiB 数据),
# 而是直接向 buddy 要 order-0 块,整页交给用户。代价是页首的 memory_block 头会
# 被用户数据覆盖 ⇒ free 时必须按"裸页"重建头再 merge;同时用位图区分"裸页"和
# 普通块,避免 merge 把已分配裸页的脏头误判成坏块。
define MAX_RAW_PAGES = 262144 # 1GiB / 4KiB
define RAW_BITS_BYTES = 32768 # MAX_RAW_PAGES / 8
static _raw_heap_start: uint64 = 0
static _raw_page_count: uint64 = 0
static _raw_bits: [RAW_BITS_BYTES]uint8
def _raw_index(addr: uint64) -> uint64:
if addr < _raw_heap_start: return uint64(0xFFFFFFFFFFFFFFFF)
off: uint64 = addr - _raw_heap_start
idx: uint64 = off >> 12
if idx >= _raw_page_count: return uint64(0xFFFFFFFFFFFFFFFF)
return idx
def _raw_is_used(addr: uint64) -> int:
idx: uint64 = _raw_index(addr)
if idx == uint64(0xFFFFFFFFFFFFFFFF): return 0
return (int(_raw_bits[idx >> 3]) >> int(idx & 7)) & 1
def _raw_set(addr: uint64, used: int):
idx: uint64 = _raw_index(addr)
if idx == uint64(0xFFFFFFFFFFFFFFFF): return
byte_i: uint64 = idx >> 3
bit_i: int = int(idx & 7)
b: int = int(_raw_bits[byte_i])
if used != 0:
b = b | (1 << bit_i)
else:
b = b & (255 - (1 << bit_i))
_raw_bits[byte_i] = uint8(b & 0xFF)
# 伙伴系统结构
class __buddy_system:
free_lists: [MAX_ORDER + 1]memory_block*
@@ -91,11 +143,17 @@ class slab_page:
def align_4k(addr: uint64) -> uint64:
return (addr + 0xFFF) & ~0xFFF
# 计算阶数
# 计算阶数:必须**向上取整**。旧实现用逐个右移(floor):
# size=0x4498+4096=0x5498? 或 _bb 的 1141760+4096 → 实际拿到 1MB 块,
# 但用户区从 block+4KB 起,写满 1.09MB 就**越过块尾 92KB**,踩坏后继块头 /
# 别人的数据(现场:[mm] BAD blk blk=0x21780000 sz=0 magic=0、随内存大小
# 出现的随机崩溃、以及 Terminal 的 Text 缓冲被踩后键盘无响应)。
# 向上取整后 1.14MB 请求拿 2MB 块,32MB 共享区拿 64MB 块(多一页头)。
static def get_order(size: uint64) -> int:
order: int = 0
while size > MIN_BLOCK_SIZE:
size >>= 1
block: uint64 = uint64(MIN_BLOCK_SIZE)
while block < size and order < MAX_ORDER:
block = block << 1
order += 1
return order
@@ -214,6 +272,10 @@ class _BuddySystemObject:
# 检查伙伴块是否存在且空闲
if buddy >= memory_block*(self.buddy.end_addr):
break # 伙伴块超出内存范围
# 裸页(mmap 匿名/文件页)的块头已被用户数据覆盖:位图标记为已分配,
# 绝不能读它的 state/magic(会把用户数据当块头,误报 BAD / 误合并)。
if _raw_is_used(uint64(buddy)) != 0:
break
if int(buddy.state) != BLOCK_FREE or int(buddy.order) != order:
break # 伙伴块不空闲或阶数不匹配
# 加固:摘链前先校验伙伴块头(magic / size-order 一致 / prev-next 在堆内且对齐)。
@@ -260,8 +322,31 @@ class _BuddySystemObject:
# 保持空闲链原样 —— 否则紧接着的 `block.next.prev = None` 就会踩坏值。
_arc: int = self._check_block(block, BLOCK_FREE)
if _arc != 0:
self._report_bad_block(1, _arc, block, found_order)
return None
# 现场:1GiB 内存布局下 order-16 空闲块头被整块清零(sz/st/ord/magic/
# nxt/prv 全 0),块体本身未分配。若整头全 0,可按链表阶数无损重建头,
# 否则一次坏块会让之后所有分配永久失败(用户态 malloc 返回 NULL →
# 写 0 触发 [0E])。重建只针对"全零头",其他损坏仍按原逻辑拒绝。
if _arc == 1:
_zeroed: int = 0
if block.size == 0 and block.state == 0 and block.order == 0 and uint64(block.next) == 0 and uint64(block.prev) == 0:
_zeroed = 1
if _zeroed == 1:
block.size = get_block_size(found_order)
block.state = BLOCK_FREE
block.order = uint8(found_order)
block.magic = uint32(BLOCK_MAGIC)
block.next = None
block.prev = None
_rbl: [128]char
viperlib.snprintf(&_rbl, 128, "[mm] repaired zeroed free block 0x%lx order=%d size=0x%lx\n",
uint64(block), found_order, block.size)
serial.puts(&_rbl)
else:
self._report_bad_block(1, _arc, block, found_order)
return None
else:
self._report_bad_block(1, _arc, block, found_order)
return None
# 从空闲链表中移除块
if block.next: block.next.prev = None
self.buddy.free_lists[found_order] = block.next
@@ -502,6 +587,8 @@ SlabAllocator: _SlabAllocator
# 内存分配函数
export def malloc(size: uint64) -> rawptr:
global _last_user, _last_size, _last_kind
_watch_check(1)
if size == 0: return None
if size <= SLAB_THRESHOLD:
sc: int = _slab_size_class(size)
@@ -512,6 +599,9 @@ export def malloc(size: uint64) -> rawptr:
_mb: [96]char
viperlib.snprintf(&_mb, 96, "[mm] malloc(%lu) slab sc=%d -> NULL\n", size, sc)
serial.puts(&_mb)
_last_user = uint64(result)
_last_size = size
_last_kind = 1
return result
hdr: uint64 = memory_block.__sizeof__()
if size >= 4096:
@@ -522,6 +612,9 @@ export def malloc(size: uint64) -> rawptr:
block: memory_block* = BuddySystemObject.allocate_block(total)
if not block: return None
user_ptr: uint64 = (uint64(block) + hdr + 4095) & ~uint64(4095)
_last_user = user_ptr
_last_size = size
_last_kind = 3
real_hdr: uint64 = user_ptr - 8
__asm__(f"""mov rax, {asminp(block, ASM_DESCR.REG_ANY)}
mov [{asminp(rawptr(real_hdr), ASM_DESCR.REG_ANY)}], rax""",
@@ -533,6 +626,9 @@ export def malloc(size: uint64) -> rawptr:
if order2 > MAX_ORDER: return None
block2: memory_block* = BuddySystemObject.allocate_block(total2)
if not block2: return None
_last_user = uint64(block2) + hdr
_last_size = size
_last_kind = 2
return rawptr(uint64(block2) + hdr)
@@ -549,6 +645,7 @@ export def malloc_direct(size: uint64) -> rawptr:
export def free(ptr: rawptr):
_watch_check(2)
if ptr is None: return
sp: slab_page* = _slab_find_page(ptr)
if sp:
@@ -575,6 +672,67 @@ export def free(ptr: rawptr):
block2: memory_block* = rawptr(uint64(ptr) - memory_block.__sizeof__())
BuddySystemObject.free_block(block2)
# 真正的 4KiB 页分配:整页归调用者,页首不再保留块头。
# ---- 4KiB 页池:mmap/brk/fork 的页不再逐页进出 buddy ----
# 旧方案 alloc_page() 直接向 buddy 要 order-0 块:用户写满整页会覆盖页首块头,
# 每次 free 都得重建头再 merge;一旦 free-list 有重复/陈旧表头,就会出现
# "页已经分给字体缓冲,同时还挂在空闲链上" —— 字体数据直接把块头写成 ASCII
# (BAD blk ... magic=0x574d6f74)。这里改成独立页池:
# · 每 2MiB 向 buddy 申请一个 order-9 块(首 4KiB 留给块头,不发给用户)
# · 剩余 511 页串成页内 free-list,alloc/free 只改页首 8 字节
# · 页永远不逐页归还 buddy,因此页数据永远不会碰到 buddy 块头/空闲链
# 代价:每 2MiB 浪费 4KiB(0.2%),且不再把空闲页合并回 buddy。
define PAGE_POOL_CHUNK_SIZE = 0x200000
define PAGE_POOL_RESERVE = 0x1000
define PAGE_POOL_MAX = 134217728 # 128MiB
static _page_free_head: rawptr = None
def _pg_next(p: rawptr) -> rawptr:
return rawptr(*(uint64*(p)))
def _pg_set_next(p: rawptr, n: rawptr):
*(uint64*(p)) = uint64(n)
# 启动时一次性预留一大块给页池,之后再不向 buddy 要块。这样页池的块永远不会
# 出现在 buddy 空闲链上,页数据也不可能踩 buddy 块头;页分配/释放只操作页内
# free-list(页首 8 字节)。预留块首 4KiB 保留给 memory_block 头。
def init_page_pool():
global _page_free_head
if _page_free_head is not None: return
b: memory_block* = BuddySystemObject.allocate_block(uint64(PAGE_POOL_MAX))
if not b:
b = BuddySystemObject.allocate_block(uint64(67108864)) # 64MiB
if not b:
b = BuddySystemObject.allocate_block(uint64(33554432)) # 32MiB
if not b:
b = BuddySystemObject.allocate_block(uint64(16777216)) # 16MiB
if not b: return
base: uint64 = uint64(b)
end: uint64 = base + b.size
first: uint64 = base + uint64(PAGE_POOL_RESERVE)
p: uint64 = first
while p < end:
nxt: uint64 = 0
if p + uint64(4096) < end: nxt = p + uint64(4096)
_pg_set_next(rawptr(p), rawptr(nxt))
p = p + uint64(4096)
_page_free_head = rawptr(first)
export def alloc_page() -> rawptr:
global _page_free_head
if _page_free_head is None: return None
p: rawptr = _page_free_head
_page_free_head = _pg_next(p)
return p
# 页池释放:只把页压回页内 free-list,不碰 buddy。
export def free_page(ptr: rawptr):
global _page_free_head
if ptr is None: return
a: uint64 = uint64(ptr) & ~uint64(0xFFF)
_pg_set_next(rawptr(a), _page_free_head)
_page_free_head = rawptr(a)
export def calloc(nmemb: uint64, size: uint64) -> rawptr:
total_size: uint64 = nmemb * size
ptr: rawptr = malloc(total_size)
@@ -620,7 +778,7 @@ def free_memory_regions():
# 初始化内存管理系统
def init(MemmapAddr: uint64, MemmapSize: uint64, MemmapDescSize: uint64, FbAddr: uint64 = 0, FbSize: uint64 = 0, KernelAddr: uint64 = 0, KernelEnd: uint64 = 0):
global BuddySystemObject
global BuddySystemObject, _raw_heap_start, _raw_page_count
start_addr: rawptr = None
size: uint64 = 0
if MemmapAddr != 0:
@@ -703,7 +861,35 @@ def init(MemmapAddr: uint64, MemmapSize: uint64, MemmapDescSize: uint64, FbAddr:
if size < min_blk2:
size = 33554432
BuddySystemObject.__init__(start_addr, size)
_raw_heap_start = uint64(start_addr)
_raw_page_count = size >> 12
if _raw_page_count > uint64(MAX_RAW_PAGES): _raw_page_count = uint64(MAX_RAW_PAGES)
init_page_pool()
SlabAllocator.__init__()
_hl: [128]char
viperlib.snprintf(&_hl, 128, "[mm] heap start=0x%lx end=0x%lx size=0x%lx\n",
uint64(start_addr), uint64(start_addr) + size, size)
serial.puts(&_hl)
check_lists(0)
# 空闲链头一致性检查:tag 只用于日志定位调用阶段。
export def check_lists(tag: int):
i: int = 0
bad: int = 0
while i <= MAX_ORDER:
b: memory_block* = BuddySystemObject.buddy.free_lists[i]
if b is not None:
if uint64(b.magic) != uint64(BLOCK_MAGIC):
_cl: [128]char
viperlib.snprintf(&_cl, 128, "[mm] check%d order=%d bad head=0x%lx magic=0x%lx\n",
tag, i, uint64(b), uint64(uint32(b.magic)))
serial.puts(&_cl)
bad += 1
if bad >= 3: return
i += 1
_ok: [64]char
viperlib.snprintf(&_ok, 64, "[mm] check%d free-list heads ok\n", tag)
serial.puts(&_ok)
# 虚拟内存映射结构
+201 -9
View File
@@ -1,5 +1,6 @@
import drivers.serial.uart.serial as serial
import viperlib
import string
define PTE_PRESENT = (1 << 0)
@@ -11,6 +12,17 @@ define PTE_ACCESSED = (1 << 5)
define PTE_DIRTY = (1 << 6)
define PTE_PS = (1 << 7)
define PTE_GLOBAL = (1 << 8)
# 4KB PTE bit 7 = PAT select (for large pages the same bit is PS).
define PTE_PAT = (1 << 7)
# PAT entry 4 is reprogrammed to write-combining (WC) for the framebuffer.
# IA32_PAT is configured in main.vp after CPUID tells us PAT is available.
_pat_wc: int = 0
def set_pat_wc():
global _pat_wc
_pat_wc = 1
def has_pat_wc() -> int:
return _pat_wc
class _p:
entries: [512]uint64
@@ -138,17 +150,45 @@ def _split_1gb_to_pd(pdp_pa: uint64, pdp_idx: uint64) -> int:
if not (e & PTE_PRESENT): return 1
if not (e & PTE_PS): return 1
base: uint64 = e & ~uint64(0x3FFFFFFF)
inherit: uint64 = e & (PTE_PCD | PTE_PWT | PTE_USER | uint64(0x8000000000000000))
# 只继承缓存/执行属性,**绝不继承 PTE_USER**:这些 2MB 条目是 identity 映射,
# 用户态只能通过 MapPage 为具体 4KB 页显式加 USER。若把固件 1GB 条目的 U=1
# 带进 2MB 叶条目,应用一旦越界写到尚未映射的 VA(例如 heap 0x21780000),
# 就会命中 identity 叶页、直接改物理内存里的 buddy 块头(BAD blk 源头)。
inherit: uint64 = e & (PTE_PCD | PTE_PWT | uint64(0x8000000000000000))
new_pd: _p* = AllocPage()
if not new_pd: return 0
for si in range(512):
new_pd.entries[si] = (base + uint64(si) * uint64(0x200000)) | PTE_PRESENT | PTE_WRITABLE | PTE_PS | inherit
# 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE|USER,**绝不带 bit63(NX)** ——
# NX 生效位置是叶条目,已由上面 512 个 2MB 条目继承;非叶条目带 bit63 会让后续所有
# `entry & ~0xFFF` 派生出的表指针变成非规范地址(正是刚修的 #GP 根因)。
pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (inherit & PTE_USER)
# 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE;USER 由 MapPage 在真正建立
# 用户页时逐级补上。非叶条目也不能带 bit63(NX)(见下)。
pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
return 1
# 把 1GiB 范围内"物理地址 == 虚拟地址"的 identity 叶条目的 USER 位清掉。
# 参数 gb_base 必须是 1GiB 对齐的虚拟基址(pdp_idx << 30)。
def _sanitize_identity_pd(pd: _p*, gb_base: uint64):
i: int = 0
while i < 512:
e: uint64 = pd.entries[i]
if (e & PTE_PRESENT) == 0:
i += 1
continue
va2m: uint64 = gb_base + uint64(i) * uint64(0x200000)
if (e & PTE_PS) != 0:
if (e & ~uint64(0x1FFFFF)) == va2m and (e & PTE_USER) != 0:
pd.entries[i] = e & ~PTE_USER
else:
pt_s: _p* = _p*(e & ~uint64(0xFFF))
j: int = 0
while j < 512:
ee: uint64 = pt_s.entries[j]
if (ee & PTE_PRESENT) != 0 and (ee & PTE_USER) != 0:
va4k: uint64 = va2m + uint64(j) * uint64(0x1000)
if (ee & ~uint64(0xFFF)) == va4k:
pt_s.entries[j] = ee & ~PTE_USER
j += 1
i += 1
def MapPage(VirtAddr: uint64, PhysAddr: uint64, flags: uint64):
global pml4, pdp, pd, pt
if VirtAddr == 0 or PhysAddr == 0: return
@@ -274,6 +314,68 @@ def EnablePaging():
global pml4, pdp, pd, pt
__asm__(f"mov cr3, {asminp(uint64(pml4), ASM_DESCR.REG_ANY)}")
# Physical address backing a 4KB user page, or 0 if unmapped / a huge page.
# munmap() needs this before UnMapPage() clears the PTE, so it can hand the
# frame back to the buddy allocator.
# (Local helper: _table_phys is defined later in this module and Viper does
# not allow same-module forward references.)
def _entry_table(e: uint64) -> uint64:
if (e & PTE_PRESENT) == 0: return 0
if (e & uint64(0x7FF0000000000000)) != 0: return 0
return e & uint64(0x000FFFFFFFFFF000)
def GetPagePhys(VirtAddr: uint64) -> uint64:
if VirtAddr == 0: return 0
p4: _p* = pml4
if not p4: return 0
va: uint64 = VirtAddr & ~uint64(0xFFF)
i4: uint64 = (va >> 39) & 0x1FF
pdp_pa: uint64 = _entry_table(p4.entries[i4])
if pdp_pa == 0: return 0
p3: _p* = _p*(pdp_pa)
i3: uint64 = (va >> 30) & 0x1FF
if p3.entries[i3] & PTE_PS: return 0
pd_pa: uint64 = _entry_table(p3.entries[i3])
if pd_pa == 0: return 0
p2: _p* = _p*(pd_pa)
i2: uint64 = (va >> 21) & 0x1FF
if p2.entries[i2] & PTE_PS: return 0
pt_pa: uint64 = _entry_table(p2.entries[i2])
if pt_pa == 0: return 0
p1: _p* = _p*(pt_pa)
i1: uint64 = (va >> 12) & 0x1FF
e: uint64 = p1.entries[i1]
if not (e & PTE_PRESENT): return 0
return e & uint64(0x000FFFFFFFFFF000)
# Rewrite the protection bits of an existing 4KB mapping, keeping the frame.
# Returns 1 on success, 0 if the page is not a present 4KB page.
def SetPageFlags(VirtAddr: uint64, flags: uint64) -> int:
if VirtAddr == 0: return 0
p4: _p* = pml4
if not p4: return 0
va: uint64 = VirtAddr & ~uint64(0xFFF)
i4: uint64 = (va >> 39) & 0x1FF
pdp_pa: uint64 = _entry_table(p4.entries[i4])
if pdp_pa == 0: return 0
p3: _p* = _p*(pdp_pa)
i3: uint64 = (va >> 30) & 0x1FF
if p3.entries[i3] & PTE_PS: return 0
pd_pa: uint64 = _entry_table(p3.entries[i3])
if pd_pa == 0: return 0
p2: _p* = _p*(pd_pa)
i2: uint64 = (va >> 21) & 0x1FF
if p2.entries[i2] & PTE_PS: return 0
pt_pa: uint64 = _entry_table(p2.entries[i2])
if pt_pa == 0: return 0
p1: _p* = _p*(pt_pa)
i1: uint64 = (va >> 12) & 0x1FF
e: uint64 = p1.entries[i1]
if not (e & PTE_PRESENT): return 0
p1.entries[i1] = (e & uint64(0x000FFFFFFFFFF000)) | flags | PTE_PRESENT
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
return 1
# Extract the physical address of a non-leaf page-table entry.
# Bits 12..51 hold the address; bits 52..62 are reserved and must be zero.
# Bit 63 (NX) is ignored for non-leaf entries. Returns 0 for unusable entries.
@@ -505,7 +607,11 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64:
if not new_pdp_page: return uint64(new_pml4_page)
for i in range(512):
new_pdp_page.entries[i] = kernel_pdp.entries[i]
new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF))
# 固件 identity 大页可能带 U=1:克隆到进程地址空间后必须降为 supervisor。
# 用户页的 USER 位由后续 MapPage 在具体 4KB PTE 上显式添加。
if (new_pdp_page.entries[i] & PTE_PRESENT) != 0 and (new_pdp_page.entries[i] & PTE_PS) != 0:
new_pdp_page.entries[i] = new_pdp_page.entries[i] & ~PTE_USER
new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF) & ~PTE_USER)
pdp_idx: uint64 = (isolate_vaddr >> 30) & 0x1FF
if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return uint64(new_pml4_page)
# 1GB 大页(PDPTE.PS=1):源端没有下层 PD。先把它拆成常规 PD(语义不变:512 个 2MB
@@ -515,11 +621,19 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64:
if kernel_pdp.entries[pdp_idx] & PTE_PS:
if _split_1gb_to_pd(uint64(kernel_pdp), pdp_idx) == 0: return uint64(new_pml4_page)
kernel_pd: _p* = _p*(kernel_pdp.entries[pdp_idx] & ~uint64(0xFFF))
# 固件 identity 映射可能把 4KB/2MB 叶条目的 U 置 1。只要 U=1,用户态一旦
# 写到尚未显式映射的 VA(应用越界写),就会直接落到**同数值物理页**上——
# buddy 块头、页表、别人的数据全会被踩(现场:1GiB 下 0x21780000 的
# order-16 空闲块头被清零)。这里把该 1GiB 内"物理地址 == 虚拟地址"的
# identity 叶条目降为 supervisor;真正的用户页(phys != va)保持原样。
_sanitize_identity_pd(kernel_pd, isolate_vaddr & ~uint64(0x3FFFFFFF))
new_pd_page: _p* = AllocPage()
if not new_pd_page: return uint64(new_pml4_page)
for i in range(512):
new_pd_page.entries[i] = kernel_pd.entries[i]
new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF))
if (new_pd_page.entries[i] & PTE_PRESENT) != 0 and (new_pd_page.entries[i] & PTE_PS) != 0:
new_pd_page.entries[i] = new_pd_page.entries[i] & ~PTE_USER
new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF) & ~PTE_USER)
pd_idx: uint64 = (isolate_vaddr >> 21) & 0x1FF
if not (kernel_pd.entries[pd_idx] & PTE_PRESENT): return uint64(new_pml4_page)
if kernel_pd.entries[pd_idx] & PTE_PS: return uint64(new_pml4_page)
@@ -527,10 +641,88 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64:
new_pt_page: _p* = AllocPage()
if not new_pt_page: return uint64(new_pml4_page)
for i in range(512):
new_pt_page.entries[i] = kernel_pt.entries[i]
new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF))
new_pt_page.entries[i] = kernel_pt.entries[i] & ~PTE_USER
new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF) & ~PTE_USER)
return uint64(new_pml4_page)
# Deep-copy every USER mapping from src into dst (dst must already contain the
# kernel mappings, e.g. from create_process_page_table()). Data frames are
# allocated through alloc_fn(4096); page-table pages come from the paging pool.
#
# A table is only duplicated when its subtree really contains user pages;
# otherwise the parent's table is shared. Without that check a single user
# page at 0x400000 drags the whole kernel identity map (one PT page per 2MB
# region) into the new address space and exhausts the page-table pool.
def _alloc_user_frame(alloc_fn: rawptr) -> rawptr:
newp: rawptr = None
__asm__(f"""mov r11, {asminp(alloc_fn, ASM_DESCR.REG_ANY)}
push rbp
mov rbp, rsp
and rsp, -16
mov rdi, 4096
call r11
mov rsp, rbp
pop rbp
mov {asmout(newp, ASM_DESCR.OUTPUT_REG)}, rax""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
return newp
# level: 1 = PT, 2 = PD, 3 = PDPT (4 = PML4 only used as the entry point).
def _table_has_user(t: _p*, level: int) -> int:
i: int = 0
while i < 512:
e: uint64 = t.entries[i]
if (e & PTE_PRESENT) != 0:
if level == 1:
if (e & PTE_USER) != 0: return 1
elif (e & PTE_PS) != 0:
if (e & PTE_USER) != 0: return 1
elif (e & PTE_USER) != 0:
if _table_has_user(_p*(e & ~uint64(0xFFF)), level - 1) != 0: return 1
i += 1
return 0
def _copy_user_table(src: _p*, dst: _p*, level: int, alloc_fn: rawptr) -> int:
i: int = 0
while i < 512:
e: uint64 = src.entries[i]
if (e & PTE_PRESENT) == 0:
dst.entries[i] = e
elif level == 1:
if (e & PTE_USER) == 0:
dst.entries[i] = e
else:
src_phys: uint64 = e & uint64(0x000FFFFFFFFFF000)
newp: rawptr = _alloc_user_frame(alloc_fn)
if newp is None: return -1
string.memcpy(newp, rawptr(src_phys), 4096)
dst.entries[i] = (uint64(newp) & ~uint64(0xFFF)) | (e & uint64(0xFFF))
elif (e & PTE_PS) != 0:
dst.entries[i] = e
elif (e & PTE_USER) == 0:
dst.entries[i] = e
else:
csrc: _p* = _p*(e & ~uint64(0xFFF))
if _table_has_user(csrc, level - 1) == 0:
dst.entries[i] = e
else:
np: _p* = AllocPage()
if not np: return -1
dst.entries[i] = (uint64(np) & ~uint64(0xFFF)) | (e & uint64(0xFFF))
if _copy_user_table(csrc, np, level - 1, alloc_fn) != 0: return -1
i += 1
return 0
def CopyUserSpace(src_pa: uint64, dst_pa: uint64, alloc_fn: rawptr) -> int:
if src_pa == 0 or dst_pa == 0: return -1
s4: _p* = _p*(src_pa)
d4: _p* = _p*(dst_pa)
return _copy_user_table(s4, d4, 4, alloc_fn)
def save_pt_range(vaddr_start: uint64, vaddr_end: uint64, save_buf: uint64*, save_count: uint32*) -> int:
global pml4
if not pml4: return -1
+131 -4
View File
@@ -12,15 +12,21 @@ define PROC_READY = 0
define PROC_RUNNING = 1
define PROC_BLOCKED = 2
define PROC_DONE = 3
define MAX_PROCESSES = 8
define MAX_PROCESSES = 32
define MAX_PROC_THREADS = 8
define PROC_MAX_FDS = 16
# fd 种类(process.fd_kind[])。定义放这里、由 intr/syscall.vp 以 process.FD_KIND_* 引用,
# 这样 process.vp 不必 import devfs —— devfs → keyboard/mouse → intr.idt → sched → process
# 会形成环,不能让 process 反向依赖 devfs。
define FD_KIND_FAT32 = 0
define FD_KIND_DEVFS = 1
define FD_KIND_DEVFS = 1
define FD_KIND_DIR = 2
define FD_KIND_PIPE = 3
define PROC_MAX_DIRS = 8
define MAX_VMAS = 64
define VMA_KIND_ANON = 1
define VMA_KIND_DEV = 2
define VMA_KIND_FILE = 3
define KERN_STACK_SIZE = 8192
define USER_STACK_SIZE = 65536
# 用户栈虚拟区:主线程栈顶 = USER_STACK_VTOP(见 execrunner/elf.vp 的映射),
@@ -42,17 +48,39 @@ class Process:
heap_start: uint64
heap_size: uint64
name: [32]char
cwd: [256]char
fd_table: [PROC_MAX_FDS]rawptr
fd_used: [PROC_MAX_FDS]uint8
# 0 = FAT32 文件(fd_table[i] 里是 fileobj*);1 = devfs 设备(fd_table[i] 里是
# devfs_fd+1,+1 只为避开"devfs fd 0"与空指针的歧义)。READ/WRITE/CLOSE/IOCTL 与
# Process.exit 都必须按它分派 —— 把 devfs fd 当 fileobj* 解引用会直接跑飞。
fd_kind: [PROC_MAX_FDS]uint8
fd_flags: [PROC_MAX_FDS]uint32
fd_shared: [PROC_MAX_FDS]uint8
dir_table: [PROC_MAX_DIRS]rawptr
dir_used: [PROC_MAX_DIRS]uint8
dir_pool: [PROC_MAX_DIRS]fat32_types.dirobj
kern_stack: uint64
user_stack: uint64
# Virtual memory areas backing mmap()/mprotect()/munmap()/mremap().
vma_base: [MAX_VMAS]uint64
vma_size: [MAX_VMAS]uint64
vma_prot: [MAX_VMAS]uint32
vma_kind: [MAX_VMAS]uint8
vma_flags: [MAX_VMAS]uint32
# brk() region (separate from the mmap bump allocator).
brk_start: uint64
brk_end: uint64
# Linux process metadata.
entry_rsp: uint64
parent_pid: int
exit_code: int
# Signals (per process; delivery happens on syscall return).
sig_handler: [65]uint64
sig_actflags: [65]uint64
sig_restorer: [65]uint64
sig_mask: uint64
sig_pending: uint64
def addThread(self, tid: int) -> int:
if self.thread_count >= MAX_PROC_THREADS: return -1
@@ -69,11 +97,12 @@ class Process:
# 当指针解引用而跑飞。devfs 的 fd 表是独立的另一张;正规退出路径本来就不
# 回收 fd(见 intr/syscall.vp 的 EXIT 不调 Process.exit),这里只清槽位,
# app 自己调 CLOSE 时才走 devfs_close。
if fp and self.fd_kind[i] == FD_KIND_FAT32:
if fp and self.fd_kind[i] == FD_KIND_FAT32 and self.fd_shared[i] == 0:
fat32.close(fp)
self.fd_used[i] = 0
self.fd_table[i] = None
self.fd_kind[i] = 0
self.fd_shared[i] = 0
for i in range(PROC_MAX_DIRS):
if self.dir_used[i]:
dp: rawptr = self.dir_table[i]
@@ -125,6 +154,7 @@ class ProcessManager:
p.name[i] = ch
i += 1
p.name[i] = 0
string.strcpy(&p.cwd[0], "/")
j: int
for j in range(MAX_PROC_THREADS):
p.threads[j] = -1
@@ -132,9 +162,28 @@ class ProcessManager:
p.fd_used[j] = 0
p.fd_table[j] = None
p.fd_kind[j] = 0
p.fd_flags[j] = 0
p.fd_shared[j] = 0
for j in range(PROC_MAX_DIRS):
p.dir_used[j] = 0
p.dir_table[j] = None
for j in range(MAX_VMAS):
p.vma_base[j] = 0
p.vma_size[j] = 0
p.vma_prot[j] = 0
p.vma_kind[j] = 0
p.vma_flags[j] = 0
p.brk_start = 0
p.brk_end = 0
p.entry_rsp = 0
p.parent_pid = 0
p.exit_code = 0
p.sig_mask = 0
p.sig_pending = 0
for j in range(65):
p.sig_handler[j] = 0
p.sig_actflags[j] = 0
p.sig_restorer[j] = 0
__asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""",
op=[ASM_DESCR.CLOBBER_MEMORY])
@@ -171,7 +220,10 @@ class ProcessManager:
@staticmethod
def create_process(name: str, entry: rawptr) -> Process*:
m: ProcessManager* = _proc_mgr_ptr
if m.count >= MAX_PROCESSES: return None
if m.count >= MAX_PROCESSES:
# 启动时已有 1(内核)+7(app) 个槽;旧上限 8 导致 run/runb 永远失败。
serial.puts("[process] create_process: pid table full\n")
return None
pid: int = m.count
m.count += 1
p: Process* = &m.processes[pid]
@@ -189,17 +241,38 @@ class ProcessManager:
p.name[i] = ch
i += 1
p.name[i] = 0
string.strcpy(&p.cwd[0], "/")
for j in range(MAX_PROC_THREADS):
p.threads[j] = -1
for j in range(PROC_MAX_FDS):
p.fd_used[j] = 0
p.fd_table[j] = None
p.fd_kind[j] = 0
p.fd_flags[j] = 0
p.fd_shared[j] = 0
for j in range(PROC_MAX_DIRS):
p.dir_used[j] = 0
p.dir_table[j] = None
for j in range(MAX_VMAS):
p.vma_base[j] = 0
p.vma_size[j] = 0
p.vma_prot[j] = 0
p.vma_kind[j] = 0
p.vma_flags[j] = 0
p.brk_start = 0
p.brk_end = 0
p.entry_rsp = 0
p.parent_pid = 0
p.exit_code = 0
p.sig_mask = 0
p.sig_pending = 0
for j in range(65):
p.sig_handler[j] = 0
p.sig_actflags[j] = 0
p.sig_restorer[j] = 0
ks: rawptr = mm.malloc(KERN_STACK_SIZE)
if ks is None:
serial.puts("[process] kernel stack alloc failed\n")
m.count = m.count - 1
return None
ks_size: uint64 = KERN_STACK_SIZE
@@ -302,6 +375,60 @@ def drop_to_user_mode(entry: rawptr, user_rsp: uint64, kern_rsp: uint64, arg: ra
ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10,
ASM_DESCR.CLOBBER_RBP])
# Resume context used by fork/clone children: they start user mode at the
# parent's post-syscall RIP with a full register set and RAX=0.
export _resume_ctx: [20]uint64
def set_resume(rip: uint64, rsp: uint64, flags: uint64, rax: uint64, regs: uint64*):
_resume_ctx[0] = rip
_resume_ctx[1] = rsp
_resume_ctx[2] = flags
_resume_ctx[3] = rax
i: int = 0
while i < 12:
_resume_ctx[4 + i] = regs[i]
i += 1
def drop_to_user_mode_ctx(kern_rsp: uint64):
__asm__(f"""cli
mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)}
mov ecx, 0xC0000101
rdmsr
shl rdx, 32
or rax, rdx
test rax, rax
jz 1f
swapgs
1:
lea rbx, [rip + _resume_ctx]
mov r15, [rbx+32]
mov r14, [rbx+40]
mov r13, [rbx+48]
mov r12, [rbx+56]
mov r10, [rbx+64]
mov r9, [rbx+72]
mov r8, [rbx+80]
mov rbp, [rbx+88]
mov rdi, [rbx+96]
mov rsi, [rbx+104]
mov rdx, [rbx+112]
mov rcx, [rbx+0]
mov r11, [rbx+16]
mov rax, [rbx+24]
push 0x23
push qword ptr [rbx+8]
push r11
push 0x2B
push rcx
mov rbx, [rbx+120]
iretq""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RBX, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8,
ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10,
ASM_DESCR.CLOBBER_RBP])
# ============================================================
# 用户态线程(THREAD_CREATE)上下文
+102 -9
View File
@@ -10,7 +10,7 @@ define THREAD_READY = 0
define THREAD_RUNNING = 1
define THREAD_BLOCKED = 2
define THREAD_DONE = 3
define MAX_THREADS = 16
define MAX_THREADS = 64
define THREAD_STACK_SIZE = 65536
define SCHED_QUANTUM = 10
@@ -21,6 +21,34 @@ export _isr_scratch_tbl: [MAX_THREADS * 6]uint64
export _switch_old_tid: int = 0
export _switch_new_tid: int = 0
# Per-thread IA32_FS_BASE (glibc TLS). Kept outside Thread: Thread has a
# hard-coded 600-byte layout checked by _thread_entry.
define IA32_FS_BASE = 0xC0000100
_fs_base_tbl: [MAX_THREADS]uint64
_syscall_frame_tbl: [MAX_THREADS]uint64
_clear_child_tid: [MAX_THREADS]uint64
def _msr_read(msr: uint32) -> uint64:
lo: uint32
hi: uint32
__asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)}
rdmsr
mov {asmout(lo, ASM_DESCR.OUTPUT_REG)}, eax
mov {asmout(hi, ASM_DESCR.OUTPUT_REG)}, edx""",
out=[asmout(lo, ASM_DESCR.OUTPUT_REG), asmout(hi, ASM_DESCR.OUTPUT_REG)],
op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX])
return uint64(lo) | (uint64(hi) << 32)
def _msr_write(msr: uint32, val: uint64):
lo: uint32 = uint32(val & uint64(0xFFFFFFFF))
hi: uint32 = uint32(val >> 32)
__asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)}
mov eax, {asminp(lo, ASM_DESCR.REG_ANY)}
mov edx, {asminp(hi, ASM_DESCR.REG_ANY)}
wrmsr""",
op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX])
class Thread:
rsp: uint64
@@ -103,12 +131,12 @@ class Scheduler:
__asm__(f"""mov [{asminp(main_rsp_ptr, ASM_DESCR.REG_ANY)}], rsp""",
op=[ASM_DESCR.CLOBBER_MEMORY])
# fail-fast:_thread_entry 的 asm 以硬编码偏移访问线程表(sizeof(Thread)=600、
# current_tid=+9600=16*600)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的
# current_tid=+38400=64*600)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的
# 偏移取 func/arg 并 call 任意地址。这里直接核对,不符立即停机暴露。
thr0_addr: uint64 = uint64(&s.threads[0])
curt_addr: uint64 = uint64(&s.current_tid)
if Thread.__sizeof__() != 600 or (curt_addr - thr0_addr) != 9600:
serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(600/9600)不一致\n")
if Thread.__sizeof__() != 600 or (curt_addr - thr0_addr) != 38400:
serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(600/38400)不一致\n")
while True:
asm.sti()
asm.hlt()
@@ -117,7 +145,8 @@ class Scheduler:
def _yield():
global _switch_old_rsp, _switch_new_rsp, _yield_cnt, _yield_lock
global _switch_old_tid, _switch_new_tid
global _switch_old_fpu, _switch_new_fpu
global _switch_old_fpu, _switch_new_fpu
global _fs_base_tbl
_if_saved: int = 0
__asm__(f"""pushfq
pop rax
@@ -176,6 +205,9 @@ class Scheduler:
cpu.set_kernel_rsp0(next_ks)
_switch_old_tid = old_tid
_switch_new_tid = next_tid
# Per-thread TLS: save the outgoing FS base, load the incoming one.
_fs_base_tbl[old_tid] = _msr_read(uint32(IA32_FS_BASE))
_msr_write(uint32(IA32_FS_BASE), _fs_base_tbl[next_tid])
_yield_lock = 0
_do_switch()
if _if_saved:
@@ -306,8 +338,14 @@ class Scheduler:
现场 [0E] rip=0x0 err=0x15(用户态取指缺页)cr2=0x0 rsp=0x7fffffde0000,tid 为该 app 线程,
紧跟在 [syscall] exit called 之后。
"""
global _clear_child_tid
s: Scheduler* = _sched_ptr
tid: int = s.current_tid
ct: uint64 = _clear_child_tid[tid]
if ct != 0:
cw: uint32* = uint32*(ct)
cw[0] = uint32(0)
_clear_child_tid[tid] = 0
if tid > 0 and tid < MAX_THREADS:
s.threads[tid].state = THREAD_DONE
proc.free_user_thread(proc.find_user_slot_by_tid(tid))
@@ -318,6 +356,33 @@ class Scheduler:
asm.hlt()
Scheduler._yield()
@staticmethod
def exit_process(pid: int):
"""Linux exit_group: retire every thread of `pid`, release the process, never return."""
global _clear_child_tid
s: Scheduler* = _sched_ptr
i: int = 0
while i < s.thread_count:
if s.threads[i].pid == pid:
ct: uint64 = _clear_child_tid[i]
if ct != 0:
cw: uint32* = uint32*(ct)
cw[0] = uint32(0)
_clear_child_tid[i] = 0
s.threads[i].state = THREAD_DONE
slot: int = proc.find_user_slot_by_tid(i)
if slot >= 0:
proc.free_user_thread(slot)
i += 1
p: proc.Process* = proc.ProcessManager.get_process(pid)
if p is not None:
p.exit()
Scheduler._yield()
while True:
asm.sti()
asm.hlt()
Scheduler._yield()
_sched_storage: Scheduler = Scheduler()
export _sched_ptr: Scheduler* = &_sched_storage
@@ -326,6 +391,34 @@ export _switch_new_rsp: rawptr
export _switch_old_fpu: rawptr
export _switch_new_fpu: rawptr
def set_fs_base(base: uint64):
global _fs_base_tbl
s: Scheduler* = _sched_ptr
_fs_base_tbl[s.current_tid] = base
_msr_write(uint32(IA32_FS_BASE), base)
def set_thread_fs_base(tid: int, base: uint64):
global _fs_base_tbl
if tid >= 0 and tid < MAX_THREADS:
_fs_base_tbl[tid] = base
def get_fs_base() -> uint64:
return _msr_read(uint32(IA32_FS_BASE))
def set_syscall_frame(f: uint64):
global _syscall_frame_tbl
s: Scheduler* = _sched_ptr
_syscall_frame_tbl[s.current_tid] = f
def get_syscall_frame() -> uint64:
s: Scheduler* = _sched_ptr
return _syscall_frame_tbl[s.current_tid]
def set_clear_child_tid(tid: int, addr: uint64):
global _clear_child_tid
if tid >= 0 and tid < MAX_THREADS:
_clear_child_tid[tid] = addr
@__attribute__(naked)
def _do_switch():
__asm__(f"""push rax
@@ -394,20 +487,20 @@ def _do_switch():
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX])
# Thread 布局:rsp0/stack8/stack_size16/func24/arg32/state40/tid44/pid48/kern_stack56/fpu_area64/fpu_ptr592
# ⇒ sizeof(Thread) = 600,Scheduler.threads[16] 占 9600 字节 ⇒ current_tid 在 +9600。
# 增删 Thread 字段时必须同步下面 asm 里的 9600 / 600(以及 +24/+32/+40 三个字段偏移)。
# ⇒ sizeof(Thread) = 600,Scheduler.threads[64] 占 38400 字节 ⇒ current_tid 在 +38400。
# 增删 Thread 字段时必须同步下面 asm 里的 38400 / 600(以及 +24/+32/+40 三个字段偏移)。
@__attribute__(naked)
def _thread_entry():
__asm__(f"""sub rsp, 8
mov rax, [rip + _sched_ptr]
mov ecx, dword ptr [rax + 9600]
mov ecx, dword ptr [rax + 38400]
movsxd rcx, ecx
imul rcx, rcx, 600
mov rdi, [rax + rcx + 32]
mov rax, [rax + rcx + 24]
call rax
mov rax, [rip + _sched_ptr]
mov ecx, dword ptr [rax + 9600]
mov ecx, dword ptr [rax + 38400]
movsxd rcx, ecx
imul rcx, rcx, 600
mov dword ptr [rax + rcx + 40], 3
+3 -1
View File
@@ -23,7 +23,9 @@
"output": "kernel.elf"
},
"includes": [
"../../../includes"
"../..",
"../../../includes",
"../"
],
"target": {
"triple": "x86_64-none-elf",
+2
View File
@@ -0,0 +1,2 @@
# Package marker for the shared Linux ABI module.
define ABI_VERSION = 1
+206
View File
@@ -0,0 +1,206 @@
# Linux x86-64 ABI constants and structures shared by the kernel and vpsdk.
#
# The byte layouts must match Linux exactly: glibc reads these structs
# directly and never goes through any ViperOS wrapper.
#
# This module is imported by both sides:
# VKernel/Kernel/intr/syscall.vp (kernel project gains "../.." include)
# vpsdk/syscall.vp / vpsdk/fs.vp (apps already include the repo root)
# ---------------------------------------------------------------- errno ----
define EPERM = 1
define ENOENT = 2
define ESRCH = 3
define EINTR = 4
define EIO = 5
define ENXIO = 6
define E2BIG = 7
define ENOEXEC = 8
define EBADF = 9
define ECHILD = 10
define EAGAIN = 11
define ENOMEM = 12
define EACCES = 13
define EFAULT = 14
define ENOTBLK = 15
define EBUSY = 16
define EEXIST = 17
define EXDEV = 18
define ENODEV = 19
define ENOTDIR = 20
define EISDIR = 21
define EINVAL = 22
define ENFILE = 23
define EMFILE = 24
define ENOTTY = 25
define ETXTBSY = 26
define EFBIG = 27
define ENOSPC = 28
define ESPIPE = 29
define EROFS = 30
define EMLINK = 31
define EPIPE = 32
define EDOM = 33
define ERANGE = 34
define EDEADLK = 35
define ENAMETOOLONG = 36
define ENOLCK = 37
define ENOSYS = 38
define ENOTEMPTY = 39
define ELOOP = 40
define EOVERFLOW = 75
define ENOTSUP = 95
define EOPNOTSUPP = 95
define ETIMEDOUT = 110
# ------------------------------------------------------------ open flags ----
# Values are the Linux x86-64 ones (octal in the man page).
define O_RDONLY = 0
define O_WRONLY = 1
define O_RDWR = 2
define O_ACCMODE = 3
define O_CREAT = 64
define O_EXCL = 128
define O_NOCTTY = 256
define O_TRUNC = 512
define O_APPEND = 1024
define O_NONBLOCK = 2048
define O_DSYNC = 4096
define O_DIRECT = 16384
define O_DIRECTORY = 65536
define O_NOFOLLOW = 131072
define O_CLOEXEC = 524288
# --------------------------------------------------------------- fcntl ----
define F_DUPFD = 0
define F_GETFD = 1
define F_SETFD = 2
define F_GETFL = 3
define F_SETFL = 4
define F_DUPFD_CLOEXEC = 1030
define FD_CLOEXEC = 1
# ----------------------------------------------------------- at / dirent ----
define AT_FDCWD = -100
define AT_SYMLINK_NOFOLLOW = 256
define AT_REMOVEDIR = 512
define AT_EACCESS = 512
define AT_EMPTY_PATH = 4096
define DT_UNKNOWN = 0
define DT_FIFO = 1
define DT_CHR = 2
define DT_DIR = 4
define DT_BLK = 6
define DT_REG = 8
define DT_LNK = 10
define DT_SOCK = 12
# ------------------------------------------------------------ mmap/mprot ----
define PROT_NONE = 0
define PROT_READ = 1
define PROT_WRITE = 2
define PROT_EXEC = 4
define MAP_SHARED = 1
define MAP_PRIVATE = 2
define MAP_FIXED = 16
define MAP_ANONYMOUS = 32
define MAP_GROWSDOWN = 256
define MAP_NORESERVE = 16384
define MAP_POPULATE = 32768
define MAP_FAILED: uint64 = 0xFFFFFFFFFFFFFFFF
# ---------------------------------------------------------------- mremap ----
define MREMAP_MAYMOVE = 1
define MREMAP_FIXED = 2
# ------------------------------------------------------------- arch_prctl ----
define ARCH_SET_GS = 0x1001
define ARCH_SET_FS = 0x1002
define ARCH_GET_FS = 0x1003
define ARCH_GET_GS = 0x1004
# ---------------------------------------------------------------- rlimit ----
define RLIMIT_CPU = 0
define RLIMIT_FSIZE = 1
define RLIMIT_DATA = 2
define RLIMIT_STACK = 3
define RLIMIT_CORE = 4
define RLIMIT_NOFILE = 7
define RLIM_INFINITY: uint64 = 0xFFFFFFFFFFFFFFFF
# ------------------------------------------------------------- file modes ----
define S_IFMT = 61440
define S_IFDIR = 16384
define S_IFCHR = 8192
define S_IFIFO = 4096
define S_IFREG = 32768
define MODE_FILE = 33188 # 0100644
define MODE_DIR = 16877 # 040755
define MODE_CHAR = 8630 # 020666
define MODE_FIFO = 4534 # 010666
define SEEK_SET = 0
define SEEK_CUR = 1
define SEEK_END = 2
define PATH_MAX = 256
define SIZEOF_LINUX_STAT = 144
define SIZEOF_UTSNAME = 390
define SIZEOF_DIRENT64 = 19
# ---------------------------------------------------------------- structs ----
# struct stat, x86-64 Linux layout (144 bytes).
@__attribute__(packed)
class linux_stat:
st_dev: uint64
st_ino: uint64
st_nlink: uint64
st_mode: uint32
st_uid: uint32
st_gid: uint32
pad0: uint32
st_rdev: uint64
st_size: uint64
st_blksize: uint64
st_blocks: uint64
st_atime_sec: uint64
st_atime_nsec: uint64
st_mtime_sec: uint64
st_mtime_nsec: uint64
st_ctime_sec: uint64
st_ctime_nsec: uint64
res0: uint64
res1: uint64
res2: uint64
# struct linux_dirent64 header; the NUL-terminated name follows immediately.
@__attribute__(packed)
class linux_dirent64:
d_ino: uint64
d_off: uint64
d_reclen: uint16
d_type: uint8
@__attribute__(packed)
class linux_timespec:
tv_sec: uint64
tv_nsec: uint64
@__attribute__(packed)
class linux_utsname:
sysname: [65]char
nodename: [65]char
release: [65]char
version: [65]char
machine: [65]char
domainname: [65]char
@__attribute__(packed)
class linux_rlimit:
cur: uint64
max: uint64
class iovec:
base: rawptr
len: uint64
+64 -3
View File
@@ -7,7 +7,7 @@ import subprocess
# ============================================================================
# viperc 编译器路径 —— 唯一来源,改这里即可
# ============================================================================
VIPERC = r'..\..\ViperC\__vpcbuild__\output\viperc.exe'
VIPERC = r'..\..\ViperC\__vpcbuild6__\output\viperc.exe'
# 解析参数
@@ -17,6 +17,18 @@ noapp = '--noapp' in sys.argv
bootloader = '--bootloader' in sys.argv
# --onlyrun:跳过全部编译/打包,直接用现有 disk.img 启动 QEMU
only_run = '--onlyrun' in sys.argv
# --headless:QEMU 不开窗口(-display none),串口仍写入 serial.log。
# 自动化调试/无人值守验证时使用;需要看画面时不要带这个参数。
headless = '--headless' in sys.argv
# --runsecs=N:启动 N 秒后自动结束 QEMU(0=一直运行到客户机退出)。
# 与 --headless 搭配可用于无人值守的冒烟测试。
run_secs = 0
for _arg in sys.argv:
if _arg.startswith('--runsecs='):
try:
run_secs = int(_arg[len('--runsecs='):])
except ValueError:
run_secs = 0
# --onlyapp:只编译库与应用(保留兼容;现在这已是默认行为)
only_app = '--onlyapp' in sys.argv
# --kernel:显式重编内核。不带 --app 时**默认就编内核**(见下方 app_filter 解析后的修正);
@@ -45,6 +57,17 @@ if kernel_only and nokernel:
sys.exit(1)
def SyncSyscallTable():
"""同步 syscall 表:Scripts/syscall_table.py 是唯一权威来源。
VKernel 与 vpsdk 各自持有一份 enum(vpsdk 不能依赖内核),构建前统一
重新生成,避免两份表手工漂移。生成器失败时保留现有文件继续构建。
"""
rc = os.system(f'"{sys.executable}" Scripts/syscall_table.py --write')
if rc != 0:
print("[build] 警告: syscall 表同步失败,继续使用现有文件")
def BuildKernel():
"""编译内核:链接器一次产出带符号 kernel.elf,再由 objcopy 二次处理成裸镜像 kernel.bin"""
cmd = rf'{VIPERC} --project ./VKernel/project.vpj --clear-cache --rebuild-includes'
@@ -74,6 +97,7 @@ APP_LIST = [
("scene3d", "Apps/Scene3D", "scene3d.elf", "scene3d.elf"),
("explorer", "Apps/Explorer", "explorer.elf", "explorer.elf"),
("fontdemo", "Apps/FontDemo", "fontdemo.elf", "fontdemo.elf"),
("imagesee", "Apps/ImageSee", "imagesee.elf", "imagesee.elf"),
]
# 动态库表:(工程目录, 产物文件名)
LIB_LIST = [
@@ -97,6 +121,26 @@ def CopyFonts():
shutil.copy(src, "./disk/fonts/font.ttf")
def CopyAssets():
"""Copy image demo assets (Assets/images) to /IMAGES on the disk."""
src_dir = "Assets/images"
if not os.path.isdir(src_dir):
print("[build] 警告: 缺少 Assets/images,ImageSee 将没有演示图片")
return
os.makedirs("./disk/images", exist_ok=True)
for name in os.listdir(src_dir):
low = name.lower()
if low.endswith(".png") or low.endswith(".jpg") or low.endswith(".jpeg"):
shutil.copy(os.path.join(src_dir, name), os.path.join("./disk/images", name))
print(f"[build] asset {name} -> disk/images/{name}")
# Desktop wallpaper used by Explorer (separate from the ImageSee gallery).
bg = "Assets/background.jpg"
if os.path.exists(bg):
os.makedirs("./disk/sys/images/screen", exist_ok=True)
shutil.copy(bg, "./disk/sys/images/screen/background.jpg")
print("[build] asset background.jpg -> disk/sys/images/screen/background.jpg")
def BuildLibs():
"""编译 Libs/ 下的动态库(应用运行期经 LOAD_SO 加载)。"""
os.makedirs("./disk/libs", exist_ok=True)
@@ -222,11 +266,25 @@ def RunQemu():
'-serial', f'file:{serial_log}',
# '-no-reboot'
]
if headless:
qemu_cmd += ['-display', 'none']
print(f"[build] QEMU cmd: {' '.join(qemu_cmd)}")
proc = subprocess.Popen(qemu_cmd)
proc.wait()
if run_secs > 0:
try:
proc.wait(timeout=run_secs)
except subprocess.TimeoutExpired:
print(f"[build] --runsecs={run_secs}: 自动结束 QEMU")
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
else:
proc.wait()
print(f"[build] serial_log path: {serial_log}")
print(f"[build] file exists: {os.path.exists(serial_log)}")
@@ -242,6 +300,7 @@ def RunQemu():
if only_run:
print("[build] --onlyrun: 跳过编译/打包,直接用现有 disk.img 启动 QEMU")
else:
SyncSyscallTable()
if kernel_only:
print("[build] --kernel-only: 只编译内核")
BuildKernel()
@@ -251,8 +310,10 @@ else:
else:
print("[build] 跳过内核编译(--app 或 --nokernel)")
BuildApps()
CopyAssets()
BuildBootloader()
CopyFonts()
BuildDisk()
RunQemu()
if "--norun" not in sys.argv:
RunQemu()
+114
View File
@@ -0,0 +1,114 @@
# ImageSee 图像解码库
`vpsdk/imagesee` 是一个纯用户态的 PNG / JPEG 解码库,配套示例应用
`Apps/ImageSee` 在窗口中并排显示解码结果。
## 1. 目录与模块
```text
vpsdk/imagesee/
├── imagesee.vp 公共 API:load / decode_memory / checksum
├── image.vp Image 类型与像素打包(0xAARRGGBB)
├── inflate.vp zlib / DEFLATE(stored / fixed / dynamic Huffman)
├── png.vp PNG:全部颜色类型、1/2/4/8/16 位、Adam7
└── jpeg.vp JPEG:baseline/extended sequential、4:4:4/4:2:2/4:2:0
Apps/ImageSee/ 示例应用(窗口 + 两块 FBCanvas)
Assets/images/ 示例图片(由 Scripts/gen_imagesee_assets.py 生成)
```
## 2. 公共 API
```python
import vpsdk.imagesee.imagesee as imagesee
img = imagesee.load("/IMAGES/SAMPLE.PNG") # 失败返回 None
if img is not None:
w = img.width
h = img.height
c = img.pixels[y * w + x] # 0xAARRGGBB
r = (c >> 16) & 0xFF
g = (c >> 8) & 0xFF
b = c & 0xFF
img2 = imagesee.decode_memory(buf, size) # 已有字节流时使用
sum32 = imagesee.checksum(img) # FNV-1a,用于回归比对
name = imagesee.format_name(img) # "png" / "jpeg"
```
像素格式与 ViperOS 窗口 / `FBCanvas` 一致:`0xAARRGGBB`,alpha 字节目前由
合成层忽略,解码器一律输出 255(PNG 的透明像素保留 alpha=0 于像素字中)。
## 3. PNG 支持
- 签名、IHDR、PLTE、tRNS、IDAT(可多块)、IEND
- 颜色类型:0(灰度)、2(RGB)、3(调色板)、4(灰度+alpha)、6(RGBA)
- 位深:1/2/4/8/16(按 PNG 规则缩小到 8 位输出)
- 过滤:None / Sub / Up / Average / Paeth
- Adam7 隔行扫描
- 解码后输出 RGBA;调色板 tRNS、灰度和 RGB 的透明色都会转成 alpha=0
## 4. JPEG 支持
- Baseline / extended sequential Huffman(SOF0 / SOF1)
- 灰度与 YCbCr,色度子采样 4:4:4 / 4:2:2 / 4:2:0(以及任意 H/V 因子)
- DQT(8/16 位)、DHT、DRI + RSTn 重启标记
- 组件按 ID 识别(1=Y、2=Cb、3=Cr),也兼容 Adobe APP14 的 RGB 变换
- 8×8 浮点 IDCT,YCbCr → RGB 后输出
**限制**:渐进式 JPEG(SOF2)目前明确返回 `None`(不做“解一半”的错误输出);
CMYK/YCCK 返回 `None`;不支持 EXIF 方向、缩略图、算术编码。
## 5. 示例应用
`/APPS/IMAGESEE.ELF` 启动后:
1. 依次解码并打印 `/IMAGES/SAMPLE.PNG`、`PHOTO.JPG`、`GRAY.JPG`、
`PAL.PNG`、`INTER.PNG` 的尺寸、FNV 校验和与 3 个采样像素;
2. 创建窗口,左画 PNG、右画 JPEG(等比缩放居中);
3. 失败时对应面板显示品红/深蓝棋盘,串口打印 `FAILED stage=N`。
`build.py` 会:
- 编译 `Apps/ImageSee` 到 `disk/apps/imagesee.elf`
- 把 `Assets/images/*.png|jpg` 复制到 `disk/images/`
- 内核 `main.vp` 在 Explorer/FontDemo 之后自动 spawn ImageSee(pid=7)
重建示例图片:
```powershell
python Scripts/gen_imagesee_assets.py # 需要 Pillow,仅生成资源时使用
```
## 6. 已验证
2026-10-28,QEMU 512MB,无头启动:
| 文件 | 类型 | 结果 |
|---|---|---|
| sample.png | RGBA,非隔行 | 解码像素/校验和与 PIL 完全一致 |
| pal.png | 调色板 + tRNS | 完全一致 |
| inter.png | RGBA,Adam7,alpha 渐变 | 完全一致 |
| photo.jpg | baseline 4:2:0 | 尺寸/采样点与 PIL 在 ±11 内(IDCT/上采样差异) |
| gray.jpg | 灰度 baseline | 采样点完全一致 |
## 7. Explorer 桌面壁纸
Explorer 启动时调用 `imagesee.load("/sys/images/screen/background.jpg")`:
- 成功:按“cover”等比裁切铺满 framebuffer,任务栏/窗口叠加在壁纸上
- 失败(文件不存在/解码失败):重试一次;仍失败则保持原来的深色纯色背景
- 构建:`build.py` 把 `Assets/background.jpg` 复制到
`disk/sys/images/screen/background.jpg`
- 重新生成壁纸:`python Scripts/gen_imagesee_assets.py`(960×540 baseline JPEG)
## 8. 内存
解码使用 `stdlib.malloc`(mmap 后端,`free` 为空操作)。一次 `load` 会保留:
- 压缩文件字节
- DEFLATE 解压后的扫描线缓冲(PNG)
- 最终 `w*h*4` 像素
- JPEG 的量化系数与分量平面
因此适合中小图片(示例 320×220)。超大图片(>4096²)不建议在当前堆上尝试。
+382
View File
@@ -0,0 +1,382 @@
# ViperOS syscall 与 Linux x86-64 ABI 对齐计划
状态:**P0–P6 完成**(含明确受限项;内核/应用构建通过,`fork`、`wait4`、信号投递已在 QEMU 冒烟验证)
最后更新:2026-10-28
目标:让 ViperOS 的 syscall 面逐步收敛到 Linux x86-64 ABI,最终能够**直接运行静态/动态链接的 glibc 程序**。
非目标(至少本计划内不做):
- 网络协议栈相关 syscall(socket/connect/…)
- ptrace / seccomp / cgroup / namespace
- 完整 VFS(符号链接、硬链接、inode 语义)
- 与 Linux 内核模块、io_uring、epoll 的兼容
---
## 1. 现状
### 1.1 入口与调用约定
| 项目 | 现状 |
|---|---|
| 指令 | `syscall`(MSR `STAR`/`LSTAR`/`FMASK`),另注册了 DPL=3 的 `int 0x80` 遗留门 |
| 调用号 | `rax` |
| 参数 | `rdi`, `rsi`, `rdx`, `r10`, `r8`, `r9`(与 Linux 一致) |
| 返回 | `rax`;目前正负混用 |
| 错误 | 无 `errno`、无 TLS;部分返回 `-1`,部分返回 FAT32 `FRESULT` **正数**,未知调用号返回 `0` |
| 表 | `VKernel/Kernel/intr/syscall.vp` 与 `vpsdk/syscall.vp` 两份手工镜像 |
### 1.2 已实现的调用
权威列表见 `Scripts/syscall_table.py`(`build.py` 构建前自动同步内核与 SDK)。
截至 P6,Linux 号段已覆盖:
```text
文件 open/openat/close/read/write/pread/pwrite/lseek/stat/fstat/newfstatat/
getdents64/dup/dup2/dup3/fcntl/mkdir/unlink/rename/rmdir/getcwd/chdir/
access/faccessat/readlink/readlinkat
内存 brk/mmap/munmap/mprotect/mremap/madvise
进程 getpid/getppid/gettid/exit/exit_group/wait4/clone/fork/execve
线程 arch_prctl(FS)/set_tid_address/set_robust_list/futex/sched_yield
时间 clock_gettime/clock_getres/gettimeofday/nanosleep
信号 rt_sigaction/rt_sigprocmask/rt_sigreturn/kill
IPC pipe/pipe2/readv/writev/poll(超时桩)
系统 uname/getuid/geteuid/getgid/getegid/prlimit64/getrandom/reboot
```
私有段仅剩兼容别名(`LOAD_SO`/`SO_CALL1`/`SPAWN_PROC`/`THREAD_CREATE_OLD`/
`REBOOT`/`SHUTDOWN`)。
### 1.3 已知偏差
- `execve(59)` 通过“加载新进程 + 退出调用者”模拟,PID 会改变
- `fork(57)` / `clone(56)` 会复制/共享地址空间;`CLONE_VM` 无 `CLONE_THREAD`
的 vfork 场景按同进程线程近似
- 信号只在 **syscall 返回** 时投递(没有中断返回钩子);`ucontext_t` 为自定义
紧凑布局,`siglongjmp` 不可用
- `futex` 为“查值 + yield”轮询;`poll` 恒返回 0(仅超时语义)
- `exit_group(231)` 终止进程内所有线程;进程退出不回收管道/文件引用计数
- fork/exec 继承的非管道 fd 标记为两侧共享:close 只清槽位不释放对象(避免
交叉释放;代价是泄漏)
- 资源上限:8 进程 / 每进程 8 线程 / 16 fd / 8 目录句柄 / 64 VMA / 8 管道
- P1 已补:`openat` / `fstat` / `newfstatat` / `getdents64` / `dup` / `fcntl`,
私有目录调用已删除
- P2 已补:`brk` / `mprotect` / `munmap` / `mremap` / `madvise`,文件映射
- P3–P6 已补:启动栈/auxv、`PT_INTERP`/ET_DYN、`execve`、`wait4`、Linux
`clone`/`fork`、TLS、时间、管道、信号投递、`reboot(169)`
### 1.4 维护风险
两份 `enum Syscall` 靠人工同步。P0 起改为**生成**:
```text
Scripts/syscall_table.py ← 唯一权威表
│
├─ VKernel/Kernel/intr/syscall.vp [BEGIN/END GENERATED]
└─ vpsdk/syscall.vp [BEGIN/END GENERATED]
```
`build.py` 在编译前运行生成器;两边不一致会直接覆盖,不再依赖记忆。
---
## 2. 目标 ABI 契约
### 2.1 调用与返回
- `rax` = 调用号,参数 `rdi/rsi/rdx/r10/r8/r9`
- 成功:返回非负值(字节数、fd、地址、0…)
- 失败:返回 `-errno`(`-ENOENT`、`-EINVAL`…),**不再返回 `FRESULT` 正数**
- 未实现:`-ENOSYS`
- 指针参数越界/为空:`-EFAULT`
- 用户态 `errno` 由 glibc 从返回值设置,内核不维护 `errno`
### 2.2 数据结构
内核新增 `abi/linux.vp`(内核工程 include 增加仓库根目录),字段布局按 x86-64 Linux;
SDK 侧需要结构体时可直接 `import abi.linux`。两者共用同一份定义,避免布局漂移。
| 类型 | 大小 | 用途 |
|---|---:|---|
| `abi.linux_stat` | 144 | `stat/fstat/newfstatat` |
| `abi.linux_dirent64` | 19+name | `getdents64` |
| `abi.linux_timespec` | 16 | 时间 |
| `abi.linux_utsname` | 390 | `uname` |
| `abi.linux_rlimit` | 16 | `prlimit64` |
| `abi.iovec` | 16 | `readv/writev` |
### 2.3 启动栈(P3)
glibc 不读 `_start(argc, argv)`,它要求内核按 Linux 方式布置用户栈:
```text
[argc][argv0..argvN][NULL][envp...][NULL][auxv pairs][AT_NULL]
```
必须提供的 auxv:`AT_PHDR`、`AT_PHENT`、`AT_PHNUM`、`AT_ENTRY`、`AT_BASE`、
`AT_PAGESZ`、`AT_RANDOM`、`AT_UID/EUID/GID/EGID`、`AT_SECURE=0`、
`AT_EXECFN`、`AT_SYSINFO_EHDR`(可选,无 vDSO 时省略)。
动态链接:`PT_INTERP` 指向的解释器按 ET_DYN 基址(`0x70000000`)加载,
主程序 PIE 基址 `0x60000000`,`AT_BASE` 指向解释器,`AT_PHDR/AT_ENTRY`
仍指向主程序。系统镜像中需要实际存在该解释器与 libc。
### 2.4 TLS 与线程(P4)
- `arch_prctl(ARCH_SET_FS)`:设置每线程 `IA32_FS_BASE`
- 线程切换时保存/恢复 `FS_BASE`
- `set_tid_address`、`set_robust_list`、`rseq`(可为 `-ENOSYS`)
- futex 至少覆盖 `FUTEX_WAIT/FUTEX_WAKE`(glibc pthread 必需)
### 2.5 兼容口径
- `uname().sysname` 报 `"Linux"`,`release` 报一个 Linux 风格版本号;真实产品名放在 `version` 字段。这样大量脚本/构建系统不会因 `sysname != Linux` 走特殊分支。
- 文件权限位使用固定值:普通文件 `0644`、目录 `0755`;FAT32 没有 uid/gid。
---
## 3. 阶段计划
### P0 — ABI 契约与基础设施(本次完成,待编译验证)
- [x] `abi/linux.vp`:errno、`O_*`、`AT_*`、`DT_*`、`PROT_*`、`MAP_*`、`ARCH_*`、结构体
- [x] 统一错误模型:Linux 号段调用返回 `-errno`;未知调用 `-ENOSYS`
(私有 0x1000+ 段在 P6 删除前保留旧返回值)
- [x] `open` 采用 Linux `O_*`,内核翻译到 FAT32 `FA_*`
- [x] `lseek` 返回新偏移(而不是状态码),支持 64 位 offset(FAT32 上限内)
- [x] 新调用:`uname` `getuid/geteuid/getgid/getegid` `getppid` `gettid`
`getcwd` `chdir` `access` `faccessat` `readlink` `readlinkat`
`set_tid_address` `set_robust_list` `rseq` `prlimit64` `getrandom`
- [x] `Process.cwd`(默认 `/`)
- [x] syscall 表由 `Scripts/syscall_table.py` 生成,`build.py` 自动同步
- [x] `vpsdk` 增加对应包装与常量
验收:现有应用行为不回退;所有失败路径可观察到负 errno;`Scripts/syscall_table.py --check` 通过。
### P1 — 文件系统 ABI(本次完成,待编译验证)
- [x] `openat(257)`(`AT_FDCWD` + 绝对路径 + cwd 相对路径)
- [x] `fstat(5)`、`newfstatat(262)`、`statx(332)`(返回 `-ENOSYS`,glibc 回退)
- [x] `getdents64(217)` + 目录 fd(`O_DIRECTORY`)
- [x] `dup(32)` / `dup2(33)` / `dup3(292)`、`fcntl(72)`(`F_GETFL/F_SETFL/F_DUPFD`)
- [x] `pread64(17)` / `pwrite64(18)`
- [x] `rename(82)`、`rmdir(84)`、`unlinkat(263)`
- [x] 删除私有 `OPENDIR/READDIR/CLOSEDIR`
- [x] SDK 的 `opendir/readdir/closedir` 改由 `openat + getdents64 + close` 实现,
Terminal 等调用方无需改动
验收:`ls` 走 `openat + getdents64`;SDK 的 `open` 也统一经 `openat(AT_FDCWD)`;
`ld.so` 的 `openat/read/fstat/getdents64` 路径可用(文件映射属于 P2)。
P1 已知限制:
- `getdents64` 每次只返回一条记录(glibc 合法,只是多一次 syscall)
- `d_ino` 固定 1、`d_off` 固定 0;`telldir/seekdir/rewinddir` 暂不保证
- `dirfd != AT_FDCWD` 的相对路径查找返回 `-ENOTSUP`(P3/P6 再补目录 fd 反查)
- `statx` 返回 `-ENOSYS`,glibc 回退到 `newfstatat`
- `rename` 只支持同一 FAT32 卷内;`dup` 共享文件偏移,最后一次 `close` 才释放对象
### P2 — 内存管理(本次完成,待编译验证)
- [x] `brk(12)`:独立 brk 区(`0x40000000` 起),按页增长/收缩
- [x] `mmap` 真语义:`MAP_FIXED`、`PROT_*`、失败返回 `MAP_FAILED`(-1)
- [x] FAT32 文件映射:把文件内容复制进新页(`MAP_PRIVATE` 语义)
- [x] `munmap(11)` 真正解映射并把物理页归还 buddy
- [x] `mprotect(10)` 修改 PTE 权限(支持 VMA 拆分)
- [x] `mremap(25)`(`MREMAP_MAYMOVE` / `MREMAP_FIXED`)、`madvise(28)`
- [x] 每进程 VMA 表(64 项),记录 base/size/prot/kind/flags
- [x] SDK 的 `mmap` 把 `MAP_FAILED` 归一化为 0,既有 Explorer/devfs 调用方无需修改
验收:glibc 的 `malloc` 能使用 brk 主 arena;共享库文件可 mmap 后按段 mprotect;
Explorer 的 framebuffer 映射不回退。
P2 已知限制:
- 文件映射是“读入页”,不支持 `MAP_SHARED` 回写/`msync`(ld.so 用 `MAP_PRIVATE`)
- `PROT_NONE` 仍映射为 present(可读),没有 NX;`PROT_EXEC` 仅记录
- `munmap` 只归还数据页,页表页仍留在 paging 池
- `brk` 区不在 VMA 表里(glibc 不会对它 munmap)
- `mremap` 增长必须带 `MREMAP_MAYMOVE`,通过“新建+拷贝+解映射”实现
### P3 — 进程、启动栈、解释器(本次完成)
- [x] 用户栈按 Linux 布局写入 `argc/argv/envp/auxv`
(AT_PHDR/PHENT/PHNUM/PAGESZ/ENTRY/BASE/RANDOM/EXECFN/UID/EUID/GID/EGID 等)
- [x] `execve(59)` 原型 `(path, argv, envp)`;SDK 保留 `spawn` 走私有 `SPAWN_PROC`
- [x] `exit_group(231)` 终止进程内所有线程;`exit(60)` 只退当前线程
- [x] `wait4(61)`(`WNOHANG`、状态字、子进程回收)、`getppid`
- [x] `clone(56)`:`(flags, stack, ptid, ctid, tls)`;CLONE_VM 建线程,
不带 CLONE_VM 时复制地址空间
- [x] `fork(57)`:复制用户页 + 启动在父进程 syscall 返回点(RAX=0)
- [x] `PT_INTERP` + ET_DYN 基址加载;`AT_BASE` 指向解释器
- [x] 旧的 ViperOS `thread_create(func,arg)` 移到私有 `THREAD_CREATE_OLD`
验收:静态非 PIE、动态 PIE(安装 `ld.so` 与 libc)都可从正确的启动栈进入;
fork 子进程在父进程同一返回点继续执行,父进程得到子 pid。
### P4 — TLS 与线程(本次完成)
- [x] `arch_prctl(158)`:`SET_FS/GET_FS`(`SET_GS/GET_GS` 返回 `-ENOSYS`)
- [x] 线程切换保存/恢复 `IA32_FS_BASE`(独立表,不改 `Thread` 的 600B 布局)
- [x] `rseq(334)` 返回 `-ENOSYS`;`set_robust_list` 记录
- [x] `futex(202)`:`WAIT/WAKE` 采用“检查值 + sched_yield”轮询实现
- [x] `clone(CLONE_VM|CLONE_SETTLS)`:子线程从 post-syscall RIP 开始,FS=tls
- [x] `CLONE_CHILD_CLEARTID`:线程退出时写 0,配合 futex 唤醒 join
验收:`pthread_create/join` 走 Linux clone 路径;TLS 随线程切换。
### P5 — 时间、信号、IPC(本次完成)
- [x] `clock_gettime(228)` / `clock_getres(229)`(RTC 秒 + 1ms tick)
- [x] `gettimeofday(96)`、`nanosleep(35)`
- [x] `readv(19)` / `writev(20)`
- [x] `pipe(22)` / `pipe2(293)`(8 个管道 × 4KB,轮询读写)
- [x] `poll(7)`(当前固定返回 0 = 超时)
- [x] `rt_sigaction(13)` / `rt_sigprocmask(14)` / `kill(62)`:完整登记
- [x] 信号投递:syscall 返回前重写用户上下文,进入 handler;
`rt_sigreturn(15)` 恢复原上下文与 syscall 返回值
- [ ] `ppoll` / `select` / `epoll`
验收:`signal()`/`sigaction()` 注册的 handler 能在下一次 syscall 返回时执行;
`raise()`/`kill(getpid(), sig)` 可触发。
### P6 — 清理私有段(部分完成)
- [x] `reboot(169)`:校验 Linux magic,支持 RESTART / POWER_OFF
- [x] SDK 的 `process.reboot/shutdown` 改用 `reboot(169)`
- [~] 私有 `LOAD_SO/SO_CALL1` 保留(内核启动仍加载 `slog.so`,用户态链接器未实现)
- [~] 私有 `SPAWN_PROC` / `THREAD_CREATE_OLD` 保留(Terminal 与旧 SDK)
- [~] 私有 `REBOOT/SHUTDOWN` 保留作为兼容别名
- [ ] 用户态动态链接器 + 0x1000+ 段清空
验收:Linux `reboot` 可用;私有段尚未清零(受用户态加载器缺失限制)。
---
## 4. 调用迁移表
`P0` = 本次;`—` 表示尚未实现(内核统一返回 `-ENOSYS`)。
| 号 | Linux | ViperOS | 状态 | 备注 |
|---:|---|---|---|---|
| 0 | read | READ | P0 | 字节数 / `-errno` |
| 1 | write | WRITE | P0 | 同上 |
| 2 | open | OPEN | P0 | Linux `O_*`,内核翻译 `FA_*` |
| 3 | close | CLOSE | P0 | 0 / `-EBADF` |
| 4 | stat | STAT | P0 | 保留私有 `fileinfo`(兼容旧 SDK) |
| 5 | fstat | FSTAT | P1 | `struct stat` |
| 7 | poll | POLL | P5 | 固定返回 0(超时) |
| 8 | lseek | SEEK | P0 | 返回新偏移 |
| 9 | mmap | MMAP | P2 | 匿名/文件/设备三种;失败 `MAP_FAILED` |
| 10 | mprotect | MPROTECT | P2 | |
| 11 | munmap | MUNMAP | P2 | |
| 12 | brk | BRK | P2 | |
| 13 | rt_sigaction | RT_SIGACTION | P5 | 登记 + 投递 |
| 14 | rt_sigprocmask | RT_SIGPROCMASK | P5 | 支持 block/unblock/setmask |
| 15 | rt_sigreturn | RT_SIGRETURN | P5 | 恢复原上下文 |
| 16 | ioctl | IOCTL | P0 | devfs 子集 |
| 17 | pread64 | PREAD64 | P1 | |
| 18 | pwrite64 | PWRITE64 | P1 | |
| 19 | readv | READV | P5 | |
| 20 | writev | WRITEV | P5 | |
| 21 | access | ACCESS | P0 | |
| 22 | pipe | PIPE | P5 | 8 × 4KB,轮询 |
| 24 | sched_yield | YIELD | P0 | |
| 25 | mremap | MREMAP | P2 | 增长需 `MREMAP_MAYMOVE` |
| 28 | madvise | MADVISE | P2 | 空操作 |
| 32 | dup | DUP | P1 | 末次 close 才释放对象 |
| 33 | dup2 | DUP2 | P1 | |
| 35 | nanosleep | NANOSLEEP | P5 | 1ms 精度 |
| 39 | getpid | GETPID | P0 | |
| 56 | clone | CLONE | P3 | Linux clone;CLONE_VM 建线程 |
| 57 | fork | FORK | P3 | 复制用户地址空间 |
| 59 | execve | SPAWN | P3 | 新进程 + 退出调用者 |
| 60 | exit | THREAD_EXIT | P0 | |
| 61 | wait4 | WAIT4 | P3 | 支持 WNOHANG |
| 62 | kill | KILL | P5 | 待下次 syscall 投递 |
| 63 | uname | UNAME | P0 | |
| 72 | fcntl | FCNTL | P1 | |
| 79 | getcwd | GETCWD | P0 | |
| 80 | chdir | CHDIR | P0 | |
| 82 | rename | RENAME | P1 | |
| 83 | mkdir | MKDIR | P0 | |
| 84 | rmdir | RMDIR | P1 | |
| 87 | unlink | REMOVE | P0 | |
| 89 | readlink | READLINK | P0 | 当前恒 `-EINVAL` |
| 96 | gettimeofday | GETTIMEOFDAY | P5 | RTC 秒 |
| 102 | getuid | GETUID | P0 | 恒 0 |
| 104 | getgid | GETGID | P0 | 恒 0 |
| 107 | geteuid | GETEUID | P0 | 恒 0 |
| 108 | getegid | GETEGID | P0 | 恒 0 |
| 110 | getppid | GETPPID | P0 | 恒 0 |
| 158 | arch_prctl | ARCH_PRCTL | P4 | SET_FS/GET_FS |
| 169 | reboot | REBOOT_LINUX | P6 | magic 校验;RESTART/POWER_OFF |
| 186 | gettid | GETTID | P0 | 返回线程 tid |
| 202 | futex | FUTEX | P4 | 轮询 WAIT/WAKE |
| 217 | getdents64 | GETDENTS64 | P1 | 每次返回一条记录 |
| 218 | set_tid_address | SET_TID_ADDRESS | P0 | |
| 228 | clock_gettime | CLOCK_GETTIME | P5 | RTC/1ms tick |
| 229 | clock_getres | CLOCK_GETRES | P5 | 1ms |
| 231 | exit_group | EXIT | P3 | 终止进程内所有线程 |
| 257 | openat | OPENAT | P1 | |
| 262 | newfstatat | NEWFSTATAT | P1 | |
| 263 | unlinkat | UNLINKAT | P1 | |
| 267 | readlinkat | READLINKAT | P0 | 当前恒 `-EINVAL` |
| 269 | faccessat | FACCESSAT | P0 | |
| 273 | set_robust_list | SET_ROBUST_LIST | P0 | 仅记录 |
| 292 | dup3 | DUP3 | P1 | flags 忽略 |
| 293 | pipe2 | PIPE2 | P5 | flags 仅记录 |
| 302 | prlimit64 | PRLIMIT64 | P0 | 只读限制 |
| 318 | getrandom | GETRANDOM | P0 | 软件 DRNG |
| 332 | statx | — | P1 | `-ENOSYS`,glibc 回退 |
| 334 | rseq | RSEQ | P0 | `-ENOSYS` |
| 0x1006 | — | LOAD_SO | P6 删除 | |
| 0x1007 | — | SO_CALL1 | P6 删除 | |
| 0x1008 | — | REBOOT | P6 保留别名 | |
| 0x1009 | — | SHUTDOWN | P6 保留别名 | |
| 0x100A | — | SPAWN_PROC | P6 保留 | Terminal `run` 用 |
| 0x100B | — | THREAD_CREATE_OLD | P6 保留 | 旧 `thread_create(func,arg)` |
---
## 5. 测试与验收
1. **ABI 单元测试**(用户态小程序)
- 每个调用至少覆盖成功 + 一个错误路径,断言返回值为 `-errno`
- 结构体偏移/大小与 Linux 对照(编译期 `__sizeof__`)
2. **`Scripts/syscall_table.py --check`**
- CI/本地构建前检查两份表是否与权威表一致
3. **glibc 里程碑**
- M1:静态 `hello`(write/exit/brk/mmap/arch_prctl)
- M2:动态 `hello`(`ld.so`、`openat`、`mmap`、`mprotect`)
- M3:`pthread` + `malloc` 压力测试
- M4:`ls`/`cat`/`sh` 等 coreutils
4. **回归**
- 现有 7 个应用(Terminal / Explorer / Scene3D / Gargantua / FontDemo / HelloWorld / ImageSee)必须继续运行
### 5.1 已完成的冒烟验证(2026-10-28,QEMU)
- 7 个内置应用全部启动,进入主循环,无异常
- ImageSee 在同一启动中解码 PNG/JPEG 并在窗口显示(见 `docs/imagesee.md`)
- 临时测试程序调用 `fork()`:父进程得到子 pid,子进程在同一返回点继续运行
(`getpid/getppid` 正确),`wait4` 返回子 pid 且状态为 `0x700`(exit code 7)
- 信号:`rt_sigaction(SIGUSR1)` + `kill(self)` → handler 在 `kill` 返回时执行,
`kill` 仍返回 0;`SIGUSR2` 被 `rt_sigprocmask` 阻塞时不投递,解除阻塞后的下一次
syscall 返回时投递
- 无人值守运行:`python build.py --headless --runsecs=45`(`-display none`,
45 秒后自动结束 QEMU,串口仍写入 `serial.log`)
---
## 6. 风险与已定决策
| 风险 | 处理 |
|---|---|
| 错误码改造影响旧应用 | SDK 已按 `<0` 判断;`FRESULT` 正数路径一并改掉,旧应用需回归测试 |
| 两份表再次漂移 | 生成器 + `build.py` 前缀同步;禁止手改生成段 |
| FAT32 无 inode/权限/符号链接 | `struct stat` 用固定值;`readlink` 一律 `-EINVAL`;`chmod` 后置 |
| `mprotect` 未实现时 RELRO 失效 | P2 前在文档标注为“安全降级”,不假装成功 |
| `getrandom` 无硬件熵 | 软件 xorshift + 启动期混合,仅用于 canary/非密码学场景 |
| `uname` 报 Linux | 提升兼容性;`version` 字段保留 ViperOS 标识 |
| 用户态栈布局改动大 | P3 单独提交,先静态后动态,避免与内存阶段交叉 |
+70 -28
View File
@@ -1,23 +1,31 @@
from . import syscall as syscall
import string
define O_RDONLY = 0x01
define O_WRONLY = 0x02
define O_RDWR = 0x03
define O_CREAT = 0x04
define O_TRUNC = 0x08
define O_APPEND = 0x30
# Linux x86-64 open flags. The kernel translates these to FatFs FA_* modes;
# never pass FatFs values from user space.
define O_RDONLY = 0
define O_WRONLY = 1
define O_RDWR = 2
define O_CREAT = 64
define O_EXCL = 128
define O_TRUNC = 512
define O_APPEND = 1024
define O_DIRECTORY = 65536
define O_CLOEXEC = 524288
define SEEK_SET = 0
define SEEK_CUR = 1
define SEEK_END = 2
define DT_UNKNOWN = 0
define DT_FILE = 1
define DT_DIR = 2
define DT_REG = 8
define DT_DIR = 4
define DT_FILE = 8 # legacy alias
def open(path: const str, flags: uint32) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.OPEN), uint64(path), uint64(flags), 0))
"""SDK open() goes through openat(AT_FDCWD) so relative paths honor cwd."""
return syscall.openat(syscall.AT_FDCWD, path, flags, 0)
def close(fd: int) -> int:
return int(syscall._syscall1(uint64(syscall.Syscall.CLOSE), uint64(fd)))
@@ -29,20 +37,19 @@ def write(fd: int, buf: rawptr, count: uint32) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.WRITE), uint64(fd), uint64(buf), uint64(count)))
def seek(fd: int, offset: uint32, whence: uint32 = 0) -> int:
"""lseek: returns the new offset (>= 0) or -errno."""
return int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(offset), uint64(whence)))
def tell(fd: int) -> uint32:
"""当前位置 = lseek(fd, 0, SEEK_CUR)(TELL 私有 syscall 已删除)。"""
return uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR)))
def tell(fd: int) -> int:
"""当前偏移 = lseek(fd, 0, SEEK_CUR)。"""
return int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR)))
def fsize(fd: int) -> uint32:
"""文件大小:SIZE 私有 syscall 已删除,改用 lseek 往返(不改变最终位置)。
三次 lseek:记住当前位置 → 跳到末尾拿到大小 → 复位。多两次 syscall,换掉一个私有号。
"""
cur: uint32 = uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR)))
end: uint32 = uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_END)))
syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(cur), uint64(SEEK_SET))
def fsize(fd: int) -> int:
"""文件大小 = lseek(fd, 0, SEEK_END),随后恢复原位置。"""
cur: int = tell(fd)
end: int = int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_END)))
if cur >= 0:
syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(cur), uint64(SEEK_SET))
return end
def mkdir(path: const str) -> int:
@@ -52,13 +59,46 @@ def remove(path: const str) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.REMOVE), uint64(path), 0, 0))
def opendir(path: const str) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.OPENDIR), uint64(path), 0, 0))
"""Linux opendir: openat(O_RDONLY|O_DIRECTORY), returns a fd."""
return syscall.openat(syscall.AT_FDCWD, path, O_RDONLY | O_DIRECTORY, 0)
def readdir(dd: int, info: rawptr) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.READDIR), uint64(dd), uint64(info), 0))
def readdir(fd: int, info: rawptr) -> int:
"""Fill the legacy fileinfo layout from one getdents64 record.
def closedir(dd: int) -> int:
return int(syscall._syscall1(uint64(syscall.Syscall.CLOSEDIR), uint64(dd)))
Returns 0 with an entry, -ENOENT at end-of-directory, or another -errno.
"""
if info is None: return -22 # -EINVAL
buf: [512]char
n: int = syscall.getdents64(fd, &buf[0], uint64(512))
if n < 0: return n
if n == 0: return -2 # -ENOENT: end of directory
string.memset(info, 0, 288) # sizeof(fileinfo)
dt: int = int(uint8(buf[18]))
attr: uint8 = 0x20
if dt == DT_DIR: attr = 0x10
ap: uint8* = uint8*(uint64(info) + 4)
ap[0] = attr
dst: char* = char*(uint64(info) + 16)
string.strcpy(dst, &buf[19])
return 0
def closedir(fd: int) -> int:
return close(fd)
def openat(dirfd: int, path: const str, flags: uint32, mode: uint32 = 0) -> int:
return syscall.openat(dirfd, path, flags, mode)
def rename(old_path: const str, new_path: const str) -> int:
return syscall.rename(old_path, new_path)
def rmdir(path: const str) -> int:
return syscall.rmdir(path)
def unlinkat(dirfd: int, path: const str, flags: uint32 = 0) -> int:
return syscall.unlinkat(dirfd, path, flags)
def fcntl(fd: int, cmd: uint32, arg: uint64 = 0) -> int:
return syscall.fcntl(fd, cmd, arg)
def stat(path: const str, info: rawptr) -> int:
return int(syscall._syscall3(uint64(syscall.Syscall.STAT), uint64(path), uint64(info), 0))
@@ -78,7 +118,9 @@ class FileIO:
self._fd = fd
self._flags = flags
self._pos = 0
self._size = fsize(fd)
sz: int = fsize(fd)
if sz < 0: sz = 0
self._size = uint32(sz)
self._closed = 0
def read(self, buf: rawptr, count: uint32) -> int:
@@ -100,8 +142,8 @@ class FileIO:
def seek(self, offset: uint32) -> int:
if self._closed: return -1
res: int = seek(self._fd, offset)
if res == 0:
self._pos = offset
if res >= 0:
self._pos = uint32(res)
return res
def tell(self) -> uint32:
+57
View File
@@ -0,0 +1,57 @@
# ImageSee shared types.
import vpsdk.stdlib as stdlib
from stdint import *
define IMAGE_FMT_UNKNOWN = 0
define IMAGE_FMT_PNG = 1
define IMAGE_FMT_JPEG = 2
class Image:
width: int
height: int
format: int
# Packed 0xAARRGGBB, matching the window/FBCanvas pixel format.
pixels: uint32*
def new_image(w: int, h: int, fmt: int) -> Image*:
if w <= 0 or h <= 0: return None
if w > 16384 or h > 16384: return None
img: Image* = Image*(stdlib.malloc(uint64(Image.__sizeof__())))
if img is None: return None
px: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4)))
if px is None: return None
img.width = w
img.height = h
img.format = fmt
img.pixels = px
return img
def free_image(img: Image*):
# stdlib.free() is a no-op over the bump mmap heap; keep the API for callers.
if img is None: return
img.pixels = None
img.width = 0
img.height = 0
def pixel_at(img: Image*, x: int, y: int) -> uint32:
if img is None: return uint32(0)
if x < 0 or y < 0 or x >= img.width or y >= img.height: return uint32(0)
return img.pixels[y * img.width + x]
def red_of(c: uint32) -> int:
return int((c >> 16) & uint32(0xFF))
def green_of(c: uint32) -> int:
return int((c >> 8) & uint32(0xFF))
def blue_of(c: uint32) -> int:
return int(c & uint32(0xFF))
def alpha_of(c: uint32) -> int:
return int((c >> 24) & uint32(0xFF))
def make_pixel(r: int, g: int, b: int, a: int = 255) -> uint32:
v: uint32 = (uint32(a & 0xFF) << 24) | (uint32(r & 0xFF) << 16)
v = v | (uint32(g & 0xFF) << 8) | uint32(b & 0xFF)
return v
+67
View File
@@ -0,0 +1,67 @@
# ImageSee: small PNG/JPEG decoder library for ViperOS user space.
#
# img = imagesee.load("/IMAGES/SAMPLE.PNG")
# if img is not None:
# ... img.pixels[y * img.width + x] is 0xAARRGGBB ...
#
# `load` reads the whole file into memory and sniffs the container format;
# `decode_memory` is available when the caller already has the bytes.
import vpsdk.imagesee.image as image
import vpsdk.imagesee.png as png
import vpsdk.imagesee.jpeg as jpeg
import vpsdk.fs as fs
import vpsdk.stdlib as stdlib
from stdint import *
define IMAGESEE_MAX_FILE = 64 * 1024 * 1024
def decode_memory(data: uint8*, n: uint64) -> image.Image*:
if data is None or n < 8: return None
if int(data[0]) == 0x89 and int(data[1]) == 0x50:
if int(data[2]) == 0x4E and int(data[3]) == 0x47:
return png.decode(data, n)
if int(data[0]) == 0xFF and int(data[1]) == 0xD8:
return jpeg.decode(data, n)
return None
def load(path: const str) -> image.Image*:
fd: int = fs.open(path, fs.O_RDONLY)
if fd < 0: return None
sz: int = fs.fsize(fd)
if sz <= 0 or uint64(sz) > uint64(IMAGESEE_MAX_FILE):
fs.close(fd)
return None
buf: uint8* = uint8*(stdlib.malloc(uint64(sz)))
if buf is None:
fs.close(fd)
return None
got: uint64 = 0
while got < uint64(sz):
n: int = fs.read(fd, rawptr(uint64(buf) + got), uint32(uint64(sz) - got))
if n <= 0: break
got = got + uint64(n)
fs.close(fd)
if got != uint64(sz): return None
return decode_memory(buf, uint64(sz))
def last_error() -> int:
"""Last JPEG decoder failure stage (0 = not started; 7 = render done)."""
return jpeg.last_error()
def format_name(img: image.Image*) -> const str:
if img is None: return "none"
if img.format == image.IMAGE_FMT_PNG: return "png"
if img.format == image.IMAGE_FMT_JPEG: return "jpeg"
return "unknown"
# 32-bit FNV-1a over packed pixels; handy for regression checks.
def checksum(img: image.Image*) -> uint32:
if img is None: return uint32(0)
h: uint32 = uint32(2166136261)
n: int = img.width * img.height
i: int = 0
while i < n:
h = (h ^ img.pixels[i]) * uint32(16777619)
i = i + 1
return h
+261
View File
@@ -0,0 +1,261 @@
# DEFLATE / zlib decompression (RFC 1950 + RFC 1951).
#
# Written for PNG's IDAT stream: self-contained, no allocation inside the
# decoder (the caller provides the output buffer), canonical Huffman tables
# built the same way as the Brotli decoder in this SDK.
define INF_MAXBITS = 15
define INF_MAXCODES = 288
define INF_MAXDIST = 32
class InfHuff:
count: [INF_MAXBITS + 1]int
symbol: [INF_MAXCODES]int
# Length codes 257..285 and distance codes 0..29.
_lbase: [29]int = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258]
_lextra: [29]int = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0]
_dbase: [30]int = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577]
_dextra: [30]int = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13]
_clorder: [19]int = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]
_inf_src: uint8* = None
_inf_len: uint64 = 0
_inf_pos: uint64 = 0
_inf_bitbuf: uint32 = 0
_inf_bitcnt: int = 0
_inf_out: uint8* = None
_inf_cap: uint64 = 0
_inf_got: uint64 = 0
_inf_err: int = 0
_inf_lens: [320]uint8
_inf_lit: InfHuff
_inf_dist: InfHuff
_inf_clen: InfHuff
def _inf_need(n: int) -> uint32:
global _inf_bitbuf, _inf_bitcnt, _inf_pos, _inf_err
while _inf_bitcnt < n:
if _inf_pos >= _inf_len:
_inf_err = 1
return uint32(0)
_inf_bitbuf = _inf_bitbuf | (uint32(_inf_src[_inf_pos]) << _inf_bitcnt)
_inf_pos = _inf_pos + 1
_inf_bitcnt = _inf_bitcnt + 8
v: uint32 = _inf_bitbuf & ((uint32(1) << uint32(n)) - uint32(1))
_inf_bitbuf = _inf_bitbuf >> uint32(n)
_inf_bitcnt = _inf_bitcnt - n
return v
def _inf_decode(h: InfHuff*) -> int:
global _inf_err
code: int = 0
first: int = 0
index: int = 0
ln: int = 1
while ln <= INF_MAXBITS:
code = code | int(_inf_need(1))
cnt: int = h.count[ln]
if code - first < cnt:
return h.symbol[index + (code - first)]
index = index + cnt
first = (first + cnt) << 1
code = code << 1
ln = ln + 1
_inf_err = 1
return -1
def _inf_build(h: InfHuff*, lens: uint8*, n: int) -> int:
i: int = 0
while i <= INF_MAXBITS:
h.count[i] = 0
i = i + 1
i = 0
while i < n:
ln8: uint8 = lens[i]
if ln8 != 0:
if int(ln8) > INF_MAXBITS: return -1
h.count[int(ln8)] = h.count[int(ln8)] + 1
i = i + 1
offs: [INF_MAXBITS + 2]int
offs[1] = 0
ln: int = 1
while ln <= INF_MAXBITS:
offs[ln + 1] = offs[ln] + h.count[ln]
ln = ln + 1
i = 0
while i < n:
ln = int(lens[i])
if ln != 0:
h.symbol[offs[ln]] = i
offs[ln] = offs[ln] + 1
i = i + 1
return 0
def _inf_put(b: int) -> int:
global _inf_got, _inf_err
if _inf_got >= _inf_cap:
_inf_err = 1
return -1
_inf_out[_inf_got] = uint8(b & 0xFF)
_inf_got = _inf_got + 1
return 0
def _inf_stored() -> int:
global _inf_bitbuf, _inf_bitcnt, _inf_pos
# Drop the partial byte and read LEN/NLEN directly from the byte stream.
_inf_bitbuf = uint32(0)
_inf_bitcnt = 0
if _inf_pos + 4 > _inf_len: return -1
lenv: int = int(_inf_src[_inf_pos]) | (int(_inf_src[_inf_pos + 1]) << 8)
nlen: int = int(_inf_src[_inf_pos + 2]) | (int(_inf_src[_inf_pos + 3]) << 8)
_inf_pos = _inf_pos + 4
if (lenv ^ 0xFFFF) != nlen: return -1
if _inf_pos + uint64(lenv) > _inf_len: return -1
i: int = 0
while i < lenv:
if _inf_put(int(_inf_src[_inf_pos])) != 0: return -1
_inf_pos = _inf_pos + 1
i = i + 1
return 0
def _inf_fixed() -> int:
i: int = 0
while i < 144:
_inf_lens[i] = uint8(8)
i = i + 1
while i < 256:
_inf_lens[i] = uint8(9)
i = i + 1
while i < 280:
_inf_lens[i] = uint8(7)
i = i + 1
while i < 288:
_inf_lens[i] = uint8(8)
i = i + 1
if _inf_build(&_inf_lit, &_inf_lens[0], 288) != 0: return -1
i = 0
while i < 32:
_inf_lens[i] = uint8(5)
i = i + 1
if _inf_build(&_inf_dist, &_inf_lens[0], 32) != 0: return -1
return 0
def _inf_dynamic() -> int:
hlit: int = int(_inf_need(5)) + 257
hdist: int = int(_inf_need(5)) + 1
hclen: int = int(_inf_need(4)) + 4
if hlit > 286 or hdist > 30: return -1
i: int = 0
while i < 320:
_inf_lens[i] = uint8(0)
i = i + 1
i = 0
while i < hclen:
_inf_lens[_clorder[i]] = uint8(_inf_need(3))
i = i + 1
if _inf_build(&_inf_clen, &_inf_lens[0], 19) != 0: return -1
n: int = hlit + hdist
i = 0
while i < n:
sym: int = _inf_decode(&_inf_clen)
if _inf_err != 0: return -1
if sym < 16:
_inf_lens[i] = uint8(sym)
i = i + 1
elif sym == 16:
if i == 0: return -1
rep: int = 3 + int(_inf_need(2))
prev: uint8 = _inf_lens[i - 1]
while rep > 0 and i < n:
_inf_lens[i] = prev
i = i + 1
rep = rep - 1
elif sym == 17:
rep: int = 3 + int(_inf_need(3))
while rep > 0 and i < n:
_inf_lens[i] = uint8(0)
i = i + 1
rep = rep - 1
elif sym == 18:
rep: int = 11 + int(_inf_need(7))
while rep > 0 and i < n:
_inf_lens[i] = uint8(0)
i = i + 1
rep = rep - 1
else:
return -1
if _inf_build(&_inf_lit, &_inf_lens[0], hlit) != 0: return -1
if _inf_build(&_inf_dist, &_inf_lens[hlit], hdist) != 0: return -1
return 0
def _inf_codes() -> int:
while True:
sym: int = _inf_decode(&_inf_lit)
if _inf_err != 0: return -1
if sym < 256:
if _inf_put(sym) != 0: return -1
elif sym == 256:
return 0
elif sym >= 257 and sym <= 285:
li: int = sym - 257
length: int = _lbase[li] + int(_inf_need(_lextra[li]))
dsym: int = _inf_decode(&_inf_dist)
if _inf_err != 0: return -1
if dsym < 0 or dsym >= 30: return -1
dist: int = _dbase[dsym] + int(_inf_need(_dextra[dsym]))
if dist <= 0 or uint64(dist) > _inf_got: return -1
i: int = 0
while i < length:
src_i: uint64 = _inf_got - uint64(dist)
if _inf_put(int(_inf_out[src_i])) != 0: return -1
i = i + 1
else:
return -1
def _inf_blocks() -> int:
while True:
final: int = int(_inf_need(1))
btype: int = int(_inf_need(2))
if _inf_err != 0: return -1
if btype == 0:
if _inf_stored() != 0: return -1
elif btype == 1:
if _inf_fixed() != 0: return -1
if _inf_codes() != 0: return -1
elif btype == 2:
if _inf_dynamic() != 0: return -1
if _inf_codes() != 0: return -1
else:
return -1
if final != 0:
return 0
# Inflate a zlib stream (skips the 2-byte header). Returns bytes written
# into `dst`, or -1 on malformed input / output overflow.
def zlib_decompress(src: uint8*, src_len: uint64, dst: uint8*, dst_cap: uint64) -> int:
global _inf_src, _inf_len, _inf_pos, _inf_bitbuf, _inf_bitcnt
global _inf_out, _inf_cap, _inf_got, _inf_err
if src is None or dst is None or src_len < 6: return -1
cmf: int = int(src[0])
flg: int = int(src[1])
if (cmf & 0x0F) != 8: return -1
if (cmf * 256 + flg) % 31 != 0: return -1
if (flg & 0x20) != 0: return -1 # FDICT unsupported
_inf_src = src
_inf_len = src_len
_inf_pos = 2
_inf_bitbuf = uint32(0)
_inf_bitcnt = 0
_inf_out = dst
_inf_cap = dst_cap
_inf_got = 0
_inf_err = 0
if _inf_blocks() != 0: return -1
return int(_inf_got)
+781
View File
@@ -0,0 +1,781 @@
# JPEG decoder: baseline/extended sequential (SOF0/SOF1) and progressive
# (SOF2), Huffman entropy coding, restart markers, 4:4:4 / 4:2:2 / 4:2:0
# chroma subsampling, 8-bit samples. Output is packed 0xAARRGGBB.
#
# Coefficient arrays are kept in natural (de-zigzagged) order so the IDCT is
# a straight 8x8 pass. Progressive scans refine the same arrays.
import vpsdk.imagesee.image as im
import vpsdk.stdlib as stdlib
import string
import math
from stdint import *
define J_MAX_COMPS = 4
class JHuff:
count: [17]int
symbol: [256]int
class JComp:
id: int
hs: int
vs: int
tq: int
td: int
ta: int
bw: int
bh: int
coef: int16*
plane: uint8*
_j_src: uint8* = None
_j_len: uint64 = 0
_j_pos: uint64 = 0
_j_bitbuf: uint32 = 0
_j_bitcnt: int = 0
_j_marker: int = -1
_j_err: int = 0
_j_w: int = 0
_j_h: int = 0
_j_ncomp: int = 0
_j_maxh: int = 1
_j_maxv: int = 1
_j_mcu_cols: int = 0
_j_mcu_rows: int = 0
_j_progressive: int = 0
_j_rgb_mode: int = 0
_j_restart: int = 0
_j_scan_ns: int = 0
_j_sc: [4]int
_j_sctd: [4]int
_j_scta: [4]int
_j_ss: int = 0
_j_se: int = 0
_j_ah: int = 0
_j_al: int = 0
_j_eobrun: int = 0
_j_dcpred: [4]int
_j_qt: [4 * 64]uint16
_j_qt_used: [4]int
_j_dc: [4]JHuff
_j_ac: [4]JHuff
_j_comps: [4]JComp
_j_planes_ready: int = 0
_j_stage: int = 0
_j_scan_no: int = 0
_j_cur_mcu: int = 0
_j_cs: [64]float
_j_cw: [8]float
_j_trig_ready: int = 0
_j_g: [64]float
_zigzag: [64]int = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
]
def _j_u8(o: uint64) -> int:
return int(_j_src[o])
def last_error() -> int:
return _j_stage
def _j_u16(o: uint64) -> int:
return (int(_j_src[o]) << 8) | int(_j_src[o + 1])
# ---------------------------------------------------------------- bit reader
# Returns the next entropy byte with 0xFF00 unstuffing; on a marker it stores
# the marker id in _j_marker and returns -1.
def _j_byte() -> int:
global _j_pos, _j_marker
if _j_pos >= _j_len: return -1
b: int = int(_j_src[_j_pos])
_j_pos = _j_pos + 1
if b == 0xFF:
while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF:
_j_pos = _j_pos + 1
if _j_pos >= _j_len: return -1
m: int = int(_j_src[_j_pos])
_j_pos = _j_pos + 1
if m == 0x00: return 0xFF
_j_marker = m
return -1
return b
def _j_bits(n: int) -> uint32:
global _j_bitbuf, _j_bitcnt, _j_err
if n <= 0: return uint32(0)
while _j_bitcnt < n:
b: int = _j_byte()
if b < 0:
_j_err = 1
return uint32(0)
_j_bitbuf = (_j_bitbuf << 8) | uint32(b)
_j_bitcnt = _j_bitcnt + 8
_j_bitcnt = _j_bitcnt - n
v: uint32 = (_j_bitbuf >> uint32(_j_bitcnt)) & ((uint32(1) << uint32(n)) - uint32(1))
return v
def _j_get_bit() -> int:
return int(_j_bits(1))
def _j_receive(s: int) -> int:
if s == 0: return 0
v: int = int(_j_bits(s))
if v < (1 << (s - 1)):
v = v - ((1 << s) - 1)
return v
def _j_huff_build(h: JHuff*, counts: uint8*, vals: uint8*, n: int) -> int:
total: int = 0
i: int = 1
while i <= 16:
c: int = int(counts[i - 1])
h.count[i] = c
total = total + c
i = i + 1
if total != n: return -1
# canonical offsets
offs: [18]int
offs[1] = 0
i = 1
while i <= 16:
offs[i + 1] = offs[i] + h.count[i]
i = i + 1
# `vals` is already ordered by code length (JPEG DHT order).
i = 0
ln: int = 1
vi: int = 0
while ln <= 16:
c = h.count[ln]
k: int = 0
while k < c:
h.symbol[offs[ln] + k] = int(vals[vi])
vi = vi + 1
k = k + 1
ln = ln + 1
return 0
def _j_hdecode(h: JHuff*) -> int:
global _j_err
code: int = 0
first: int = 0
index: int = 0
ln: int = 1
while ln <= 16:
code = code | _j_get_bit()
cnt: int = h.count[ln]
if code - first < cnt:
return h.symbol[index + (code - first)]
index = index + cnt
first = (first + cnt) << 1
code = code << 1
ln = ln + 1
_j_err = 1
return 0
# --------------------------------------------------------------- markers
def _j_next_marker() -> int:
global _j_pos, _j_marker
if _j_marker >= 0:
m: int = _j_marker
_j_marker = -1
return m
while _j_pos < _j_len:
b: int = int(_j_src[_j_pos])
_j_pos = _j_pos + 1
if b != 0xFF: continue
while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF:
_j_pos = _j_pos + 1
if _j_pos >= _j_len: return -1
m = int(_j_src[_j_pos])
_j_pos = _j_pos + 1
if m == 0x00: continue
return m
return -1
def _j_parse_dqt(p: uint64, pend: uint64) -> int:
while p < pend:
pq: int = int(_j_src[p]) >> 4
tq: int = int(_j_src[p]) & 15
p = p + 1
if tq >= 4: return -1
i: int = 0
while i < 64:
if pq != 0:
if p + 1 >= pend: return -1
v: int = (int(_j_src[p]) << 8) | int(_j_src[p + 1])
p = p + 2
else:
if p >= pend: return -1
v = int(_j_src[p])
p = p + 1
_j_qt[tq * 64 + _zigzag[i]] = uint16(v)
i = i + 1
_j_qt_used[tq] = 1
return 0
def _j_parse_dht(p: uint64, pend: uint64) -> int:
while p < pend:
if p + 17 > pend: return -1
tc: int = int(_j_src[p]) >> 4
th: int = int(_j_src[p]) & 15
p = p + 1
if tc > 1 or th > 3: return -1
counts: [16]uint8
total: int = 0
i: int = 0
while i < 16:
counts[i] = _j_src[p + i]
total = total + int(counts[i])
i = i + 1
p = p + 16
if total <= 0 or total > 256 or p + uint64(total) > pend: return -1
vals: [256]uint8
i = 0
while i < total:
vals[i] = _j_src[p + i]
i = i + 1
p = p + uint64(total)
if tc == 0:
if _j_huff_build(&_j_dc[th], &counts[0], &vals[0], total) != 0: return -1
else:
if _j_huff_build(&_j_ac[th], &counts[0], &vals[0], total) != 0: return -1
return 0
def _j_parse_sof(p: uint64, pend: uint64, prog: int) -> int:
global _j_w, _j_h, _j_ncomp, _j_progressive, _j_maxh, _j_maxv, _j_mcu_cols, _j_mcu_rows, _j_planes_ready
if p + 6 > pend: return -1
prec: int = int(_j_src[p])
h: int = _j_u16(p + 1)
w: int = _j_u16(p + 3)
nf: int = int(_j_src[p + 5])
p = p + 6
if prec != 8 or w <= 0 or h <= 0 or nf < 1 or nf > J_MAX_COMPS: return -1
if w > 16384 or h > 16384: return -1
if p + uint64(nf * 3) > pend: return -1
_j_w = w
_j_h = h
_j_ncomp = nf
_j_progressive = prog
_j_maxh = 1
_j_maxv = 1
i: int = 0
while i < nf:
cid: int = int(_j_src[p + i * 3])
hv: int = int(_j_src[p + i * 3 + 1])
tq: int = int(_j_src[p + i * 3 + 2])
hs: int = hv >> 4
vs: int = hv & 15
if hs < 1 or hs > 4 or vs < 1 or vs > 4 or tq > 3: return -1
_j_comps[i].id = cid
_j_comps[i].hs = hs
_j_comps[i].vs = vs
_j_comps[i].tq = tq
_j_comps[i].td = 0
_j_comps[i].ta = 0
if hs > _j_maxh: _j_maxh = hs
if vs > _j_maxv: _j_maxv = vs
i = i + 1
_j_mcu_cols = (w + 8 * _j_maxh - 1) / (8 * _j_maxh)
_j_mcu_rows = (h + 8 * _j_maxv - 1) / (8 * _j_maxv)
_j_planes_ready = 0
return 0
def _j_prepare() -> int:
global _j_planes_ready
if _j_planes_ready != 0: return 0
i: int = 0
while i < _j_ncomp:
comp: JComp* = &_j_comps[i]
comp.bw = _j_mcu_cols * comp.hs
comp.bh = _j_mcu_rows * comp.vs
if comp.bw <= 0 or comp.bh <= 0: return -1
if _j_progressive != 0:
comp.coef = int16*(stdlib.malloc(uint64(comp.bw * comp.bh * 64 * 2)))
if comp.coef is None: return -1
string.memset(rawptr(comp.coef), 0, uint64(comp.bw * comp.bh * 64 * 2))
else:
comp.coef = int16*(stdlib.malloc(uint64(comp.bw * comp.bh * 64 * 2)))
if comp.coef is None: return -1
string.memset(rawptr(comp.coef), 0, uint64(comp.bw * comp.bh * 64 * 2))
i = i + 1
_j_planes_ready = 1
return 0
# ------------------------------------------------------------------- blocks
def _j_block_seq(c: int, blk: int):
comp: JComp* = &_j_comps[c]
base: int = blk * 64
t: int = _j_hdecode(&_j_dc[comp.td])
s: int = t & 15
_j_dcpred[c] = _j_dcpred[c] + _j_receive(s)
comp.coef[base] = int16(_j_dcpred[c])
k: int = 1
while k < 64:
rs: int = _j_hdecode(&_j_ac[comp.ta])
if _j_err != 0: break
s = rs & 15
r: int = rs >> 4
if s == 0:
if r == 15:
k = k + 16
continue
break
k = k + r
if k > 63: break
v: int = _j_receive(s)
comp.coef[base + _zigzag[k]] = int16(v)
k = k + 1
def _j_block_dc_first(c: int, blk: int):
comp: JComp* = &_j_comps[c]
t: int = _j_hdecode(&_j_dc[comp.td])
s: int = t & 15
_j_dcpred[c] = _j_dcpred[c] + _j_receive(s)
comp.coef[blk * 64] = int16(_j_dcpred[c] << _j_al)
def _j_block_dc_refine(c: int, blk: int):
comp: JComp* = &_j_comps[c]
if _j_get_bit() != 0:
comp.coef[blk * 64] = int16(int(comp.coef[blk * 64]) | (1 << _j_al))
def _j_block_ac_first(c: int, blk: int):
global _j_eobrun
if _j_eobrun > 0:
_j_eobrun = _j_eobrun - 1
return
comp: JComp* = &_j_comps[c]
base: int = blk * 64
k: int = _j_ss
while k <= _j_se:
rs: int = _j_hdecode(&_j_ac[comp.ta])
if _j_err != 0: break
s: int = rs & 15
r: int = rs >> 4
if s == 0:
if r < 15:
_j_eobrun = (1 << r) + int(_j_bits(r)) - 1
break
k = k + 16
else:
k = k + r
if k > _j_se: break
v: int = _j_receive(s)
comp.coef[base + _zigzag[k]] = int16(v << _j_al)
k = k + 1
def _j_block_ac_refine(c: int, blk: int):
global _j_eobrun, _j_err
comp: JComp* = &_j_comps[c]
base: int = blk * 64
p1: int = 1 << _j_al
m1: int = (0 - 1) << _j_al
k: int = _j_ss
if _j_eobrun == 0:
while k <= _j_se:
rs: int = _j_hdecode(&_j_ac[comp.ta])
if _j_err != 0: break
s: int = rs & 15
r: int = rs >> 4
newv: int = 0
zrl: int = 0
if s == 0:
if r < 15:
_j_eobrun = (1 << r) + int(_j_bits(r)) - 1
break
zrl = 1
else:
if s != 1:
_j_err = 1
break
if _j_get_bit() != 0: newv = p1
else: newv = m1
# Advance over r zero coefficients, refining non-zero ones.
while k <= _j_se:
idx: int = base + _zigzag[k]
cv: int = int(comp.coef[idx])
if cv != 0:
if _j_get_bit() != 0 and (cv & p1) == 0:
if cv > 0: comp.coef[idx] = int16(cv + p1)
else: comp.coef[idx] = int16(cv + m1)
else:
if r == 0: break
r = r - 1
k = k + 1
if newv != 0 and k <= _j_se:
comp.coef[base + _zigzag[k]] = int16(newv)
k = k + 1
elif zrl != 0 and k <= _j_se:
# ZRL skips 16 zero coefficients; the inner loop leaves us
# parked on the 16th one (r == 0), so step past it.
k = k + 1
else:
while k <= _j_se:
idx: int = base + _zigzag[k]
cv: int = int(comp.coef[idx])
if cv != 0:
if _j_get_bit() != 0 and (cv & p1) == 0:
if cv > 0: comp.coef[idx] = int16(cv + p1)
else: comp.coef[idx] = int16(cv + m1)
k = k + 1
_j_eobrun = _j_eobrun - 1
# --------------------------------------------------------------------- scan
def _j_restart_scan() -> int:
global _j_bitbuf, _j_bitcnt, _j_err, _j_eobrun, _j_marker, _j_pos
_j_bitbuf = uint32(0)
_j_bitcnt = 0
_j_err = 0
_j_eobrun = 0
i: int = 0
while i < J_MAX_COMPS:
_j_dcpred[i] = 0
i = i + 1
if _j_marker >= 0:
m: int = _j_marker
_j_marker = -1
if m >= 0xD0 and m <= 0xD7: return 0
_j_marker = m
return -1
while _j_pos < _j_len:
b: int = int(_j_src[_j_pos])
_j_pos = _j_pos + 1
if b != 0xFF: continue
while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF:
_j_pos = _j_pos + 1
if _j_pos >= _j_len: return -1
m = int(_j_src[_j_pos])
_j_pos = _j_pos + 1
if m == 0x00: continue
if m >= 0xD0 and m <= 0xD7: return 0
_j_marker = m
return -1
return -1
def _j_decode_block(c: int, blk: int):
if _j_progressive == 0:
_j_block_seq(c, blk)
else:
if _j_ss == 0:
if _j_ah == 0: _j_block_dc_first(c, blk)
else: _j_block_dc_refine(c, blk)
else:
if _j_ah == 0: _j_block_ac_first(c, blk)
else: _j_block_ac_refine(c, blk)
def _j_scan() -> int:
global _j_bitbuf, _j_bitcnt, _j_err, _j_eobrun, _j_cur_mcu, _j_stage
_j_bitbuf = uint32(0)
_j_bitcnt = 0
_j_err = 0
_j_eobrun = 0
i: int = 0
while i < J_MAX_COMPS:
_j_dcpred[i] = 0
i = i + 1
if _j_scan_ns < 1: return -1
if _j_scan_ns > 1:
total: int = _j_mcu_cols * _j_mcu_rows
mcu: int = 0
while mcu < total:
_j_cur_mcu = mcu
if _j_restart > 0 and mcu > 0 and (mcu % _j_restart) == 0:
if _j_restart_scan() != 0: return -1
si: int = 0
while si < _j_scan_ns:
c: int = _j_sc[si]
comp: JComp* = &_j_comps[c]
vy: int = 0
while vy < comp.vs:
hx: int = 0
while hx < comp.hs:
bx: int = (mcu % _j_mcu_cols) * comp.hs + hx
by: int = (mcu / _j_mcu_cols) * comp.vs + vy
_j_decode_block(c, by * comp.bw + bx)
if _j_err != 0:
_j_stage = 1000 + mcu
return -1
hx = hx + 1
vy = vy + 1
si = si + 1
mcu = mcu + 1
else:
c = _j_sc[0]
comp = &_j_comps[c]
cw: int = (_j_w * comp.hs + _j_maxh - 1) / _j_maxh
ch: int = (_j_h * comp.vs + _j_maxv - 1) / _j_maxv
bwc: int = (cw + 7) / 8
bhc: int = (ch + 7) / 8
total = bwc * bhc
k: int = 0
while k < total:
_j_cur_mcu = k
if _j_restart > 0 and k > 0 and (k % _j_restart) == 0:
if _j_restart_scan() != 0: return -1
by: int = k / bwc
bx: int = k % bwc
_j_decode_block(c, by * comp.bw + bx)
if _j_err != 0:
_j_stage = 2000 + k
return -1
k = k + 1
_j_bitbuf = uint32(0)
_j_bitcnt = 0
return 0
def _j_parse_sos(p: uint64, pend: uint64) -> int:
global _j_scan_ns, _j_ss, _j_se, _j_ah, _j_al
if p + 4 > pend: return -1
ns: int = int(_j_src[p])
if ns < 1 or ns > J_MAX_COMPS or p + uint64(3 + ns * 2) > pend: return -1
p = p + 1
i: int = 0
while i < ns:
cs: int = int(_j_src[p])
td: int = int(_j_src[p + 1]) >> 4
ta: int = int(_j_src[p + 1]) & 15
ci: int = -1
j: int = 0
while j < _j_ncomp:
if _j_comps[j].id == cs: ci = j
j = j + 1
if ci < 0: return -1
_j_sc[i] = ci
_j_sctd[i] = td
_j_scta[i] = ta
_j_comps[ci].td = td
_j_comps[ci].ta = ta
p = p + 2
i = i + 1
ss: int = int(_j_src[p])
se: int = int(_j_src[p + 1])
ahal: int = int(_j_src[p + 2])
_j_scan_ns = ns
_j_ss = ss
_j_se = se
_j_ah = ahal >> 4
_j_al = ahal & 15
if se > 63 or ss > se: return -1
return 0
# --------------------------------------------------------------------- IDCT
def _j_trig():
global _j_trig_ready
if _j_trig_ready != 0: return
x: int = 0
while x < 8:
u: int = 0
while u < 8:
_j_cs[x * 8 + u] = float(math.cos(float((2 * x + 1) * u) * 3.141592653589793 / 16.0))
u = u + 1
x = x + 1
_j_cw[0] = float(0.7071067811865476)
u = 1
while u < 8:
_j_cw[u] = float(1.0)
u = u + 1
_j_trig_ready = 1
def _j_idct_block(comp: JComp*, bx: int, by: int):
qbase: int = comp.tq * 64
cbase: int = (by * comp.bw + bx) * 64
v: int = 0
while v < 8:
x: int = 0
while x < 8:
s: float = float(0.0)
u: int = 0
while u < 8:
s = s + _j_cw[u] * (float(int(comp.coef[cbase + v * 8 + u])) * float(int(_j_qt[qbase + v * 8 + u]))) * _j_cs[x * 8 + u]
u = u + 1
_j_g[v * 8 + x] = s
x = x + 1
v = v + 1
y: int = 0
pw: int = comp.bw * 8
while y < 8:
x = 0
while x < 8:
s: float = float(0.0)
v = 0
while v < 8:
s = s + _j_cw[v] * _j_g[v * 8 + x] * _j_cs[y * 8 + v]
v = v + 1
val: int = int(s * float(0.25) + float(128.5))
if val < 0: val = 0
if val > 255: val = 255
comp.plane[(by * 8 + y) * pw + bx * 8 + x] = uint8(val)
x = x + 1
y = y + 1
def _j_render() -> im.Image*:
global _j_stage
_j_trig()
i: int = 0
while i < _j_ncomp:
comp: JComp* = &_j_comps[i]
pw: int = comp.bw * 8
ph: int = comp.bh * 8
comp.plane = uint8*(stdlib.malloc(uint64(pw * ph)))
if comp.plane is None:
_j_stage = 8
return None
by: int = 0
while by < comp.bh:
bx: int = 0
while bx < comp.bw:
_j_idct_block(comp, bx, by)
bx = bx + 1
by = by + 1
i = i + 1
img: im.Image* = im.new_image(_j_w, _j_h, im.IMAGE_FMT_JPEG)
if img is None:
_j_stage = 9
return None
ci_y: int = 0
ci_cb: int = 1
ci_cr: int = 2
i = 0
while i < _j_ncomp:
if _j_comps[i].id == 1: ci_y = i
elif _j_comps[i].id == 2: ci_cb = i
elif _j_comps[i].id == 3: ci_cr = i
i = i + 1
c0: JComp* = &_j_comps[ci_y]
pw0: int = c0.bw * 8
y: int = 0
while y < _j_h:
x: int = 0
while x < _j_w:
if _j_ncomp == 1:
cy: int = (y * c0.vs) / _j_maxv
cx: int = (x * c0.hs) / _j_maxh
if cy >= c0.bh * 8: cy = c0.bh * 8 - 1
if cx >= pw0: cx = pw0 - 1
g: int = int(c0.plane[cy * pw0 + cx])
img.pixels[y * _j_w + x] = im.make_pixel(g, g, g, 255)
else:
cr: JComp* = &_j_comps[ci_cr]
cb: JComp* = &_j_comps[ci_cb]
yv: int = int(c0.plane[((y * c0.vs) / _j_maxv) * pw0 + ((x * c0.hs) / _j_maxh)])
crv: int = int(cr.plane[((y * cr.vs) / _j_maxv) * (cr.bw * 8) + ((x * cr.hs) / _j_maxh)])
cbv: int = int(cb.plane[((y * cb.vs) / _j_maxv) * (cb.bw * 8) + ((x * cb.hs) / _j_maxh)])
if _j_rgb_mode != 0:
img.pixels[y * _j_w + x] = im.make_pixel(yv, crv, cbv, 255)
else:
r: int = yv + int(float(1.402) * float(crv - 128))
g: int = yv - int(float(0.344136) * float(cbv - 128)) - int(float(0.714136) * float(crv - 128))
b: int = yv + int(float(1.772) * float(cbv - 128))
if r < 0: r = 0
if r > 255: r = 255
if g < 0: g = 0
if g > 255: g = 255
if b < 0: b = 0
if b > 255: b = 255
img.pixels[y * _j_w + x] = im.make_pixel(r, g, b, 255)
x = x + 1
y = y + 1
return img
# --------------------------------------------------------------------- main
def decode(src: uint8*, src_len: uint64) -> im.Image*:
global _j_src, _j_len, _j_pos, _j_marker, _j_err, _j_w, _j_h, _j_ncomp, _j_restart, _j_rgb_mode, _j_planes_ready
global _j_stage, _j_scan_no
_j_stage = 0
_j_scan_no = 0
if src is None or src_len < 4: return None
if int(src[0]) != 0xFF or int(src[1]) != 0xD8: return None
_j_stage = 1
_j_src = src
_j_len = src_len
_j_pos = 2
_j_marker = -1
_j_err = 0
_j_w = 0
_j_h = 0
_j_ncomp = 0
_j_restart = 0
_j_rgb_mode = 0
_j_planes_ready = 0
i: int = 0
while i < 4:
_j_qt_used[i] = 0
i = i + 1
while True:
m: int = _j_next_marker()
if m < 0 or m == 0xD9: break
if m == 0xD8: continue
if m >= 0xD0 and m <= 0xD7: continue
if m == 0x01: continue
if _j_pos + 2 > _j_len: break
seclen: int = _j_u16(_j_pos)
if seclen < 2: break
pend: uint64 = _j_pos + uint64(seclen)
if pend > _j_len: break
p: uint64 = _j_pos + 2
if m == 0xDB:
if _j_parse_dqt(p, pend) != 0: return None
_j_pos = pend
elif m == 0xC4:
if _j_parse_dht(p, pend) != 0: return None
_j_pos = pend
elif m == 0xC0 or m == 0xC1:
if _j_parse_sof(p, pend, 0) != 0: return None
_j_pos = pend
elif m == 0xC2:
# Progressive JPEG (SOF2) is parsed by the coefficient machinery
# below but successive-approximation refinement is not trusted yet:
# reject it instead of returning a partially-wrong image.
_j_stage = 15
return None
elif m == 0xDD:
if p + 2 <= pend: _j_restart = _j_u16(p)
_j_pos = pend
elif m == 0xEE:
# Adobe APP14: transform 0 means the components are RGB.
if pend - p >= 12:
tr: int = int(_j_src[p + 11])
if tr == 0: _j_rgb_mode = 1
_j_pos = pend
elif m == 0xDA:
if _j_parse_sos(p, pend) != 0:
_j_stage = 12
return None
_j_stage = 3
if _j_prepare() != 0:
_j_stage = 13
return None
_j_stage = 4
kind: int = 0
if _j_ss == 0:
if _j_ah != 0: kind = 1
else:
if _j_ah != 0: kind = 3
else: kind = 2
_j_stage = 100 + _j_scan_no * 10 + kind
_j_pos = pend
if _j_scan() != 0:
return None
_j_scan_no = _j_scan_no + 1
_j_stage = 5
else:
_j_pos = pend
if _j_w <= 0 or _j_h <= 0 or _j_ncomp < 1:
_j_stage = 10
return None
if _j_ncomp == 4:
_j_stage = 11
return None
_j_stage = 6
img: im.Image* = _j_render()
if img is None: return None
_j_stage = 7
return img
+303
View File
@@ -0,0 +1,303 @@
# PNG decoder (RFC 2083): all standard color types, bit depths 1/2/4/8/16,
# plus Adam7 interlacing. Output is packed 0xAARRGGBB in `Image.pixels`.
import vpsdk.imagesee.inflate as inflate
import vpsdk.imagesee.image as im
import vpsdk.stdlib as stdlib
import string
from stdint import *
define PNG_SIG_0 = 0x89
define PNG_SIG_1 = 0x50
define PNG_SIG_2 = 0x4E
define PNG_SIG_3 = 0x47
_png_src: uint8* = None
_png_len: uint64 = 0
_png_idat: uint8* = None
_png_idat_len: uint64 = 0
_png_w: int = 0
_png_h: int = 0
_png_depth: int = 0
_png_ctype: int = 0
_png_interlace: int = 0
_png_channels: int = 0
_png_raw: uint8* = None
_png_plte: [768]uint8
_png_plte_n: int = 0
_png_trns: [256]uint8
_png_trns_n: int = 0
_png_err: int = 0
_png_raw_off: uint64 = 0
# Adam7 pass origins / steps.
_ax0: [7]int = [0, 4, 0, 2, 0, 1, 0]
_ay0: [7]int = [0, 0, 4, 0, 2, 0, 1]
_adx: [7]int = [8, 8, 4, 4, 2, 2, 1]
_ady: [7]int = [8, 8, 8, 4, 4, 2, 2]
def _be32(d: uint8*, o: uint64) -> uint32:
v: uint32 = (uint32(d[o]) << 24) | (uint32(d[o + 1]) << 16)
v = v | (uint32(d[o + 2]) << 8) | uint32(d[o + 3])
return v
def _sig_ok(d: uint8*) -> int:
if d[0] != uint8(PNG_SIG_0): return 0
if d[1] != uint8(PNG_SIG_1): return 0
if d[2] != uint8(PNG_SIG_2): return 0
if d[3] != uint8(PNG_SIG_3): return 0
return 1
def _channels_of(ct: int) -> int:
if ct == 0: return 1
if ct == 2: return 3
if ct == 3: return 1
if ct == 4: return 2
if ct == 6: return 4
return 0
def _depth_ok(ct: int, bd: int) -> int:
if bd != 1 and bd != 2 and bd != 4 and bd != 8 and bd != 16: return 0
if ct == 0: return 1
if ct == 3: return 1 if bd <= 8 else 0
if ct == 2 or ct == 4 or ct == 6: return 1 if bd == 8 or bd == 16 else 0
return 0
def _rowbytes(w: int) -> int:
bits: int = w * _png_channels * _png_depth
return (bits + 7) / 8
def _pass_w(w: int, p: int) -> int:
x0: int = _ax0[p]
if w <= x0: return 0
return (w - x0 + _adx[p] - 1) / _adx[p]
def _pass_h(h: int, p: int) -> int:
y0: int = _ay0[p]
if h <= y0: return 0
return (h - y0 + _ady[p] - 1) / _ady[p]
def _raw_size() -> uint64:
if _png_interlace == 0:
return uint64(_png_h * (1 + _rowbytes(_png_w)))
total: uint64 = 0
p: int = 0
while p < 7:
pw: int = _pass_w(_png_w, p)
ph: int = _pass_h(_png_h, p)
if pw > 0 and ph > 0:
total = total + uint64(ph * (1 + _rowbytes(pw)))
p = p + 1
return total
def _sample(line: uint8*, idx: int, depth: int) -> int:
if depth == 8:
return int(line[idx])
if depth == 16:
return (int(line[idx * 2]) << 8) | int(line[idx * 2 + 1])
bitpos: int = idx * depth
byte: int = int(line[bitpos >> 3])
shift: int = 8 - depth - (bitpos & 7)
return (byte >> shift) & ((1 << depth) - 1)
def _paeth(a: int, b: int, c: int) -> int:
p: int = a + b - c
pa: int = p - a
if pa < 0: pa = -pa
pb: int = p - b
if pb < 0: pb = -pb
pc: int = p - c
if pc < 0: pc = -pc
if pa <= pb and pa <= pc: return a
if pb <= pc: return b
return c
def _unfilter(ft: int, cur: uint8*, prev: uint8*, n: int, bpp: int):
i: int = 0
while i < n:
a: int = 0
c: int = 0
if i >= bpp:
a = int(cur[i - bpp])
c = int(prev[i - bpp])
b: int = int(prev[i])
x: int = int(cur[i])
if ft == 1:
x = x + a
elif ft == 2:
x = x + b
elif ft == 3:
x = x + ((a + b) >> 1)
elif ft == 4:
x = x + _paeth(a, b, c)
cur[i] = uint8(x & 0xFF)
i = i + 1
def _scale(v: int, depth: int) -> int:
if depth == 8: return v
if depth == 16: return v >> 8
mx: int = (1 << depth) - 1
return (v * 255 + (mx / 2)) / mx
def _pixel_argb(line: uint8*, idx: int) -> uint32:
ct: int = _png_ctype
bd: int = _png_depth
if ct == 0:
raw: int = _sample(line, idx, bd)
g: int = _scale(raw, bd)
a: int = 255
if _png_trns_n >= 2:
tv: int = (int(_png_trns[0]) << 8) | int(_png_trns[1])
if raw == tv: a = 0
return im.make_pixel(g, g, g, a)
if ct == 2:
r: int = _sample(line, idx * 3, bd)
g: int = _sample(line, idx * 3 + 1, bd)
b: int = _sample(line, idx * 3 + 2, bd)
a: int = 255
if _png_trns_n >= 6:
tr: int = (int(_png_trns[0]) << 8) | int(_png_trns[1])
tg: int = (int(_png_trns[2]) << 8) | int(_png_trns[3])
tb: int = (int(_png_trns[4]) << 8) | int(_png_trns[5])
if r == tr and g == tg and b == tb: a = 0
return im.make_pixel(_scale(r, bd), _scale(g, bd), _scale(b, bd), a)
if ct == 3:
pi: int = _sample(line, idx, bd)
if pi * 3 + 2 >= 768: return im.make_pixel(0, 0, 0, 255)
r: int = int(_png_plte[pi * 3])
g: int = int(_png_plte[pi * 3 + 1])
b: int = int(_png_plte[pi * 3 + 2])
a: int = 255
if pi < _png_trns_n: a = int(_png_trns[pi])
return im.make_pixel(r, g, b, a)
if ct == 4:
g: int = _sample(line, idx * 2, bd)
a: int = _sample(line, idx * 2 + 1, bd)
return im.make_pixel(_scale(g, bd), _scale(g, bd), _scale(g, bd), _scale(a, bd))
if ct == 6:
r: int = _sample(line, idx * 4, bd)
g: int = _sample(line, idx * 4 + 1, bd)
b: int = _sample(line, idx * 4 + 2, bd)
a: int = _sample(line, idx * 4 + 3, bd)
return im.make_pixel(_scale(r, bd), _scale(g, bd), _scale(b, bd), _scale(a, bd))
return im.make_pixel(0, 0, 0, 255)
def _decode_pass(out: uint32*, p: int):
global _png_raw_off
pw: int = _pass_w(_png_w, p)
ph: int = _pass_h(_png_h, p)
px0: int = _ax0[p]
py0: int = _ay0[p]
pdx: int = _adx[p]
pdy: int = _ady[p]
if _png_interlace == 0:
# Non-interlaced images are a single full-size pass, not Adam7 pass 0.
pw = _png_w
ph = _png_h
px0 = 0
py0 = 0
pdx = 1
pdy = 1
if pw <= 0 or ph <= 0: return
rb: int = _rowbytes(pw)
bpp: int = (_png_channels * _png_depth + 7) / 8
if bpp < 1: bpp = 1
cur: uint8* = uint8*(stdlib.malloc(uint64(rb)))
prev: uint8* = uint8*(stdlib.malloc(uint64(rb)))
if cur is None or prev is None: return
string.memset(rawptr(cur), 0, uint64(rb))
string.memset(rawptr(prev), 0, uint64(rb))
off: uint64 = _png_raw_off
ry: int = 0
while ry < ph:
ft: int = int(_png_raw[off])
off = off + 1
string.memcpy(rawptr(cur), rawptr(uint64(_png_raw) + off), uint64(rb))
off = off + uint64(rb)
_unfilter(ft, cur, prev, rb, bpp)
y: int = py0 + ry * pdy
rx: int = 0
while rx < pw:
x: int = px0 + rx * pdx
if x < _png_w and y < _png_h:
out[y * _png_w + x] = _pixel_argb(cur, rx)
rx = rx + 1
string.memcpy(rawptr(prev), rawptr(cur), uint64(rb))
ry = ry + 1
_png_raw_off = off
def decode(src: uint8*, src_len: uint64) -> im.Image*:
global _png_src, _png_len, _png_w, _png_h, _png_depth, _png_ctype
global _png_interlace, _png_channels, _png_raw, _png_plte_n, _png_trns_n
global _png_idat, _png_idat_len, _png_err, _png_raw_off
if src is None or src_len < 33: return None
if _sig_ok(src) == 0: return None
_png_src = src
_png_len = src_len
_png_w = 0
_png_h = 0
_png_plte_n = 0
_png_trns_n = 0
_png_err = 0
_png_raw_off = 0
_png_idat = uint8*(stdlib.malloc(src_len))
if _png_idat is None: return None
_png_idat_len = 0
pos: uint64 = 8
got_ihdr: int = 0
while pos + 12 <= src_len:
clen: uint64 = uint64(_be32(src, pos))
if pos + 12 + clen > src_len: return None
typ0: int = int(src[pos + 4])
typ1: int = int(src[pos + 5])
typ2: int = int(src[pos + 6])
typ3: int = int(src[pos + 7])
dpos: uint64 = pos + 8
if typ0 == 73 and typ1 == 72 and typ2 == 68 and typ3 == 82: # IHDR
if clen < 13: return None
_png_w = int(_be32(src, dpos))
_png_h = int(_be32(src, dpos + 4))
_png_depth = int(src[dpos + 8])
_png_ctype = int(src[dpos + 9])
_png_interlace = int(src[dpos + 12])
_png_channels = _channels_of(_png_ctype)
if _png_w <= 0 or _png_h <= 0 or _png_w > 16384 or _png_h > 16384: return None
if _png_channels == 0 or _depth_ok(_png_ctype, _png_depth) == 0: return None
if int(src[dpos + 10]) != 0 or int(src[dpos + 11]) != 0: return None
got_ihdr = 1
elif typ0 == 80 and typ1 == 76 and typ2 == 84 and typ3 == 69: # PLTE
n: uint64 = clen
if n > 768: n = 768
string.memcpy(rawptr(&_png_plte[0]), rawptr(uint64(src) + dpos), n)
_png_plte_n = int(n / 3)
elif typ0 == 116 and typ1 == 82 and typ2 == 78 and typ3 == 83: # tRNS
n: uint64 = clen
if n > 256: n = 256
string.memcpy(rawptr(&_png_trns[0]), rawptr(uint64(src) + dpos), n)
_png_trns_n = int(n)
elif typ0 == 73 and typ1 == 68 and typ2 == 65 and typ3 == 84: # IDAT
string.memcpy(rawptr(uint64(_png_idat) + _png_idat_len),
rawptr(uint64(src) + dpos), clen)
_png_idat_len = _png_idat_len + clen
elif typ0 == 73 and typ1 == 69 and typ2 == 78 and typ3 == 68: # IEND
break
pos = pos + 12 + clen
if got_ihdr == 0 or _png_idat_len == 0: return None
rawsz: uint64 = _raw_size()
if rawsz == 0: return None
_png_raw = uint8*(stdlib.malloc(rawsz))
if _png_raw is None: return None
got_raw: int = inflate.zlib_decompress(_png_idat, _png_idat_len, _png_raw, rawsz)
if got_raw < 0 or uint64(got_raw) != rawsz: return None
img: im.Image* = im.new_image(_png_w, _png_h, im.IMAGE_FMT_PNG)
if img is None: return None
if _png_interlace == 0:
_decode_pass(img.pixels, 0)
else:
p: int = 0
while p < 7:
_decode_pass(img.pixels, p)
p = p + 1
return img
+24 -6
View File
@@ -5,12 +5,12 @@ def exit(code: int):
syscall._syscall1(uint64(syscall.Syscall.EXIT), uint64(code))
def reboot():
"""Reset the machine."""
syscall._syscall0(uint64(syscall.Syscall.REBOOT))
"""Reset the machine via Linux reboot(169, LINUX_REBOOT_CMD_RESTART)."""
syscall.reboot_linux(uint64(0x01234567))
def shutdown():
"""Power off the machine via ACPI S5."""
syscall._syscall0(uint64(syscall.Syscall.SHUTDOWN))
"""Power off the machine via Linux reboot(169, LINUX_REBOOT_CMD_POWER_OFF)."""
syscall.reboot_linux(uint64(0x4321FEDC))
def thread_exit():
"""当前用户线程显式退出:内核回收其用户栈并标记线程结束(不返回)。"""
@@ -25,11 +25,29 @@ def get_pid() -> int:
"""当前进程 pid(getpid)。/dev/desk 的事件按 pid 过滤,建窗前就要拿到它。"""
return int(syscall._syscall0(uint64(syscall.Syscall.GETPID)))
def get_ppid() -> int:
return int(syscall._syscall0(uint64(syscall.Syscall.GETPPID)))
def spawn(path: const str, blocking: int = 0) -> int:
return int(syscall._syscall2(uint64(syscall.Syscall.SPAWN), uint64(path), uint64(blocking)))
"""ViperOS launcher: start a new child and keep the caller alive."""
return int(syscall._syscall2(uint64(syscall.Syscall.SPAWN_PROC), uint64(path), uint64(blocking)))
def execve(path: const str, argv: rawptr, envp: rawptr) -> int:
"""Linux execve: replace the current process (returns only on failure)."""
return int(syscall._syscall3(uint64(syscall.Syscall.SPAWN), uint64(path), uint64(argv), uint64(envp)))
def fork() -> int:
return int(syscall._syscall0(uint64(syscall.Syscall.FORK)))
def clone(flags: uint64, stack: uint64, ptid: uint64 = 0, ctid: uint64 = 0, tls: uint64 = 0) -> int:
return int(syscall._syscall5(uint64(syscall.Syscall.CLONE), flags, stack, ptid, ctid, tls))
def wait4(pid: int, status: rawptr, options: uint32 = 0) -> int:
return int(syscall._syscall4(uint64(syscall.Syscall.WAIT4), uint64(pid),
uint64(status), uint64(options), 0))
def thread_create(func: rawptr, arg: rawptr = None) -> int:
return int(syscall._syscall2(uint64(syscall.Syscall.THREAD_CREATE), uint64(func), uint64(arg)))
return int(syscall._syscall2(uint64(syscall.Syscall.THREAD_CREATE_OLD), uint64(func), uint64(arg)))
class Thread:
+311 -38
View File
@@ -1,39 +1,89 @@
# ============================================================================
# 系统调用号表(SDK 侧镜像)。**与 VKernel/Kernel/intr/syscall.vp 的同名 enum 逐条对应**:
# vpsdk 不允许依赖 VKernel,所以这里是一份必须手工同步的副本 —— 增删/改号/改名两边一起改。
# 编号口径:与 Linux x86-64 ABI 同名同号;Linux 有而我们未实现的留空不占号;
# 我们独有的能力不再新增 syscall,最终走 devfs 设备文件或下沉到用户层(见 0x1000+ 段的标注)。
# 系统调用号表(SDK 侧镜像,由 Scripts/syscall_table.py 生成,禁止手改)。
# 编号口径:与 Linux x86-64 ABI 同名同号;Linux 有而尚未实现的调用暂不占号。
# ============================================================================
# BEGIN GENERATED SYSCALL TABLE
enum Syscall:
# ---- Linux 对齐段(号 = Linux x86-64 的号)----
READ = 0 # read(fd, buf, count)
WRITE = 1 # write(fd, buf, count)
OPEN = 2 # open(path, flags)
CLOSE = 3 # close(fd)
STAT = 4 # stat(path, info)(结构体仍是本系统的 fileinfo)
SEEK = 8 # lseek(fd, offset, whence)(whence 目前恒 0 = SEEK_SET)
MMAP = 9 # mmap(size) → VA(ABI 待向 Linux 的 addr/len/prot/flags/fd/off 靠拢)
MUNMAP = 11 # munmap(ptr, size)(目前空实现)
IOCTL = 16 # ioctl(fd, request, arg):设备控制(当前用于 /dev/fb0 查几何)
YIELD = 24 # sched_yield()
GETPID = 39 # getpid():返回当前进程 pid(app 用它过滤 /dev/desk 事件)
THREAD_CREATE = 56 # clone():建用户线程,返回 tid
SPAWN = 59 # execve():加载并运行新 ELF,返回 pid
THREAD_EXIT = 60 # exit():退役当前线程
MKDIR = 83 # mkdir(path)
REMOVE = 87 # unlink(path)
EXIT = 231 # exit_group():退役当前线程(窗口由 explorer 侧回收)
# ---- 临时私有段(0x1000+):Linux 无对应,且**逐条都会被消掉** ----
# 目标形态:私有能力不占 syscall 号 → 改 devfs 设备文件。
# S3e 删 UI 11 个;S4 删 SERIAL_PUTS(改 /dev/serial0);S5 删 TELL/SIZE
# (改 lseek 的 SEEK_CUR / SEEK_END,见 vpsdk/fs.vp 的 tell/fsize)。
OPENDIR = 0x1003 # TODO(linux):openat(O_DIRECTORY) + getdents64
READDIR = 0x1004 # TODO(linux):getdents64
CLOSEDIR = 0x1005 # TODO(linux):close
LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading
SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call
REBOOT = 0x1008
SHUTDOWN = 0x1009
# ---- Linux 对齐段(号 = Linux x86-64 的号) ----
READ = 0 # read(fd, buf, count)
WRITE = 1 # write(fd, buf, count)
OPEN = 2 # open(path, flags) - Linux O_*
CLOSE = 3 # close(fd)
STAT = 4 # stat(path, fileinfo*) - legacy struct
FSTAT = 5 # fstat(fd, struct stat*)
POLL = 7 # poll(fds, nfds, timeout) - stub 0
SEEK = 8 # lseek(fd, offset, whence) -> new offset
MMAP = 9 # mmap(addr, len, prot, flags, fd, off)
MPROTECT = 10 # mprotect(addr, len, prot)
MUNMAP = 11 # munmap(addr, len)
BRK = 12 # brk(addr) -> new break
RT_SIGACTION = 13 # rt_sigaction() - bookkeeping
RT_SIGPROCMASK = 14 # rt_sigprocmask() - bookkeeping
RT_SIGRETURN = 15 # rt_sigreturn()
IOCTL = 16 # ioctl(fd, request, arg)
PREAD64 = 17 # pread64(fd, buf, count, offset)
PWRITE64 = 18 # pwrite64(fd, buf, count, offset)
READV = 19 # readv(fd, iov, iovcnt)
WRITEV = 20 # writev(fd, iov, iovcnt)
ACCESS = 21 # access(path, mode)
PIPE = 22 # pipe(int[2])
YIELD = 24 # sched_yield()
MREMAP = 25 # mremap(old, oldsz, newsz, flags, newaddr)
MADVISE = 28 # madvise(addr, len, advice)
DUP = 32 # dup(fd)
DUP2 = 33 # dup2(oldfd, newfd)
NANOSLEEP = 35 # nanosleep(req, rem)
GETPID = 39 # getpid()
CLONE = 56 # clone(flags, stack, ptid, ctid, tls)
FORK = 57 # fork() - copy address space
SPAWN = 59 # execve(path, argv, envp)
THREAD_EXIT = 60 # exit(): retire current thread
WAIT4 = 61 # wait4(pid, status, options, rusage)
KILL = 62 # kill(pid, sig) - bookkeeping
UNAME = 63 # uname(utsname*)
FCNTL = 72 # fcntl(fd, cmd, arg)
GETCWD = 79 # getcwd(buf, size)
CHDIR = 80 # chdir(path)
RENAME = 82 # rename(old, new)
MKDIR = 83 # mkdir(path)
RMDIR = 84 # rmdir(path)
REMOVE = 87 # unlink(path)
READLINK = 89 # readlink(path, buf, size) - no symlinks
GETUID = 102 # getuid()
GETGID = 104 # getgid()
GETEUID = 107 # geteuid()
GETEGID = 108 # getegid()
GETTIMEOFDAY = 96 # gettimeofday(tv, tz)
GETPPID = 110 # getppid()
ARCH_PRCTL = 158 # arch_prctl(code, addr)
REBOOT_LINUX = 169 # reboot(magic1, magic2, cmd, arg)
GETTID = 186 # gettid()
FUTEX = 202 # futex(uaddr, op, val, timeout)
GETDENTS64 = 217 # getdents64(fd, buf, count)
SET_TID_ADDRESS = 218 # set_tid_address(tidptr)
CLOCK_GETTIME = 228 # clock_gettime(clockid, timespec*)
CLOCK_GETRES = 229 # clock_getres(clockid, timespec*)
EXIT = 231 # exit_group(): terminate all threads
OPENAT = 257 # openat(dirfd, path, flags, mode)
NEWFSTATAT = 262 # newfstatat(dirfd, path, stat*, flags)
UNLINKAT = 263 # unlinkat(dirfd, path, flags)
READLINKAT = 267 # readlinkat(dirfd, path, buf, size)
FACCESSAT = 269 # faccessat(dirfd, path, mode, flags)
SET_ROBUST_LIST = 273 # set_robust_list(head, len)
DUP3 = 292 # dup3(oldfd, newfd, flags)
PIPE2 = 293 # pipe2(int[2], flags)
PRLIMIT64 = 302 # prlimit64(pid, resource, new, old)
GETRANDOM = 318 # getrandom(buf, len, flags)
STATX = 332 # statx(...) - -ENOSYS, glibc falls back
RSEQ = 334 # rseq(rseq, len, flags) - -ENOSYS
# ---- 临时私有段(0x1000+):Linux 无对应,逐条迁出 ----
LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading
SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call
REBOOT = 0x1008 # TODO(linux): reboot(169)
SHUTDOWN = 0x1009 # TODO(linux): reboot(RB_POWER_OFF)
SPAWN_PROC = 0x100A # TODO(userspace): Terminal run helper
THREAD_CREATE_OLD = 0x100B # TODO(userspace): ViperOS thread_create
# END GENERATED SYSCALL TABLE
# ---- mmap 的 prot / flags(与内核 VKernel/intr/syscall.vp 及各 Linux 同名常量一致)----
define PROT_NONE = 0x0
@@ -46,6 +96,39 @@ define MAP_FIXED = 0x10
define MAP_ANONYMOUS = 0x20
# fd = -1(Linux 匿名映射约定)
define MMAP_FD_ANON: uint64 = 0xFFFFFFFFFFFFFFFF
define MAP_FAILED: uint64 = 0xFFFFFFFFFFFFFFFF
define MREMAP_MAYMOVE = 1
define MREMAP_FIXED = 2
# ---- AT_* / fcntl(Linux 值)----
define AT_FDCWD = -100
define AT_SYMLINK_NOFOLLOW = 256
define AT_REMOVEDIR = 512
define AT_EMPTY_PATH = 4096
define F_DUPFD = 0
define F_GETFD = 1
define F_SETFD = 2
define F_GETFL = 3
define F_SETFL = 4
define F_DUPFD_CLOEXEC = 1030
define FD_CLOEXEC = 1
# ---- TLS / futex / time / wait / reboot ----
define ARCH_SET_GS = 0x1001
define ARCH_SET_FS = 0x1002
define ARCH_GET_FS = 0x1003
define ARCH_GET_GS = 0x1004
define FUTEX_WAIT = 0
define FUTEX_WAKE = 1
define FUTEX_PRIVATE_FLAG = 128
define CLOCK_REALTIME = 0
define CLOCK_MONOTONIC = 1
define WNOHANG = 1
define LINUX_REBOOT_MAGIC1 = 0xFEE1DEAD
define LINUX_REBOOT_MAGIC2 = 672274793
define LINUX_REBOOT_CMD_RESTART = 0x01234567
define LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC
def _syscall0(num: uint64) -> uint64:
ret: uint64
@@ -91,6 +174,21 @@ def _syscall3(num: uint64, a1: uint64, a2: uint64, a3: uint64) -> uint64:
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDX])
return ret
def _syscall4(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64) -> uint64:
ret: uint64
__asm__(f"""mov rdi, {asminp(a1, ASM_DESCR.REG_ANY)}
mov rsi, {asminp(a2, ASM_DESCR.REG_ANY)}
mov rdx, {asminp(a3, ASM_DESCR.REG_ANY)}
mov r10, {asminp(a4, ASM_DESCR.REG_ANY)}
mov rax, {asminp(num, ASM_DESCR.REG_ANY)}
syscall""",
out=[asmout(ret, ASM_DESCR.OUTPUT_REG)],
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_R11, ASM_DESCR.CLOBBER_RDI,
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDX,
ASM_DESCR.CLOBBER_R10])
return ret
def _syscall5(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64, a5: uint64) -> uint64:
ret: uint64
__asm__(f"""mov rdi, {asminp(a1, ASM_DESCR.REG_ANY)}
@@ -126,10 +224,185 @@ def _syscall6(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64, a5: u
return ret
def mmap(addr: uint64, length: uint64, prot: uint64, flags: uint64, fd: uint64, offset: uint64) -> uint64:
"""mmap(addr, length, prot, flags, fd, offset):返回映射起始 VA,失败返回 0。
"""mmap(addr, length, prot, flags, fd, offset):返回映射起始 VA。
匿名映射:fd = MMAP_FD_ANON(-1) 且 flags 带 MAP_ANONYMOUS;
映射设备(如 /dev/fb0):fd 传该设备的 fd。addr / MAP_FIXED 由内核挑地址。
(定义放在 _syscall6 之后,避免同模块内前向引用。)
内核遵循 Linux 语义(失败返回 MAP_FAILED = -1);SDK 为兼容既有调用方
把它归一化为 0(C 代码里判 `== 0`)。
"""
return _syscall6(uint64(Syscall.MMAP), addr, length, prot, flags, fd, offset)
va: uint64 = _syscall6(uint64(Syscall.MMAP), addr, length, prot, flags, fd, offset)
if va == MAP_FAILED: return uint64(0)
return va
def munmap(addr: uint64, length: uint64) -> int:
return int(_syscall2(uint64(Syscall.MUNMAP), addr, length))
def mprotect(addr: uint64, length: uint64, prot: uint64) -> int:
return int(_syscall3(uint64(Syscall.MPROTECT), addr, length, prot))
def brk(addr: uint64) -> uint64:
return _syscall1(uint64(Syscall.BRK), addr)
def mremap(old_addr: uint64, old_size: uint64, new_size: uint64, flags: uint64, new_addr: uint64 = 0) -> uint64:
va: uint64 = _syscall5(uint64(Syscall.MREMAP), old_addr, old_size, new_size, flags, new_addr)
if va == MAP_FAILED: return uint64(0)
return va
def madvise(addr: uint64, length: uint64, advice: uint32) -> int:
return int(_syscall3(uint64(Syscall.MADVISE), addr, length, uint64(advice)))
# ============================================================================
# P0 flat wrappers (Linux ABI). Higher level helpers live in vpsdk.fs / process.
# ============================================================================
def uname(buf: rawptr) -> int:
return int(_syscall1(uint64(Syscall.UNAME), uint64(buf)))
def getuid() -> int:
return int(_syscall0(uint64(Syscall.GETUID)))
def geteuid() -> int:
return int(_syscall0(uint64(Syscall.GETEUID)))
def getgid() -> int:
return int(_syscall0(uint64(Syscall.GETGID)))
def getegid() -> int:
return int(_syscall0(uint64(Syscall.GETEGID)))
def getppid() -> int:
return int(_syscall0(uint64(Syscall.GETPPID)))
def gettid() -> int:
return int(_syscall0(uint64(Syscall.GETTID)))
def getcwd(buf: rawptr, size: uint64) -> int:
return int(_syscall2(uint64(Syscall.GETCWD), uint64(buf), size))
def chdir(path: const str) -> int:
return int(_syscall1(uint64(Syscall.CHDIR), uint64(path)))
def access(path: const str, mode: uint32) -> int:
return int(_syscall2(uint64(Syscall.ACCESS), uint64(path), uint64(mode)))
def faccessat(dirfd: int, path: const str, mode: uint32, flags: uint32) -> int:
return int(_syscall4(uint64(Syscall.FACCESSAT), uint64(dirfd), uint64(path),
uint64(mode), uint64(flags)))
def readlink(path: const str, buf: rawptr, size: uint64) -> int:
return int(_syscall3(uint64(Syscall.READLINK), uint64(path), uint64(buf), size))
def readlinkat(dirfd: int, path: const str, buf: rawptr, size: uint64) -> int:
return int(_syscall4(uint64(Syscall.READLINKAT), uint64(dirfd), uint64(path),
uint64(buf), size))
def set_tid_address(tidptr: rawptr) -> int: return int(_syscall1(uint64(Syscall.SET_TID_ADDRESS), uint64(tidptr)))
def set_robust_list(head: rawptr, length: uint64) -> int:
return int(_syscall2(uint64(Syscall.SET_ROBUST_LIST), uint64(head), length))
def rseq(rseq_ptr: rawptr, length: uint32, flags: uint32) -> int:
return int(_syscall3(uint64(Syscall.RSEQ), uint64(rseq_ptr),
uint64(length), uint64(flags)))
def prlimit64(pid: int, resource: uint32, new_limit: rawptr, old_limit: rawptr) -> int:
return int(_syscall4(uint64(Syscall.PRLIMIT64), uint64(pid), uint64(resource),
uint64(new_limit), uint64(old_limit)))
def getrandom(buf: rawptr, length: uint64, flags: uint32) -> int:
return int(_syscall3(uint64(Syscall.GETRANDOM), uint64(buf), length, uint64(flags)))
# ---------------------------------------------------------------- P1 ----
def openat(dirfd: int, path: const str, flags: uint32, mode: uint32 = 0) -> int:
return int(_syscall4(uint64(Syscall.OPENAT), uint64(dirfd), uint64(path),
uint64(flags), uint64(mode)))
def fstat(fd: int, st: rawptr) -> int:
return int(_syscall2(uint64(Syscall.FSTAT), uint64(fd), uint64(st)))
def newfstatat(dirfd: int, path: const str, st: rawptr, flags: uint32) -> int:
return int(_syscall4(uint64(Syscall.NEWFSTATAT), uint64(dirfd), uint64(path),
uint64(st), uint64(flags)))
def getdents64(fd: int, buf: rawptr, count: uint64) -> int:
return int(_syscall3(uint64(Syscall.GETDENTS64), uint64(fd), uint64(buf), count))
def dup(fd: int) -> int:
return int(_syscall1(uint64(Syscall.DUP), uint64(fd)))
def dup2(oldfd: int, newfd: int) -> int:
return int(_syscall2(uint64(Syscall.DUP2), uint64(oldfd), uint64(newfd)))
def dup3(oldfd: int, newfd: int, flags: uint32) -> int:
return int(_syscall3(uint64(Syscall.DUP3), uint64(oldfd), uint64(newfd), uint64(flags)))
def fcntl(fd: int, cmd: uint32, arg: uint64) -> int:
return int(_syscall3(uint64(Syscall.FCNTL), uint64(fd), uint64(cmd), arg))
def pread64(fd: int, buf: rawptr, count: uint32, offset: uint64) -> int:
return int(_syscall4(uint64(Syscall.PREAD64), uint64(fd), uint64(buf),
uint64(count), offset))
def pwrite64(fd: int, buf: rawptr, count: uint32, offset: uint64) -> int:
return int(_syscall4(uint64(Syscall.PWRITE64), uint64(fd), uint64(buf),
uint64(count), offset))
def rename(old_path: const str, new_path: const str) -> int:
return int(_syscall2(uint64(Syscall.RENAME), uint64(old_path), uint64(new_path)))
def rmdir(path: const str) -> int:
return int(_syscall1(uint64(Syscall.RMDIR), uint64(path)))
def unlinkat(dirfd: int, path: const str, flags: uint32) -> int:
return int(_syscall3(uint64(Syscall.UNLINKAT), uint64(dirfd), uint64(path),
uint64(flags)))
# ---------------------------------------------------------------- P4/P5/P6 ----
def arch_prctl(code: int, addr: uint64) -> int:
return int(_syscall2(uint64(Syscall.ARCH_PRCTL), uint64(code), addr))
def futex(uaddr: uint64, op: uint32, val: uint32, timeout: uint64 = 0) -> int:
return int(_syscall4(uint64(Syscall.FUTEX), uaddr, uint64(op), uint64(val), timeout))
def clock_gettime(clockid: int, ts: rawptr) -> int:
return int(_syscall2(uint64(Syscall.CLOCK_GETTIME), uint64(clockid), uint64(ts)))
def clock_getres(clockid: int, ts: rawptr) -> int:
return int(_syscall2(uint64(Syscall.CLOCK_GETRES), uint64(clockid), uint64(ts)))
def gettimeofday(tv: rawptr, tz: rawptr = None) -> int:
return int(_syscall2(uint64(Syscall.GETTIMEOFDAY), uint64(tv), uint64(tz)))
def nanosleep(req: rawptr, rem: rawptr = None) -> int:
return int(_syscall2(uint64(Syscall.NANOSLEEP), uint64(req), uint64(rem)))
def rt_sigaction(sig: int, act: rawptr, oldact: rawptr, sigsetsize: uint64 = 8) -> int:
return int(_syscall4(uint64(Syscall.RT_SIGACTION), uint64(sig), uint64(act),
uint64(oldact), sigsetsize))
def rt_sigprocmask(how: int, setp: rawptr, oldset: rawptr, sigsetsize: uint64 = 8) -> int:
return int(_syscall4(uint64(Syscall.RT_SIGPROCMASK), uint64(how), uint64(setp),
uint64(oldset), sigsetsize))
def kill(pid: int, sig: int) -> int:
return int(_syscall2(uint64(Syscall.KILL), uint64(pid), uint64(sig)))
def readv(fd: int, iov: rawptr, iovcnt: int) -> int:
return int(_syscall3(uint64(Syscall.READV), uint64(fd), uint64(iov), uint64(iovcnt)))
def writev(fd: int, iov: rawptr, iovcnt: int) -> int:
return int(_syscall3(uint64(Syscall.WRITEV), uint64(fd), uint64(iov), uint64(iovcnt)))
def poll(fds: rawptr, nfds: uint64, timeout: int) -> int:
return int(_syscall3(uint64(Syscall.POLL), uint64(fds), nfds, uint64(timeout)))
def pipe2(fds: rawptr, flags: uint32 = 0) -> int:
return int(_syscall2(uint64(Syscall.PIPE2), uint64(fds), uint64(flags)))
def pipe(fds: rawptr) -> int:
return int(_syscall1(uint64(Syscall.PIPE), uint64(fds)))
def reboot_linux(cmd: uint64) -> int:
return int(_syscall4(uint64(Syscall.REBOOT_LINUX), uint64(LINUX_REBOOT_MAGIC1),
uint64(LINUX_REBOOT_MAGIC2), cmd, uint64(0)))
+2099 -2074
View File
File diff suppressed because it is too large. Load diff