dr2001 发表于 2009-4-10 21:51:11

RVCT,C语言运行库初始化/裁剪的简单说明和Demo

这几天看到有人问一些问题,涉及C运行库的初始化和剪裁问题;这里用一个简单的Demo,简要说一说,RVCT CRTL初始化和剪裁的事情。
注意,这个不适用于编译在Linux之类的系统上的情况,也不适用于GCC,IAR等编译器。
适用于RVCT编译系统,编译成一个BIN,直接烧在片子上运行的情况。比如ADS,Keil,等等。

RESET向量:Vector_RST.S
Vector_RST.Sourdev_435310.txt(文件大小:5K) (原文件名:Vector_RST.txt)
值得注意的几点:
- STACK Size的定义,必须是8的倍数。这是现在ARM编译器AAPCS的规定,要求堆栈必须8Bytes对齐。否则就会出错。原因是_int64的支持和LDRD系列指令。
- 由于初始化PLL等等的操作,MMU初始化操作都是C语言写的(当然这些函数只能是简单的判断,赋值等),所以,在C RTL正式初始化之前,需要给一个临时的堆栈。
- CODE32,ARM指令编译;PRESERVE8,堆栈是8Byte对齐的,通知编译系统一下。
- BLX Rn是ARMv5E内核才有的指令,926EJ-S之后的。之前的这条指令用MOV LR, PC; BX Rn两个等效替换。用BLX的目的是能支持Thumb和ARM的切换。
- 跳转到main函数的目标标号:__main。这个特别注意,这是CRTL初始化的入口。如果要利用CRTL自己的初始化,那就要走这个入口。
- 很多代码中,$$Base,$$Limit之类的东西这里不存在。因为那些搬移的操作,CRTL自己会干。
- 堆栈定义中,注意描述:NOINIT, ALIGN = 3。启始8Bytes对齐;不把堆栈区间放在ZI里边。也就是说,CRTL在初始化的时候,不会把堆栈全写0。
- BLX __main 那个后边的B .实际上是不可能执行到的。因为有可能用到不初始化CRTL的情况,特别这么写的,这样C退出的时候,会掉在死循环,而不是跑飞。或者这里修改成复位,WDT超时等等都好。

C运行库裁剪:TailorCRTL.s
TailorCRTL.sourdev_435311.txt(文件大小:3K) (原文件名:TailorCRTL.txt)
注意的内容
- IMPORT __use_no_semihosting_swi,这是禁止使用SemiHosting,这个本来用的就不多,而且开了SWI还会有新的乐子。不非常清楚这是干什么的话,一定禁止。
- IMPORT __use_no_heap_region,这是禁止C标准库中所有关于堆Heap的操作。也就是说,至少malloc系列函数一定不能用。如果非要用堆,可以允许之。不过malloc有产生碎片的可能,建议还是另找个内存管理的库来用。对于C标准库中,如果代码用到了使用堆的函数,编译器会给警告的。放心好了。
- CODE32,PRESERVE8同上

- 后边的就是函数了,属于剪裁CRTL的范畴:
@ _sys_exit:这是main函数正常结束(忘记在main函数最后for(;;){}的同学常见)后,关闭C运行库,清理各种东西,确认C运行库已经撤除之后,最后调用的函数。这个东西不能返回。必须是死循环,或者找别的事情干。用户愿意输出点啥什么的都行。当然,如果C运行库出错,用户调用exit函数等等情况下,最后的最后,“C程序”结束之后的归宿,也是这里。
@ __rt_raise:这是C运行库出错,要调用的一个处理函数比较接近终点的处理函数。
@ exit:这就是那个exit()的函数了。
把这三个都重新实现(裁剪)的目的,是因为开头的假定,即在独立系统上运行,这已经是最底层的程序了。如果当前的C环境挂了,那么系统里就没有别的运行环境可以去处理目前的错误。所以我们不需要任何的退出机制,但凡退出,就给我死循环挂掉,或者复位什么的,这里选择了挂掉。这样能省出来一些字节数。

