目录
2.1、C库代码
由于某些原因我们的elua开源项目中不能采用标准的c库函数,而是内置了一套专用的c库代码,这套c库代码与标准c库的命名保持一致。不过其内部的实现却发生了很大的变化。
有的同学在学习STM32的时候,可能会遇到一个问题那就是串口通信printf重定向。st官方并没有提供printf函数给我们用,这时候如果想要使用printf向串口打印日志的话那就需要对printf进行重定向。
在标准的c库函数中printf是向控制台打印输出信息。而在嵌入式平台上,是没有控制台的。如果想要用printf函数的话那就只有两个办法。
第一就是大家在使用stm32时用到的方法,对printf函数进行输出重定向。那么这个办法在裸机编程上倒是看不出来什么弊端。大家在使用操作系统编程比如freertos这些就会发现一个问题。如果任务的堆栈空间分配比较小,同时又在任务中使用到了printf打印数据那么就会出现堆栈溢出导致程序崩溃。这是因为printf函数使用时会在栈空间定义一个大缓冲区存储数据,这个缓冲区只是临时定义的,用完就会被析构掉。在使用printf时临时缓冲区会被算到任务堆栈大小中,只要给任务分配到空间小了,那么就会导致程序堆栈空间溢出程序崩溃。按照我以前踩坑的经验,即使你只是点个灯,只要用到了printf函数,如果此时任务的堆栈空间小于2048程序就会崩溃。这样就不太好了是吧,起步2048哪有这么多内存去搞。
为了解决这个问题,那么我们就需要用到第二个方法,那就是我们即将要讲到的重写c标准库,把它里面的空间缩小,改变实现的方法。
C库代码这一小节对应的是我们的elua开源项目中的newlib文件夹里面的内容,里面工作是将标准的C库函数重新实现了一下。主要就是一些我们经常使用到的这些,比如#include"string.h"
、#include"stdio.h"
、#include"stdlib.h"
、#include"malloc.h"
、#include"math.h"
。还有一些用的比较少,但是elua解析器里面会用到的,这里就不讲了。
newlib文件夹的结构如下图所示,涉及到的文件数量也是一大把。有兴趣的自己去看。
有的函数需要和硬件平台打交道,不能直接用标准库。比如stdio库中的一些函数,就拿fopen_ext"
函数来看一下,它里面的实现用到了一些和平台相关的代码。
char *fgets_ext(char *buf, int n, FILE *fp);
FILE *fopen_ext(const char *file, const char *mode);
int fclose_ext(FILE *fp);
int getc_ext(FILE *fp);
int ungetc_ext(int c, FILE *fp);
size_t fread_ext(void *buf, size_t size, size_t count, FILE *fp);
int fseek_ext(FILE *fp, long offset, int whence);
long ftell_ext(FILE *fp);
int feof_ext(FILE *fp);
FILE *fopen_ext(const char *file, const char *mode)
{
FILE *fp = NULL;
E_LUA_SCRIPT_TABLE_SECTION section = LUA_SCRIPT_TABLE_MAX_SECTION;
T_UNCOMPRESS_FILE_TABLE_ITEM *pItem = NULL;
int fileNameLen = strlen(file);
if((!file) || (strlen(file) == 0))
{
OPENAT_print("[fopen_ext]: para error!\n");
return fp;
}
if(FindUncompressFileItem(§ion, &pItem, file))
{
fp = L_CALLOC(1,sizeof(FILE));
if(fp)
{
fp->_flags = section;
fp->_cookie = pItem;
}
fp->_type = LUA_UNCOMPRESS_FILE;
#ifdef AM_LUA_CRYPTO_SUPPORT
if(strncmp(&file[fileNameLen - 5],".luac", 5) == 0)
{
fp->_type |= ENC_FILE;
}
#endif
//printf("[fopen_ext]: %s %d!\n", file, fp->_type);
}
return fp;
}
当然这其中个例不能说明一切,那我们就再来看一个函数_malloc_r
。
void* _malloc_r( size_t size )
{
#ifdef MUXUSE_DLMALLOC_MEMORY_AS_LUA_SCRIPT_LOAD
if(bScriptLoaded)
{
return dlmalloc(size);
}
else
#endif
{
return CNAME( malloc )( size );
}
这个_malloc_r
经过if判断调用的是CNAME( malloc )( size )
,而CNAME( malloc )( size )
最终又指向platform_malloc
这是我们的elua抽象层的代码,最终它调用的是函数是需要和硬件打交道的,直接采用标准版的c库肯定是不适用的。
void* platform_malloc( size_t size )
{
return OPENAT_malloc( size );
}
这个其实也算是一个抽象层,只不过它抽象的是标准库函数。使elua解析器能够在嵌入式平台上面正常运行。
有人可能会讲,那我用其他的开发平台怎么能直接用malloc函数、printf函数,这些标准函数直接用也没有什么问题啊,哪有这么多事情。这里我提醒一下,那是因为开发包底层已经把这部分代码处理好了,有可能是重写,也有可能是重定向。对于用户什么来讲都不用管,直接用。但是这个底层绝对不是直接使用的c库的标准代码的。
各位同学不信的话可以打开我们的iot_sdk_4g_8910
开发包看下components
文件夹下是不是也有一个newlib
文件夹。
那我们可能又有一个疑问,既然elua开源项目是运行在iot_sdk_4g_8910
上面的,为什么不直接用iot_sdk_4g_8910
的newlib呢。这里那就说一下,既然我们做开源,那就说明这个elua软件包不仅仅是只能用在iot_sdk_4g_8910
上面的,只要各位有能力那就可以把它运行在任何平台上。那就拿stm32来讲,他就没有给用户提供这些c标准库(现在我也不清楚有没有提供,至少几年前是没有的)。它都没有,那用户移植起来岂不是很复杂,甚至有可能移植不成功。