文件系统与系统文件用途

一个约 8 MB 的裸机系统,为什么还要自己写文件系统?答案在分工:romfs 只读、随镜像走;TinyFS 可写、持久化到磁盘。下面按模块拆解两套 FS、分区表、多磁盘抽象,以及 romfs 里 20 个真实文件的用途。

引言:两套文件系统,各管一摊

8 MB 的体量装不下一个通用文件系统(ext2/fat 都太重、还要驱动层)。Genesis 把需求劈成两半:稳定运行所需的、永远不变的代码与配置放进 romfs;用户会改、要活过重启的数据放进 TinyFS。两者都挂进同一个 VFS,对用户态看起来是同一棵树。

  1. romfs 只读 · 压缩 · 编译进内核镜像
  2. VFS 统一挂载点 /
  3. TinyFS 可写 · 持久化 · 落在磁盘分区
  • romfs

    内核编译期由 tools/build_users.py 打包、压缩,启动时由内核解压挂载。不可写 —— 所有写请求走到这里会被 VFS 拒绝。

  • TinyFS

    自研、落在磁盘分区(基址由 MBR 决定)。首次启动自动格式化并写入内置文件,之后的用户改动都活在这里。

romfs:只读、压缩、编译进内核

romfs 是一条极简的串行记录流。内核启动时由 vfs_load_romfs 调 kernel/inflate.c 的 inflate_raw 解压,再挂上 VFS。它没有目录树结构 —— 目录只是名字里带斜杠的“伪文件”,由 VFS 在挂载时展开。

记录格式(每条文件一条)

压缩方案

flag=1 时,内容用 zlib 的 raw-deflate 编码(Python 侧 compressobj(wbits=-15) 生成)。内核侧不依赖完整 zlib,而是自己实现 kernel/inflate.c 里的 inflate_raw 做流式解压,从而把解压器压进极小的内核镜像。

两边必须同步改格式

打包在 tools/build_users.py,解包在内核 kernel/vfs.c。只要改了记录布局(例如多一个字段),这两个文件必须一起同步:打包端写出的字节顺序、长度字段语义,必须和内核解包端逐字节对应,否则启动期解压挂载会错位、文件系统“看不见”。

实测体积

压缩后 romfs 实测 56 KB(上一代同类系统 336 KB,约 6× 更小)。这 56 KB 里塞着 20 个文件:从 4.6 KB 的 tinysh 到 210 KB 的 Lua 解释器都在内。

TinyFS:可写、持久化、在磁盘上

TinyFS 是 Genesis 自研的文件系统,实现于 kernel/fs/。它不写死任何起始地址 —— 基址 LBA 2048 只是一个“分区表决定后才生效”的默认值。布局从基址起:bitmap 32 扇区 + table 32 扇区,数据从第 65 扇区开始。

磁盘布局(从基址 LBA 2048 起)

LBA 2048 分区基址
bitmap 32 sectors
table 32 sectors
data 起点 sector 65

所有偏移都相对分区基址,不是绝对扇区号;基址来自 MBR,见下节。

首次启动:自动格式化

TinyFS 卷不存在时,内核会在首次启动自动格式化并写入内置文件(/etc 下的配置、安装标记等)。这意味着用户拿到的是“已经能用的盘”,无需手动 mkfs。

用 MBR 定位分区基址

挂载前必须调 mbr_lookup() 从分区表读出 TinyFS 分区的起始 LBA。漏掉这一步会回退到默认 LBA 2048:对于自定义分区的盘,重启后内核找错位置,表现为“认不出文件系统”并重新格式化 —— 用户数据凭空消失。这是 TinyFS 最容易踩的坑。

tfs_set_limit():防止格式化越界

tfs_set_limit() 显式限定 TinyFS 分区能占用的扇区数。设置上限后,格式化只会在划定范围内写 bitmap/table/data,不会越界吃掉同盘上别的分区(例如用户空间或别的系统)。

分区表:扇区 0 既是引导扇区,又是 MBR

Genesis 让引导扇区和标准 MBR 共用同一个扇区 0。引导代码与数据只有 446 字节预算(0x000..0x1BD),剩下的 0x1BE..0x1FD 是 4 条标准分区表项;末尾 0x1FE..0x1FF 是 0x55AA 引导签名。

分区类型号

  • 0x7E

    内核分区,标记为活动(active)。引导器从这里加载内核。

  • 0x7F

    TinyFS 分区。mbr_lookup() 按这个类型号找到它,从而拿到正确的起始 LBA。

为什么不能写死 LBA

基址来自分区表,不是常数。把 LBA 2048 写死进代码,遇到非默认分区布局的盘就会读错位置(见 TinyFS 一节)。正确做法是 mbr_lookup() 动态取出,再用 tfs_set_limit() 收住边界。

多磁盘:ATA 抽象层

