TobudOS 中的 MicroPython os 模块实战指南:文件系统访问、VFS 挂载与块设备接口

原创2026-10-11 20:29:021,409 阅读

TobudOS 中的 MicroPython os 模块实战指南:文件系统访问、VFS 挂载与块设备接口

导读

os 模块是 MicroPython 提供"基本操作系统服务"的核心模块,涵盖文件系统访问与挂载、终端(REPL)重定向与复制,以及 uname、urandom 等系统级函数。本文以 TobudOS 仓库中 os.rst 文档为骨架,结合 moduos.c、vfs.c、vfs_tos.c 等源码与 os.py 示例脚本,完整讲解每个 API 的签名、参数含义、返回结构以及底层实现原理。读完本文,你将能够在 TobudOS 的 MicroPython 运行时中熟练完成目录遍历、文件增删改查、文件系统挂载/卸载,并理解如何为自定义存储硬件实现块设备驱动。

一、os 模块功能总览

在 MicroPython 中,os 模块(模块内部名为 uos,通过弱链接暴露为 os)主要提供三大类能力:

  1. 通用函数:uname() 获取系统信息、urandom(n) 获取随机字节;
  2. 文件系统访问:chdir、getcwd、ilistdir、listdir、mkdir、remove、rmdir、rename、stat、statvfs、sync 等;
  3. 文件系统挂载(VFS):mount、umount,以及 VfsFat、VfsLfs1、VfsLfs2 三类文件系统类。

从源码层面看,这些函数的注册表定义在 extmod/moduos.c 的 os_module_globals_table 中。值得注意,表中还出现了 unlink(它是 remove 的别名,{ MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }),以及 MicroPython 扩展的 dupterm、errno 等条目。

在 TobudOS 的 port/mpconfigport.h 中,该模块的启用条件与 VFS 特性开关如下:

#ifdef MP_USING_VFS
#define MICROPY_PY_IO                   (1)
#define MICROPY_PY_UOS                  (1)
#define MICROPY_READER_VFS              (1)
#define MICROPY_VFS                     (1)
#define MICROPY_VFS_TOS                 (1)   // 使用 TobudOS 原生 VFS 层
#define MICROPY_VFS_POSIX               (0)
#define MICROPY_VFS_FAT                 (0)   // FAT 由 TobudOS VFS 桥接,而非 MicroPython 自带实现
#else
#define MICROPY_PY_IO                   (0)
#define MICROPY_READER_VFS              (0)
#endif

即在 TobudOS 移植中,os 模块与 VFS 功能默认随 MP_USING_VFS 宏一同开启,并且文件系统能力是桥接到 TobudOS 内核自带的 VFS(MICROPY_VFS_TOS)之上,这一点是理解后文所有源码示例的关键前提。

二、通用函数:uname 与 urandom

2.1 uname() —— 获取系统信息

uname() 返回一个包含底层机器和/或操作系统信息的元组(在支持具名元组的平台上是一个具名元组)。元组共 5 个字段,全部为字符串,顺序固定:

字段 含义
sysname 底层系统名称
nodename 网络名称(通常与 sysname 相同)
release 底层系统版本
version MicroPython 版本与构建日期
machine 底层硬件标识(如板卡、CPU)

在 moduos.c 中可以看到该元组的真实构造方式:它通过 MP_DEFINE_ATTRTUPLE 定义了一个包含 sysname/nodename/release/version/machine 五个字段的具名元组,其中:

  • sysname、nodename 取 MICROPY_PY_SYS_PLATFORM 宏;
  • release 取 MICROPY_VERSION_STRING;
  • version 取 MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE 组合;
  • machine 取 MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME 组合。

因此在 TobudOS 的 MicroPython REPL 中执行:

import os
print(os.uname())

输出的 machine 字段会形如"某板卡 with 某 MCU",version 会带上 git 标签和构建日期。uname() 由 MICROPY_PY_UOS_UNAME 宏控制,TobudOS 的 mpconfigport.h 中已默认置 1 启用。

2.2 urandom(n) —— 获取随机字节

urandom(n) 返回一个包含 n 个随机字节 的 bytes 对象。只要可能,它由硬件随机数发生器(HRNG)生成;在没有硬件 RNG 的平台上,则由端口实现退化为其他随机源。

