32
\$\begingroup\$

I am mostly doing development on devices that have ported Linux so the standard C library provides lots of it's functionality through implementing system calls which have a standardised behaviour.

However for bare metal, there is no underlying OS. Is there a standard related to how a c library should be implemented or do you have to relearn peculiarity of a library implementations when you switch to new board which provides a different BSP?

\$\endgroup\$
6
  • 4
    \$\begingroup\$ Wrong site for your question. \$\endgroup\$
    – ott--
    Commented Mar 21, 2016 at 22:34
  • 9
    \$\begingroup\$ I'm voting to close this question as off-topic because it belongs on Stack Overflow. \$\endgroup\$
    – uint128_t
    Commented Mar 22, 2016 at 0:50
  • 1
    \$\begingroup\$ Generally you do without. Why would you need such things without an operating system to support them? memcpy and such sure. File systems, not necessarily, although implemented fopen, close, etc is trivial against ram for example. printf() is very very very heavy, tons and tons of code required, do without. any I/O replace or do without. newlib is pretty extreme, but does help if you cant do without, but you have to implement the system on the backend anyway, so do you need the extra layer? \$\endgroup\$
    – old_timer
    Commented Mar 22, 2016 at 2:36
  • 16
    \$\begingroup\$ While this question is about software, it is very specific to embedded programming, which generally rejected by SO. Since we already have some good answers here, migration is not appropriate. \$\endgroup\$
    – Dave Tweed
    Commented Mar 22, 2016 at 12:34
  • 1
    \$\begingroup\$ While newlib is mentioned below in an answer, you may also find newlib-nano useful -- its intended to be a stripped-back version for use in resource constrained embedded systems. I use it in projects on Cortex M0 MCUs. A number of compilers (Atollic TrueSTUDIO being one) will give an option to use newlib or newlib-nano. \$\endgroup\$
    – jjmilburn
    Commented Mar 23, 2016 at 14:42

5 Answers 5

25
\$\begingroup\$

Yes, there is a standard, simply the C standard library. The library functions do not require a "full blown" OS, or any OS at all, and there are a number of implementations out there tailored to "bare metal" code, Newlib perhaps being the best known.

Taking Newlib as an example, it requires you to write a small subset of core functions, mainly how files and memory allocation is handled in your system. If you're using a common target platform, chances are that someone already did this job for you.

If you're using linux (probably also OSX and maybe even cygwin/msys?) and type man strlen, it should have a section called something like CONFORMING TO, which would tell you that the implementation conforms to a specific standard. This way you can figure out if something you've been using is a standard function or if it depends on a specific OS.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ i am curious as to how a stdlib implements stdio without being dependent on the OS. like fopen(), fclose(), fread(), fwrite(), putc() and getc()? and how does malloc() work without talking to the OS? \$\endgroup\$ Commented Mar 21, 2016 at 22:13
  • 4
    \$\begingroup\$ With Newlib, there is a layer below it called "libgloss" which contains (or you write) a couple of dozen functions for your platform. For example, a getchar and putchar which know about your hardware's UART; then Newlib layers printf on top of these. File I/O will similarly rely on a few primitives. \$\endgroup\$
    – user16324
    Commented Mar 21, 2016 at 23:02
  • \$\begingroup\$ yeah, i didn't read pipe's 2nd paragraph carefully. besides dealing with stdin and stdout and stderr (which takes care of putchar() and getchar()) which directs I/O from/to a UART, if your platform has file storage, like with a flash, then you have to write glue for that also. and you have to have the means to malloc() and free(). i think if you take care of those issues, you can pretty much run portable C in your embedded target (no argv nor argc). \$\endgroup\$ Commented Mar 21, 2016 at 23:27
  • 3
    \$\begingroup\$ Newlib is also huge if you're dealing with MCU's with 1 or 2kB of code space... \$\endgroup\$
    – user16324
    Commented Mar 22, 2016 at 12:36
  • 2
    \$\begingroup\$ @dwelch You're not making your own OS, you're making the C library. If you don't want that, then yeah, it's unnecessary large. \$\endgroup\$
    – pipe
    Commented Mar 22, 2016 at 13:15
10
\$\begingroup\$

Is there a standard related to how a c library should be implemented or do you have to relearn peculiarity of a library implementations when you switch to new board which provides a different BSP?

First off, the C standard defines something called a "freestanding" implementation, as opposed to a "hosted" implementation (which is what most of us are familiar with, the full range of C functions supported by the underlying OS).

