树莓派Pico中运行lua并直接执行lua字节码

测试环境

用了树莓派 pico进行测试,pico-sdk, 文件系统使用了Littlefs。
关于LittleFs LittleFs提供了一套类POSIX的接口,与FatFs不同,相近的部分也有很多。不同的地方在于使用C的fopen函数会有很多的模式,像"w","r","a","w+","r+","a+","rb"........ 在LittleFs中有以下的一个枚举:


            // File open flags
            enum lfs_open_flags {
                // open flags
                LFS_O_RDONLY = 1,         // Open a file as read only
            #ifndef LFS_READONLY
                LFS_O_WRONLY = 2,         // Open a file as write only
                LFS_O_RDWR   = 3,         // Open a file as read and write
                LFS_O_CREAT  = 0x0100,    // Create a file if it does not exist
                LFS_O_EXCL   = 0x0200,    // Fail if a file already exists
                LFS_O_TRUNC  = 0x0400,    // Truncate the existing file to zero size
                LFS_O_APPEND = 0x0800,    // Move to end of file on every write
            #endif
            
                // internally used flags
            #ifndef LFS_READONLY
                LFS_F_DIRTY   = 0x010000, // File does not match storage
                LFS_F_WRITING = 0x020000, // File has been written since last flush
            #endif
                LFS_F_READING = 0x040000, // File has been read since last flush
            #ifndef LFS_READONLY
                LFS_F_ERRED   = 0x080000, // An error occurred during write
            #endif
                LFS_F_INLINE  = 0x100000, // Currently inlined in directory entry
            };
        

在LittleFs通过对这个枚举变量进行或的运算可以搭配出类似于"w","r"这些模式,重点是LittleFs不区分二进制读写与普通文本模式的读写,所有读或者写都是以二进制的形式进行的。

Can LittleFs read and write binary files?

为此通过littlefs库也可以很轻易的实现fopen,fwrite,fclose,fread,fseek等函数的功能。

lurk101/pico-littlefs: A small C/C++ Posix-like journalng file system for the Raspberry Pico using a size configurable portion of its SPI flash. (github.com) 库的基础上简单实现fget, ungetc 这两个函数在后面会用到很多次。


            int pico_getc( int f )
            {
                lfs_file_t* fp = ( lfs_file_t* )f;
                char buffer;
                int err            = pico_read( f, &buffer, sizeof( char ) );
                const char* errmsg = pico_errmsg( err );
                if ( !strcmp( errmsg, "Unknown error" ) )
                {
                    return buffer;
                }
                return err;
            }
            int pico_ungetc( char c, int f )
            {
                lfs_file_t* fp     = ( lfs_file_t* )f;
                int err            = pico_write( f, &c, sizeof( char ) );
                const char* errmsg = pico_errmsg( err );
                if ( !strcmp( errmsg, "Unknown error" ) )
                {
                    return 1;
                }
                return err;
            }
        

生成Lua字节码

使用luac生成字节码。
例如test.lua的内容为:

            print("hello world!")
            
使用命令
 luac -o test test.lua 

生成字节码内容为:

Lua执行字节码

在lua中可以在解释器里使用dofile方法执行字节码,也可以采用lua filename 传递文件名的方式执行字节码。

Lua的源码实现

在这里我以 lbaselib.c 中的 luaB_dofile() 函数为例。

                static int luaB_dofile( lua_State* L )
                {
                    const char* fname = luaL_optstring( L, 1, NULL );
                    lua_settop( L, 1 );
                    if ( l_unlikely( luaL_loadfile( L, fname ) != LUA_OK ) )
                        return lua_error( L );
                    lua_callk( L, 0, LUA_MULTRET, 0, dofilecont );
                    return dofilecont( L, 0, 0 );
                }
            

