|
这几天看到有人问一些问题,涉及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 [ARM DUI 0067D]
Keil MDK 3.50 RealView Libraries and Floating Point Support Guide v4.0
RealView Compilation Tools v4 Libraries and Floating Point Support Guide [ARM DUI 0349B]
RVCT Link两个Guide。
RVCT3,RVCT2没有测试过,从RVCT4的手册上看,不需要修改。 |
阿莫论坛20周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|