eLua 体系结构概述

eLua体系结构

下图整体概述了eLua的体系结构

eLua 体系结构概述

eLua使用平台的概念表示一组具有相同内核结构的CPU,尽管这些CPU针对不同的具体外设,内存和其他一些属性分别有特定的实现。一个eLua实现给定平台的一个或多个CPU。比如,eLua的lm3s移植可以运行LMSS8962,LM3S6965,LM3S6918处理器,它们都是lm3s平台下的一部分。从上图可以看出,eLua试着尽可能在不同平台之间使用一些简单的设计规则。

  • 所有代码跟具体平台无关的是通用代码,而且它应该尽可能使用标准C语言书写。这样就可以使它在不同的体系结构和编译器间有高度的可移植性,就像Lua本身一样。
  • 所有那些不可能通用的代码(几乎都是外设和具体CPU相关)应该尽可能的使之可移植,通过使用通用接口,这种通用接口要能让eLua能在所有的平台上都能运行。这种接口称为平台接口。
  • 所有平台(和它们的外设)不能同等的创建,因为在功能上有巨大的变化。如前所述,平台接口试着组合不同平台的共同特性。如果想在一个给定平台上实现具体的一个功能的话,可以通过使用一个平台模块。这些平台模块是数据特定平台的,它们的目的就是填补平台接口和一个具体平台所提供的所有具体特性之间的缺口。

通用代码

下面给出了不完整的一些项可以被归类于通用代码。

  • Lua代码(很明显是)加上LTR补丁
  • 所有在eLua的组件(比如ROM文件系统,XMODEM接收代码,eLua shell,TCP/IP栈等等)
  • 所有通用模块,这些模块是lua的模块,展示了Lua平台的不同功能。
  • 通用外设支持代码,比如ADC支持代码(src/common/elua_adc.c),此代码独立于真实的ADC硬件。
  • libc代码(比如allocators和Newlib)

上述的描述应该会给你一个好的关于通用代码的印象。注意通用代码所占据的层应该尽可能的多,也就是说,应该尽可能的有更多的通用代码。比如如下例子:

  • 如果你想给eLua添加一个新的文件系统,这很清楚应该是通用代码。这就好像代码产生的文件系统与具体依赖相关的物理媒介的处理。如果你幸运的话,你可以使用在平台接口的一些函数解决这种依赖关系。比如通过SPI控制SD卡,因为这个平台拥有SPI层。如果不是那样的话,你必须在一个独立的接口中组合与特定的平台相关的函数,然后实现它让其在所有平台上都能使用这种文件系统。这给了代码最大的移植性。
  • 如果你想为特定的ADC外设(此ADC通过SPI工作)添加驱动,那么可以使用如前所述的同样理论,尽你可能的把它写成通用代码,并且使用平台接口中的函数实现你需要的特定的SPI。
当设计和实现一个新的组件时,记住另外一个eLua的设计目标:灵活性。用户应该可以选择在二进制文件里面包含哪些组件,实现的时候就要把这些考虑进去。同样的理论也适用于通用模块:用户能根据需要选择相应的模块。
为了移植性的最大化,尽可能让你的代码工作在各种可能遇到的情景中。这样的例子就如在stdio/stdout/stderr中代码的处理。
代码(src/Newlib/genstd.c),此中代码就承认了这样一个事实,终端可以通过可以多种我物理传输接口实现(RS232,SPI控制的液晶/键盘等等)。所以代码使用指针来处理收和发的函数。这样在速度和资源的消耗上是最小的,但是它关系到重要的移植部分。

平台接口

运用得当的话,平台接口能允许在不同的平台上书写具有可移植性的代码,包括C和Lua.平台接口的一个重要特性就是它试着组合不同平台下共同特性。比如,一个eLua所支持的平台的串口能工作在回路模式,但是其它平台不能,那么关于回路模式的支持就不能被包括进平台接口里面。
关于平台接口使用的特别的一点是:记住它不但可以用C,还可以用Lua.平台接口主要通过通用模块允许Lua控制平台的外设,但这不是它唯一的用法。如果可能的话也可以用C语言实现一个通用模块而且也能用C控制外设。这个例子就是上面所述的打造一个新的文件系统。
平台接口的定义总是在inc/platform.h头文件里。

