我主要在移植Linux的设备上进行开发,因此标准C库通过实现具有标准化行为的系统调用来提供许多功能。
但是对于裸机,没有底层操作系统。是否有一个与应如何实现ac库有关的标准,或者当您切换到提供不同BSP的新板时是否必须重新学习库实现的特殊性?
我主要在移植Linux的设备上进行开发,因此标准C库通过实现具有标准化行为的系统调用来提供许多功能。
但是对于裸机,没有底层操作系统。是否有一个与应如何实现ac库有关的标准,或者当您切换到提供不同BSP的新板时是否必须重新学习库实现的特殊性?
Answers:
是的,有一个标准,只是C标准库。库函数不需要“完整的”操作系统,也不需要任何操作系统,并且有许多针对“裸机”代码的实现,Newlib 也许是最著名的。
以Newlib为例,它要求您编写一小部分核心功能,主要是如何在系统中处理文件和内存分配。如果您使用的是通用目标平台,则很可能有人已经为您完成了这项工作。
如果您使用的是linux(可能还包括OSX,甚至还有cygwin / msys?)和type man strlen
,它应该有一个名为like之类的部分CONFORMING TO
,它将告诉您实现符合特定标准。这样,您就可以确定所使用的功能是标准功能还是取决于特定的操作系统。
stdlib
情况stdio
下实现工具。喜欢fopen()
,fclose()
,fread()
,fwrite()
,putc()
和getc()
?以及如何在malloc()
不与操作系统对话的情况下如何工作?
getchar
和putchar
,它们了解您的硬件的UART;然后将Newlib printf
置于这些图层之上。文件I / O同样会依赖一些原语。
stdin
and stdout
和stderr
(负责putchar()
and getchar()
)将I / O从UART定向到UART之外,如果您的平台具有文件存储功能(如闪存),那么您还必须为此写胶水。并且你必须有手段去malloc()
和free()
。我认为,如果您解决了这些问题,几乎可以在嵌入式目标中运行可移植的C(no argv
或argc
)。
是否有一个与应如何实现ac库有关的标准,或者当您切换到提供不同BSP的新板时是否必须重新学习库实现的特殊性?
首先,C标准定义了一种称为“独立”的实现,而不是“托管”的实现(这是我们大多数人所熟悉的,底层操作系统支持的所有C函数)。
A“独立”的实施需要定义只有C库头的子集,即那些不要求支持或功能甚至定义(他们只是做#define
S和typedef
补):
<float.h>
<iso646.h>
<limits.h>
<stdalign.h>
<stdarg.h>
<stdbool.h>
<stddef.h>
<stdint.h>
<stdnoreturn.h>
在迈向托管实现的下一步时,您会发现只有很少的函数真正需要以任何方式与“系统”接口,而其余的库可在这些“原语”之上实现”。在实现PDCLib时,我做了一些努力将它们隔离在单独的子目录中,以便在将lib移植到新平台时轻松识别(括号中的Linux端口示例):
getenv()
(extern char * * environ
)system()
(fork()
/ execve()
/ wait()
)malloc()
和free()
(brk()
/ sbrk()
)_Exit()
(_exit()
)time()
(尚未实施)对于<stdio.h>
(可以说是C99头中最“涉及OS的”):
open()
)close()
)unlink()
)link()
/ unlink()
)write()
)read()
)lseek()
)库的某些细节是可选的,该标准仅提供以标准方式实现的库,而没有将这种实现作为要求。
如果没有计时机制,该time()
函数可能会合法地返回(time_t)-1
。
所描述的信号处理程序<signal.h>
除了调用之外,不需要其他任何调用raise()
,不要求系统实际向SIGSEGV
应用程序发送类似信息。
C11标头<threads.h>
(由于明显的原因)非常依赖于OS,如果实现定义了,则根本不需要提供C11标头__STDC_NO_THREADS__
。
还有更多示例,但是我现在没有它们。
库的其余部分无需环境的任何帮助即可实现。(*)
(*)注意:PDCLib实现尚未完成,所以我可能忽略了一两件事。;-)
实际上,标准C是与操作环境分开定义的。没有关于存在主机OS的任何假设,并且主机相关的那些部分也已定义为主机OS。
也就是说,C标准已经是裸机了。
当然,我们非常喜欢的那些语言部分(库)通常是核心语言推送托管特定内容的地方。因此,可以在许多裸机平台工具中找到典型的“ xxx-lib”交叉编译器。
Newlib最小可运行示例
在这里,我提供了一个高度自动化和记录在案的示例,该示例显示了newlib在QEMU中的作用。
使用newlib,您可以为裸机平台实现自己的系统调用。
例如,在上面的示例中,我们有一个示例程序exit.c
:
#include <stdio.h>
#include <stdlib.h>
void main(void) {
exit(0);
}
在一个单独的C文件中common.c
,我们exit
使用ARM半主机实现:
void _exit(int status) {
__asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}
您将实现的其他典型syscall是:
write
将结果输出到主机。可以使用以下任一方法完成此操作:
brk
为malloc
。
轻松使用裸机,因为我们不必关心分页!
待办事项我不知道达到抢占式调度syscall执行而不进入像Zephyr或FreeRTOS这样的功能强大的RTOS是否现实。
关于Newlib的很棒的事情是,它实现了像string.h
您一样的所有非OS特定功能,并允许您仅实现OS存根。
另外,您不必实现所有存根,而只需实现所需的存根。例如,如果您的程序只需要exit
,则不必提供print
。
Newlib源代码树已经有一些实现,包括中的ARM半主机实现newlib/libc/sys/arm
,但是在大多数情况下,您必须实现自己的实现。但是,它确实为任务提供了坚实的基础。
设置Newlib的最简单方法是使用crosstool-NG构建自己的编译器,只需告诉它您要将Newlib用作C库即可。我的设置程序将使用此脚本为您自动处理该脚本,该脚本使用位于的newlib配置crosstool_ng_config
。
我认为C ++也可以,但是TODO对其进行了测试。
使用裸机时,您会发现一些未实现的依赖项,必须处理它们。所有这些依赖项都是关于根据系统的个性调整内部的。例如,当我尝试使用在内部使用malloc()的sprintf()时。Malloc具有“ t_sbrk”功能符号作为代码中的钩子,必须由用户实施才能强制执行硬件约束。如果我相信我可以为嵌入式硬件(主要用于其他用途,而不只是sprintf)做一个更好的嵌入式硬件,则可以在这里实现它,或者制作自己的malloc()。