There are fundamental differences between developing general-purpose software applications and making software for embedded systems. Embedded systems software generally runs on resource-constrained hardware, in contrast to general-purpose server or client applications that run on more capable hardware.
For this reason, embedded system software is usually not directly developed on the electronic board it will run on - also referred to as the target. It is rather developed on a computer - referred to as a host - that has a higher computational capacity than the target board.
Most host computers are based on an x86 processor, while target boards are often ARM based. Thus, traditionally, embedded systems software is developed across platforms, from a host computer to a target board. A toolchain, installed on the host computer, is needed to build an embedded system for a target board.
At a minimum, it comprises a text editor, along with a compiler, libraries, a linker, and debugger. Code is written and compiled on the host. The resulting binaries are then transferred manually to the target board, usually via a storage card or a serial interface. Once booted, the embedded application can be debugged from the host.
Embedded systems are single-purpose computers typically built to carry out one single task. Programs and operating systems are loaded as a monolithic block to electronic boards in the factory. It is usually not possible to modify binaries to add new applications or patches to the embedded systems once shipped.
Most embedded systems are intended to run independently offline, for a long period of time, and without any maintenance.
Let’s deep dive into the aspects that change with Ubuntu Core. With Ubuntu Core, monolithic embedded software is broken into several modules; this is a key innovation. Each module is packed into snaps. As snaps, these modules are managed in the same fashion as software packages are managed by Linux package management systems.
Therefore, they can easily be searched, installed and removed in a single command, without local compilation. Most importantly they can be updated automatically. Additionally, these broken-down OS modules are fully isolated, leveraging container technologies like Linux namespaces and control groups (cgroups).
Cross-development is, therefore, the predominant approach to developing workloads for embedded systems using snaps. Let’s dive into the toolchain to analyze how that works.
Snapcraft in the toolchain
Snapcraft is the developer command-line tool for building snaps. Beyond building snaps, it enables lifecycle management, including testing, debugging, releasing and updating snaps. This tool, therefore, needs to be integrated with any toolchain intended for developing embedded systems based on Ubuntu Core.
To package software into a snap, a so-called snap.yml file needs to be created with Snapcraft. It contains all the instructions that Snapcraft needs to build a snap. Based on this file, binaries will be automatically built, their dependencies collected, and interfaces between snaps created.
The build process can occur locally, however, Snapcraft also supports remote builds with possible integration into CI/CD workflows. This is brand new in the embedded world.
Last but not least, target architectures can be specified in Snapcraft, allowing developers to easily cross-build for various architectures in a single command.
Building OS layers independently
A distinctive feature of Ubuntu Core is the modularity of OS layers, thanks to snaps. This makes the embedded development process more efficient, for it becomes possible to reuse fundamental elements like the kernel across different embedded systems.
Board specific configuration files can also be packaged in a snap (called the gadget snap). The actual software stack that turns an embedded system into an appliance can be packaged in a snap. Developers can thus neatly break down, organise their work more flexibility. Fitting embedded applications in snaps, makes it possible to interchange them in a couple of commands.
This feature of Ubuntu Core effectively decouples software from the underlying embedded system hardware.
Pushing snaps to the target through the snap store
The tedious process of manually flashing a storage card with binaries of the whole OS to update embedded systems is abolished in Ubuntu Core. Snaps, once built can be pushed to a central snap registry called the Snap Store.
From there, any snap can be discovered by any device, pulled and installed locally. Developers can thus search and reuse components built by other developers. This makes the embedded development process much more efficient than it has ever been.
This will also decrease development cost and time to market for embedded systems, thereby accelerating innovation and making it more accessible.
Updates and patches
Modularity also confers the novel ability to modify OS components independently from others. Every time a snap is rebuilt and published, versions installed locally on any device can be updated on command or automatically.
Tedious manual device updates with scripts, risking to blank out the device in an A/B reboot are no longer necessary. Similarly to Linux package management systems, only the deltas are sent to devices to update installed binaries. This minimises the bandwidth cost necessary for updates. Ubuntu Core permanently stores backup copies of existing snaps, so that the system can revert to the previous version if an update is not successful.
This is a feature that ensures the availability of mission-critical embedded systems while they are being updated. Security updates and patches are applied in the same manner, in a continuous, reliable and cost-efficient way.
Ubuntu Core redefines what embedded system development means in the age of smart connected devices. Software-defined embedded systems require new development tools and workflows.
Embedded system cross-development with Ubuntu Core ushers the way to new forms of innovation for smarter and application-centric embedded systems.