ATA 多磁盘抽象层(kernel/disk/)用一个注册表支持多块盘。每块盘是一个 disk_t;控制器驱动自己管寄存器与队列,因为读写回调收的是 void *drv 私有状态。

默认盘转发:存量调用者零改动

抽象层保留一个“默认盘”指针。旧的、无参的 ata_read / ata_write 等 API 全部转发到默认盘 —— 这样早期直接调这些无参函数的代码不必改成“先查盘、再带 drv 调 ops”,存量调用者几乎不用动。

系统文件用途总览(romfs 里 20 个文件)

下面是 romfs 里真实存在的 20 个条目,逐个说明用途与大小(字节)。目录 / /bin /home /etc /sys /dev 由 VFS 从带斜杠的名字展开,不占 romfs 记录。

romfs 内置文件清单(大小单位:字节)
路径 大小 用途
/bin/tinysh.TNCR 104144 默认用户态 shell(TNCR 程序),登录后自动进入。
/bin/LUA.TNCR 210584 Lua 5.4.7 解释器(TNCR 程序),可 REPL 或执行 .lua 脚本。
/bin/SSHD.TNCR 114500 SSH 服务端(TNCR 程序),独立的用户态进程。
/bin/CC.TNCR 162680 C 编译器(TNCR 程序),独立的用户态进程。
/bin/EDIT.TNCR 45980 内置编辑器(TNCR 程序),独立的用户态进程。
/bin/JVM.TNCR 5851 JVM 运行时(TNCR 程序),独立的用户态进程。
/bin/hello.TNCR 214 示例程序:最小可运行用例。
/bin/count.TNCR 877 示例程序:计数演示。
/bin/basic_demo.TNCR 500 示例:基础语法演示。
/bin/cpp_demo.TNCR 339 示例:C++ 风格语法演示。
/bin/minic_demo.TNCR 500 示例:最小程序演示。
/bin/net_demo.TNCR 467 示例:网络用法演示。
/bin/gui_demo.TNCR 470 示例:图形界面用法演示。
/bin/tiny_demo.TNCR 238 示例:迷你程序演示。
/DESKTOP.TNCR 34 桌面程序,VGA 文本桌面的入口。
/home/readme.txt 804 系统说明文档(只读,随镜像走)。
/home/notes.txt 660 笔记文件(只读,随镜像走)。
/home/hello.mc 1215 方块脚本示例(Minecraft 风格 / 方块世界脚本,非 C)。
/etc/passwd 85 账号数据库:用户名与 UID 等(不含口令)。
/etc/shadow 224 口令哈希库:存放各账号的口令哈希(受保护)。

目录(由 VFS 展开,不占 romfs 记录)

根下的目录:/ /bin /home /etc /sys /dev。/sys 与 /dev 是内核在挂载时虚拟出来的(设备与系统信息),不是 romfs 里的真实条目。

首次启动后写到磁盘的文件(TinyFS,可写)

  • /etc/sshd.conf

    SSH 服务配置,由安装向导/SSHD 写入。

  • /etc/net.conf

    网络配置,记录 IP 等安装时设定的网络参数。

  • /etc/installed

    TinyOS Genesis v0.1 installed by the setup wizard

    安装标记;内容即上方字符串,表明已跑过安装向导。

用户程序如何访问文件系统

TNCR 用户程序不能直接调内核的 vfs_* 函数。它们只能通过 tinyos_api_t 函数指针表访问内核能力 —— 这是一道架构边界:用户态拿到的是受控接口,内核维持对 VFS、内存、进程的唯一控制权。

Genesis v0.1 在表末尾追加的扩展字段

下表字段都追加在 tinyos_api_t 的末尾。因为只是“往后加”,磁盘上已有的旧 TNCR 程序在调用时仍然按老布局对得上前面的字段,新字段对它不可见 —— 所以旧程序不会被破坏(向后兼容)。

绑定方式:静态初始化器

这些字段由 kernel/genesis_api.c 实现,并在 kernel/api.c 的 g_api 静态初始化器里完成绑定。绑定发生在编译期,不依赖运行期初始化顺序 —— 即便别的模块还没跑起来,函数指针表已经就绪。

架构含义:为什么不能直接调 vfs_*

vfs_* 是内核私有符号,用户态既看不到也没有调用特权。所有访问都经 tinyos_api_t 这一道“安检门”,内核可以校验路径、权限与配额,也方便将来替换底层实现而不影响用户程序。

磁盘占用实测(来自引导日志)

下面是真实引导日志里 TinyFS 挂载时打印的一行,可直接核对上面对布局的描述。

  • total 129024

    TinyFS 分区总扇区数(约 63 MB @ 512B/扇区)。

  • bitmap 32 / table 32

    位图与索引表各占 32 扇区,排在分区基址之后。

  • data start 65

    数据区起点扇区 = 基址 + 32 + 32 + 1(扇区从 0 计),与上节布局图一致。

注意:日志里的 2048 来自 MBR 分区表,不是写死常数;若分区布局不同,这个数字会变。