Chinaunix首页 | 论坛 | 博客
  • 博客访问: 181984
  • 博文数量: 20
  • 博客积分: 125
  • 博客等级: 入伍新兵
  • 技术积分: 985
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-08 13:48
个人简介

热爱开源,喜欢分析操作系统架构

文章分类

全部博文(20)

文章存档

2013年(17)

2012年(3)

分类: C/C++

2012-11-03 22:09:27

   于对程序员来说,除了机器码之外,不管你用什么语言编写程序都离不开编译器。但是我们所写下的程序,都是以我们能够看的懂的文本形式保存的,虽然人能看得懂但不代表机器也能够看的懂。所以在文本程序到编译到可执行文件之前最先要完成的步骤就是语法的解释。比如下段简单的代码

int a=2+3×(2-1);

    编译器从开始读一直读取到‘;’,获取整个语法段。按照c语言的优先级,这个语句先被分为3个部分,{int a} {=}和{2+3*(2-1)}。对于int a是需要申请一个typeof(int)大小的内存,这个内存标记为符合’a’,之后这个namespace空间里所有出现'a'的地方都会被指定到这块内存。‘=’是一个赋值符合,这个符合会联合之前和之后的两个语法段,也就是将{2+3*(2-)}的结果赋给‘a’所指的内存。具体针对于{2+3*(2-1)},语法解释器会先按照优先级的顺序,先括号、乘法再是加法,继而获得这个部分的值。总的来说是以下这个一个步骤:

点击(此处)折叠或打开

  1. int a
  2. 2-1=1
  3. 3*(1)=3
  4. 2+3=5
  5. a=5

    如果说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添加到系统中的。

点击(此处)折叠或打开

  1. long hello()
  2. {
  3.    rt_kprintf("Hello RT-Thread!\n");
  4.    return 0;
  5. }
  6. FINSH_FUNCTION_EXPORT(hello, say hello world);

    对于上述的hello函数,通过调用FINSH_FUNCTION_EXPORT就可以添加到finish shell中,在终端中输入hello(),串口就可以打印"Hello RT-Thread!"。

点击(此处)折叠或打开

  1. #define FINSH_FUNCTION_EXPORT(name, desc)                     \
  2.         const char __fsym_##name##_name[] = #name;                     \
  3.         const char __fsym_##name##_desc[] = #desc;                     \
  4.         const struct finsh_syscall __fsym_##name SECTION("FSymTab")= \
  5.         {                            \
  6.             __fsym_##name##_name,    \
  7.             __fsym_##name##_desc,    \
  8.             (syscall_func)&name        \
  9.         };

    具体分析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文件我们可以看到

点击(此处)折叠或打开

  1. LR_IROM1 0x08000000 0x00080000 { ; load region size_region
  2.   ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
  3.    *.o (RESET, +First)
  4.    *(InRoot$$Sections)
  5.    .ANY (+RO)
  6.   }
  7.   RW_IRAM1 0x20000000 0x00010000 { ; RW data
  8.    .ANY (+RW +ZI)
  9.   }
  10. }
     这个文件作用主要是指出了编译工程后链接文件的各个段的地址空间。我们知道编译程序一般会生成text,rodata,data,bss以及一些其他的段,我们可以通过sct将某个函数的代码段放到某个地址空间,也可以将它的数据段放到另一个地址空间。一般来说,我们不会蛋疼的去安排每个.c文件或是函数的链接地址空间,因为系统已经默认优化好了但是我们必须知道是可以通过修改sct来改变链接地址空间的。这个相信研究过linux内核编译的同学一定心领神会。然后再看rtthread-stm32.map文件,我截取了与FSymTab相关的一段。

