当前位置: 首页 > luatOSMV > LuatOS sys 模块加载机制详解

LuatOS sys 模块加载机制详解

发布于:2026-2-2 luatOSMV 0条评论 119 views

目录


模块类型对比

C 模块 vs Lua 模块

LuatOS 中有两种类型的模块,使用方式完全不同:

特征C 模块(如 gpio, ledstrip)Lua 模块(如 sys)
文件类型.c 文件.lua 文件
注册方式luaL_requiref(L, name, func, 1)内嵌到 VFS 虚拟文件系统
是否需要 require不需要(已全局注册)需要
使用方式gpio.setup() 直接用local sys = require("sys") 后使用
示例模块gpio, uart, log, timer, ledstripsys, sysplus
存储位置编译到固件中(代码段)编译到固件中(字节码数组)

使用示例

-- ✅ C 模块 - 不需要 require(直接使用全局变量)
gpio.setup(8, 1)
log.info("test", "hello")
ledstrip.init(8, 0, ledstrip.TX, 2)

-- ✅ Lua 模块 - 需要 require
local sys = require("sys")
sys.taskInit(function()
    sys.wait(1000)
    log.info("sys", "hello")
end)
sys.run()

C 模块的注册机制

luat_base_idf5.c 中,所有 C 模块通过 luaL_requiref() 注册为全局变量:

static const luaL_Reg loadedlibs[] = {
    {"gpio",    luaopen_gpio},      // GPIO模块
    {"uart",    luaopen_uart},      // 串口模块
    {"log",     luaopen_log},       // 日志模块
    {"ledstrip", luaopen_ledstrip}, // LED灯带模块
    // ... 更多模块
    {NULL, NULL}
};

void luat_openlibs(lua_State *L) {
    const luaL_Reg *lib;
    for (lib = loadedlibs; lib->func; lib++) {
        luaL_requiref(L, lib->name, lib->func, 1);  // glb=1 表示注册为全局变量
        lua_pop(L, 1);
    }
}

luaL_requiref() 的第四个参数 glb 决定是否需要 require:

  • glb = 1 (true): 模块注册为全局变量不需要 require
  • glb = 0 (false): 模块只在 package.loaded 中,需要 require

sys 模块加载流程

完整流程图

编译时
  ↓
1. sys.lua 被编译成字节码
  ↓
2. 字节码嵌入到 C 数组中 (luat_inline_libs.c)
  ↓
固件启动
  ↓
3. luat_vfs_init() 初始化 VFS
  ↓
4. 注册内联文件系统 (vfs_fs_inline)
  ↓
5. 挂载到 /lua/ 路径
  ↓
Lua 运行时
  ↓
6. require("sys") 被调用
  ↓
7. Lua 搜索路径: /lua/sys.lua
  ↓
8. 从内存中读取字节码
  ↓
9. 加载并执行,返回 sys 模块

详细说明

1. 编译时嵌入(构建阶段)

文件 LuatOS/script/corelib/sys.lua 被编译成字节码并嵌入到 C 代码中:

// LuatOS/luat/vfs/luat_inline_libs.c
const char luat_inline2_sys[] = {
    0x1B, 0x4C, 0x75, 0x61, 0x53, 0x00, ...  // Lua 5.3 字节码
};

const luadb_file_t luat_inline2_libs[] = {
    {.name="sys.lua", .size=4969, .ptr=luat_inline2_sys},
    {.name="sysplus.lua", .size=2657, .ptr=luat_inline2_sysplus},
    {.name="", .size=0, .ptr=NULL}  // 结束标记
};

字节码头部格式:

0x1B, 0x4C, 0x75, 0x61, 0x53  // "\x1BLuaS" - Lua 5.3 字节码魔数
0x00, 0x19, 0x93              // 版本信息
// ... 后续是指令和常量表

2. VFS 初始化(固件启动时)

bsp_c3/luatos/components/luat/port/luat_fs_idf5.c 中:

int luat_fs_init(void) {
    // 初始化 VFS
    luat_vfs_init(NULL);

    // 注册 spiffs 文件系统
    luat_vfs_reg(&vfs_fs_spiffs);

    // 在 luat_vfs.c 的 mount 函数中自动挂载内联文件系统:
    // vfs.mounted[1].prefix = "/lua/"
    // vfs.mounted[1].fs = &vfs_fs_inline
}

VFS 自动挂载逻辑luat_vfs.c):

int luat_fs_mount(luat_fs_conf_t *conf) {
    // ... 挂载第一个文件系统后
    #ifdef __LUATOS__
    if (j == 0) {
        // 自动挂载内嵌文件系统到 /lua/
        vfs.mounted[j+1].fs = &vfs_fs_inline;
        vfs.mounted[j+1].ok = 1;
        memcpy(vfs.mounted[j+1].prefix, "/lua/", strlen("/lua/") + 1);
    }
    #endif
}

