0827b2399c
引导器 UEFI.boot.py (纯 stdlib, 内核的管家) 体检 6 项 (解释器/venv/包/驱动目录/PG) / venv 重建 / 包台账 / 内核进程启停记账 / 参数原样透传 内核日志 / 引导器日志 / 内核 stdio 三条道的查看入口都归它 内核 内核/ (常驻调度 = 甲) 命令消费 (PG commands + LISTEN/NOTIFY) / 驱动调用仲裁六条 (越权·成环·无人提供·按需拉起·同锁排队·超时收权) 依赖链巡检 10s / 心跳 300s / 独一份调度锁 (pg_try_advisory_lock 0x65666901) 扫描: 9 条校验 + 契约匹配 + 拓扑排序; 状态: 快照原子写 + 断电收尸判定 db.py 是唯一碰 SQL 的文件 (drivers/driver_state/events/scans/commands/calls/kernel_env/kernel_runs) 日志系统 (2026-09-16 完整化, 设计/04-日志系统.md) 三条道一个文件一种内容: 内核.log (结构化行) / 内核.out.log (命令输出 + 崩溃原文) / 引导器.log (引导器动作) 驱动日志每轮启动前插分隔头; 门槛 log_level / 轮转 log_max_mb + log_keep / -n · -f · --级别 · -g · --json · --全部 实现 内核/日志.py (内核与引导器共用一份); 修掉"命令输出混进日志文件" (实证存档 归档/20260916-日志系统重做前/) 驱动样板 (也是写驱动的示范): Json解码 (oneshot) / 样板常驻 (provides 样板:心跳) / 样例消费器 (needs 只发契约名) 代码标点统一为 ASCII (保留界面用的框线 ─│◄▶ 与表格占位符 —); 已用 AST 等价对比证明逻辑零改动 自测 (真机, 不 mock): 进程 (真起进程真收子树) / 内核 58 项 / 配置 / db 182 项 / 日志 86 项 验收: 试跑引导器.py PASS 11 / FAIL 0 / 残留无; uvx pyright 与 uvx basedpyright 均 0 errors 0 warnings
346 lines
20 KiB
Python
346 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
"""内核项目设计图生成器 —— 按老板标准格式(椭圆起止/菱形判断/红虚线失败分支/平行四边形输入输出)。
|
||
|
||
生成 5 张图到本目录:
|
||
00-总览.drawio 四层架构 + PG 内存总线
|
||
01-引导器-启动时序.drawio 引导器三职责的启动主链
|
||
02-内核-总调度.drawio 扫描 → 契约匹配 → 排序 → 就绪 → 拉起
|
||
03-内核调用-时序.drawio 主驱动 / PG / 内核 / 副驱动 四方时序(零通道)
|
||
04-驱动-生命周期.drawio 驱动状态机 invalid→stopped→starting→running→…
|
||
|
||
边元组可带第 6 项 waypoints=[(x,y),...](绝对坐标),用于把回环/跨列线走到空白带。
|
||
"""
|
||
import sys
|
||
import xml.etree.ElementTree as ET
|
||
from pathlib import Path
|
||
|
||
OUT = Path(__file__).resolve().parent
|
||
|
||
|
||
def esc(s: str) -> str:
|
||
return (s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||
.replace('"', '"').replace('\n', ' '))
|
||
|
||
|
||
VS = {
|
||
'box': 'rounded=0;whiteSpace=wrap;html=1;',
|
||
'start': 'ellipse;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;',
|
||
'io': 'shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;',
|
||
'sub': 'shape=mxgraph.flowchart.predefined_process;whiteSpace=wrap;html=1;',
|
||
'diamond': 'rhombus;whiteSpace=wrap;html=1;',
|
||
'db': 'shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#dae8fc;strokeColor=#6c8ebf;',
|
||
'note': 'shape=note;whiteSpace=wrap;html=1;size=15;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=6;',
|
||
'hdr': 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;',
|
||
'cont': 'swimlane;html=1;startSize=30;fillColor=#f5f5f5;strokeColor=#666666;fontStyle=1;',
|
||
}
|
||
|
||
|
||
def ED(ex, ey, nx, ny, dashed=False, color='#000000', bidir=False):
|
||
s = ('edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;'
|
||
'horizontal=1;labelBackgroundColor=#ffffff;'
|
||
f'exitX={ex};exitY={ey};exitDx=0;exitDy=0;entryX={nx};entryY={ny};entryDx=0;entryDy=0;')
|
||
if bidir:
|
||
s += 'startArrow=classic;endArrow=classic;'
|
||
if dashed or color != '#000000':
|
||
s += f'dashed=1;strokeColor={color};fontColor={color};'
|
||
return s
|
||
|
||
|
||
def V(**k): # 竖直向下:底出口 → 顶入口
|
||
return ED(0.5, 1, 0.5, 0, **k)
|
||
|
||
|
||
def R(**k): # 向右:右出口 → 左入口
|
||
return ED(1, 0.5, 0, 0.5, **k)
|
||
|
||
|
||
def L(**k): # 向左:左出口 → 右入口
|
||
return ED(0, 0.5, 1, 0.5, **k)
|
||
|
||
|
||
RED = {'dashed': True, 'color': '#cc0000'}
|
||
GRAY = {'dashed': True, 'color': '#888888'}
|
||
|
||
|
||
def doc(page_w, page_h, hdr, hdr_box, nodes, edges, containers=()):
|
||
"""hdr_box=(x,y,w,h);nodes=(id,text,kind,x,y,w,h,parent);containers=(id,title,x,y,w,h)"""
|
||
hx, hy, hw, hh = hdr_box
|
||
L = ['<mxfile host="app.diagrams.net">',
|
||
' <diagram name="第 1 页" id="d1">',
|
||
f' <mxGraphModel dx="900" dy="600" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="{page_w}" pageHeight="{page_h}" math="0" shadow="0">',
|
||
' <root>',
|
||
' <mxCell id="0" />',
|
||
' <mxCell id="1" parent="0" />',
|
||
f' <mxCell id="hdr" parent="1" value="{esc(hdr)}" style="{VS["hdr"]}" vertex="1">',
|
||
f' <mxGeometry height="{hh}" width="{hw}" x="{hx}" y="{hy}" as="geometry" />',
|
||
' </mxCell>']
|
||
for cid, title, x, y, w, h in containers:
|
||
L.append(f' <mxCell id="{cid}" parent="1" value="{esc(title)}" style="{VS["cont"]}" vertex="1">')
|
||
L.append(f' <mxGeometry height="{h}" width="{w}" x="{x}" y="{y}" as="geometry" />')
|
||
L.append(' </mxCell>')
|
||
for nid, text, kind, x, y, w, h, parent in nodes:
|
||
L.append(f' <mxCell id="{nid}" parent="{parent}" value="{esc(text)}" style="{VS[kind]}" vertex="1">')
|
||
L.append(f' <mxGeometry height="{h}" width="{w}" x="{x}" y="{y}" as="geometry" />')
|
||
L.append(' </mxCell>')
|
||
for rec in edges:
|
||
eid, src, dst, style, label = rec[0], rec[1], rec[2], rec[3], rec[4]
|
||
pts = rec[5] if len(rec) > 5 else None
|
||
v = f' value="{esc(label)}"' if label else ''
|
||
L.append(f' <mxCell id="{eid}" edge="1" parent="1" source="{src}" target="{dst}" style="{style}"{v}>')
|
||
if pts:
|
||
L.append(' <mxGeometry relative="1" as="geometry">')
|
||
L.append(' <Array as="points">')
|
||
for px, py in pts:
|
||
L.append(f' <mxPoint x="{px}" y="{py}" />')
|
||
L.append(' </Array>')
|
||
L.append(' </mxGeometry>')
|
||
else:
|
||
L.append(' <mxGeometry relative="1" as="geometry" />')
|
||
L.append(' </mxCell>')
|
||
L += [' </root>', ' </mxGraphModel>', ' </diagram>', '</mxfile>']
|
||
return '\n'.join(L)
|
||
|
||
|
||
# ───────────────────────── 00 总览 ─────────────────────────
|
||
def g00():
|
||
nodes = [
|
||
('cli', 'UEFI.boot.py <子命令>\n列表 / 启动 / 停止 / 状态 / 日志', 'box', 20, 44, 480, 50, 'C1'),
|
||
('out', '答案与日志直接打终端(纯 CLI,没有 TUI)', 'box', 540, 44, 480, 50, 'C1'),
|
||
('env', '管内核环境\n解释器 / venv 健康 / 路径', 'box', 20, 46, 320, 68, 'C2'),
|
||
('pkg', '管包状态\n缺什么 / 版本不符 / 索引源', 'box', 360, 46, 320, 68, 'C2'),
|
||
('kpr', '管内核进程\n拉起 / 判活 / 日志 / 记账', 'box', 700, 46, 320, 68, 'C2'),
|
||
('kcmd', '命令循环\ncommands + LISTEN/NOTIFY', 'box', 20, 46, 240, 70, 'C3'),
|
||
('kmatch', '契约匹配与排序\nprovides ↔ needs 拓扑', 'box', 280, 46, 240, 70, 'C3'),
|
||
('karb', '转发仲裁\n锁 / 超时 / 越权 / 成环', 'box', 540, 46, 240, 70, 'C3'),
|
||
('kproc', '驱动进程与状态\n/proc 判活 · 快照', 'box', 800, 46, 220, 70, 'C3'),
|
||
('r1', '主驱动 resident\n常驻等活 · LISTEN driver_<名>', 'box', 15, 46, 270, 70, 'C4'),
|
||
('r2', '副驱动 oneshot\n按需拉起 · 跑完收尸', 'box', 315, 46, 270, 70, 'C4'),
|
||
('skill', 'web.json = {html, css, js}\n由驱动解释执行(样本:Json解码)', 'box', 20, 46, 340, 80, 'C6'),
|
||
('t1', 'drivers', 'db', 20, 46, 190, 46, 'C5'),
|
||
('t2', 'driver_state', 'db', 230, 46, 190, 46, 'C5'),
|
||
('t3', 'events', 'db', 440, 46, 190, 46, 'C5'),
|
||
('t4', 'calls', 'db', 650, 46, 190, 46, 'C5'),
|
||
('t5', 'commands', 'db', 860, 46, 160, 46, 'C5'),
|
||
('t6', 'scans', 'db', 20, 108, 190, 46, 'C5'),
|
||
('t7', 'kernel_env', 'db', 230, 108, 190, 46, 'C5'),
|
||
('t8', 'kernel_runs', 'db', 440, 108, 190, 46, 'C5'),
|
||
('t9', '契约数据表(驱动产出 / 取用,表名由内核约定并注入)', 'db', 650, 108, 370, 46, 'C5'),
|
||
('sum', '一句话:引导器管内核(环境 / 包 / 进程)→ 内核管驱动(总调度 / 转发 / 反打架)→ 驱动互不认识、零通道。\n'
|
||
'所有状态与请求都落在 PostgreSQL 里 —— 零 socket、零自造协议。',
|
||
'note', 60, 1000, 1040, 80, '1'),
|
||
]
|
||
edges = [
|
||
('e1', 'C1', 'C2', V(), '命令 + 参数'),
|
||
('e2', 'C2', 'C3', V(), '启动并看住内核进程'),
|
||
('e3', 'C3', 'C4', ED(0.5, 1, 0.5, 0, bidir=True), '启停 / 转发 / 判活'),
|
||
('e4', 'C3', 'C5', ED(0.5, 1, 0.5, 0, bidir=True), '',
|
||
[(1120, 570), (1120, 770)]),
|
||
('e5', 'C4', 'C6', R(**GRAY), 'v0.2'),
|
||
('e6', 'C6', 'C5', ED(0.5, 1, 0.85, 0, **GRAY), 'v0.2 加载'),
|
||
('e7', 'C4', 'C5', ED(0.35, 1, 0.35, 0, **GRAY), '只写 calls / events,读契约表'),
|
||
]
|
||
containers = [
|
||
('C1', '用户 / CLI(人敲命令)', 60, 100, 1040, 110),
|
||
('C2', '① 引导器 UEFI.boot.py(短命 CLI · 零第三方依赖)', 60, 240, 1040, 140),
|
||
('C3', '② 内核(常驻 · 总调度 · 纯 CLI 命令 + PG 当内存)', 60, 410, 1040, 160),
|
||
('C4', '③ 驱动(互不认识 · 零通道 · 只声明「我要什么 / 我产出什么」)', 60, 600, 600, 140),
|
||
('C6', '④ 程序 / Skill(声明式 · v0.2 占位)', 720, 600, 380, 140),
|
||
('C5', 'PostgreSQL efi_kernel = 内存 / 总线(唯一通道)', 60, 770, 1040, 180),
|
||
]
|
||
return doc(1160, 1120, '内核项目 总览 / 用户级虚拟操作系统框架\n'
|
||
'四层:引导器(UEFI) → 内核 → 驱动 → 程序(Skill)',
|
||
(100, 20, 960, 54), nodes, edges, containers)
|
||
|
||
|
||
# ───────────────────────── 01 引导器 ─────────────────────────
|
||
def g01():
|
||
nodes = [
|
||
('st', '开始:python3 UEFI.boot.py <子命令>', 'start', 253, 60, 320, 50, '1'),
|
||
('n1', '读 环境.efi.json(唯一一份配置)', 'box', 283, 138, 260, 50, '1'),
|
||
('n2', '环境体检(6 项 · 纯 stdlib)\n解释器 / venv 健康 / 包 / 驱动目录 / PG', 'sub', 233, 216, 360, 62, '1'),
|
||
('d1', '体检通过 ?', 'diamond', 308, 306, 210, 80, '1'),
|
||
('f1', '打印原因 + 修复命令\n退出 1(不自动改)', 'box', 570, 314, 230, 64, '1'),
|
||
('n3', '写 环境状态.efi.json(快照,原子替换)', 'box', 253, 416, 320, 50, '1'),
|
||
('n4', '写 PG kernel_env\n(连不上 → WARN 降级,不算失败)', 'box', 263, 494, 300, 58, '1'),
|
||
('d2', '内核在跑 ?', 'diamond', 308, 580, 210, 80, '1'),
|
||
('n5', '拉起内核进程\n(常驻调度器)', 'box', 570, 588, 230, 64, '1'),
|
||
('n6', '把子命令交给内核(argv 透传)→ 等结果', 'box', 253, 690, 320, 50, '1'),
|
||
('n7', '打印内核输出 + 记 kernel_runs\n(起止 / 耗时 / 退出码)', 'box', 253, 768, 320, 58, '1'),
|
||
('en', '结束(不管子进程:驱动是独立进程组)', 'start', 253, 856, 320, 50, '1'),
|
||
('tip', '引导器自己的命令(自检 / 环境 / 包)PG 连不上只降级;\n透传给内核的命令不降级 —— 内核的内存就是 PG。',
|
||
'note', 60, 950, 700, 66, '1'),
|
||
]
|
||
edges = [
|
||
('e1', 'st', 'n1', V(), ''),
|
||
('e2', 'n1', 'n2', V(), ''),
|
||
('e3', 'n2', 'd1', V(), ''),
|
||
('e4', 'd1', 'n3', V(), '是'),
|
||
('e5', 'd1', 'f1', R(**RED), '否'),
|
||
('e6', 'n3', 'n4', V(), ''),
|
||
('e7', 'n4', 'd2', V(), ''),
|
||
('e8', 'd2', 'n6', ED(0.5, 1, 0.35, 0), '是'),
|
||
('e9', 'd2', 'n5', R(), '否'),
|
||
('e10', 'n5', 'n6', ED(0.5, 1, 0.75, 0), ''),
|
||
('e11', 'n6', 'n7', V(), ''),
|
||
('e12', 'n7', 'en', V(), ''),
|
||
]
|
||
return doc(827, 1060, '模块:引导器 UEFI.boot.py(内核的管家)\n'
|
||
'输入: 子命令 | 输出: 内核结果 + 环境状态快照 + 台账',
|
||
(93, 20, 640, 54), nodes, edges)
|
||
|
||
|
||
# ───────────────────────── 02 内核 总调度 ─────────────────────────
|
||
def g02():
|
||
nodes = [
|
||
('st', '开始:内核 boot / 收到「扫描」命令', 'start', 253, 60, 320, 50, '1'),
|
||
('n1', '扫 驱动/:认根目录的 配置.efi.json', 'sub', 253, 138, 320, 50, '1'),
|
||
('n2', '读配置 + 9 条校验\n(入口越界 / 重名 / 版本 / venv…)', 'box', 253, 216, 320, 58, '1'),
|
||
('n3', '建契约图:provides ↔ needs\n只认契约名,不认驱动名', 'io', 253, 302, 320, 58, '1'),
|
||
('d1', '契约齐全且无环 ?', 'diamond', 308, 392, 210, 80, '1'),
|
||
('f1', '相关驱动标 invalid\n(契约无人提供 / 成环)\n不影响其他驱动', 'box', 570, 392, 230, 80, '1'),
|
||
('n4', '拓扑排序 → 得到启动顺序', 'box', 283, 502, 260, 50, '1'),
|
||
('n5', '按序就绪等待:上游起来才放行下游', 'box', 253, 580, 320, 50, '1'),
|
||
('n6', '拉起驱动进程(resident 常驻 / oneshot 按需)', 'box', 253, 658, 320, 50, '1'),
|
||
('n7', '写 PG driver_state + 运行.efi.json 快照', 'box', 253, 736, 320, 50, '1'),
|
||
('n8', '进常驻调度环:听 commands / calls', 'box', 253, 814, 320, 50, '1'),
|
||
('en', '本轮调度结束(内核进程不退出)', 'start', 253, 892, 320, 50, '1'),
|
||
('f2', '上游崩 → 下游标「依赖失效」\n停上游 → 下游级联停', 'box', 600, 658, 200, 76, '1'),
|
||
('f3', '请求越权 / 调用成环 → denied\n超时 → timeout + 释放锁', 'box', 600, 814, 200, 76, '1'),
|
||
]
|
||
edges = [
|
||
('e1', 'st', 'n1', V(), ''),
|
||
('e2', 'n1', 'n2', V(), ''),
|
||
('e3', 'n2', 'n3', V(), ''),
|
||
('e4', 'n3', 'd1', V(), ''),
|
||
('e5', 'd1', 'n4', V(), '是'),
|
||
('e6', 'd1', 'f1', R(**RED), '否'),
|
||
('e7', 'n4', 'n5', V(), ''),
|
||
('e8', 'n5', 'n6', V(), ''),
|
||
('e9', 'n6', 'n7', V(), ''),
|
||
('e10', 'n7', 'n8', V(), ''),
|
||
('e11', 'n8', 'en', V(), ''),
|
||
('e12', 'n6', 'f2', R(**RED), '崩溃 / 级联'),
|
||
('e13', 'n8', 'f3', R(**RED), '反打架'),
|
||
]
|
||
return doc(827, 1000, '模块:内核 · 总调度(启动期)\n'
|
||
'输入: 驱动目录 | 输出: 启动顺序 + driver_state + 常驻调度环',
|
||
(93, 20, 640, 54), nodes, edges)
|
||
|
||
|
||
# ───────────────────────── 03 内核调用 时序 ─────────────────────────
|
||
def g03():
|
||
nodes = [
|
||
('a1', '① 写请求\ncalls: want=契约名', 'box', 20, 60, 230, 64, 'CA'),
|
||
('a2', '⑩ 读回数据\n(从 PG 契约表取)', 'box', 20, 890, 230, 70, 'CA'),
|
||
('p1', '② 请求入库\nstate = pending', 'box', 20, 60, 230, 64, 'CP'),
|
||
('p2', '③ NOTIFY 唤醒内核', 'box', 20, 150, 230, 50, 'CP'),
|
||
('p3', '⑦ 产出入库(契约表)', 'box', 20, 620, 230, 58, 'CP'),
|
||
('p4', '⑨ result 回填 + 释放锁', 'box', 20, 800, 230, 58, 'CP'),
|
||
('p5', '⑩ NOTIFY 请求方(或轮询)', 'box', 20, 890, 230, 58, 'CP'),
|
||
('k1', '④ 校验:谁提供 / 权限 / 锁 /\n超时 / 调用链成环', 'box', 20, 150, 260, 70, 'CK'),
|
||
('k2', '⑤ 已被占用 → 排队 waiting', 'box', 20, 270, 260, 50, 'CK'),
|
||
('k3', '⑥ 匹配提供方 → 转发', 'box', 20, 366, 260, 58, 'CK'),
|
||
('k4', '⑧ LISTEN 到完成 → 写 result', 'box', 20, 720, 260, 58, 'CK'),
|
||
('b1', '执行\nresident 收转发 / oneshot 被拉起', 'box', 20, 366, 230, 70, 'CB'),
|
||
('b2', '产出写 PG(不回传)', 'box', 20, 620, 230, 58, 'CB'),
|
||
('note', '防打架六条:① 同一 lock_key 串行化(先到先执行,后面排队)② 调用链成环 → denied ③ 提供方没起 → 先起 / 按需拉起\n'
|
||
'④ deadline 超时 → 内核收权并释放锁 ⑤ 只能要自己 needs 里声明过的契约 ⑥ 权限分家:驱动只写 calls / events,\n'
|
||
'commands 只有 CLI 和引导器能写,drivers / driver_state 只有内核能写\n'
|
||
'★ 左列(主驱动)与右列(副驱动)之间没有任何连线 —— 这就是「打架无从谈起」的物理保证。',
|
||
'note', 60, 1150, 1170, 150, '1'),
|
||
]
|
||
edges = [
|
||
('e1', 'a1', 'p1', R(), '① INSERT'),
|
||
('e2', 'p1', 'p2', V(), ''),
|
||
('e3', 'p2', 'k1', R(), '③ NOTIFY'),
|
||
('e4', 'k1', 'k2', V(), '否:已被占用'),
|
||
('e5', 'k2', 'k3', V(), '轮到自己'),
|
||
('e6', 'k3', 'b1', R(), '⑥ 转发(提供方由内核匹配)'),
|
||
('e7', 'b1', 'b2', V(), ''),
|
||
('e8', 'b2', 'p3', L(), '⑦ 写契约表'),
|
||
('e9', 'p3', 'k4', R(), '⑧ 内核 LISTEN 到完成'),
|
||
('e10', 'k4', 'p4', ED(0.5, 1, 0.5, 0), '⑨ 写 result + 释放锁'),
|
||
('e11', 'p4', 'p5', V(), ''),
|
||
('e12', 'p5', 'a2', L(), '⑩ 通知 done'),
|
||
]
|
||
containers = [
|
||
('CA', '主驱动(请求方 · 不认识对面是谁)', 60, 110, 270, 1000),
|
||
('CP', 'PostgreSQL efi_kernel(总线)', 350, 110, 270, 1000),
|
||
('CK', '内核(常驻调度器 · 唯一仲裁者)', 640, 110, 300, 1000),
|
||
('CB', '副驱动(提供方)', 960, 110, 270, 1000),
|
||
]
|
||
return doc(1320, 1330, '模块:内核调用时序(驱动 → 内核 → 驱动,数据经 PG)\n'
|
||
'输入: calls 请求 | 输出: result + 契约表产出(驱动之间零通道)',
|
||
(340, 20, 640, 54), nodes, edges, containers)
|
||
|
||
|
||
# ───────────────────────── 04 驱动 生命周期 ─────────────────────────
|
||
def g04():
|
||
nodes = [
|
||
('st', '开始:内核扫描驱动', 'start', 253, 60, 320, 50, '1'),
|
||
('d0', '配置合法 ?', 'diamond', 308, 138, 210, 80, '1'),
|
||
('f0', 'invalid\n拒载(不影响其他驱动)', 'box', 590, 146, 220, 64, '1'),
|
||
('n1', 'stopped(没在跑,正常态)', 'box', 283, 250, 260, 50, '1'),
|
||
('n2', 'starting(已 spawn,未确认存活)', 'box', 263, 328, 300, 50, '1'),
|
||
('d1', '探活通过 ?', 'diamond', 308, 406, 210, 80, '1'),
|
||
('f1', 'failed\n入口不存在 / 秒退(记 exit_code)', 'box', 580, 414, 230, 76, '1'),
|
||
('n3', 'running(/proc cmdline 校验通过才算)', 'box', 243, 514, 340, 56, '1'),
|
||
('n4', 'crashed(断电收尸)\nPG 说 running 但 /proc 不在', 'box', 610, 514, 200, 76, '1'),
|
||
('d2', '还在跑 ?', 'diamond', 308, 604, 210, 80, '1'),
|
||
('n5', 'exited(进程没了,记 exit_code)', 'box', 263, 710, 300, 50, '1'),
|
||
('d3', 'restart = on-failure ?', 'diamond', 308, 788, 210, 80, '1'),
|
||
('n6', 'stopped(收尾完成)', 'box', 283, 894, 260, 50, '1'),
|
||
('en', '结束:状态写 PG driver_state + 运行.efi.json 快照', 'start', 213, 972, 400, 50, '1'),
|
||
('tip', 'resident = 常驻等活;oneshot = 按需拉起、跑完收尸。\n两种状态机完全相同,区别只在「谁触发启动」。',
|
||
'note', 60, 1052, 700, 66, '1'),
|
||
]
|
||
edges = [
|
||
('e1', 'st', 'd0', V(), ''),
|
||
('e2', 'd0', 'n1', V(), '是'),
|
||
('e3', 'd0', 'f0', R(**RED), '否'),
|
||
('e4', 'n1', 'n2', V(), '启动'),
|
||
('e5', 'n2', 'd1', V(), ''),
|
||
('e6', 'd1', 'n3', V(), '是'),
|
||
('e7', 'd1', 'f1', R(**RED), '否'),
|
||
('e8', 'n3', 'd2', V(), ''),
|
||
('e9', 'n3', 'n4', R(**RED), '断电 / 被杀'),
|
||
('e10', 'd2', 'n3', ED(0, 0.5, 0, 0.5), '是(持续判活)', [(110, 644), (110, 542)]),
|
||
('e11', 'd2', 'n5', V(), '否'),
|
||
('e12', 'n5', 'd3', V(), ''),
|
||
('e13', 'd3', 'n2', ED(0, 0.5, 0, 0.5, **RED), '是 → 重拉,并按契约恢复下游', [(190, 828), (190, 353)]),
|
||
('e14', 'd3', 'n6', V(), '否'),
|
||
('e15', 'n6', 'en', V(), ''),
|
||
('e16', 'n4', 'n6', V(), '收尸完成'),
|
||
]
|
||
return doc(827, 1150, '模块:驱动生命周期(状态机)\n'
|
||
'输入: 扫描结果 / 启停命令 | 输出: driver_state + 运行.efi.json',
|
||
(93, 20, 640, 54), nodes, edges)
|
||
|
||
|
||
def validate(path: Path) -> None:
|
||
t = ET.parse(path)
|
||
cells = t.getroot().findall('.//mxCell')
|
||
ids = {c.get('id') for c in cells}
|
||
bad = [(c.get('id'), c.get('source'), c.get('target')) for c in cells
|
||
if c.get('edge') == '1' and (c.get('source') not in ids or c.get('target') not in ids)]
|
||
nv = len([c for c in cells if c.get('vertex') == '1'])
|
||
ne = len([c for c in cells if c.get('edge') == '1'])
|
||
print(f'{path.name}: {nv} 节点 {ne} 连线 断链={"无" if not bad else bad}')
|
||
assert not bad, bad
|
||
|
||
|
||
if __name__ == '__main__':
|
||
files = {
|
||
'00-总览.drawio': g00(),
|
||
'01-引导器-启动时序.drawio': g01(),
|
||
'02-内核-总调度.drawio': g02(),
|
||
'03-内核调用-时序.drawio': g03(),
|
||
'04-驱动-生命周期.drawio': g04(),
|
||
}
|
||
for name, xml in files.items():
|
||
p = OUT / name
|
||
p.write_text(xml, encoding='utf-8')
|
||
validate(p)
|
||
print('全部生成完毕 ->', OUT)
|
||
sys.exit(0)
|