平台和移植

所有运行eLua的平台(和i平台接口的实现)都是在它的概念层上实现的。移植就是eLua能在一个给定的平台下运行。
移植应当包括特定的外设驱动,很多时候这都是来自CPU所在平台的支持包。这些驱动用于实现平台接口。注意下面几点:
  • 移植不要实现平台下的所有功能,只是它需要的就够了。也就是说,通过所构建的eLua二进制文件用户能够取得相应的需要控制的部分。如果你不需要SPI模块,你就不需要实现关于它的平台接口。
  • 平台接口中的一部分可以在一个文件中实现,让其够工作在所有平台上(src/common.c),这简化了一些模块的设计(比如定时器模块),这样也实现了所绑定平台的通用特性,但在所有平台上也有共同的表现(比如虚拟定时器)。如果你正在写特定平台下代码的话,尽可能不要去修改它,但是最好记住它是干什么的。
一个平台的也许包括一个或多个独立平台的模块。正如已经解释的那样,这样做的目的是让Lua能发挥所有平台相关外设的潜能,而不只是被平台接口覆盖到的外设功能。当然外设具有特定的功能时也不会被归类到平台接口。按照惯例,所有平台的独立模块应该被组合进一个单独的模块,此模块的名字与它本身的平台是一样的。如果一个平台的独立模块增加的功能是平台接口所拥有的,它应该也是相同的名字。否则,应该给它一个不一样的名字。比如:

  • 如果在LM3S平台实现了一个新的功能关于UART模块,那么响应的模块应该被称作lms3s.uart。
  • 如果实现了一个特定外设驱动在LPC2888平台下,比如双通道DAC,给它一个特别的名字,比如lpc288x.audiodac.

移植相关结构

所有关于平台名字的代码(包括外设驱动)必须集中在一个叫做src/platform/<name>的文件里(比如src/platform/lm3s就是针对lm3s平台的)。每一个这样的特定于平台的子目录必须包含至少这些文件:
type.h:其中定义的特定的数据类型。下图是关于i386平台下一些数据类型。
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned long u32;
typedef signed long s32;
typedef unsigned long long u64;
typedef signed long long s64;

conf.py:这是特定平台的构建文件。构建系统会使用此文件,此文件有以下目的:
获得特定平台下将被编译成eLua二进制文件的文件列表。它们通过specific_files字符串 加载,空格隔开。而且必须给出相关文件的路径。下面的例子是关于i386平台的:
specific_files = "boot.s common.c descriptor_tables.c gdt.s interrupt.s isr.c kb.c  monitor.c timer.c platform.c"
# Prepend with path
specific_files = " ".join( [ "src/platform/%s/%s" % ( platform, f ) for f in specific_files.split() ] )

为了获得不同工具链单元的所有需要的命令来编译eLua。它们需要被声明在tools变量里。每个独立的字典里的关键字是相应平台的名字,linkcom代码linker,ascom代表assember,比如下面就是关于i386平台工具变量的设置:
# Toolset data
 tools[ 'i386' ] = {}
 tools[ 'i386' ][ 'cccom' ] = "%s %s %s -march=i386 -mfpmath=387 -m32 -ffunction-sections -fdata-sections -fno-builtin -fno-stack-protector %s -Wall -c $SOURCE -o $TARGET" % ( toolset[ 'compile' ], opt, local_include, cdefs )
 tools[ 'i386' ][ 'linkcom' ] = "%s -nostartfiles -nostdlib -march=i386 -mfpmath=387 -m32 -T %s -Wl,--gc-sections -Wl,-e,start -Wl,--allow-multiple-definition -o $TARGET $SOURCES -lc -lgcc -lm %s" % ( toolset[ 'compile' ], ldscript, local_libs )

