This post is designed to give an overview of how to get your own program to start when the PYNQ board boots while also giving an introduction to systemd. If you are only interested in the recipe feel free to skip to the end of the post where I give two ways of getting your script booting.
Systemd is now the default init system for most Linux distributions including Ubuntu 2018.04 on which PYNQ Linux is based. Systemd uses a configuration file based approach for defining services which can be a bit daunting if you’ve not used it before.
Anatomy of a systemd service configuration
A systemd configuration file is an INI-like format with a
.service extension that lives in the
/lib/systemd/system folder. For this guide we’re just going to look at three core sections of a configuration file necessary for starting a PYNQ program at boot. For more details you’ll need to consult the systemd unit reference.
This section describes basic metadata for our service - name it’s
Description, boot timing using
Before, and dependencies using
Wants both define dependencies but differ in what happens when the dependency fails to start. With
Requires a failing dependency results in the service also failing or not being started at all.
Wants is less strict and lets the service continue starting even if dependencies have failed.
Before determine what order services are started if multiple services are started at the same time - normally through dependencies or a common boot target.
Before don’t define dependencies though. If a service specified in
After isn’t being started for another reason it won’t be started. As a result
After is generally used together with a
Wants dependency to make sure that services are triggered at the same time.
We’ll use the service we created to flash the LEDs at boot as an example. Here we only want to flash the LEDs after both the PL server and Jupyter have successfully started so we use an
After property to make sure the script doesn’t start until after Jupyter is running and
Requires so that if either Jupyter or the PL server fail the LEDs won’t flash.
[Unit] Description=Boot LED flashing Requires=pl_server.service jupyter.service After=pl_server.service jupyter.service
The Service section defines the commands that execute the service and how systemd should track the service’s execution. The two properties we’ll look at here are
ExecStart is simply the command to run that starts the service. This command should always be specified as an absolute path and you can’t make any assumptions about the environment the command will be executed in. In particular a shell may not have been created so any environment setup in your bash configuration files will not have been run. If you need the environment configured before running your Python program you can create a wrapper bash script which handles the environment setup before calling Python.
Depending on how the command runs we need to set the
Type appropriately. There are many different types of service but the three most applicable for the use cases we are considering in this example are:
simple- The ExecStart command runs for as long as the service is active. If the command dies the server has had an error.
oneshot- The ExecStart command runs for a short period of time after which the service is active even though no process is running.
forking- The ExecStart command runs for a short period of time during which it forks the main service process. The service is considered started when ExecStart finishes and is running for as long as the forked process is alive.
Our boot_leds script is an example of a
oneshot process. It only runs for a short amount of time and we don’t think of the service of having “failed” when it returns.
[Service] Type=oneshot ExecStart=/usr/local/bin/boot_leds.sh
Finally we have the Install section which specifies what happens when the service is enabled and disabled. The only property we use here is
WantedBy which specifies which target will result in the service being started.
WantedBy is the inverse to
Wants in the [Unit] section and has the same semantics. You can also specify
RequiredBy but this is strongly discouraged as it’s easy to end up in a situation where if your service fails the entire boot process fails.
systemd has a number of special targets that corresponded to different points in the boot process. As we always want our service to start we’ll attach it to
basic.target which happens early in the boot process.
Now when we run the
systemctl enable command systemd will add a link in the
basic.target folder to our service.
A standard PYNQ image on a PYNQ-Z1, PYNQ-Z2 or ZCU104 has three services that start at boot time
- The PL server which co-ordinates access to the hardware (
- The Jupyter server (
- Flashing the LEDs to signal a successful boot (
The boot_leds service depends on the other two so that by the time the LEDs are flashing Jupyter is ready to be accessed.
Running your own program
There are two ways of running your own program at boot: piggy-backing off one of the existing services; or creating a new service from scratch. Each approach as its own pros and cons
This is the quick and somewhat dirty way of getting a script to run at startup in place of flashing the LEDs. We can start by looking at the existing service:
[Unit] Description=Boot LED flashing Requires=pl_server.service jupyter.service After=pl_server.service jupyter.service [Service] Type=oneshot ExecStart=/usr/local/bin/boot_leds.sh [Install] WantedBy=basic.target
From this point we can change either the
ExecStart entry to point to our own script or edit the
/usr/local/bin/boot_leds.sh script to change what it does.
There are a couple of other subtleties we need to think about as well. One is
Type of the service. The script is a oneshot service which means that systemd expects both the
ExecStart script to finish running and to leave no processes left when it does so. This is incompatible with a continuously running program so we need to change the type to
forking depending on what our script does.
Secondly we may no longer want the Jupyter dependency. If we are not planning to have the system be interacted with via Jupyter then we need to remove the
jupyter.service dependencies from the
After section. If we don’t, systemd will always start Jupyter before running our script, even if we try and disable the jupyter service explicitly.
Creating a new service
Creating a new service is the cleaner approach but you need to make sure that the boot_leds script is disabled if you are planning to use anything other than the Base overlay. Otherwise, as systemd will parallelise starting of services, a race condition can occur where both services are trying to load different designs into the bitstream simultaneously.
systemctl disable boot_leds
If we’re using the board completely standalone we can disable Jupyter as well to reduce the boot time of the system
systemctl disable jupyter
We can now start writing our service file. For this example we’ll assume that our script acts like a simple service, i.e. it starts and continues running unless an error occurs.
[Unit] Description=My Startup Script Requires=pl_server.service After=pl_server.service [Service] Type=simple ExecStart=/usr/bin/python3 /home/xilinx/my_script.py [Install] WantedBy=basic.target
With the service file created we can now enable the service so it will start at boot
systemctl enable my_script
system command reference
All systemd control functions are handled through the
systemctl command with various subcommands. The general command format is
systemctl <command> <service>
Where service is the name of your
.service file. Some of the more common commands are
enable: enable a service at boot time.
disable: disable a service at boot time - note it may still get started if another enable service depends on it.
status: the current status of the service and recent log messages
start: start the service immediately - does not impact boot-time behaviour
stop: stop the service immediately - does not impact boot-time behaviour
restart: restart the service immediately
For more detailed logging information you can use the
journalctl command to interact with journald - the logging counterpart to systemd.
journalctl -u <service>.service