简易文件系统
(简易)文件系统¶
貌似没有带来新的系统观,于是没有很多感悟,仅仅是OS的抽象落地了。。。。。。 * 了解需求(对接讲义,需求和系统级的实现有不同,教学级实现一般个性化) * 状态机心流(在脑子里构思,纸上大概写一下执行流,控制流) * 确定约束(对接手册具体细节,以及框架代码中具体数据类型实现,考虑需求转换可行性) * 实现最小闭环(整个机制流畅通过测试) * 优化(strace配套,个性化实现)
一切皆文件¶
为什么认为对每个设备进行系统调用是低可行性的?¶
先建立基准:syscall 的本质是什么? syscall 是内核对外暴露的、有限且稳定的服务接口。 关键词:有限、稳定。
核心矛盾:设备空间是开放集合,syscall 表是封闭集合 | | 设备 | syscall 表 | |---|---|---| | 规模 | 无上界,随硬件发展持续增长 | 有限,x86 Linux 目前约 300+ | | 变化 | 随时可以接入新设备 | ABI 级别的承诺,几乎不可变 | | 多样性 | 每类设备语义完全不同 | 必须在编译内核时静态确定 | 根本矛盾:你无法用一个静态的、有限的表,去覆盖一个动态的、无界的集合。
具体的不可行性 ① 数量爆炸且无法穷举 鼠标、键盘、磁盘、网卡、GPU、声卡、摄像头…… 每类设备的每种操作都要一个 syscall 号, 这个表在设计时就无法写完,更无法预见未来设备。 ② 接入新设备 = 修改内核 ABI 新设备需要新 syscall → 内核版本变动 → 所有依赖旧接口的用户程序面临兼容性断裂。 这违背了 OS 最基本的职责:对上层提供稳定抽象。 ③ 用户程序必须感知设备类型 程序要操作设备,必须知道"这是什么设备,该调哪个 syscall"。 这意味着设备的硬件细节泄漏到了用户态——抽象彻底失败。
一句话总结
设备是运行时动态接入的开放世界,而 syscall 表是编译时静态确定的封闭接口。用封闭接口直接对接开放世界,在工程上根本无法成立。 "一切皆文件"的本质,就是在两者之间插入一个语义稳定的中间层,把无界的设备操作,折叠进有界的
open/read/write/close原语里。
对于一切皆文件——水木石的思考¶
设备的本质:人类直接需求的硬件化表达。用户程序是人类需求的软件化表达。两者同属需求语义空间——无界、异构、不可枚举。
syscall 直接抽象设备为何失败:试图在需求语义空间上建立有限映射,数学上不可能。不是因为设备"太多"或"差异太大"这种工程表象,而是用有限接口覆盖无界空间,在逻辑上就不成立。
破局:换抽象维度:放弃在语义层抽象,下沉到物理层。所有设备无论语义多复杂,在物理层只做一件事——在某地址空间上读写二进制序列。这个维度有界、同构、可被有限原语完备覆盖。
"扁平化"的代价与收益:将设备从需求空间压扁投影到物理空间,代价是主动丢弃语义;收益是获得稳定、有限、同构的接口。丢失的语义不是消失了,而是责任上移,交由用户程序和库自行重建。
结论:"一切皆文件"不是一个工程技巧,而是一次抽象维度的根本性转换:内核不理解设备在说什么,只负责搬运字节;意义由人来赋予。
对水木石的思考——的一点思考¶
本体论优先(Ontology First):
在问"怎么做"之前,先问"这个东西本质上是什么"。
大多数人的路径:
现象 → 工程解法 → 记住结论
水木石的路径:
现象 → 追问本质 → 本质暴露矛盾 → 矛盾逼出解法 → 结论是推导的必然
后者的结论不需要记忆,因为它是可以被重新推导出来的。
能否复用?能,而且有具体操作¶
这不是灵光一现,它是一个可以显式执行的方法:
① 对任何设计决策,先做本体论定位
"X 的本质是什么?它属于哪个空间?有什么固有属性?"
② 找空间之间的结构性矛盾
"现有方案试图在什么空间上操作?这个空间的属性和需求兼容吗?"
③ 矛盾不可调和时,换维度而不是修补
"有没有另一个空间,其固有属性天然匹配需求?"
这个方法有一个容易失效的地方:本体论定位做错了,后续推导全部作废。
把设备定位为"需求空间的东西"是准确的,所以推导成立。但如果第一步定位偏了,逻辑链越严密,跑偏越远。
所以复用这个方法时,第一步的定位需要反复质疑,不能因为后续推导流畅就认为前提正确。
功能实现的套路与哲学——水木石的思考¶
>本质——路径依赖——自顶向下的规约与架构设计 (Top-Down Specification & Architecture)¶
需求思考:RRM (Requirement-Resource-Mapping) 架构思维模型¶
阻抗匹配 (Impedance Matching)。一个成熟的系统工程师在写下第一行代码前,必须在脑内或白板上完成以下四个步骤:
-
- 目标规约 (Target Specification)
- 定义:剥离所有实现细节,用最精确的语言描述最终状态与约束。
-
关注点:输入是什么?输出是什么?生命周期是什么?有哪些隐藏的非功能性约束(如时间复杂度、单调性)?
-
- 资源盘点 (Resource Inventory)
- 定义:向下看,底层(硬件、OS、库)为你提供了哪些原语 (Primitives)?
-
关注点:这些原语的数据结构、单位、精度、副作用是什么?
-
- 阻抗匹配 (Impedance Matching / Gap Analysis)
-
定义:对比“目标规约”与“底层资源”,精准定位它们在维度、单位、状态上的断层 (Gap)。
-
- 逻辑合成 (Logic Synthesis)
- 定义:设计转换逻辑(数学运算、状态机、缓冲机制)来填补断层。
💡 简要案例:NDL_GetTicks 的 RRM 推演¶
- 1. 目标规约:需要一个函数,返回自
NDL_Init被调用起,经过的毫秒数 (ms)。类型为uint32_t。要求相对时间。 - 2. 资源盘点:底层只有
gettimeofday。它返回的是自 1970 年以来的绝对时间,单位是秒 (s) 和微秒 (us),数据结构是struct timeval。 - 3. 阻抗匹配 (断层分析):
- 单位断层:底层是 s 和 us,目标是 ms。
- 维度断层:底层是绝对时间,目标是相对时间。
- 类型断层:底层是结构体,目标是 32 位无符号整数。
- 4. 逻辑合成:
- 解决单位断层:$$ms = s \times 1000 + us / 1000$$。
- 解决维度断层:在
NDL_Init中引入全局静态变量boot_time记录初始绝对时间。每次调用时计算差值:$$\Delta t = current_time - boot_time$$。 - 解决类型断层:将计算结果强制转换为
uint32_t。
编码细节+需求与原理的双重刺穿¶
(综合性能考虑)与(编码逻辑链路)
架构认知升级 (Architecture Insights)¶
- 路径分离 (Cold/Hot Path Isolation)
- 反思:模块在用户层被调用时,必须基于其执行频率指导编码。
- 实践:
poll_event属于极高频的数据流(Fast Path),每秒执行成千上万次。寻找文件 (fs_open) 和变量初始化属于控制流(Slow Path),必须剥离并前置到初始化函数中。否则将引发严重的性能雪崩(如游戏掉帧)。
需求在系统中的思考方式¶
- 双向推演模型 (Bi-directional Deduction) 面对贯穿全栈的复杂链路,单向追踪极易迷失。必须采用“两头堵截法”:
- Top-Down:从用户态
read-> Syscall (a0寄存器传递) -> VFS (fs_read)。 - Bottom-Up:从物理按键 -> SDL 窗口捕获 -> MMIO 寄存器 ->
events_read插桩 (printf验证)。 通过双向推演,在中间层(VFS 路由)完成逻辑闭环。