Building Multiple Applications and Managing the Compilation Pipeline

Engineering

Posted by Bruce Lee on 2024-09-20

About Me

Welcome to my blog! This is where I collect my observations and notes on programming and technology. The main subjects range from implementation details to broader ideas about programming.

Main Topics

  • Engineering Projects: Exploring implementation details and how technical systems work.
  • C/C++: Notes on language features and programming techniques.
  • The Programmer’s Perspective: Ideas about developing a career and a way of thinking as a programmer.

For more, visit the categories page.

Contact

If you have questions or would like to discuss something, please get in touch through the About page.

Thank you for reading and for your support. I hope these notes help you on your own technical journey!


Overview

This article follows the navy-apps build from individual applications and libraries to a combined ramdisk.img, then examines how Nanos-lite incorporates that image. The makefiles connect application builds, filesystem metadata, syscall definitions, and the OS resources. Navy also supplies the library layers through which applications use the OS.

Laying Out Multiple Files

The automated build compiles the selected programs and installs the resulting files under navy-apps/fsimg. It then builds a ramdisk image and a ramdisk.h table describing the embedded files.

Nanos-lite exposes that table as src/files.h. Including it inside the file_table initializer in fs.c makes those file descriptions part of the OS’s filesystem implementation.

The Nanos-lite Makefile

src/resources.S depends on RAMDISK_FILE, normally build/ramdisk.img. When the Navy integration is not enabled, the build can create an empty image.

With HAS_NAVY enabled, the relevant generated resources include the image, src/files.h, and src/syscall.h. A wildcard check detects a missing syscall header and raises a Make error rather than continuing with an incomplete setup.

Navy applications are intended to run on Nanos-lite, which must provide at least the filesystem and execution services they require. The update target refreshes both the image and its file table.

Invoking Navy’s Build From Nanos

1
$(MAKE) -s -C $(NAVY_HOME) ISA=$(ISA) ramdisk

$(MAKE) uses the recursive Make mechanism. The invocation changes into the Navy directory, passes the selected architecture, and requests the ramdisk target. Silent mode suppresses routine command echoing while still allowing failures to be reported.

When applications or their libraries change, the corresponding executables must be rebuilt before assembling the image. Navy writes its image under its own build directory, while resources.S expects the Nanos-side path. Symbolic links connect these locations.

The same arrangement connects Nanos’s files.h to Navy’s ramdisk.h, and connects the syscall-number header. These are shared build artifacts, not independent copies to edit inconsistently.

The Boundary Between Navy and Nanos

When adding an OS service, the application side needs a matching declaration and wrapper as well as an agreed syscall number. Changing only the kernel does not give the application’s compiler and linker a usable function interface.

Navy supplies those interfaces. Its role resembles a system C library: expose OS operations and wrap them in more convenient abstractions. Applications can still use lower-level syscall wrappers when appropriate.

libos is close to the syscall boundary. Libraries such as NDL and miniSDL build higher-level interfaces on top. A program could hard-code a private syscall number and call the low-level mechanism directly, but that bypasses the clear shared interface the framework is meant to provide.

The Navy Makefile Hierarchy

The top-level makefile first checks the requested target against the platform fragments in scripts. Each supported target has a .mk file defining its special compilation and linking requirements.

Shared RISC-V Rules

The common rules select the cross-compiler prefix and link address. In the version studied here, the address is chosen between 0x83000000 and 0x40000000 according to the virtual-memory configuration.

The simple loader expects fixed-location, statically linked executables. It can copy segments to their specified addresses and jump to the entry point without performing dynamic relocation. Position dependence and static linking are related choices in this build, though they are not synonymous in general.

The common rules initially select an RV64 target and supply compiler and linker options. The linker options control relaxation and place the text section at the chosen address.

RV32-Specific Rules

The RV32 fragment includes the shared rules and overrides the architecture to an RV32 variant. Its ABI setting uses 32-bit int, long, and pointers, matching the intended guest. The linker is configured to produce a 32-bit little-endian RISC-V ELF object.

Output Targets and Libraries

Application outputs are named under Navy’s build directory using the application name and ISA. Archive outputs follow a corresponding naming scheme.

For the Nanos target, the build includes its C library and libos support rather than assuming the host’s normal GNU/Linux runtime. Some C-library operations eventually delegate to the low-level OS wrappers.

For a 32-bit target, compiler-rt supplies helper routines that the compiler may need for operations such as wider multiplication and division. This is separate from the later choice to use fixed-point arithmetic when floating-point hardware support is unavailable.

The selected libraries also contribute their include paths. I encountered unresolved NDL symbols because libndl had not been added to LIBS; adding it fixed that particular link failure.

Building Archives

An archive target builds a library archive without linking an application. Make’s filename functions construct the appropriate path under each library’s build directory. Sorting removes duplicate entries, and each selected library is mapped to its complete archive path.

CC, LD, and related variables choose the tools, including their cross-compilation prefix. The selected platform fragment provides the target-specific settings.

From Objects to the Ramdisk

Pattern rules compile .c, .cpp, and .S sources into object files. Libraries are built into separate archives; application objects are then linked with the required archives.

Installation places the resulting executables into fsimg. The ramdisk target depends on that populated tree, which in turn depends on the selected applications and tests.

The image-building step gathers the files, lays out their contents with the required alignment, and generates metadata recording sizes and offsets. The Nanos makefile then consumes the image and generated header through the links described earlier.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !