Practical Tips for Creating Systemd Services in Yocto

Reading Time: 3 minutes

Introduction

Systemd is a widely adopted service manager in modern Linux systems responsible for initializing, loading, and managing essential components and services required for the system to operate correctly. Its popularity extends beyond traditional Linux systems into the realm of Embedded Linux, where it is increasingly becoming the default choice in Yocto based distributions.

Given that Yocto has become a dominant force in the Embedded Linux world, it is crucial for engineers to grasp how to effectively bridge the gap between Yocto systems and systemd. This integration knowledge enables them to leverage the powerful features of both to create robust and efficient Embedded Linux solutions.

While the integration of Yocto and systemd is a vast topic, a common and practical need is creating Yocto recipes that leverage systemd services. Today we will focus on this aspect and explore how to effectively implement systemd services within your Yocto projects.

This article assumes the reader has some basic Yocto knowledge on how to integrate recipes and deploy them on a target. The article also assumes the user has some basic systemd knowledge of unit services, or that they are willing to consult the extensive documentation on the topic. All that being said, we can begin by focusing on recipe “code” examples that create systemd services directly. 

Creating the Service

To start, we will define what exactly we are going to create today. For simplicity, we will be creating a Yocto recipe that will deploy a “hello world” type script along with a systemd unit that will automatically run the script at boot time. We will finish by wrapping these into a recipe and when the associated package is installed in the target image and booted, systemd will run the service and execute the script at boot.

Let us create the script first, again, we will keep things as simple as possible, so let us call this “hello_systemd.sh” 😀

# hello_systemd.sh

#!/bin/sh
echo "Hello systemd!"

Next we will create a simple systemd service file to turn this into a service. We will name it “hello-systemd.service” for simplicity’s sake:

# hello-systemd.service

[Unit]
Description=Simple Hello Systemd Service

[Service]
Type=oneshot
ExecStart=/usr/bin/hello_systemd.sh
RemainAfterExit=no

[Install]
WantedBy=default.target

Integration to Yocto

Now to join this with Yocto, let us create the recipe that will wrap in script and service file. Yocto has a specific recipe class available for this, unsurprisingly named “systemd.” Inheriting this class automates some functionality such as creating the ability to auto enable the services that are deployed to the board image:

LICENSE = "CLOSED"
DESCRIPTION = "Run hello_systemd script"

SRC_URI = " \
    file://hello_systemd.sh \
    file://hello-systemd.service \
"

inherit systemd

SYSTEMD_SERVICE:${PN} = "hello-systemd.service"
SYSTEMD_AUTO_ENABLE:${PN} = "enable"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/hello_systemd.sh ${D}${bindir}

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/hello-systemd.service ${D}${systemd_system_unitdir}
}

FILES:${PN} = " \
    ${bindir}/* \
    ${systemd_system_unitdir}/* \
"

The key components here for systemd integration are the following:

  1. Inheriting the systemd class
    inherit systemd
  2. Setting the SYSTEMD_SERVICE variable

    This informs the class the names of the service file(s) that will be deployed, among other functions of the class. This is important, as the systemd class has features such as the ability to auto-enable services. To do this, it must know the names of any associated services. In this case, we only have one service, though multiple can be listed if contained in the recipe:

    SYSTEMD_SERVICE:${PN} = "hello-systemd.service"
  3. Setting SYSTEMD_AUTO_ENABLE

    SYSTEMD_AUTO_ENABLE:${PN} = "enable"

    This tells the class to auto enable the listed services in the image. Technically, we do not need to set it “enable” as the Yocto default is enabled. However, we will do this for clarity’s sake in this example, and mention that if you want your service to be disabled by default, you should specify SYSTEMD_AUTO_ENABLE:${PN} = "disable" here instead.

Specifics of these variables and other functionality can be found within the Yocto documentation below:

https://docs.yoctoproject.org/ref-manual/classes.html#systemd

Install and Run

Now we can install our new package on the board. There are multiple ways to do this, but for our example, we will again keep it simple:

# local.conf

IMAGE_INSTALL:append = " hello-systemd"

Let us see it in action. After building our image, programming and booting up, we should be able to run the following and see the output similar to below:

root@linux-board:~# journalctl -u hello-systemd

-- Logs begin at Tue 2023-08-15 14:22:31 EDT --
Sep 13 09:30:00 linux-board systemd[1]: Starting Simple Hello Systemd Service...
Sep 13 09:30:00 linux-board hello_systemd.sh[1234]: Hello systemd!
Sep 13 09:30:00 linux-board systemd[1]: Started Simple Hello Systemd Service.

Success!

Summary

In this article, we explored how to integrate basic systemd services into Yocto recipes. While this is a simple concept, it is a fundamental task that appears often in the integration of Yocto based Embedded Linux systems. By understanding this integration, users can be more confident in their creation of robust Linux systems.

Have you found systemd services to be a common need in your Embedded Linux system? Leave a comment below, we would love to discuss with you and address any questions you may have!

The logo used in this article was obtained from https://brand.systemd.io/ see the license below:
https://creativecommons.org/licenses/by-sa/4.0/


Leave a Comment

Your email address will not be published. Required fields are marked *