需要说明的是:该函数对象 mp_uos_urandom_obj 在 moduos.c 的全局表中被注册,但具体实现由各端口通过 MICROPY_PY_UOS_INCLUDEFILE 机制或直接提供,因此是否真正基于硬件 RNG 取决于目标板卡。在 TobudOS 中,若板卡具备硬件随机数能力,即可用它为会话令牌、密钥盐值等提供随机数据:

import os
token = os.urandom(16)   # 生成 16 字节随机数

三、文件系统访问 API 详解

本节 API 在 vfs.c 中实现(如 mp_vfs_chdir、mp_vfs_ilistdir、mp_vfs_mkdir 等),通过 mp_vfs_lookup_path() 先做路径解析,再把调用代理到对应挂载点上的 VFS 对象方法(mp_vfs_proxy_call)。在 TobudOS 中,这个"VFS 对象"即 vfs_tos.c 里的 VfsTos,其方法最终调用 TobudOS 内核 VFS 层的 tos_vfs_* 系列接口。

3.1 目录操作:chdir / getcwd / mkdir / rmdir

函数 作用 参数
chdir(path) 切换当前目录 path:目标目录路径
getcwd() 获取当前目录 无参数,返回字符串
mkdir(path) 创建新目录 path:要创建的目录路径
rmdir(path) 删除目录 path:要删除的目录路径

TobudOS 特定行为说明(依据 vfs_tos.c):

  • chdir 在 vfs_tos 中当前未实现(源码注释 FIXME chdir() is not supported by tos),调用会抛出 OSError(EPERM);
  • getcwd 当前固定返回根路径字符串 "/";
  • mkdir 实际调用 tos_vfs_mkdir(path),失败时通过 vfs_err_to_errno_table 错误码映射表转换为对应 OSError 抛出;
  • rmdir 实际调用 tos_vfs_rmdir(path)(源码注释提示底层 fatfs 对 rmdir 的支持有限)。

3.2 目录列举:ilistdir / listdir

ilistdir([dir]) 返回一个迭代器,逐项产生对应目录条目的元组。无参数时列举当前目录,否则列举 dir 指定的目录。元组格式为 (name, type, inode[, size]):

  • name:条目名称。若 dir 是 bytes 对象,则返回 bytes,否则返回 str;
  • type:条目类型整数,0x4000 表示目录,0x8000 表示普通文件;
  • inode:文件 inode 号,对没有该概念的文件系统可能为 0;
  • 某些平台返回 4 元组并附带 size(文件大小,未知为 -1;对目录条目含义未定义)。

在 TobudOS 的 vfs_tos.c 中,迭代器通过 tos_vfs_readdir 逐条读取目录项,跳过 . 和 ..,构造 3 元组 (name, type, inode):类型映射为 MP_S_IFDIR(即 0x4000)或 MP_S_IFREG(即 0x8000),inode 因 TobudOS VFS 不支持而固定为 0。

