Files
efi-kernel/文档/07-模块与接口.md
T
lou 53e7d1e5f2 底座文档 11 份 + 修掉"同锁调用互相排队、双双卡死"
文档/ (2026-09-16; 老板定调: 这是 agent 底座, 所以必须扎实, 现在越扎实以后开发越简单)
  00-索引           文档地图 + 30 秒概念速查 + 三条命令跑起来 + 事实源优先级
  01-快速上手       体检 -> 建库 -> 起内核 -> 起驱动 -> 收工, 全带实测输出; 第一次最易踩的四个坑
  02-写一个驱动     五分钟最小驱动 / 形态选择 / 能碰哪些表 / 汇报与调用两份模板 / 交付检查表
  03-命令手册       两层每条命令 + 日志选项 + 退出码约定 + --json 样例 + 日常十条
  04-契约与调用     一次调用的完整生命周期 / 六条仲裁 / 锁与按需拉起 / 排障表
  05-日志与排障     三条道怎么读 + "症状->判据->处置"总表 + 断电收尸语义
  06-架构与不变量   分层 / 14 条硬不变量 / 主流程表 / 双真相 / 为什么故意不做 / 已知薄弱点
  07-模块与接口     逐模块职责与公开接口 + "想改 X -> 动哪几处"连带清单
  08-数据模型       8 张表逐字段 (谁写谁读) + events.kind 字典 + 状态机 + 快照 + 排查 SQL
  09-扩展指南       六个配方 (加子命令/加字段/加表/加日志来源/加自测/改判定) + 同步清单
  10-验收与质量门   四道门 + 五份自测明细 + pyright 严格档 + 26 条已知坑总表 + 发布 checklist
  规矩: 不重复设计文档 / 每条命令实测过再写 (含 jq 表达式) / 代码>设计>文档 的事实源优先级 /
        改代码必须同步文档 (清单在 09 末尾) / 暂时没做到的事写成"已知边界"不含糊过去

修复: 同锁串行化原来是死的 (实测抓到的真缺陷)
  旧行为: db.领调用 只领 pending (waiting 没人再碰) + db.同锁在跑 把 waiting 也算"占着锁"
          -> 同一把锁上两条请求互相排队, 双双停在 waiting 谁也不跑 (实测 id 16/17);
             而 收权超时 只收 running -> 排队连超时都没有 = 死锁
  修法:   ① db.领调用 的 SQL 改 state IN ('pending','waiting') -- 每轮把排队的领回来重判, 锁一空就推进
          ② db.同锁在跑 只认 state='running' (排队的还没拿到锁, 不挡人)
          ③ 内核.转发调用 waiting 分支补 deadline (排队也立期限); 内核.收权超时 遍历 running + waiting
          ④ 抽出 内核.期限文本() 统一算 deadline
  实测:   两条同锁调用串行跑完 (19.started_at == 18.finished_at); 排队者超时被收权 (events 有记录)
  回归:   自测db.py 调用组 +4 条断言 (waiting 不算占着锁 / waiting 会被重新领 / ...);
          去掉一条依赖生产库全局计数的脆弱断言

其它: 内核 与 引导器 的 用法() 末尾加文档指引
验收: uvx pyright 0 errors / 0 warnings; 五份自测全过 (进程/内核 58/配置/db/日志 86);
      试跑引导器.py PASS 11 / FAIL 0 / 残留无; 残留进程 0
2026-09-16 21:13:31 +08:00

13 KiB
Raw Blame History

07 · 模块与接口(改哪一层、动哪几处)

一套职责一个文件,不合并;一份实现不复制进程/日志/文本/状态 都是唯一实现,内核与引导器共用)。 这份文档给"要改代码的人":每个文件管什么、对外接口是什么、改一件事要连带改哪些地方。 函数级细节看源码里的 docstring(每个函数都写了"为什么")。

1. 依赖方向(只能自上往下,不许反向)

