The Bare Metal

As a first step in the development of embedded software, it may be of interest to create and run a simple program. It could also be of interest to create a minimal program, which is possible to run without using an operating system. Performing such an exercise will illustrate the necessary steps for writing, compiling, and executing a program on the chosen hardware platform. It will also, since the goal is to run without an operating system, provide insight into the mechanisms used by the hardware to start a stand-alone, small, program.

The program to be executed is a program, of the Hello World-type. It is written in C, as many embedded programs are, and its source code is shown in Figure 1.

#include "console.h"

int main(void)
    console_put_string("Hello from a bare metal C-program!\n"); 
    return 0; 

Figure 1. A program to be run on a processor, without any operating system. The program in Figure 1 uses a function console_put_string. The function console_put_string prints a string. The string is given as argument to the function. The actual printing needs to be done so that the user can see the printed string. This can be accomplished by sending the string to an appropriate output unit.

Our goal is to run the program as a bare metal program, without the help of an operating system. This means that the program is the only program running on the computer, and that we have to ensure that it can be started without the help of an operating system.

When running the program on a PC with an x86-processor, it could be interesting to try to make the program run directly after boot. For simplicity, we can make it run at an early stage, where no graphics have been activated. The function console_put_string can be designed to print a string on the alphanumeric screen, at a predefined position.

  • Intel-x86
  • ARM

Calling main

The program in Figure 1 contains a function called main. This is common for programs written in C. When a C-program is executed under the control of an operating system, the main-function is called automatically when the program is started. When a C-program is executed as a bare metal program, the main-function will not be called automatically.

The main function in a bare-metal program can be called by adding extra code, outside of the program, and making sure that this added code calls main. The added code is referred to as startup code. Of course it must be ensured that the startup code is executed.

The purpose of the startup code is to perform a minimum of initialization, and when this is done, call the main-function. This initialization often involves operations that are specific for the chosen hardware, such as writing certain values to processor registers. For this reason, it may be convenient to write the startup code in assembly.

When using a personal computer with an Intel-processor, it is possible to execute a program by placing the program, together with its startup code, on a bootable medium. Examples of bootable media are a bootable USB stick or a bootable CD. This requires that yet another piece of code, here referred to as boot code, is executed. The boot code is executed when the computer is started, and it may be referred to as BIOS or UEFI. The boot code may start other code, referred to as a boot loader. As an example boot loader, we mention GRUB. By letting the boot loader call the startup code, which in turn calls the main-function, the program is started.

Read more

In the beginning

The startup code in Figure 2 calls the main-function. In this case, as shown in Figure 1, it is a main-function of a small stand-alone program. In a larger system, it may be the main-function of an operating system, for example Linux.

As described above, the startup code is executed by boot code, in the form of BIOS or perhaps in the form of a boot loader such as GRUB, during the startup of the computer.

One might wonder how the very first code that runs, when a computer starts, is invoked. Considering a scenario where a computer is started, by switching on the power, it can be seen in Section 9.1.4 of Volume 3 of the Intel 64 and IA-32 Architectures Developer's Manual, that execution starts at an address given by the hexadecimal number 0xFFFFFFF0. At this address, boot code must be stored. This code has the task of performing necessary initialisations, by itself or in collaboration with boot loader code such as BIOS or GRUB. The initialisations done may involve initialisations of important devices such as interrupt controller and cache controller, and also initialisation of communication devices, such as serial ports and network interfaces, may be necessary. When the initialisations are done, startup code like the one in Figure 2 is executed.

Read more

From source to binary

Executable code which is possible to run without an operating system can be created from the source code shown in Figure 1. The executable code can be executed on a computer with an Intel-x86 processor, by placing the code in a place where it will be started by boot code. The executable code is created by compiling the source code in Figure 1, and then linking the resulting object code with object code generated from the startup code in Figure 2.

The compilation is done using a compiler for the chosen hardware. For the case of a computer with an Intel-compatible processor, which is running some flavour of the Linux operating system, it is possible to use the native gcc compiler for the compilation.

For the case of a Mac computer with an Intel-compatible processer, which is running the Mac OS X operating system, it is possible to use a variant of the gcc compiler. It may be difficult, though, to get the program to run using the built-in gcc compiler. It is possible to create a compiler for yourself, as described in this article.

It may also be possible to create executable code, which is possible to run without an operating system, on a computer which runs some variant of Microsoft Windows. For this case, it is recommended to use an add-on program which gives the user a Unix/Linux-like environment. Examples of such programs are Cygwin and MinGW.

It will be assumed, throughout the remainder of the book, that if nothing else is stated, the examples shown are compiled and linked using a personal computer with Linux. When knowledge about other environments is available, it will be shared using links to places where additional information is available.

The actual execution of the programs will be done on a computer equipped with a 32-bit Intel-compatible x86-processor, which may, or may not, be the same processor as used for compilation and linking.

As an alternative means for execution, a computer simulator will be used.

Assume that the source code in Figure 1 is stored in a file named bare_metal.c. Assuming that a compiler is available, as described above, the file bare_metal.c can be compiled using the command

gcc -c -m32 -Wall bare_metal.c

In the above command the argument -m32 is used. The purpose of this argument is to instruct the compiler to generate code for a 32-bit Intel-processor. The argument -Wall is also used, for the purpose of enabling all warnings. As a result of the command, an object file named bare_metal.o is generated.

The startup code in Figure 2 is written in assembly language. An assembler can be used for the purpose of translating the assembly code in Figure 2 to object code.

Read more

Make it run

A file prog.elf, containing executable code which is generated as described in Section From source to binary, from source code as shown in Figure 1 and from assembly code as shown in Figure 2, can be executed from a bootable media.

A bootable media, in the form of a bootable USB stick, can be created. It is possible to do this, for example, by following instructions on how to create a bootable USB stick with GRUB.

Assuming that a bootable USB stick has been created, the file prog.elf can be copied to the USB stick. This can be done by first inserting the USB stick into a computer, and then giving the command

cp prog.elf /media/ola/EC25-4633/boot/kernel.bin

The above command copies the file prog.elf to a directory named boot, while at the same time renaming the file to kernel.bin. The reason for renaming the file is that the name kernel.bin has been given in the startup file for GRUB, as the name of the file where the program to be started is located.

In a larger example, a file, corresponding to the file kernel.bin used here, may contain an operating system, e.g. a Linux kernel.

The program can now be executed by starting the computer, after having ensured that the computer will boot from the USB stick.

Read more

Read more

You can read more about bare-metal programming for Intel e.g. from the OSDev Wiki, where there is a tutorial called Bare Bones, describing the steps needed to start a program without an operating system.

Read more