注意到tools的定义使用到了toolset的定义,toolset是一个包含当前所使用工具名的字典。这也是eLua构建系统的一部分。

为了实现一个可以接收eLua可执行文件的函数(构建步骤的结果),并且产生一个在相应平台上合适的编程文件,这个函数的名字应该被设置进tools字典中,如下所示(关于str7平台的例子代码):
# Programming function for STR7
 def progfunc_str7( target, source, env ):
   outname = output + ".elf"
   os.system( "%s %s" % ( toolset[ 'size' ], outname ) )
   print "Generating binary image..."
   os.system( "%s -O binary %s %s.bin" % ( toolset[ 'bin' ], outname, output ) )
           
   tools[ 'str7' ][ 'progfunc' ] = progfunc_str7

再一次提醒这个函数使用相同的toolset变量跟上面的一样。
stacks.h:按照惯例,系统所使用的栈空间被声明在这里。下面给出的例子是来自at91sam7x平台下的一段代码:
#define  STACK_SIZE_USR   2048
#define  STACK_SIZE_IRQ   64
#define  STACK_SIZE_TOTAL ( STACK_SIZE_USR + STACK_SIZE_IRQ )

platform.c:按照惯例,平台接口的实现就在这个文件里。它也包含一些与平台无关的初始化函数。
platform_conf.h:这是关于平台配置的头文件,其中给出了平台本身和构建相关平台的配置信息。从这个文件中,你将看到下列内容:
  • 将要被构建的组件列表
  • 将要被构建的模块列表
  • 静态配置数据
  • CPU的各种外设的数目,下面的例子代码展示了来自同一平台(lm3s)下不同CPU的外设情况。宏FORXXX被定义在conf.py文件中
// Number of resources (0 if not available/not implemented)
#define NUM_PIO               7
#define NUM_SPI               1
#ifdef FORLM3S6965
  #define NUM_UART            3
#else
  #define NUM_UART            2
#endif
#define NUM_TIMER             4
#ifndef FORLM3S6918
  #define NUM_PWM             6
#else
  #define NUM_PWM             0
#endif  
#define NUM_ADC               4

  • 特定的外设配置:包括UART使能缓冲,使能和开启虚拟定时器,设置PIO配置等等。
  • 内存配置:描述了内存分布区域。此区域将被标准系统分配。两个宏MEM_START_ADDRESS 和MEM_END_ADDRESS定义了空闲RAM的起始地址和结束地址。如果你的板子有外部RAM空间,你也应该在这里定义。如果没有的话,你将只能使用内部内存,并且一般你需要使用链接定义标志end找出你空闲内存从哪里开始,下面的例子来自ATEVK1100(AVR32)开发板,它拥有内部和外部RAM
// Allocator data: define your free memory zones here in two arrays
// (start address and end address)
#define MEM_START_ADDRESS     { ( void* )end, ( void* )SDRAM }
#define MEM_END_ADDRESS       { ( void* )( 0x10000 - STACK_SIZE_TOTAL - 1 ), ( void* )( SDRAM + SDRAM_SIZE - 1 ) }

  • 网络配置:如果需要TCP/IP在你的开发板上,你需要向eLua添加网络支持。你也需要另一个叫做uip_conf.h文件用来配置uip以用于你特定的处理器架构。

除了上述那些被要求的文件外,常见的情况是在移植中需要添加跟特定平台相关的文件:
  • 启动文件:一般用汇编书写,做一些低档次的初始化,设置堆栈指针,归零BSS区域,将ROM中DATA区域移动到RAM中,然后跳转到main函数。
  • 链接命令文件
  • CPU支持包:一般来自设备制造商,包括的代码有控制外设,配置内核,开启中断等等。

水平有限,如有错误,给出指正。

你可能感兴趣的:(eLua 体系结构概述)