用了树莓派 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;
}
print("hello world!")
使用命令 luac -o test test.lua
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;
}