32

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?

pipe
  • 13,748
  • 5
  • 42
  • 72
TheMeaningfulEngineer
  • 961
  • 3
  • 11
  • 25
  • 4
    Wrong site for your question. – ott-- Mar 21 '16 at 22:34
  • 9
    I'm voting to close this question as off-topic because it belongs on [Stack Overflow](https://stackoverflow.com/). – uint128_t Mar 22 '16 at 00:50
  • 1
    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? – old_timer Mar 22 '16 at 02:36
  • save bare metal for things that dont need system calls. – old_timer Mar 22 '16 at 02:36
  • 16
    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. – Dave Tweed Mar 22 '16 at 12:34
  • 1
    While newlib is mentioned below in an answer, you may also find [newlib-nano](https://github.com/32bitmicro/newlib-nano-1.0) 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. – jjmilburn Mar 23 '16 at 14:42

5 Answers5

25

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.

pipe
  • 13,748
  • 5
  • 42
  • 72
  • 1
    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? – robert bristow-johnson Mar 21 '16 at 22:13
  • 4
    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. –  Mar 21 '16 at 23:02
  • 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`). – robert bristow-johnson Mar 21 '16 at 23:27
  • newlib is pretty heavy duty, you are basically making your own OS to some extent, blurring the baremetal line... – old_timer Mar 22 '16 at 01:47
  • 3
    Newlib is also *huge* if you're dealing with MCU's with 1 or 2kB of code space... –  Mar 22 '16 at 12:36
  • 2
    @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. – pipe Mar 22 '16 at 13:15
  • you have to provide the system backend. Is that truly an OS, no. you can do what I do and just return success on every function in the backend and then only implement the ones you want. but these are still system calls, not just a bare metal C library but one that assumes there is a system to make calls. Excess overhead if you want bare metal. – old_timer Mar 22 '16 at 19:06
  • @dwelch It sounds to me that you are confusing for example the standard library calls with the operating system functions. As far as I remember, there's nothing in the standard library that requires any concept of "system calls", whatever you mean by that. _fopen_, _malloc_ and such does not need to use any system calls. Maybe you're mixing them up with OS-specific ways to do this. – pipe Mar 22 '16 at 19:20
  • fopen and fclose and malloc for that matter ARE system calls they imply a system, file system, memory management, etc. memcpy, strcpy, are not (granted gcclib may take care of some of these, or just build glibc to get them). Ported/used newlib for years before giving up because it was not worth it...Been there, done that. Decades of bare metal work. The thing about bare metal is it is not the operating system application experience, if you want that use an operating system and write applications. – old_timer Mar 22 '16 at 20:44
10

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. ;-)

DevSolar
  • 211
  • 1
  • 7
9

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.

  • 5
    @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 :-) – Ciro Santilli OurBigBook.com Oct 08 '18 at 23:02
4

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.

2

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.

Ayhan
  • 1,194
  • 9
  • 17
  • Why should sprintf need malloc()? – supercat Mar 22 '16 at 20:37
  • 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 – Ayhan Mar 23 '16 at 10:03
  • 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. – supercat Mar 23 '16 at 14:38