在这个函数中首先从lua的全局状态表中读出文件名。在栈顶压入1。之后调用 luaL_loadfile() 函数加载文件,如果发生错误则报错,否则继续向西执行 lua_callk() 函数执行 main chunk。最后执行 dofilecont() 函数。
进入到 luaL_loadfile() 它是一个宏


                #defineluaL_loadfile( L, f ) luaL_loadfilex( L, f, NULL )

                LUALIB_API int luaL_loadfilex( lua_State* L, const char* filename, const char* mode )
                {
                    LoadF lf;
                    int status, readstatus;
                    int c;
                    int fnameindex = lua_gettop( L ) + 1; /* index of filename on the stack */
                    if ( filename == NULL )
                    {
                        lua_pushliteral( L, "=stdin" );
                        lf.f = stdin;
                    }
                    else
                    {
                        lua_pushfstring( L, "@%s", filename );
                        lf.f = fopen( filename, "r" );
                        if ( lf.f == NULL )
                            return errfile( L, "open", fnameindex );
                    }
                    lf.n = 0;
                    if ( skipcomment( lf.f, &c ) ) /* read initial portion */
                        lf.buff[lf.n++] = '\n';    /* add newline to correct line numbers */
                    if ( c == LUA_SIGNATURE[0] )
                    {             /* binary file? */
                        lf.n = 0; /* remove possible newline */
                        if ( filename )
                        {                                           /* "real" file? */
                            lf.f = freopen( filename, "rb", lf.f ); /* reopen in binary mode */
                            if ( lf.f == NULL )
                                return errfile( L, "reopen", fnameindex );
                            skipcomment( lf.f, &c ); /* re-read initial portion */
                        }
                    }
                    if ( c != EOF )
                        lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */
                    status     = lua_load( L, getF, &lf, lua_tostring( L, -1 ), mode );
                    readstatus = ferror( lf.f );
                    if ( filename )
                        fclose( lf.f ); /* close file (even in case of errors) */
                    if ( readstatus )
                    {
                        lua_settop( L, fnameindex ); /* ignore results from 'lua_load' */
                        return errfile( L, "read", fnameindex );
                    }
                    lua_remove( L, fnameindex );
                    return status;
                }
            

这个函数把一个文件加载为 Lua 代码块。 这个函数使用lua_load加载文件中的数据。 代码块的名字被命名filename。 如果filename为NULL, 它从标准输入加载。 如果文件的第一行以#打头,则忽略这一行。
Lua源码分析 - 虚拟机篇 - 语义解析之loadfile文件读取(14) 在这里完成文件的读取之后进入 lua_callk() 执行 Lua 代码块.


                LUA_API void lua_callk( lua_State* L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k )
                {
                    StkId func;
                    lua_lock( L );
                    api_check( L, k == NULL || !isLua( L->ci ), "cannot use continuations inside hooks" );
                    api_checknelems( L, nargs + 1 );
                    api_check( L, L->status == LUA_OK, "cannot do calls on non-normal thread" );
                    checkresults( L, nargs, nresults );
                    func = L->top.p - ( nargs + 1 );
                    if ( k != NULL && yieldable( L ) )
                    {                                   /* need to prepare continuation? */
                        L->ci->u.c.k   = k;             /* save continuation */
                        L->ci->u.c.ctx = ctx;           /* save context */
                        luaD_call( L, func, nresults ); /* do the call */
                    }
                    else                                       /* no continuation or no yieldable */
                        luaD_callnoyield( L, func, nresults ); /* just do the call */
                    adjustresults( L, nresults );
                    lua_unlock( L );
                }
            

