裸机上的C标准库


24

我主要在移植Linux的设备上进行开发,因此标准C库通过实现具有标准化行为的系统调用来提供许多功能。

但是对于裸机,没有底层操作系统。是否有一个与应如何实现ac库有关的标准,或者当您切换到提供不同BSP的新板时是否必须重新学习库实现的特殊性?


4
您问题的网站错误。
ott--

8
我投票结束这个问题是因为题外,因为它属于Stack Overflow
uint128_t

1
通常,您不这样做。为什么在没有操作系统支持的情况下需要这些东西?memcpy这样肯定。文件系统不是必需的,尽管实现了fopen,close等对于ram来说是微不足道的。printf()非常非常繁重,需要大量的代码,而无需这样做。任何I / O替换或不做。newlib非常极端,但是如果您不能做不到的话会有所帮助,但是无论如何您都必须在后端上实现系统,因此是否需要额外的层?
old_timer '16

12
尽管这个问题与软件有关,但它非常特定于嵌入式编程,因此通常被SO拒绝。既然我们已经在这里有了一些好的答案,那么迁移是不合适的。
戴夫·特威德

1
虽然在下面的答案中提到了newlib,但您也可能会发现newlib-nano很有用-它旨在成为资源受限的嵌入式系统中使用的简化版本。我在Cortex M0 MCU的项目中使用了它。许多编译器(Atollic TrueSTUDIO就是其中之一)将提供使用newlib或newlib-nano的选项。
jjmilburn

Answers:


20

是的,有一个标准,只是C标准库。库函数不需要“完整的”操作系统,也不需要任何操作系统,并且有许多针对“裸机”代码的实现,Newlib 也许是最著名的。

以Newlib为例,它要求您编写一小部分核心功能,主要是如何在系统中处理文件和内存分配。如果您使用的是通用目标平台,则很可能有人已经为您完成了这项工作。

如果您使用的是linux(可能还包括OSX,甚至还有cygwin / msys?)和type man strlen,它应该有一个名为like之类的部分CONFORMING TO,它将告诉您实现符合特定标准。这样,您就可以确定所使用的功能是标准功能还是取决于特定的操作系统。


1
我很好奇如何在不依赖操作系统的stdlib情况stdio下实现工具。喜欢fopen()fclose()fread()fwrite()putc()getc()?以及如何在malloc()不与操作系统对话的情况下如何工作?
罗伯特·布里斯托

4
使用Newlib,在其下面有一层称为“ libgloss”,其中包含(或您编写)了针对您平台的数十个函数。例如,getcharputchar,它们了解您的硬件的UART;然后将Newlib printf置于这些图层之上。文件I / O同样会依赖一些原语。
Brian Drummond

是的,我没有仔细阅读Pipe的第二段。除了处理stdinand stdoutstderr (负责putchar()and getchar())将I / O从UART定向到UART之外,如果您的平台具有文件存储功能(如闪存),那么您还必须为此写胶水。并且你必须有手段去malloc()free()。我认为,如果您解决了这些问题,几乎可以在嵌入式目标中运行可移植的C(no argvargc)。
罗伯特·布里斯托

2
如果您要处理具有1或2kB代码空间的MCU,Newlib也非常庞大 ……
Brian Drummond

2
@dwelch您不是在创建自己的操作系统,而是在创建C库。如果您不想要那样,那是的,这没必要很大。
管道

8

是否有一个与应如何实现ac库有关的标准,或者当您切换到提供不同BSP的新板时是否必须重新学习库实现的特殊性?

首先,C标准定义了一种称为“独立”的实现,而不是“托管”的实现(这是我们大多数人所熟悉的,底层操作系统支持的所有C函数)。

A“独立”的实施需要定义只有C库头的子集,即那些要求支持或功能甚至定义(他们只是做#defineS和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实现尚未完成,所以我可能忽略了一两件事。;-)


4

实际上,标准C是与操作环境分开定义的。没有关于存在主机OS的任何假设,并且主机相关的那些部分也已定义为主机OS。

也就是说,C标准已经是裸机了。

当然,我们非常喜欢的那些语言部分(库)通常是核心语言推送托管特定内容的地方。因此,可以在许多裸机平台工具中找到典型的“ xxx-lib”交叉编译器。


3

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将结果输出到主机。可以使用以下任一方法完成此操作:

    • 更多半主机
    • UART硬件
  • brkmalloc

    轻松使用裸机,因为我们不必关心分页!

待办事项我不知道达到抢占式调度syscall执行而不进入像ZephyrFreeRTOS这样的功能强大的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对其进行了测试。


3
@downvoters:请解释一下,以便我学习和改进信息。希望未来的读者可以在网上看到唯一可使用的Newlib介绍性设置的价值:-)
Ciro Santilli新疆改造中心法轮功六四事件

2

使用裸机时,您会发现一些未实现的依赖项,必须处理它们。所有这些依赖项都是关于根据系统的个性调整内部的。例如,当我尝试使用在内部使用malloc()的sprintf()时。Malloc具有“ t_sbrk”功能符号作为代码中的钩子,必须由用户实施才能强制执行硬件约束。如果我相信我可以为嵌入式硬件(主要用于其他用途,而不只是sprintf)做一个更好的嵌入式硬件,则可以在这里实现它,或者制作自己的malloc()。


为什么sprintf需要malloc()?
超级猫

我不知道。我认为您的意思是它已经具有的缓冲区,不是吗?但是,即使printf也不需要malloc。当请求输出的计算比堆叠分配的预见性(函数的动态变量)重时,也许动态分配一些内部变量?我确定sprintf需要malloc(arm-none-eabi-newlib)。现在,我尝试了一个在计算机(glibc)上使用sprintf的简单程序。它从未调用过malloc。然后用printf。它叫做malloc。Malloc是伪造的,始终返回0。但是它工作正常。他们将打印一个字符串和一个十进制变量。@supercat
Ayhan

我自己制作了一些版本的printf或类似方法,这些版本经过定制以支持我的应用程序使用的格式。十进制输出需要足够长的缓冲区来容纳可能的最长数字,否则基本例程将接受一个结构,该结构的第一个成员是输出函数,该函数接受指向该结构的指针以及要输出的数据。这样的设计使得可以添加输出到curses风格的控制台,套接字等的printf变体。在这种情况下,我从不需要“ malloc”。
超级猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.