finish shell分析之语法解释器
如果说linux 的shell是属于脚本类的语言风格,那finsih shell的语言风格明显是c的。毕竟c比较偏底层,资源占用少,这个对于资源贫瘠的嵌入式系统而言是非常适合的。finish shell从usart获取文本信息,在根据一定的语法规矩将文本语言重新按执行顺序组织一遍,最后再将重新组织的语句翻译成汇编指令,最后交由cpu执行指令。当然finsih shell的汇编指令都是伪汇编指令,而且执行的环境也是虚拟出来的,而这个是编译执行过程,我之后再提。
语句的执行是以数据为基础的。受限于系统的架构,finish shell里面的数据类型并不多,整体来说分成以下几个部分
针对上述的代码,在int a中,int是指代变量类型,a是变量名,因为这条指令是在串口中读取的,所以a是动态申请的变量,隶属于VAR中。像‘1’‘2’‘3’是属于int型常量,而“+”“*”“-”是符号。而SYS_VAR和SYS_CALL是在编译的时候生成的,分别通过宏FINSH_VAR_EXPORT和FINSH_FUNCTION_EXPORT添加到系统中的。
long hello()
{
rt_kprintf("Hello RT-Thread!\n");
return 0;
}
FINSH_FUNCTION_EXPORT(hello, say hello world);
对于上述的hello函数,通过调用FINSH_FUNCTION_EXPORT就可以添加到finish shell中,在终端中输入hello(),串口就可以打印"Hello RT-Thread!"。
#define FINSH_FUNCTION_EXPORT(name, desc) \
const char __fsym_##name##_name[] = #name; \
const char __fsym_##name##_desc[] = #desc; \
const struct finsh_syscall __fsym_##name SECTION("FSymTab")= \
{ \
__fsym_##name##_name, \
__fsym_##name##_desc, \
(syscall_func)&name \
};
具体分析FINSH_FUNCTION_EXPORT(hello, say hello world),其实这个宏就是申请了3个变量_fsym_hello_name[]=’hello’,_fsm_hello_desc[]=’Hello RT-Thread!\n’,
_fsym_hello={_fsym_hello_name,,_fsm_hello_desc,hello}。说穿了就是_fsym_hello里有3个4字节的指针,分别指向_fsym_hello_name,_fsm_hello_desc[]和hello()函数。那_fsym_hello是如何和SYS_CALL扯上关系的呢?这就要从SECTION("FSymTab")说起了。SECTION()是一个宏变量,针对不同的编译平台对于不同的操作,但无论编译平台如何其作用是相同的。本人编译平台是MDK,打开rtthread-stm32.sct文件我们可以看到
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
这个文件作用主要是指出了编译工程后链接文件的各个段的地址空间。我们知道编译程序一般会生成text,rodata,data,bss以及一些其他的段,我们可以通过sct将某个函数的代码段放到某个地址空间,也可以将它的数据段放到另一个地址空间。一般来说,我们不会蛋疼的去安排每个.c文件或是函数的链接地址空间,因为系统已经默认优化好了但是我们必须知道是可以通过修改sct来改变链接地址空间的。这个相信研究过linux内核编译的同学一定心领神会。然后再看rtthread-stm32.map文件,我截取了与FSymTab相关的一段。
FSymTab$$Base 0x08013614 Number 0 led.o(FSymTab) __fsym_led 0x08013614 Data 12 led.o(FSymTab) __fsym_list_mem 0x08013620 Data 12 mem.o(FSymTab) __fsym_hello 0x0801362c Data 12 cmd.o(FSymTab) __fsym_version 0x08013638 Data 12 cmd.o(FSymTab) __fsym_list_sem 0x08013644 Data 12 cmd.o(FSymTab) __fsym_list_event 0x08013650 Data 12 cmd.o(FSymTab) __fsym_list_mutex 0x0801365c Data 12 cmd.o(FSymTab) __fsym_list_mailbox 0x08013668 Data 12 cmd.o(FSymTab) __fsym_list_msgqueue 0x08013674 Data 12 cmd.o(FSymTab) __fsym_list_mempool 0x08013680 Data 12 cmd.o(FSymTab) __fsym_list_timer 0x0801368c Data 12 cmd.o(FSymTab) __fsym_list_device 0x08013698 Data 12 cmd.o(FSymTab) __fsym_list 0x080136a4 Data 12 cmd.o(FSymTab) __fsym_ls 0x080136b0 Data 12 dfs_raw.o(FSymTab) __fsym_mkdir 0x080136bc Data 12 dfs_raw.o(FSymTab) __fsym_rm 0x080136c8 Data 12 dfs_raw.o(FSymTab) __fsym_cat 0x080136d4 Data 12 dfs_raw.o(FSymTab) __fsym_mkfs 0x080136e0 Data 12 dfs_elm.o(FSymTab) FSymTab$$Limit 0x080136ec Number 0 dfs_elm.o(FSymTab) Region$$Table$$Base 0x080136ec Number 0 anon$$obj.o(Region$$Table) Region$$Table$$Limit 0x0801370c Number 0 anon$$obj.o(Region$$Table) VSymTab$$Base 0x0801370c Number 0 cmd.o(VSymTab) __vsym_dummy 0x0801370c Data 16 cmd.o(VSymTab) VSymTab$$Limit 0x0801371c Number 0 cmd.o(VSymTab)
相信大家找到了_fsym_hello了吧,所谓的SECTION("FSymTab")也就是把_fsym_hello这个12个字节的常量,保存在FsymTab这个段内,链接的时候根据sct文件安排按顺序将FSymTab放到Flash的地址空间里去。也就是说,无论我在这个或那个文件用调用了FINSH_FUNCTION_EXPORT,说生成的_fsym***一定是连续分布在flash的地址空间了。FINSH_VAR_EXPORT的执行原理和FINSH_FUNCTION_EXPORT一样,只不过存的是变量的地址,我就不再重复说明了。
至于为什么要这么大费周章的将FSymTab和VSymTab的变量放到一起,就是为了方便生成syscall_table和sysvar_table。在finsh_system_init中有两个函数
finsh_system_function_init(&FSymTab$$Base, &FSymTab$$Limit);
finsh_system_var_init(&VSymTab$$Base, &VSymTab$$Limit);
void finsh_system_function_init(void* begin, void* end)
{
_syscall_table_begin = (struct finsh_syscall*) begin;
_syscall_table_end = (struct finsh_syscall*) end;
}
void finsh_system_var_init(void* begin, void* end)
{
_sysvar_table_begin = (struct finsh_sysvar*) begin;
_sysvar_table_end = (struct finsh_sysvar*) end;
}
再看一下rtthread-stm32.map,是不是找到了FSymTab$$Base,FSymTab$$Limit和VSymTab$$Base, VSymTab$$Limit,就这样我们生成了syscall_table和sysvar_table,而这系统就是用这两个表来查找SYS_CALL和SYS_VAR变量的。
除了静态的生成SYS_CALL函数外,rt_thread也支持动态的加载SYS_CALL函数。
void finsh_syscall_append(const char* name, syscall_func func)
{
/* create the syscall */
struct finsh_syscall_item* item;
item = (struct finsh_syscall_item*)rt_malloc(sizeof(struct finsh_syscall_item));
if (item != RT_NULL)
{
item->next = NULL;
item->syscall.name = strdup(name);
item->syscall.func = func;
if (global_syscall_list == NULL)
{
global_syscall_list = item;
}
else
{
item->next = global_syscall_list;
global_syscall_list = item;
}
}
}
注意这里将函数添加到了global_syscall_list链表中了,而这个链表的功能是相当于syscall_table的,只不过它是负责动态生成的SYS_CALL。与之对应的有global_sysvar_list链表,其对应的是SYS_VAR。