点击(此处)折叠或打开

  1. FSymTab$$Base       0x08013614   Number   0 led.o(FSymTab)
  2.  __fsym_led         0x08013614   Data    12 led.o(FSymTab)
  3.  __fsym_list_mem    0x08013620   Data    12 mem.o(FSymTab)
  4.  __fsym_hello       0x0801362c   Data    12 cmd.o(FSymTab)
  5.  __fsym_version     0x08013638   Data    12 cmd.o(FSymTab)
  6.  __fsym_list_sem    0x08013644   Data    12 cmd.o(FSymTab)
  7.  __fsym_list_event  0x08013650   Data    12 cmd.o(FSymTab)
  8.  __fsym_list_mutex  0x0801365c   Data    12 cmd.o(FSymTab)
  9.  __fsym_list_mailbox  0x08013668 Data    12 cmd.o(FSymTab)
  10.  __fsym_list_msgqueue 0x08013674 Data    12 cmd.o(FSymTab)
  11.  __fsym_list_mempool  0x08013680 Data    12 cmd.o(FSymTab)
  12.  __fsym_list_timer  0x0801368c   Data    12 cmd.o(FSymTab)
  13.  __fsym_list_device 0x08013698   Data    12 cmd.o(FSymTab)
  14.  __fsym_list        0x080136a4   Data    12 cmd.o(FSymTab)
  15.  __fsym_ls          0x080136b0   Data    12 dfs_raw.o(FSymTab)
  16.  __fsym_mkdir       0x080136bc   Data    12 dfs_raw.o(FSymTab)
  17.  __fsym_rm          0x080136c8   Data    12 dfs_raw.o(FSymTab)
  18.  __fsym_cat         0x080136d4   Data    12 dfs_raw.o(FSymTab)
  19.  __fsym_mkfs        0x080136e0   Data    12 dfs_elm.o(FSymTab)
  20. FSymTab$$Limit      0x080136ec   Number   0 dfs_elm.o(FSymTab)
  21. Region$$Table$$Base 0x080136ec   Number   0 anon$$obj.o(Region$$Table)
  22. Region$$Table$$Limit 0x0801370c  Number   0 anon$$obj.o(Region$$Table)
  23. VSymTab$$Base        0x0801370c  Number   0 cmd.o(VSymTab)
  24.   __vsym_dummy      0x0801370c   Data    16 cmd.o(VSymTab)
  25. 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);

点击(此处)折叠或打开

  1. void finsh_system_function_init(void* begin, void* end)
  2. {
  3.     _syscall_table_begin = (struct finsh_syscall*) begin;
  4.     _syscall_table_end = (struct finsh_syscall*) end;
  5. }

  6. void finsh_system_var_init(void* begin, void* end)
  7. {
  8.     _sysvar_table_begin = (struct finsh_sysvar*) begin;
  9.     _sysvar_table_end = (struct finsh_sysvar*) end;
  10. }

    再看一下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函数。

点击(此处)折叠或打开

  1. void finsh_syscall_append(const char* name, syscall_func func)
  2. {
  3.     /* create the syscall */
  4.     struct finsh_syscall_item* item;

  5.     item = (struct finsh_syscall_item*)rt_malloc(sizeof(struct finsh_syscall_item));
  6.     if (item != RT_NULL)
  7.     {
  8.         item->next = NULL;
  9.         item->syscall.name = strdup(name);
  10.         item->syscall.func = func;

  11.         if (global_syscall_list == NULL)
  12.         {
  13.             global_syscall_list = item;
  14.         }
  15.         else
  16.         {
  17.             item->next = global_syscall_list;
  18.             global_syscall_list = item;
  19.         }
  20.     }

     注意这里将函数添加到了global_syscall_list链表中了,而这个链表的功能是相当于syscall_table的,只不过它是负责动态生成的SYS_CALL。与之对应的有global_sysvar_list链表,其对应的是SYS_VAR。

阅读(6686) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

取个ID好难啊2014-11-08 11:10:20

请问楼主我想用数字代表函数需要怎么改啊?比如用1代替hello(),在终端输入1就可以打印"Hello RT-Thread!"