UEFI.boot.py ──┬─► 进程.py          通用进程库: 启停 / 判活 / 收子树 / 日志读取
               ├─► 日志.py          日志系统: 写 / 解析 / 过滤 / 轮转 / 实时跟
               ├─► 文本.py          CJK 宽度感知的对齐 / 表格 / 横线
               └─► db.py            唯一碰 SQL 的文件 (只建引导器那两张表, 写 kernel_env/kernel_runs)

内核/内核.py ──┬─► 扫描.py ──► 状态.py ──► 进程.py
               ├─► 状态.py
               ├─► db.py            唯一碰 SQL 的文件 (建全部 8 张表)
               ├─► 日志.py / 文本.py / 进程.py
               └─► 汇总: 命令分发 / 单驱动动作 / 常驻调度

驱动/<名>/*.py ──► psycopg2 直连 PG (只写 events / calls; 不 import 项目里任何模块)

规矩:db.py唯一写 SQL 的地方(别处一律不出现 SQL 字符串);进程.py / 日志.py / 文本.py唯一实现(哪一层要用都 import 它,不许抄一遍)。

2. 各模块职责与接口

UEFI.boot.py — 引导器(纯 stdlib

关键接口 说明
配置 读环境() -> (配置, 警告) 缺文件按默认模板生成;解析失败不覆盖用户文件,用默认值继续 + WARN
体检 体检(配置) -> 体检报告 / 打印体检 / 报告快照 / 例行体检(配置, 详细) 6 项(1-5 阻塞、6 PG 只 WARN);例行体检 = 体检 + 写快照 + 记台账 + 按需打印
读包要求 / 读实装包 / 核包 / 解析版本 / 满足要求 自己撸的版本比较(不引 packaging);uv pip list 失败回落 importlib.metadata
venv venv路径 / 检查venv健康 / 环境重建 重建 = 旧 venv 改名留退路(.venv.bak-<时间戳>)→ uv venv / python -m venv → 复检
内核进程 内核状态 / 内核启动 / 内核停止 / 内核重启 / 内核日志 全部走 进程.py--守护进程.启动stdout/stderr → 内核.out.logEFI_LOG_CONSOLE=0
记账 PG连接 / PG记体检 / 写快照 引导器自己的命令 PG 不通只 WARN(降级写 环境状态.efi.json
日志 记日志(级别, 消息, 安静=False) / 起日志(配置) / 打日志(路径, 选, 称呼) 引导器动作落 引导器.log;每次跑先轮转一次自己的
分发 main(argv)分发(argv, 配置) 顶层 5 词:自检/环境/包/内核/日志;其余原样透传内核

不做:不实现内核的子命令、不改驱动配置、不自动修环境/装包/重建。

内核/内核.py — 常驻总调度 + 单驱动动作

关键接口 说明
日志/配置 说(级别, 消息) / 用环境(环境) / 读环境 / 驱动根 / 日志行数·日志上限·日志保留 / 轮转日志 说() 是薄包装(落 内核.log;是否打 stderr 看 EFI_LOG_CONSOLE
连库 连库 / 重连 连不上直接退出(不降级);重连 失败 10 次退出,让看门狗发现
拼命令 拼命令(驱动, 环境) -> (argv, env, 警告表) 解释器解析 + args/env/EFI_DB 注入;cwd = 驱动根
单驱动动作 拉起一个 / 停一个 / 收僵尸 / 下游名 / 停序 拉起一个 是启动/重启/按需/自动重拉唯一实现停一个 级联停下游
调用仲裁 取链 / 校验调用 / 期限文本 / 转发调用 / 收权超时 六条仲裁;转发 = 写 running + pg_notify driver_<提供方>
巡检 巡检(连接, 环境) 收尸 → 重拉 → 级联标"依赖失效" → 收权超时
CLI 执行命令(连接, 环境, 命令, 参数) + 命令列表/扫描/启动/停止/重启/状态/日志/事件/清单 CLI 与常驻内核共用这一份实现(不复制)
日志命令 命令日志 / 打印某个日志 / 打印全部驱动日志 / 打印日志台账 / 用法日志 四形态:台账 / 单驱动 / --全部 / --内核·--引导器·--输出
常驻 命令调度(连接, 环境) 独一份锁 → 收尸 → 扫描 → autostart → LISTEN → 主循环(命令/调用/巡检/心跳/定期重扫)

不做:不解析驱动 stdout(只重定向)、不替引导器体检环境。

内核/扫描.py — 认文件夹 + 校验 + 契约

接口 说明
扫目录(驱动根) 只认根目录有 配置.efi.json 的文件夹(按名字排序,结果稳定)
读配置(目录) json 读不了给 (空, 原因)
校验单个(目录) -> 注册表行 第 2-6 条校验(JSON/版本/runtime/entry 路径安全/entry 存在+x 位);不过也返回(带 valid=False + error
查重名(记录表) 第 7 条(后到的 invalid
取契约 / 定契约 第 8 条(收 provides / 查 needs / DFS 找环),最多 3 轮
排顺序(记录表, 契约) 拓扑排序(提供方在消费者前,无依赖按名字)
扫描(连接, 驱动根, 内核版本) -> 扫描结果 全流程 + 收尸 + 落快照 + 记 scans/events(scan)
扫描结果dataclass 驱动/状态表/顺序/契约/问题/该拉起/清单版本/总数/有效/无效/在跑

不做:不碰 SQL(走 db.py)、不起进程(那是内核的单驱动动作)。

内核/状态.py — 状态机 + 快照 + 收尸判定

接口 说明
常量 配置名/快照名/日志目录名/快照版本7 个状态常量 + 中文表 + 显示状态()
路径 驱动根/入口路径/日志目录/日志路径(从注册表行算,唯一路径来源)
快照 读快照 / 写快照(原子:.tmp + os.replace/ 组装快照 / 待重启
判定 复核(驱动, 状态行) -> (状态, 原因)(判活回 /proc/ 该拉起(驱动, 状态行, 判定) -> 理由

不做:不碰 SQL(快照是文件、判定是纯逻辑)。

内核/db.py — 唯一碰 SQL 的文件(43 个函数 / 8 张表)

关键接口
连接 数据库(dataclass) / 从配置 / (autocommit) / 建表 / 建引导器表 / 库存在 / (公开转手,给自测/诊断)
注册表 记驱动(upsert) / 清不在 / 取驱动 / 取全部驱动
状态 确保状态行 / 写状态(白名单动态 UPDATE,只改传进来的列) / 取状态 / 取全部状态
事件 写事件(连接, source, kind, message, driver=None, level="info", data=None) / 读事件(条数, driver=None)
扫描 记扫描批次 / 取最近扫描
命令 记命令 / 领命令(pending→runningFOR UPDATE SKIP LOCKED) / 记命令结果 / 读命令 / 收尸命令
调用 领调用(pending/waiting→waiting) / 写调用 / 读调用 / 取调用(state) / 同锁在跑(只认 running)
通知/锁 监听 / 收通知 / 通知 / 试锁(会话级咨询锁)
台账 记体检 / 记运行开始 / 记运行结束 / 记内核pid / 最近运行 / 未结束运行 / 收尾未结束 / 今日运行次数

规矩text[] 列传 listjsonb 列传 "json.dumps 出来的串 + ::jsonb 强转"写调用result 给 dict/list 时也会自动 dumps);写状态/写调用 都有列白名单,拼错的列名会被挡(自测里有)。

内核/进程.py — 通用进程库(内核管驱动、引导器管内核,都用这一份)

接口 说明
读cmdline / 读stat / 读进程信息 / 读开机秒 直读 /proc(不用 psbusybox 截断、`ps
判活(pid, 入口) -> 状态字符串 running/stopped/crashed/exited/zombie入口决定是否认领
匹配入口 / 按入口找进程 / 进程组成员 cmdline 精确点名;组员跳僵尸(Z 杀不动)
启动(argv, cwd, 日志, env, 入口, 探活秒, 分隔="") 独立会话 + stdio 重定向 + 分隔头 + 探活;秒退不抛,返回 ok=False + 本次日志尾巴
停止(pid, 入口, 超时) 校验 cmdline → SIGTERM 进程组 → 超时升 SIGKILL → 复查
从位置读日志 / 尾日志 / 跟日志 进程层通用读取(带过滤/轮转感知的看 日志.py
跑命令 / 跑并转发 前台跑并回收输出(引导器透传内核用)

内核/日志.py — 日志系统(见 设计/04

接口 说明
路径 内核日志路径/内核输出路径/引导器日志路径/驱动日志路径(集中定义,别再各处拼)
级别 级别表 / 规范化级别 / 级别序号 / 达标
记(路径, 级别, 来源, 消息, 门槛, 控制台) / 追加
解析 解析行(拆字段)/ 猜级别(驱动裸行)/ 命中(过滤判据)
尾(路径, 行数, 级别, 关键词, 含轮转) / 跟(路径, 级别, 关键词)(按行缓冲 + 轮转感知)
轮转 轮转(路径, 上限字节, 保留份数) / 轮转名单 / 带序路径
CLI 选项(dataclass) / 解析选项(参数, 默认行数) / 打印(行表, json输出) / 转json行
大小文本 / 现在文本 / 控制台开()

内核/文本.py — CJK 宽度感知的输出

左/右(对齐字面量)、字符宽显示宽度截断填充横线表格(表头, 行表, 对齐=None)打印。 中文是双宽 —— 直接用 len() 排表格是歪的,所有表格都过它。

验收与自测文件

文件 覆盖
内核/自测进程.py 真起进程、真收子树(11 项)
内核/自测内核.py 58 项纯逻辑:脏配置 / 契约 / 环 / 状态机 / 拼命令
内核/自测配置.py 配置解析 / 版本比较
内核/自测db.py 真库真 SQL 182+ 项(11 组,三层隔离:前缀 / 事务回滚 / 两道终检)
内核/自测日志.py 86 项:真写文件 / 真轮转 / 真起子进程跟日志 / 真起进程验分隔头
内核/自测AST等价.py 工具:改注释后证明"逻辑一行没动"
试跑引导器.py 端到端 11 项(老板亲自跑的验收)

3. 想改 X → 动哪几处(连带清单,漏一处就是坑)

想改 必须同步
加/改一个内核子命令 内核/内核.py执行命令 + 命令Xxx + 用法() UEFI.boot.py 用法()(透传清单)、文档/03自测内核.py(纯逻辑那部分)
加/改驱动配置字段 扫描.校验单个 + db.drivers DDL 设计/01 字段表、状态.组装快照(要进快照的话)、自测db.py(注册表组)、文档/02
加状态取值 状态.py 常量 + 中文表 db.driver_statetext,不用改)、状态.复核/该拉起自测内核.py设计/01 状态机、设计/02 判定表、文档/05
加一张表 db.内核表 里的 DDL 字符串 + 读写函数 自测db.py 的两道终检(8 张表 → 9 张)、文档/08设计/02
改调用仲裁某条 内核/内核.py校验调用+ db.同锁在跑 之类) 自测db.py 调用组、文档/04 六条表、设计/02
改日志格式/选项 内核/日志.py(解析/格式化/选项) 内核/内核.py 命令日志 + 引导器 内核日志/命令日志(共用解析)、设计/04文档/03/05自测日志.py
改体检项 UEFI.boot.py体检 + 检查项 设计/03试跑引导器.py(第 2 项查 6 项齐全)、文档/01
改轮转策略 内核/日志.py 轮转 + 调用点(内核 轮转日志 / 引导器启动段) 设计/04自测日志.py 第 5 组、.gitignore(如果加了新文件后缀)
改"什么时候自动拉起" 状态.该拉起 自测内核.py设计/02 第 5 节、文档/05 断电表
加一个样板驱动 驱动/<新名>/(配置 + 入口) 设计/01 若有新字段、文档/02 的样板清单

改完一律跑uvx pyright + 五份自测 + python3 试跑引导器.py(明细见 10-验收与质量门.md)。