3. Lua 模块搜索(运行时)

当执行 require("sys") 时,Lua 按以下路径搜索(loadlib.c):

static const char* search_paths[] = {
    "/%s.luac", "/%s.lua",           // 根目录
    "/luadb/%s.luac", "/luadb/%s.lua", // luadb 分区
    "/lua/%s.luac", "/lua/%s.lua",     // ← sys.lua 在这里找到!
};

搜索 require("sys") 的过程:

  1. 尝试打开 /lua/sys.lua
  2. VFS 匹配到 /lua/ 前缀
  3. 调用 vfs_fs_inline 的文件操作函数
  4. 从内存数组 luat_inline2_sys 读取字节码
  5. 执行并返回模块

4. 内联文件系统实现

// LuatOS/luat/vfs/luat_fs_inline.c
FILE* luat_vfs_inline_fopen(void* userdata, const char *filename, const char *mode) {
    const luadb_file_t* file = luat_inline2_libs;

    // 遍历查找匹配的文件名
    while (file->ptr != NULL) {
        if (!memcmp(file->name, filename, strlen(filename)+1)) {
            // 找到了!创建文件描述符
            luat_fs_inline_t* fd = luat_heap_malloc(sizeof(luat_fs_inline_t));
            fd->ptr = file->ptr;    // 字节码指针
            fd->size = file->size;  // 文件大小
            fd->offset = 0;
            return (FILE*)fd;
        }
        file++;
    }
    return NULL;  // 未找到
}

// 读取字节码
size_t luat_vfs_inline_fread(void* userdata, void *ptr, size_t size, size_t nmemb, FILE *stream) {
    luat_fs_inline_t* fd = (luat_fs_inline_t*)stream;
    size_t read_size = size * nmemb;
    if (fd->offset + read_size > fd->size) {
        read_size = fd->size - fd->offset;
    }
    memcpy(ptr, fd->ptr + fd->offset, read_size);  // 从内存复制数据
    fd->offset += read_size;
    return read_size;
}

字节码生成机制

生成工具

工具链:

  • 主程序: luatos_32bit.exe (LuatOS 自身的解释器)
  • 脚本: LuatOS/script/update_inline_libs.lua
  • 核心函数: Lua 标准库的 string.dump()

工作原理

核心代码(update_inline_libs.lua)

-- 配置编译模式
local bittype = "32bit"  -- ESP32 使用 32 位模式

-- 遍历 corelib 目录下的所有 .lua 文件
local files = {}
lsdir("corelib", files, true)

-- 生成 C 文件
local f = io.open("..\\luat\\vfs\\luat_inline_libs.c", "wb")

for _, value in ipairs(files) do
    -- 关键步骤:将 Lua 源码编译为字节码
    local lf = loadfile("corelib\\" .. value)  -- 加载 Lua 文件
    local data = string.dump(lf, true)         -- 编译成字节码!

    -- 写入 C 数组
    f:write("const char luat_inline2_" .. value:sub(1, -5) .. "[] = {\r\n")
    for i = 0, #data - 1 do
        f:write(string.format("0x%02X, ", data:byte(i+1)))
    end
    f:write("};\r\n\r\n")
end

-- 生成文件表
f:write("const luadb_file_t luat_inline2_libs[] = {\r\n")
f:write("   {.name=\"sys.lua\",.size=4969, .ptr=luat_inline2_sys},\r\n")
f:write("   {.name=\"sysplus.lua\",.size=2657, .ptr=luat_inline2_sysplus},\r\n")
f:write("   {.name=\"\",.size=0,.ptr=NULL}\r\n")
f:write("};\r\n")

string.dump() 函数

string.dump(function [, strip]) 是 Lua 5.3 的标准库函数:

  • 将 Lua 函数编译成字节码
  • strip = true 表示去除调试信息(减小体积)
  • 返回值是包含字节码的字符串

三种编译模式

-- 模式配置
-- local bittype = "64bit_size32"  -- 64位虚拟机, 32位size_t
local bittype = "32bit"             -- ✅ 默认: 32位 (ESP32使用)
-- local bittype = "source"         -- 保存源码而非字节码

if bittype == "source" then
    data = io.readFile("corelib\\" .. value)  -- 直接读取源码
else
    local lf = loadfile("corelib\\" .. value)
    data = string.dump(lf, true)              -- 编译为字节码
end

生成的文件:

  • 32bit 模式 → luat_inline_libs.c
  • 64bit_size32 模式 → luat_inline_libs_64bit_size32.c
  • source 模式 → luat_inline_libs_source.c

字节码格式

Lua 5.3 字节码头部:

0x1B          ESC 字符(标识字节码开始)
0x4C 0x75 0x61  "Lua"
0x53          "S"
0x00          版本号(5.3)
0x19 0x93     格式版本
...           指令和常量表

