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
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.
[Unit]
section
This section describes basic metadata for our service - name it’s Description
, boot timing using After
/Before
, and dependencies using Requires
and Wants
.
Requires
and 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.
After
and Before
determine what order services are started if multiple services are started at the same time - normally through dependencies or a common boot target. After
and 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 Requires
or 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
[Service]
section
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
and Type
. 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
{Install]
section
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.
[Install]
WantedBy=basic.target
Now when we run the systemctl enable
command systemd will add a link in the basic.target
folder to our service.
PYNQ services
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 (
pl_server.service
) - The Jupyter server (
jupyter.service
) - Flashing the LEDs to signal a successful boot (
boot_leds.service
)
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
Modifying boot_leds
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:
vim /lib/systemd/system/boot_leds.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 simple
or 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 Requires
and 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.
vim /lib/systemd/system/my_script.service
[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