使用ARM标准C库进行嵌入式应用程序开发---转

随着对高处理能力、实时多任务、超低功耗等方面需求的增长,高端嵌入式处理器已经进入了国内开发人员的视野,并在国内得到了普遍的重视和应用。 ARM 是目前嵌入式领域应用最广泛的 RISC 微处理器结构,凭借低成本、低功耗、高性能等优点占据了嵌入式系统应用领域的领先地位。 ADS ARM 公司推出的 ARM 集成开发环境,提供了对 C C++ 的支持,是目前开发 ARM 的主要工具。本文针对日益缩短的嵌入式开发周期,结合 ARM 系统开发调试经验,对使用 ARM 标准库进行应用程序开发作了比较系统的分析。

1 ARM 标准库介绍

ADS 提供了 ANSI C C++ 标准库,本文仅讨论 ANSI C 库,该库包含下面几个部分:

IS0 C 库标准所定义的函数;

semlhosted 环境下用来实现 C 库函数与目标相关的函数;

C C++ 编译器要使用的 heIper 函数。

该库提供的诸如文件输入输出之类的设备,使用了标准的 ARM semihosted 执行环境 (semihosting 是针对 ARM 目标机的一种机制,它能够根据应用程序代码的输入 / 输出请求,与运行有调度功能的主机通信,这种技术允许主机为通常没有输入和输出功能的目标硬件提供主机资源 ) ARMulator Angel Multi-lCE 都支持这个环境,可以使用 ADs 中提供的开发工具开发应用程序,然后在 ARMulator 或者是开发板上运行和调试该程序。如果要使应用系统独立于这个环境,则必须重新实现 C 库中依赖于这个环境的相关函数,根据用户系统的运行环境对 C 库进行适当的裁减。

使用 ANSI 标准 C 库进行程序开发,不仅可以提高开发效率而且可以增强程序的可移植性。在程序中使用库函数,必须先建立一个库函数可以执行的环境,这些工作都由库中的函数完成。当应用程序链接了 C 库中的函数时, C 库中的函数将完成:

创建 C 程序所需的执行环境 ( 建立栈,如果需要创建一个堆,初始化程序使用的部分库 )

调用 main() 函数开始执行 C 程序;

支持程序使用的 Is0 定义的函数;

捕获运行时的错误和信号,如果需要,根据错误终止执行或程序退出。

2 裁减 ARM 标准 C 函数库

标准库中包含了部分依赖于 ARM semihosted 执行环境的函数,这部分函数的函数名中包含有单个或两个下划线 “-” ,需要重新实现这部分函数。如果在程序中定义这些函数,则编译器就会使用新定义的函数,这个过程称为库函数的裁减。一般情况下,只需要重新定义很少的几个函数就可以使用 C 库。

ARM 应用系统开始执行用户应用程序,必须先将应用程序加载到执行域,建立应用程序的执行环境。使用 C 库时,这些繁琐的工作就大部分由 c 函数来完成了。汇编程序完成系统初始化后,跳转到 C 程序的人口 _main()( 注意:不是 main() ,当 C 程序中定义了 main() 主函数时,编译器就会生成 _main 代码 ) 。由 _main() 引导库函数完成 C 执行环境的初始化,具体过程如下:

将非启动代码的 RO RW 执行域代码从加载域地址复制到执行域地址;

ZI 域清零;

跳转到 _rt_entry

调用 _main() 将大大简化汇编启动代码的编写,汇编代码仅需完成系统硬件的初始化,而没有必要将代码从加载域地址复制到执行域地址,以及 ZI 域清零等工作。特别是当使用分布式加载时 _main() 的作用就更加明显了。但是 _main() 并没有建立 C 库运行必须的环境,这项工作由 _rt_entry() 完成,主要调用过程为:

调用 _rt_stackheap_init() 建立堆和栈;

调用 _rt_lib_init() 初始化引用的库函数;如果需要,建立 main() 函数的参数 argc argv 等;

调用 main() 函数,执行应用程序,可以应用库函数;

main() 函数的返回值作参数调用 exit()   

_rt_entry 并不是 C 函数,它是用 ARM C 库编程的起始点。 _rt_entry 不能用 C 语言宴现,因为这时候堆栈还没有建立,堆栈由_ rt_stackheap_init() 来建立。

上面简单介绍了 C 程序使用库函数时的调用过程,由 _rt—stackheap_init() 建立 C 库使用的内存模型--堆和栈。因为 ARM 库是建立在 semihosted 执行环境的,它实现的内存模型是基于这个环境的,所以必须修改这个内存模型建立机制。表 1 列出了需要重新实现的函数,实现了这些函数,应用程序就可以脱离宿主机环境独立运行了。其中,必须重新实现的是_ user initial _ stackheap() ,因为默认的实现是基于 semihosted 执行环境的,该函数被_ n _ stackheap _ init() 调用创建内存模型,其他两个函数没有默认的实现。

实现该函数,必须满足下面的条件:

使用不超过 96 字节的栈空间;

除了 R12(ip) 外不要污染其他寄存器;

将堆基址、栈基址、堆边界和栈边界分别存在 RO R3 作为返回参数;

堆必须保持 8 个字节对齐。

实现例程如下:

 

为了提高应用程序开发效率和可移植性,希望在目标系统上使用 ARM 库提供的标准输人输出库函数。

高层输入输出函数是不依赖于目标系统环境的,但是高层输入输出函数必须调用依赖于目标系统的底层函数,才能实现应用系统的输入输出。依据目标系统硬件环境重新定义这些底层函数,就可以使用库提供的标准 input output 库函数了。下面以裁减 ARM 标准库提供的 printf 系列输出函数为例来作说明。

标准 I/O 库中最常用的是 printf 系列函数,包括 _printf() printf() _fprintf() fprintf() vprintf() vfprintf() 。所有这些函数非透明地使用 _FILE ,并且仅依赖于 fputc() ferror() 两个函数。函数 _printf() _fprintf() printf() fprintf() 的区别仅在于前两个函数不能格式化浮点值。只要定义了自己的 _FILE 版本和 fputc() ferror() 函数,外加定义一个具有 FILE 类型的 _stdout 变量,就可以不作任何修改地使用 printf 系列、 fwrite() fputs() puts() 函数了。

下面给出了具体实现的模板,可以根据实际需要修改。

#include<stdio h>

struct__FILE

{

  int handle   /* 用户需要的任何代码 ( 如果使用文件仅是为了调试使用 prinft 在标准输出端输出信息,则不需要任何文件处理代码 )*/

}

FlLE_stdout /*FILE stdio.h 中定义为: typedef struct_

FILE FILE */

int fputc(int ch FILE*f)

{    /* 用户实现的 fpute 代码。输出一个字符,可以根据需要实现 */

return ch

}

int ferror(FILE*f)

{    /* 用户实现的 ferror 代码 */

return EOF

}

结语

本文分析了 ARM 标准库的工作机理,给出了裁减 C 库进行程序开发的关键步骤。实际应用时需要根据具体的硬件环境和应用要求裁减 C 库,提高代码执行效率。

 

备注:转载于http://gongxue.cn/xuexishequ/ShowArticle.asp?ArticleID=20091&Page=2

发布了22 篇原创文章 · 获赞 1 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章