如果你写了一个c程序,必然会和c库链接,尽管你没有直接使用c库函数。这是因为编译器为了改进程序,可能隐含的产生了对c库函数调用。
即便你的程序没有main()函数,也只是说c库没变初始化而已,一些c库函数仍然可以使用并且编译器可以隐含地调用这些函数。
二。ARM的c的运行时库
c标准库由以下组成:
ISO99标准库定义的所有函数。
依赖于目标的函数,用来在semihosted环境中执行c库函数。你可以在你的应用程序中重定义这些函数。
被编译器隐含调用的函数。
由ARM扩展的,但是不是由ISO C定义,且包含在这个库里的。
c微型库由以下组成(可以代替c标准库,它是非常适合只有小容量内存的深度嵌入式应用):
为了达到最小的代码体积,已经高度优化的函数。
不服从ISO C规范的函数。
不服从1985 IEEE 754 标准
三。c库的特性
c库用标准的ARM semihosted 环境提供一些功能,不如说输入、输出。该环境由ARM RVI调试单元和Real-Time Simulator Model/(RTSM)所支持的。
你可以重新定义任何与设备相关的c库函数,写在自己的应用程序中即可。这允许你裁剪c库以应用在自己的执行环境中。
也可以裁剪与设备无关的函数,以适应自己的特殊应用要求。例如malloc簇,ctype簇,所有的locale-specific函数。
许多c库函数与其他函数是独立的并且是目标无关的,你可以简单的从库里利用这些函数。
c库函数负责以下事情:
1.创建一个c环境的执行环境(创建栈,如果需要创建堆,初始化程序里用到的库的部分)
2.开始执行main()
3.支持ISO C定义的函数
4.捕获允许错误、信号,根据情况终止程序或者退出程序
四。ARM C库的堆使用需求
任何暗含的使用堆或者显示调用使用堆都需要事先准备好堆。
在c标准库里,暗含的堆使用发生在:
1.调用库函数fopen()并且一个I/O操作第一次使用这个fopen()所产生的stream
2.传递命令行参数给main()函数
分配80字节堆作为FILE结构体的存储空间。当第一次I/O操作发生,且直到这个操作产生,一个附加的512字节堆空间分配给与该操作相关的buffer区。可以利用setvbuf()重新
定义堆大小。
当fclose()调用的时候,80字节未被释放,保存在一个自由表中以备再次使用。512字节在fclose()时候被释放了。
声明带参数的main()需要256字节的堆空间。这个内存一直未释放,因为要在main()期间有效。在microlib里,不允许声明带参的main(),因此这个用法仅使用于标准库。在
标准库的环境里,如果存在堆,它就可以生效。
五。c库基于不同的build options会选择不同的目标集
比如说,目标架构,指令集(ARM,Thumb,Thumb-2);字节须(大小端);浮点支持(softVFP,VFP);位置无关
六。Thumb C 库
当连接器探测到以下事情发生的时候,会自动连接Thumb C库:
1.Thumb or Thumb-2 or --thumb选项 or #pragma thumb
2.在ARMv4T下,使用--apcs / interwork
3.ARMv6-M Cortex-M1/MO
4.ARMv7-M Cortex-M3
七。ARM C库和多线程
当你使用RTOS,ARM C库支持多线程
八。__user_libspace
__user_libspace为C库保持了静态数据。这是一个96字节,0初始化的数据块,该块由C库创建。在C库初始化期间可以用来当做临时栈。
默认的ARM C库用__user_libspace区域保持以下内容:
1.errno,由可以设置errno的函数使用。默认,__rt_errno_addr()返回指向errno的指针。
2.浮点状态字,用于软件浮点。在硬件浮点里,不使用。默认,__re_fp_status_addr()返回指向FP状态字的指针。
3.一个指向堆基址的指针,也就是__Heap_Descriptor,被所有malloc-相关的函数使用。
4.当前的locale设置,被像setlocale()函数使用,同时也被所有依赖于它们的其它函数使用。
九。在一个程序中使用库
可以用以下方法在应用程序使用C库:
1.建立一个semihosting应用,它可以在一个semihosted环境中调试,比如说利用RV。
2.建立一个非host的应用,它可以嵌入到ROM中。
3.建立一个不包含main()的应用,且不初始化库。该应用具有非常有限的库功能,除非重定义一些函数。
十。在一个semihosting环境利用C库
如果你开发一个应用,为了调试,运行在semihosted环境,你必须有一个执行环境,可以支持ARM 或者 Thumb semihosting,且有足够的存储空间。
1.用标准的半主机功能,默认情况下,在RVI中会提供。
2.为半主机调用执行自己的处理过程。
如果使用默认的C库的半主机功能,不必重写任何函数和头文件。ARM调试代理支持半主机,但是C库所假定的内存格局需要裁减以匹配正在调试的硬件。
十一。使用$sub$$使用半主机和非半主机I/O功能
例如,fputc()的实现是直接往UART写,还有1个fputc()的实现是半主机的。可以提供这两个版本,具体要看传递到函数的FILE指针的性质。
int $Super$fputc(int c, FILE *fp); int $Sub$fputc(int c, FILE *fp) { if (fp == (FILE *)MAGIC_NUM) // where MAGIC_NUM is a special value that { // is different to all normal FILE * pointer // values. write_to_UART(c); return c; } else { return $Super$fputc(c, fp); } }可以看到根据FILE指针判断是哪种类型,$$super$代表正常的,$$sub$代表特殊的,一般和自己的目标硬件相关。
十二。以非半主机模式使用库
一些C库函数使用半主机。如果不想使用的话,下列方式之一可以实现:
1.移除所有的半主机函数调用
2.重定义低层函数,比如fputc()。不必重定义所有的半主机函数。只是重定义在程序里需要使用到的函数。
有些函数虽然不直接与目标依赖,但是它依赖于某些底层函数,而底层函数依赖于目标。例如,printf(),你必须重定义fputc(),如果不用得话,那就不必重定义了。
3.以自己特别的方式实现所有半主机调用的一种方式。一个作为这种处理的例子是,拦截调用,将它们重指向自己的非主机实现,也就是,具体目标的函数。
IMPORT __use_no_semihosting from ASM
#pragma import(__use_no_semihosting) from C
十三。直接的半主机依赖
十四。间接的半主机依赖
十五。建立一个不使用C库的应用程序
如果程序中不包含main(),那么库不会被初始化,且如下功能不可用:
1.带有_sys_前缀的底层stdio函数
2.信号处理函数3.其它函数,如atexit()
有些函数即便库未初始化也能使用,一些其它不可用的函数也能被使用,除非它们所依赖的库函数被重定义。
十六。裸机代码
如果你写一个程序,里面没有使用库,且没有任何环境初始化,你必须:
1.如果用到heap的话,重定义__rt_raise()
2.不定义main()
3.写一个汇编语言代码建立c语言运行需要的环境,必须要跳转到你c函数的入口
4.提供自己的RW/ZI初始化代码
5.确保你的汇编代码在你的重启处理段
6.用--fpu=none
当满足这些要求,连接器用合适的C库变体寻找任何需要编译的函数,它们是隐形调用的。
尽管没有main(),__user_lbspace()被自动创建,占用96字节的ZI段。
十七。定制C库启动代码且访问C库函数
应用程序里不需要C库,如果你程序里有main(),连接器会自动包含初始化代码。可能存在一些情况,这是不必的。例如,一个运行RTOS系统可能通过RTOS启动代码
有它自己的执行环境配置。
你可以创建一个应用包含自己的启动代码且仍然可以使用库函数的大部分功能。你可以做下列之一的选择:
1.避免使用需要初始化的函数
2.提供初始化和底层函数函数实现
十八。利用C库使用底层函数
如果你的应用中没有main(),而使用库的话,必须重定义一些在库里的函数
注意:如果用heap的话,__rt_raise()是必须的。
十九。利用库中的高层函数
如果低层的函数被重定义了,那么高层I/O函数能被使用。高层函数是像fprintf() printf() scanf() puts()等,低层函数是那些如fputc() fgetc() 以及__backspace()函数。
大多数格式化输出函数也需要调用setlocale()。
二十。利用库中的malloc()函数
如果在裸机C代码中需要堆得支持,那么__init_alloc()必须要首先调用支持堆的边界初始化,__rt_heap_extend()必须要提供,尽管返回失败。如果没有__rt_heap_extend()函数,特定的库功能会被包含进去,这会导致裸机C代码出现问题。
二十一。执行环境的初始化和程序的执行
一个程序的入口是位于C库里的__main,它会做如下事情:
1.拷贝非根区代码从它们的加载地址到运行地址。如果任何数据段被压缩了得话,它们也会被解压缩。
2.用0初始化ZI区。
3.跳转到__rt_entry。
如果你不想C库做这些事情,你可以定义自己的__main,__main里可以跳转到__rt_entry。例如:
IMPORT __rt_entry EXPORT __main ENTRY __main B __rt_entry END
库函数__rt_entry像下面这样运行程序:
1.建立堆和栈通过以下方式之一:调用__user_setup_srackheap(),调用__rt_stackheap_int()或加载scatter文件里的绝对地址。
2.调用__rt_lib_init()初始化引用的库函数,初始化locale,如果必要的话,为main()建立argc和argv。
3.调用main(),用户级的根函数。
4.从main()函数起,你的程序可能调用库函数。
5.利用main()返回的数值作为参数调用exit()函数。
二十二。在main()里调用库函数
.main()是用户级的根函数。它需要已经初始化了的执行环境和可以使用的输入输出函数。在main()里程序可能执行下列的动作之一,下列动作调用了用户定制的C库函数:
1.扩展堆,栈。
2.调用库函数。该库函数需要调用用户定义的函数。例如__rt_fp_status_addr() 或者clock()。
3.调用使用locale或CTYPE的库函数。
4.执行需要浮点单元或者浮点库支持的浮点运算。
5.通过低层函数的输入输出函数,如putc()或间接通过高层函数,如fprintf()等。
6.发起一个错误或其他信号,例如ferror。
二十三。修改用于错误信号,错误处理和程序退出的C库修改
所有由C库发起的陷阱或错误信号都是通过__raise()函数。可以重定义这个函数或者它所使用到的低层函数。
二十四。避免使用堆和使用堆的库函数
IMPORT __use_no_heap from assembly language #pragma import(__use_no_heap) from C.
二十五。在裸机C代码里使用heap
1.调用__init_alloc(base, top)
2.定义函数unsigned _rt_heap_extend(unsigned size, void block)处理扩展堆得需要。
二十六。栈的初始化和堆界限
可以指定栈指针,指定哪一块区域为堆,用以下任何一种方式:
1.定义__initial_sp,如果需要堆, 定义__heap_base and __heap_limit
2.用scatter文件,下列方式之一
2.1定义ARM_LIB_STACK and ARM_LIB_STACK区
2.2不用堆,只需ARM_LIB_STACK
2.3定义一个ARM_LIB_STACKHEAP,此时堆栈是一体的,相向生长。
微库仅支持上述2种方式。
3.实现__user_setup_stackheap()建立栈指针和返回初始堆区域。
4.用遗留的__user_initial_stackheap()也可以实现
初始化堆指针必须指向的时8字节倍数对齐的区域。
默认情况下,潜在的堆栈冲突会被自动探测到且请求的堆分配失败。如果不希望自动的冲突检测,可以通过用#pragma import __use_two_region_memory预留一小段空间。
5.作为遗留的原因,也可以使用__rt_stackheap_init()和__rt_heap_extend()。
二十七。C库中对低层函数的依赖
表中显示了高层函数对低层函数的依赖,如果定义了自己的低层函数版本,可以直接使用高层函数库中的版本。
fgetc()用__FILE,但是fputc()用__FILE和ferror()。
必须提供__stdin和__stdout的定义,如果使用和它们相关的高层函数。虽然重定义了其它函数,如fgetc()和fputc(),它们没有引用任何在__stdin __stdout里的数据。
Table key:
-
__FILE, the file structure.
-
__stdin, the standard input object of type __FILE.
-
__stdout, the standard output object of type __FILE.
-
fputc(), outputs a character to a file.
-
ferror(), returns the error status accumulated during file I/O.
-
fgetc(), gets a character from a file.
-
fgetwc()
-
fputwc()
-
__backspace(), moves the file pointer to the previous character.
-
__backspacewc().
C库输出函数族仅依赖fputc() ferror();C库输入函数族仅依赖于fgetc() __FILE __backspace()。
二十八。重定义低层函数,以便直接使用高层函数库函数
如果你重定义了__FILE fputc() ferror() __stdout可以使用所有的printf()函数族。
Example 9. Retargeting printf() #includestruct __FILE { int handle; /* Whatever you require here. If the only file you are using is */ /* standard output using printf() for debugging, no file handling */ /* is required. */ }; /* FILE is typedef’d in stdio.h. */ FILE __stdout; int fputc(int ch, FILE *f) { /* Your implementation of fputc(). */ return ch; } int ferror(FILE *f) { /* Your implementation of ferror(). */ return 0; } void test(void) { printf("Hello world\n"); }
二十九。
__backspace()被scanf()函数族使用。
__backspace()必须仅在从流里读取一个字符后调用。
上一篇:S3C2440开发工具realview MDK4.22使用入门
下一篇:S3C2440在MDK4.22下使用printf向串口打印调试
推荐阅读最新更新时间:2024-03-16 14:56