@ __user_setup_stackheap,这个是RVCT4提出来的新的初始化函数,给定main函数开始,使用的堆栈和堆空间。因为我们已经把堆放弃了,给好了栈参数就行了。setup是自己设置SP和栈顶,如果开了编译器的栈检查,那么会有对应的机制。(这个我没测试栈检查,而且,出错了因为__rt_raise的原因,会直接死循环掉)。堆么,没有用,就不管了。
@ __user_initial_stackheap,这个是之前的设置函数,只不过是栈会由CRTL来设置,不需要用户自己弄。
在RVCT4上,setup的好处是如果没有用到相应的东西,96字节的CRTL静态存储区会被节约出来。init的话,就一定会有那96字节的。因为setup在之前的库里没有,所以这么写也不会有什么冲突。

@ _ttywrch,如果出错误了,CRTL是会往外输出字符的,可以用来指明错误类型。这个函数用来输出这些错误字串。如果要用这个功能,就不能裁剪掉__rt_raise。否则就看不到这些东西(该功能从来没用过。。。)


附加说明:
- 这是大致的CRTL裁剪的东西,并不是完善的裁剪和retarget的demo。详尽且权威的信息,请参考RVCT附带的厚厚的手册,有专门很长很长的章节来讲这些东西,在应用程序库手册里。
- 如果用了Scatter文件加载,并且使用了多Load Region,多Exec Region的情况,比如用ATmel 9261,开启了DTCM,ITCM等,建议使用CRTL来初始化,代码搬移的工作是自动进行的,不需要手工来加载。
- 对于带有初值的全局变量(RW的),CRTL在BIN保存的时候,RVCT4,可以提供简单的数据压缩,类似ZIP,减小BIN文件的体积。const的变量没有这个优惠。
- 注意,初值为0的全局变量,有可能被编译器归在ZI区,不管用不用CRTL初始化,一定要清楚Scatter中对ZI的INIT类型的定义。否则,不要说编译器有问题。实际上是自己有问题。安全的建议是RW类型全局变量有专门函数给一个初值。否则就要确认初始化代码OK。
- 这个Tailor的,是可以用在没有CRTL的情况下的。比如没有main,直接Jump到Main函数。因为__rt_raise已经处理了。但是相应的初始化函数,比如__fp_init()给浮点库,建议还是要执行的。(多一句,FP的默认是ZI,状态字也是0,不初始化,在ZI区的话,正好没事儿。setlocale就没测试过了)。
- 这东西不能用于MicroLibs,那个的初始化我没看,而且对那个兴趣不大…… -_-b 毕竟重入问题好象不如标准库好。
- 如果使用了uCOS,FreeRTOS之类的操作系统,C RTL是可以进程隔离的,就是搬Lib的位置,或者手工保存Lib区。这个我没研究,有需求的先看手册看谁不是可重入的,需要依赖静态存储区的。手册写了。
- 如果用了浮点,提醒保存标准库的浮点的状态字。不知道那个干什么用了,但是,不保存,多个Task调用浮点库,肯定会出问题。MicroLibs没这个问题。

仅供参考,如果看得头晕脑胀不知所云,那就忽略之。直到你发现有东西不对了,再翻出手册来逐条查看。
参考手册:
ADS v1.2 ARM Developer Suite Compilers and Libraries Guide
Keil MDK 3.50 RealView Libraries and Floating Point Support Guide v4.0
RealView Compilation Tools v4 Libraries and Floating Point Support Guide
RVCT Link两个Guide。
RVCT3,RVCT2没有测试过,从RVCT4的手册上看,不需要修改。

zchong 发表于 2009-4-16 08:37:25

收藏了,出问题就翻出来参考参考

ch2003_23 发表于 2009-4-16 08:42:51

这个必须顶
dr2001 水平高,人还热情

grqd_xp 发表于 2009-4-16 12:45:29

看了几个dr2001 回答,水平确实高,而已人很热情

hebj 发表于 2009-4-17 08:17:16

学习了,非常谢谢!

feng200808 发表于 2010-6-23 16:08:06

好东西,值得深究一下

edkaifa 发表于 2013-6-25 15:47:04

谢谢耐心讲解 哈哈
页: [1]
查看完整版本: RVCT,C语言运行库初始化/裁剪的简单说明和Demo