A "freestanding" implementation needs to define only a subset of the C library headers, namely those that do not require support, or even the definition of functions (they merely do #defines and typedefs):

  • <float.h>
  • <iso646.h>
  • <limits.h>
  • <stdalign.h>
  • <stdarg.h>
  • <stdbool.h>
  • <stddef.h>
  • <stdint.h>
  • <stdnoreturn.h>

When you're taking the next step toward a hosted implementation, you will find that there are only very few functions that really need to interface "the system" in any way, with the rest of the library being implementable on top of those "primitives". In implementing the PDCLib, I made some effort to isolate them in a separate subdirectory for easy identification when porting the lib to a new platform (examples for the Linux port in parenthesis):

  • getenv() (extern char * * environ)
  • system() (fork() / execve() / wait())
  • malloc() and free() (brk() / sbrk() / mmap())
  • _Exit() (_exit())
  • time() (gettimeofday())

And for <stdio.h> (arguably the most "OS-involved" of the C99 headers):

  • some way to open a file (open())
  • some way to close it (close())
  • some way to remove it (unlink())
  • some way to rename it (link() / unlink())
  • some way to write to it (write())
  • some way to read from it (read())
  • some way to reposition within it (lseek())

Certain details of the library are optional, with the standard merely offering them to be implemented in a standard way but not making such an implementation a requirement.

  • The time() function may legally just return (time_t)-1 if no time-keeping mechanics are available.

  • The signal handlers described for <signal.h> need not be invoked by anything other than a call to raise(), there is no requirement that the system actually sends something like SIGSEGV to the application.

  • The C11 header <threads.h>, which is (for obvious reasons) very dependent on the OS, need not be provided at all if the implementation defines __STDC_NO_THREADS__...

There are more examples, but I don't have them at hand right now.

The rest of the library can be implemented without any help from the environment.(*)


(*)Caveat: The PDCLib implementation is not complete yet, so I might have overlooked a thing or two. ;-)

\$\endgroup\$
0
9
\$\begingroup\$

Newlib minimal runnable example

https://sourceware.org/newlib/

With Newlib, you implement your own system calls for your baremetal platform.

Here I provide a highly automated and documented example that shows Newlib built with crosstool-NG running in QEMU aarch64.

For example, on the above example, we have an example program exit.c:

#include <stdio.h>
#include <stdlib.h>

void main(void) {
    exit(0);
}

and in a separate C file common.c, we implement the exit with ARM semihosting:

void _exit(int status) {
    __asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}

The other typical syscalls that you will implement are:

  • write to output results to the host. This can be done either with:

    • more semihosting
    • an UART hardware
  • brk for malloc.

    Easy on baremetal, since we don't have to care about paging!

TODO I wonder if it is realistic to reach preemptive scheduling syscalls execution without going into a full blown RTOS like Zephyr or FreeRTOS.

The cool thing about Newlib, is that it implements all the non-OS specific things like string.h for you, and lets you implement just the OS stubs.

Also, you don't have to implement all the stubs, but only the ones you will need. E.g., if your program only need exit, then you don't have to provide a print. Newlib achieves this by giving dummy do-nothing implementations of all syscalls as weak symbols.

The Newlib source tree does already have some implementations, including an ARM semihosting implementation under newlib/libc/sys/arm, but for the most part you have to implement your own. It does however provide a solid base for the task.

The easiest way to setup Newlib is by building your own compiler with crosstool-NG, you just have to tell it that you want to use Newlib as the C library. My setup handles that automatically for you with this script, which uses the newlib configs present at crosstool_ng_config.

C++ should also work in theory, but my initial attempt was unsucessful.

\$\endgroup\$
1
  • 5
    \$\begingroup\$ @downvoters: please explain so I can learn and improve info. Hopefully future readers can see the value of the only introductory Newlib setup available on the web that just works :-) \$\endgroup\$ Commented Oct 8, 2018 at 23:02
4
\$\begingroup\$

Standard C is actually defined separate from the operating environment. No assumption is made about a host OS being present, and those parts that are host dependent are defined as such.

That is, the C Standard is already pretty bare metal.

Of course, those language parts we love so much, the libraries, are often where the core language pushes that host specific stuff. Hence, the typical "xxx-lib" cross compiler stuff found for many bare metal platform tools.

\$\endgroup\$
2
\$\begingroup\$

When you use it baremetal, you discover some unimplemented dependencies and have to handle them. All these dependencies are about tuning the internals according to your system's personality. For example when I tried to use sprintf() which uses malloc() inside. Malloc has "t_sbrk" function symbol as a hook in code, which has to be implemented by user to enforce the hardware consrains. Here I may implement it, or make my own malloc() if I believe I could do a better one for the embedded hardware, mainly for other uses, not only sprintf.

\$\endgroup\$
3
  • \$\begingroup\$ Why should sprintf need malloc()? \$\endgroup\$
    – supercat
    Commented Mar 22, 2016 at 20:37
  • \$\begingroup\$ I don't know. I think your point is the buffer it already has, isn't it? But even printf shouldn't need malloc. Maybe to dynamically allocate some internal variables, when the calculation of requested output is heavier than foresight of stacked allocation (dynamic variables of function)? I am sure that sprintf required malloc (arm-none-eabi-newlib). Now I experimented a simple program uses sprintf on computer (glibc). It never called malloc. Then used printf. It called malloc. Malloc was fake, always returning 0. But it worked fine. They were to print a string and a decimal variable. @supercat \$\endgroup\$
    – Ayhan
    Commented Mar 23, 2016 at 10:03
  • \$\begingroup\$ I've made a few versions of printf or similar methods myself, customized to support the formats my applications used. Decimal output requires a buffer long enough to hold the longest possible number, but otherwise the base routine accepts a structure that whose first member is an output function that accepts a pointer to that structure along with the data to be output. Such a design makes it possible to add printf variants that output to curses-style consoles, sockets, etc. I've never had a need for "malloc" in such a thing. \$\endgroup\$
    – supercat
    Commented Mar 23, 2016 at 14:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.