9. Working with Systemd
The basics of creating, configuring, and starting systemd services are explained in the software guide. This section provides a more detailed look in to how systemd can be configured in special cases i.e when applications and services need to be tied to specific devices or other services.
9.1 Creating services with dependencies
Sometimes services can include dependencies to other services and a requirement might be that these services need to be executed in a specific order. Additionally, services may require a specific device to be operational before the service can be started.
9.1.1 Example: service that depends on other services
Assume you have a backend.service that provides connectivity and a userapp.service that provides the user interface. Thus, you want to make sure that the backend is running before the user application is started.
backend.service:
[Unit]
Description="Our backend that provides data"
[Service]
Type=simple
ExecStart=/usr/bin/launch-backend.sh
[Install]
WantedBy=multi-user.target
user.app.serivce:
[Unit]
Description="User interface to view data"
After=backend.service
Requires=backend.service
[Service]
Type=simple
ExecStart=/usr/bin/launch-gui.sh
[Install]
WantedBy=multi-user.target
Finally, enable the service with systemctl enable user.app.service. This will automatically load the backend also.
9.1.2 Example: service that depends on a specific device
Assume that your backed provides data from a can-bus, and thus requires the driver to be loaded before starting the service.
First find out the .device that you want to bind the service to:
ccs@v700:~# sudo systemctl --type=device --all|grep can0
sys-subsystem-net-devices-can0.device
loaded active plugged /sys/subsystem/net/devices/can0
ccs@v700:~#
Then, add the binding to the service that requires the specific interface.
[Unit]
Description="Our backend that provides data from CAN0"
BindsTo=sys-subsystem-net-devices-can0.device
After=sys-subsystem-net-devices-can0.device
[Service]
Type=simple
ExecStart=/usr/bin/launch-backend.sh
Now, your service will be bound to wait after the specific device driver has been loaded.
If you cannot find the device, you will need to add a specific udev-rule, creating a .device node with system. See 9.3
9.1.3 Service file locations
For systemd to find service files they must be located in predefined locations. These locations can be checked for each system with the following commands.
For user files:
ccs@v700:~# sudo systemd-analyze --user unit-paths
…
/home/root/.config/systemd/user.control
/home/root/.config/systemd/user
/etc/systemd/user
…
For system related files:
ccs@v700:~# sudo systemd-analyze --system unit-paths
…
/etc/systemd/system
/usr/local/lib/systemd/system
/lib/systemd/system
/usr/lib/systemd/system
…
9.2 Viewing dependencies with systemctl list-dependencies
Finding out dependencies might become cumbersome at times and systemd provides tools to help out in this task. It is possible to view dependencies for each service, but also in the reverse.
As an example the System Supervisor runs a specific service on the device on each boot. To see the dependencies for this, run:
ccs@v700:~# sudo systemctl list-dependencies --all ss.service
ss.service
● ├─system.slice
● └─sysinit.target
● ├─dev-hugepages.mount
● ├─dev-mqueue.mount
● ├─kmod-static-nodes.service
● ├─ldconfig.service
● ├─psplash-start.service
● ├─psplash-systemd.service
● ├─run-postinsts.service
● ├─sys-fs-fuse-connections.mount
● ├─sys-kernel-config.mount
● ├─sys-kernel-debug.mount
● ├─sys-kernel-tracing.mount
● ├─systemd-ask-password-console.path
● ├─systemd-hwdb-update.service
● ├─systemd-journal-catalog-update.service
● ├─systemd-journal-flush.service
● ├─systemd-journald.service
● ├─systemd-machine-id-commit.service
● ├─systemd-modules-load.service
● ├─systemd-network-generator.service
● ├─systemd-pstore.service
● ├─systemd-sysctl.service
● ├─systemd-sysusers.service
● ├─systemd-tmpfiles-setup-dev.service
● ├─systemd-tmpfiles-setup.service
● ├─systemd-udev-trigger.service
● ├─systemd-udevd.service
● ├─systemd-update-done.service
● ├─systemd-update-utmp.service
● ├─local-fs.target
● │ ├─systemd-remount-fs.service
● │ ├─tmp.mount
● │ ├─var-volatile-cache.service
● │ ├─var-volatile-lib.service
● │ ├─var-volatile-spool.service
● │ ├─var-volatile-srv.service
● │ └─var-volatile.mount
● └─swap.target
This will provide you with a dependency tree of the service.
To find out what services depend on a specific one, use the -reverse -flag:
ccs@v700:~# sudo systemctl list-dependencies ss.service --all --reverse
ss.service
● ├─ccauxd.service
● │ └─multi-user.target
● │ └─graphical.target
● └─sys-module-ss.device
The reverse depends will help you to see, where a specific service is started if it is not listed in any of the targets (but is launched as a dependency from another .service).
9.3 Adding systemd .devices
By default, systemd maps most of the devices (all tagged with “systemd”). However if the device is not visible you may create a mapping to use. For this example, let’s use the light sensor that is located on the i2c-bus, but also as a symlink on /dev/lightsensor.
First use udevadm to check the details of the device:
ccs@v700:~# sudo udevadm info /sys/devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
P: /devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
L: 0
E: DEVPATH=/devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
E: DRIVER=isl29018
E: OF_NAME=isl29035
E: OF_FULLNAME=/bus@5a000000/i2c@5a810000/isl29035@44
E: OF_COMPATIBLE_0=isil,isl29035
E: OF_COMPATIBLE_N=1
E: MODALIAS=of:Nisl29035T(null)Cisil,isl29035
E: SUBSYSTEM=i2c
E: USEC_INITIALIZED=4914427
Then, create a rule in udev, to add the tag “systemd” to the device, and also map the /dev/lightsenor link to make it more useable.
Under /etc/udev/rules.d/lightsensor.rules, add:
SUBSYSTEM=="i2c", KERNELS=="16-0044", TAG="systemd",
ENV{SYSTEMD_ALIAS}="/dev/lightsensor"
Save the file, and reload udev:
udevadm control --reload-rules && udevadm trigger
Device will now be visible as a systemd.device:
ccs@v700:~# sudo systemctl --type=device --all |grep -e 044 -e lightsensor
dev-lightsensor.device
loaded active plugged /dev/lightsensor
sys-devices-platform-bus\x405a000000-5a810000.i2c-i2c\x2d16-16\x2d0044.device
loaded active plugged /sys/devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
Additionally, you will be able to see the added tags with udevadm:
ccs@v700:~# sudo udevadm info /sys/devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
P: /devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
L: 0
E: DEVPATH=/devices/platform/bus@5a000000/5a810000.i2c/i2c-16/16-0044
E: DRIVER=isl29018
E: OF_NAME=isl29035
E: OF_FULLNAME=/bus@5a000000/i2c@5a810000/isl29035@44
E: OF_COMPATIBLE_0=isil,isl29035
E: OF_COMPATIBLE_N=1
E: MODALIAS=of:Nisl29035T(null)Cisil,isl29035
E: SUBSYSTEM=i2c
E: USEC_INITIALIZED=4914427
E: SYSTEMD_ALIAS=/dev/lightsensor
E: TAGS=:systemd:
9.3.1 Launching services from udev (not recommended)
In some cases it might make sense to launch services directly from udev rules. In this case, just add the necessary service to the rule.
ENV{SYSTEMD_WANTS}+="lightsensor.service"
This will run the specific service when the device is connected. The pitfall is, that if you have the service enabled elsewhere, it will be hard to know where it is launched from. Thus, the preferred way is to run all services from targets.