There are fundamental differences between developing general-purpose software applications and making software for embedded systems. Embedded systems software 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 not directly developed on the electronic board it will run on – referred to as the target. It is rather developed on a computer – the host – that has a higher computational capacity than the target board.
Traditional cross-development workflow
Most host computers are based on x86 processors, whereas 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 offline, for a long period of time, and without any maintenance.
Cross-development with Ubuntu Core works differently
With Ubuntu Core, monolithic embedded software is modularised into snaps; this is a key innovation. As snaps, these modules are managed in the same fashion as with traditional 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, snaps are fully isolated, leveraging container technologies like Linux namespaces and control groups (cgroups).
Snaps are created on a host computer and deployed on target boards via the cloud. Cross-development is therefore the predominant approach to developing 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. Snapcraft, therefore, needs to be integrated with toolchains 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 will contain instructions needed to build a snap. Based on this file, binaries will be built, dependencies collected, and interfaces between snaps created. The build process can occur locally, however, Snapcraft also supports remote builds with integration into CI/CD workflows. Target architectures can be specified, allowing developers to cross-build for various architectures with a single command.
Building OS layers independently
A distinctive feature of Ubuntu Core is the modularity of OS layers, thanks to snaps. 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 application stack that determines the appliance is also packaged in a snap.
Embedded systems built on Ubuntu Core are therefore composable. 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 ever. This also decreases development cost and time-to-market for embedded systems, thereby accelerating innovation and making it more accessible.
Updates and patches
OS components can be modified independently from one another in Ubuntu Core. Every time a snap is rebuilt and published to the snap store, devices can be updated on command or automatically. Tedious manual 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 binary deltas are sent to devices. This minimises the bandwidth required. Ubuntu Core permanently stores backup copies of existing snaps, so that the system can rollback on failure. This is a feature that maintains the availability of mission-critical embedded systems during updates. 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. Cloud-native 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.