listdir(<a href="https://link.gitcode.com/i/33085e46bbe53c60b8b548f6234023a6" target="_blank">dir]) 则是 ilistdir 的便捷封装:在 [vfs.c 中,mp_vfs_listdir 内部先调用 ilistdir 拿到迭代器,再循环取出每个元组的第 0 项(名称)组装成一个 list 返回。

3.3 文件操作:remove / rename

函数 作用 参数
remove(path) 删除文件 path:文件路径(unlink 为其别名)
rename(old_path, new_path) 重命名文件 旧路径、新路径

源码层面:

  • vfs.c 的 mp_vfs_rename 会分别解析新旧路径,若新旧路径落在不同文件系统(挂载点)上则直接抛 OSError(EPERM),禁止跨文件系统重命名;
  • vfs_tos.c 中 remove 实际调用 tos_vfs_unlink(path),rename 调用 tos_vfs_rename(old, new),失败时均转换为对应 errno 的 OSError。

3.4 状态查询:stat / statvfs

stat(path) 获取文件或目录的状态。在 TobudOS 的 vfs_tos.c 中,它调用 tos_vfs_stat(path, &st) 得到一个 10 元组,字段与 POSIX stat 语义对应:

索引 字段 说明(TobudOS 取值)
0 st_mode 文件类型/权限位(来自 vfs_fstat_t.mode)
1 st_ino 固定 0(无 inode 概念)
2 st_dev 固定 0
3 st_nlink 固定 0
4 st_uid 固定 0
5 st_gid 固定 0
6 st_size 文件大小(字节)
7 st_atime 最后访问时间
8 st_mtime 最后修改时间
9 st_ctime 状态变更时间

statvfs(path) 获取文件系统状态,返回 10 元组,字段顺序及含义如下:

字段 含义
f_bsize 文件系统块大小
f_frsize 分片大小
f_blocks 以 f_frsize 为单位的总块数
f_bfree 空闲块数
f_bavail 非特权用户可用空闲块数
f_files inode 总数
f_ffree 空闲 inode 数
f_favail 非特权用户可用空闲 inode 数
f_flag 挂载标志
f_namemax 最大文件名长度

其中 f_files、f_ffree、f_favail、f_flag 在端口实现可能不可用时返回 0。TobudOS 的 vfs_tos.c 中 statvfs 调用 tos_vfs_statfs(path, &st),将 TobudOS 内核 VFS 的 vfs_fsstat_t(定义见 components/fs/vfs/include/tos_vfs_file.h:blk_size/blk_num/blk_free/blk_avail/file_num/file_free/name_len 等)逐项映射到上述 10 元组,f_flag 固定为 0。

利用 statvfs 可以计算存储剩余空间,例如:

import os
info = os.statvfs('/fs')
total = info[2] * info[0]      # 总字节 = f_blocks * f_bsize
free  = info[3] * info[0]      # 可用字节 = f_bfree * f_bsize
print('total: %d, free: %d' % (total, free))

3.5 sync() —— 同步文件系统

sync() 用于同步所有文件系统,确保缓存数据落盘。在 moduos.c 中,sync 受 MICROPY_PY_UOS_SYNC 宏控制,TobudOS 默认构建中该宏未在 mpconfigport.h 中显式置 1,因此该函数是否可用取决于具体构建配置;在使用 fatfs/littlefs 等带缓存的底层文件系统时,写文件后调用 sync() 是防止掉电丢失数据的常见做法。

3.6 完整实操示例:来自仓库的 os.py

TobudOS 仓库自带的 examples/micropython_demo/scripts/os.py 完整演示了上述 API 的组合用法,可直接在设备上运行:

import uos as os

# test mkdir()
print('{:-^40}'.format('test mkdir'))
print(os.listdir())
os.mkdir('test_dir')
print(os.listdir())

# test file open() and write()
print('{:-^40}'.format('test write'))
f = open('test_dir/test_file', 'w')
f.write("Hello TencentOS-tiny\n")
print("Hello MicroPython", file=f)
f.close()
print(os.listdir('test_dir'))

# test file open() and read()
print('{:-^40}'.format('test read'))
f = open('test_dir/test_file', 'r')
print(f.readlines())
f.close()

# test 'with' statement and iterator
print('{:-^40}'.format('test with statement'))
with open('test_dir/test_file', 'r') as file:
    for line in file:
        print(line)

# test rename()
print('{:-^40}'.format('test rename'))
os.rename('test_dir', 'test_dir2')
print(os.listdir())
print(os.listdir('test_dir2'))

# test unlink()
print('{:-^40}'.format('test unlink'))
os.unlink('test_dir2/test_file')
print(os.listdir('test_dir2'))
os.unlink('test_dir2')
print(os.listdir())

该脚本覆盖了 listdir(无参列出当前目录)、mkdir、文件 open/write/read、with 语句迭代、rename(此处直接重命名目录)、unlink(即 remove 别名,既删文件也删目录)。它演示了一个重要事实:TobudOS 的 MicroPython 文件 I/O 与 os 模块是打通的——open() 通过 vfs.c 的 mp_vfs_open 解析路径后,代理到 VfsTos.open(),最终落到 TobudOS VFS 的文件接口上。

四、终端重定向与复制:dupterm

dupterm(stream_object, index=0, /) 用于在给定的流式(stream-like)对象上复制或切换 MicroPython 终端(REPL):

  • stream_object 必须是原生流对象,或继承自 io.IOBase 并实现 readinto() 与 write() 方法;流应处于非阻塞模式,readinto() 在无数据可读时应返回 None;
  • 调用之后,所有终端输出都会在该流上重复输出,流上可用的输入也会被转发到终端输入;
  • index 为非负整数,指定设置的复制槽位;同一端口可能支持多个槽位(槽 0 始终可用),此时终端输入输出会复制到所有已设置的槽位;
  • 传入 None 作为 stream_object 则取消指定槽位的复制;
  • 函数返回该槽位上先前的流对象。

例如把 REPL 复制到一个 UART 流上:

import os, machine
uart = machine.UART(1, 115200)
old = os.dupterm(uart)   # 复制终端到 uart,old 保存原槽位对象
# 之后 uart 上会镜像 REPL 的输入输出
os.dupterm(None)         # 取消复制

在 TobudOS 的 mpconfigport.h 中,MICROPY_PY_OS_DUPTERM 与 MICROPY_PY_UOS_DUPTERM_NOTIFY 均默认为 0,即默认构建未启用 dupterm;需要该特性时需在移植配置中打开对应宏。其通用实现位于 extmod/uos_dupterm.c,负责多槽位的轮询(mp_uos_dupterm_poll)、接收(mp_uos_dupterm_rx_chr)与发送(mp_uos_dupterm_tx_strn)。

五、文件系统挂载:mount / umount 与 VFS 类

5.1 VFS 概念

某些端口提供虚拟文件系统(VFS),并支持在此 VFS 内挂载多个"真实"文件系统。文件系统对象可以挂在 VFS 根目录,也可以挂在根目录下的某个子目录,从而让 Python 程序看到动态、灵活组合的文件系统视图。提供该功能的端口会暴露 mount、umount 函数以及若干以 VFS 类表示的文件系统实现。

在 vfs.c 的 mp_vfs_lookup_path 中可以看到核心机制:MicroPython 维护一张挂载表(vfs_mount_table),路径解析时从根开始逐段匹配挂载点前缀,相对路径则直接委托给"当前卷"(vfs_cur)。所有挂载点被合并成一个从 / 开始的单一虚拟文件系统。

5.2 mount(fsobj, mount_point, *, readonly)

将文件系统对象 fsobj 挂载到 VFS 中由 mount_point 字符串指定的位置:

  • fsobj 可以是带有 mount() 方法的 VFS 对象,也可以是块设备对象。若为块设备,则文件系统类型会被自动检测(无法识别时抛出异常);
  • mount_point 可以是 '/'(挂到根),也可以是 '/<name>'(挂到根下子目录);
  • readonly=True 时以只读方式挂载;
  • 挂载过程中会调用文件系统对象的 mount() 方法;
  • 若 mount_point 已被占用,抛出 OSError(EPERM)。

源码细节(vfs.c 的 mp_vfs_mount):

  • 除 readonly 外还支持 mkfs 关键字参数(挂载时若文件系统为空则自动格式化);
  • 若 fsobj 没有 mount 方法,则视为块设备,调用 mp_vfs_autodetect 自动识别文件系统。识别逻辑(vfs.c)会读取块设备第 0、1 两个块:在偏移 32 处发现 "littlefs" 魔数判为 LFS1,在偏移 0 处发现则判为 LFS2;都未命中时回退到 FAT;
  • 挂载完成后将新挂载点插入挂载表,根挂载(len == 1)始终保持在表尾。

5.3 umount(mount_point)

卸载一个文件系统:

  • mount_point 可以是命名挂载位置的字符串,也可以是先前挂载的文件系统对象;
  • 卸载过程中会调用文件系统对象的 umount() 方法;
  • 若 mount_point 找不到,抛出 OSError(EINVAL)。

5.4 三类文件系统类

VfsFat(block_dev):基于 FAT 格式的文件系统对象,存储由 block_dev 提供。静态方法 VfsFat.mkfs(block_dev) 在块设备上构建 FAT 文件系统(对应实现见 extmod/vfs_fat.c,内部调用 FatFs 的 f_mkfs)。FAT 的主要优势是可在支持 USB MSC 的板卡上免驱动被 PC 直接访问,但断电写入时容错较差,易损坏。

VfsLfs1(block_dev, readsize=32, progsize=32, lookahead=32):littlefs v1 格式的文件系统对象。block_dev 必须支持扩展接口。静态方法 VfsLfs1.mkfs(...) 构建 LFS1 文件系统。

VfsLfs2(block_dev, readsize=32, progsize=32, lookahead=32, mtime=True):littlefs v2 格式的文件系统对象,参数含义同上,另加 mtime 参数:

  • mtime=True 时启用文件修改时间戳,通过 littlefs 属性存储;
  • 该选项可每次挂载时单独开启或关闭,时间戳只会在启用 mtime 时被添加或更新,否则保持不变;
  • 没有时间戳的 LFS2 文件系统无需重新格式化即可使用;一旦文件被打开写入,时间戳会被透明地添加;
  • mtime 启用时,对无时间戳文件执行 os.stat 会返回 0 作为时间戳值。

littlefs 是为闪存设备设计的文件系统,抗损坏能力远强于 FAT,是无需 USB MSC 场景下的推荐选择。上述类由 extmod/vfs_lfsx.c 实现,其构造函数将 readsize/progsize/lookahead/mtime 参数映射到底层 littlefs 配置(read_size、prog_size、lookahead 缓冲区按 lookahead/8 字节分配)。

5.5 挂载实操:RAM 块设备示例

综合使用 VfsFat.mkfs 与 mount 挂载一个基于 RAM 的磁盘(本示例与 docs/reference/filesystem.rst 中的规范用法一致):

import os

class RAMBlockDev:
    def __init__(self, block_size, num_blocks):
        self.block_size = block_size
        self.data = bytearray(block_size * num_blocks)

    def readblocks(self, block_num, buf):
        for i in range(len(buf)):
            buf[i] = self.data[block_num * self.block_size + i]

    def writeblocks(self, block_num, buf):
        for i in range(len(buf)):
            self.data[block_num * self.block_size + i] = buf[i]

    def ioctl(self, op, arg):
        if op == 4:  # get number of blocks
            return len(self.data) // self.block_size
        if op == 5:  # get block size
            return self.block_size

bdev = RAMBlockDev(512, 50)
os.VfsFat.mkfs(bdev)     # 格式化
os.mount(bdev, '/ramdisk')  # 挂载到 /ramdisk

# 挂载后即可正常读写
with open('/ramdisk/hello.txt', 'w') as f:
    f.write('Hello world')
print(open('/ramdisk/hello.txt').read())

5.6 TobudOS 中的真实挂载流程

在 TobudOS 的嵌入式移植中,VFS 挂载通常先在 C 层完成。以 examples/micropython_demo/micropython_demo.c 的 mp_entry 为例:

#ifdef MP_USING_VFS
extern vfs_blkdev_ops_t sd_dev;
extern vfs_fs_ops_t fatfs_ops;

tos_vfs_block_device_register("/dev/sd", &sd_dev);          // 注册块设备
tos_vfs_fs_register("fatfs_sd", &fatfs_ops);               // 注册文件系统驱动
if (tos_vfs_fs_mount("/dev/sd", "/fs", "fatfs_sd") != 0) {  // 挂载到 /fs
    printf("mount failed!\n");
}
#endif

C 层把 SD 卡挂载到 TobudOS VFS 的 /fs 之后,Python 脚本里就能直接 os.listdir('/fs')、open('/fs/xxx', 'w') 等。这正是前文所述 MICROPY_VFS_TOS 桥接层的价值:Python 层的 os 文件操作通过 VfsTos 代理到 TobudOS 内核 VFS 的 tos_vfs_* 接口,从而复用同一套块设备与文件系统驱动栈。

六、块设备接口:AbstractBlockDev

块设备(block device)是实现块协议的对象,它让硬件设备能够承载 MicroPython 文件系统。物理硬件由用户自定义类表示;MicroPython 并不真正提供 AbstractBlockDev 类,它只是一个设计模板——真正的块设备类必须实现下述方法。一个具体的块设备实现通常允许访问某块硬件(如闪存)的类内存功能,可以被格式化为任何受支持的文件系统,并用 os 的方法挂载。

6.1 简单接口与扩展接口

readblocks 与 writeblocks 有两种兼容的签名,以支持多种使用场景;一个块设备可以实现其中一种、另一种,或两者同时实现。带 offset 参数的第二种形式称为扩展接口:

readblocks(block_num, buf)
readblocks(block_num, buf, offset)

writeblocks(block_num, buf)
writeblocks(block_num, buf, offset)
  • 简单形式:读取/写入对齐的、块大小的整数倍数据。从 block_num 索引的块开始,把 buf(字节数组)读入/写出;buf 长度是块大小的整数倍。写入简单形式要求目标块先被本方法擦除(如必要);
  • 扩展形式:允许在块内任意偏移、任意长度读写。从块索引 block_num 和块内字节偏移 offset 开始读写 buf 长度的字节;写入时只应改变被写的字节,调用方必须保证相关块已通过先前的 ioctl 调用擦除;
  • 注意:实现方在指定了 offset 参数时,绝不能隐式擦除块,即使 offset 为 0 也一样。

某些文件系统(如 littlefs)需要更强的写控制——例如不擦除就写子块区域——因此要求块设备支持扩展接口。这也是 VfsLfs1/VfsLfs2 构造函数要求 block_dev 支持扩展接口的原因。

6.2 ioctl 操作码

ioctl(op, arg) 用于控制块设备并查询其参数,op 为下列整数之一:

op 操作 arg 返回
1 初始化设备 未使用 —
2 关闭设备 未使用 —
3 同步设备 未使用 —
4 获取块数量 未使用 返回整数(块数)
5 获取块大小(字节) 未使用 返回整数,或 None(此时使用默认值 512)
6 擦除一个块 块号 —

约束要点:

  • 最低要求必须拦截 ioctl(4, ...);对 littlefs 还必须拦截 ioctl(6, ...),其余是否实现取决于硬件;
  • 在每次 writeblocks(block, ...) 之前,littlefs 都会先发出 ioctl(6, block),以便驱动在硬件需要时先擦除块;驱动也可以拦截 ioctl(6, block) 并返回 0(成功),此时由驱动自行负责检测擦除需求;
  • 除非另有说明,ioctl(op, arg) 可以返回 None,因此实现可以忽略未使用的 op 值;对拦截的操作,操作 4 和 5 的返回值如上所述,其余操作成功返回 0,失败返回非零(值为 OSError errno 码)。

6.3 同时支持两种接口的块设备模板

同时支持简单与扩展接口的块设备模板(RAM 存储,可配合 littlefs 使用):

class RAMBlockDev:
    def __init__(self, block_size, num_blocks):
        self.block_size = block_size
        self.data = bytearray(block_size * num_blocks)

    def readblocks(self, block_num, buf, offset=0):
        addr = block_num * self.block_size + offset
        for i in range(len(buf)):
            buf[i] = self.data[addr + i]

    def writeblocks(self, block_num, buf, offset=None):
        if offset is None:
            # 简单接口:先擦除再写
            for i in range(len(buf) // self.block_size):
                self.ioctl(6, block_num + i)
            offset = 0
        addr = block_num * self.block_size + offset
        for i in range(len(buf)):
            self.data[addr + i] = buf[i]

    def ioctl(self, op, arg):
        if op == 4:  # 块数量
            return len(self.data) // self.block_size
        if op == 5:  # 块大小
            return self.block_size
        if op == 6:  # 擦除块
            return 0

import os
bdev = RAMBlockDev(512, 50)
os.VfsLfs2.mkfs(bdev)
os.mount(bdev, '/ramdisk')

在 TobudOS 上为真实闪存芯片实现驱动时,只需按上述协议封装 readblocks/writeblocks/ioctl(块大小通常取 4096 或芯片扇区大小,块数由容量除以块大小得出),即可被 os.mount 自动识别并格式化。

七、结语

os 模块是 TobudOS MicroPython 运行时操作存储与系统的统一入口。本文完整梳理了 os.rst 文档定义的全部 API——从 uname/urandom 到目录与文件操作、dupterm 终端复制、mount/umount 挂载机制,以及 AbstractBlockDev 块设备协议——并逐一对照 moduos.c、vfs.c、vfs_tos.c 等源码,揭示了 Python 层 API 与 TobudOS 内核 VFS 层 tos_vfs_* 接口之间的桥接关系。掌握了这些接口,你就可以在 TobudOS 设备上自由组织文件系统布局、接入自定义存储硬件,并借助 os.py 示例快速验证整套文件操作流程。

登录后查看全文
TobudOS