luaD_call( L, func, nresults );/* do the call */ 通过 luaD_call 执行。


                /*
                ** Call a function (C or Lua) through C. 'inc' can be 1 (increment
                ** number of recursive invocations in the C stack) or nyci (the same
                ** plus increment number of non-yieldable calls).
                ** This function can be called with some use of EXTRA_STACK, so it should
                ** check the stack before doing anything else. 'luaD_precall' already
                ** does that.
                */
                l_sinline void ccall( lua_State* L, StkId func, int nResults, l_uint32 inc )
                {
                    CallInfo* ci;
                    L->nCcalls += inc;
                    if ( l_unlikely( getCcalls( L ) >= LUAI_MAXCCALLS ) )
                    {
                        checkstackp( L, 0, func ); /* free any use of EXTRA_STACK */
                        luaE_checkcstack( L );
                    }
                    if ( ( ci = luaD_precall( L, func, nResults ) ) != NULL )
                    {                                /* Lua function? */
                        ci->callstatus = CIST_FRESH; /* mark that it is a "fresh" execute */
                        luaV_execute( L, ci );       /* call it */
                    }
                    L->nCcalls -= inc;
                }
            

修改 对应 luaL_loadfilex 在 LittleFs 下修改为了:


                LUALIB_API int luaL_loadfilex( lua_State* L, const char* filename, const char* mode )
                {
                    LoadF lf;
                    int status, readstatus;
                    int c;
                    int fnameindex = lua_gettop( L ) + 1; /* index of filename on the stack */
                    lua_pushfstring( L, "@%s", filename );
                    lf.f = pico_open( filename, lfs_mode("r") );
                    if ( lf.f < 0 )
                        return errfile( L, "open", fnameindex );
                    lf.n = 0;
                    if ( skipcomment( lf.f, &c ) ) /* read initial portion */
                        lf.buff[lf.n++] = '\n';    /* add newline to correct line numbers */
                    if ( c == LUA_SIGNATURE[0] )
                    {             /* binary file? */
                        lf.n = 0; /* remove possible newline */
                        if ( filename )
                        {                      
                            pico_close(lf.f);                     /* "real" file? */
                            lf.f = pico_open( filename, lfs_mode("r") ); /* reopen in binary mode */
                            if ( lf.f < 0 )
                                return errfile( L, "reopen", fnameindex );
                            skipcomment( lf.f, &c ); /* re-read initial portion */
                        }
                    }
                    if ( c != EOF )
                        lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */
                    status     = lua_load( L, getF, &lf, lua_tostring( L, -1 ), mode );
                    if ( filename )
                        pico_close( lf.f ); /* close file (even in case of errors) */
                    lua_remove( L, fnameindex );
                    return status;
                }
                

在pmain函数中的修改,因为在嵌入式设备中运行不需要进行命令函参数的判断,所以可以直接去掉。


                static int dofilecont( lua_State* L, int d1, lua_KContext d2 )
                {
                    ( void )d1;
                    ( void )d2; /* only to match 'lua_Kfunction' prototype */
                    return lua_gettop( L ) - 1;
                }
                
                static int luaB_dofile( lua_State* L, const char* fname )
                {
                    lua_settop( L, 1 );
                    if ( luai_unlikely( luaL_loadfile( L, fname ) != LUA_OK ) )
                        return lua_error( L );
                    lua_callk( L, 0, LUA_MULTRET, 0, dofilecont );
                    return dofilecont( L, 0, 0 );
                }
                
                
                /* }================================================================== */
                
                /*
                ** Main body of stand-alone interpreter (to be called in protected mode).
                ** Reads the options and handles them all.
                */
                static int pmain( lua_State* L )
                {
                    luaL_checkversion( L );       /* check that interpreter has correct version */
                    luaL_openlibs( L );           /* open standard libraries */
                    lua_gc( L, LUA_GCRESTART );   /* start GC... */
                    lua_gc( L, LUA_GCGEN, 0, 0 ); /* ...in generational mode */
                #ifndef MINIMIZE_NO_COMPILER
                    if ( lua_stdin_is_tty() )
                    { /* running in interactive mode? */
                        print_version();
                        doREPL( L ); /* do read-eval-print loop */
                    }
                #else
                    luaB_dofile(L, "boot");
                #endif
                    lua_pushboolean( L, 1 ); /* signal no errors */
                    return 1;
                }
            

完整代码