VFS 虚拟文件系统

架构

用户代码: require("sys")
    ↓
Lua 包加载器: 搜索 /lua/sys.lua
    ↓
VFS 层: 路径匹配 /lua/ → vfs_fs_inline
    ↓
内联文件系统: 从 luat_inline2_libs 查找
    ↓
内存读取: luat_inline2_sys 字节码数组
    ↓
Lua 虚拟机: 加载并执行字节码

VFS 挂载点

挂载点文件系统说明
/spiffsSPIFFS 文件系统(Flash)
/lua/inline内嵌文件系统(内存中的字节码)
/luadb/romfs/luadb脚本分区(可选)
/sd/fatfsSD 卡(需手动挂载)

关键配置

luat_conf_bsp.h 中:

#define LUAT_USE_FS_VFS 1              // 启用 VFS
#define LUAT_USE_VFS_INLINE_LIB 1      // 启用内联库
#define LUA_USE_VFS_FILENAME_OFFSET 1  // 文件名偏移处理

如何更新 sys 模块

方法一:使用官方工具(推荐)

步骤:

  1. 下载 LuatOS 工具

    https://gitee.com/openLuat/LuatOS/attach_files
    下载 luatos_32bit.exe
  2. 修改源文件

    编辑 LuatOS/script/corelib/sys.lua
  3. 重新生成字节码

    cd LuatOS\script
    luatos_32bit.exe update_inline_libs.lua
  4. 重新编译固件

    cd bsp_c3\luatos
    idf.py build

方法二:手动编译(高级)

如果你熟悉 Lua,可以自己编写脚本:

-- compile_sys.lua
local lf = loadfile("sys.lua")
local bytecode = string.dump(lf, true)

-- 写入 C 文件
local f = io.open("luat_inline_libs.c", "wb")
f:write("const char luat_inline2_sys[] = {\n")
for i = 1, #bytecode do
    f:write(string.format("0x%02X, ", bytecode:byte(i)))
    if i % 8 == 0 then f:write("\n") end
end
f:write("};\n")
f:close()

生成文件说明

LuatOS/script/update_inline_libs.lua
    ↓ 执行后生成 ↓
LuatOS/luat/vfs/luat_inline_libs.c  ← 这个文件会被编译进固件

注意事项:

  • ⚠️ 每次修改 sys.lua 后,必须重新运行 update_inline_libs.lua
  • ⚠️ 生成的 C 文件会自动包含在编译过程中
  • ✅ 不需要手动修改 CMakeLists.txt

优势总结

sys 模块采用内嵌字节码的优势:

  1. 无需外部文件 - sys.lua 嵌入固件,不占用 Flash 文件系统空间
  2. 快速加载 - 直接从内存读取,无需 Flash I/O
  3. 节省空间 - 字节码比源码小(约 30-40%)
  4. 透明使用 - 对用户来说就像普通的 require()
  5. 无法被误删 - 固件内嵌,不会被用户脚本覆盖
  6. 启动必需 - sys 是核心库,确保总是可用

与其他方案对比:

方案优点缺点
内嵌字节码(sys)快速、可靠、节省空间更新需重新编译固件
Flash 文件(用户脚本)易于更新可能被删除、启动慢
C 模块(gpio)最快、最小开发成本高、不灵活

附录:相关文件索引

关键源文件

文件路径说明
LuatOS/script/corelib/sys.luasys 模块源码
LuatOS/script/update_inline_libs.lua字节码生成脚本
LuatOS/luat/vfs/luat_inline_libs.c生成的字节码 C 数组
LuatOS/luat/vfs/luat_fs_inline.c内联文件系统实现
LuatOS/luat/vfs/luat_vfs.cVFS 核心逻辑
LuatOS/lua/src/loadlib.cLua 包加载器(模块搜索路径)
bsp_c3/luatos/components/luat/port/luat_base_idf5.cC 模块注册
bsp_c3/luatos/components/luat/port/luat_fs_idf5.cESP32 VFS 初始化
bsp_c3/luatos/include/luat_conf_bsp.hBSP 配置文件

工具下载


总结

sys 模块的加载路径:

require("sys")
    ↓
Lua 搜索 /lua/sys.lua
    ↓
VFS 路由到 vfs_fs_inline
    ↓
从内存数组 luat_inline2_sys 读取字节码
    ↓
Lua 虚拟机执行字节码
    ↓
返回 sys 模块表

核心机制:

  • 使用 Lua 标准库的 string.dump() 将源码编译为字节码
  • 通过 VFS 虚拟文件系统将内存数组伪装成文件
  • Lua 的 require() 透明地从"虚拟文件"加载模块

这是一个精巧的设计,兼顾了性能、可靠性和易用性!


标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注