An overview of systemd

Systemd is a Linux init system and software suite developed mainly as an effort to standardize the Linux init system. It has effectively replaced others like SysV init or upstart in most distros, although it keeps a very high degree of backwards compatibility with the former. It has been adopted by most Linux distributions since 2015.

What is an init system?

An init (short for initialization) system or process is the first process started by the Linux kernel during boot, bringing up and maintaining userspace services. Systemd shares a few traits with most init systems:

  • Since it's the first process to start, it bears the process identifier (PID) 1.
  • It has no parent process. This is denoted with a parent PID (PPID) of 0.
  • It will continue to run until the system is shut down.
  • It is the common ancestor for every process.
  • It automatically adopts all orphaned processes.

The kernel thread daemon, kthreadd, has in most cases PID 2 and is usually the only process other than systemd that has no parent. All kernel threads are forked from this process.

Separate instances of systemd are started for logged-in users to start their services. That is why if you start going down the tree, i.e. towards the root (init) or looking for the parent of each process, you will most likely hit a process whose command looks something like /lib/systemd/systemd --user and in most cases this will be a direct child of the actual init process with PID 1.

The standard init process can actually be replaced with some other binary (e.g. /bin/bash) via the kernel init argument, but this is meant to be a temporary change in order to do some troubleshooting (or hacking ;) ) and of course it will deprive the user or sysadmin from every service offered by it.

Why is systemd so hated by many?

Even though to the eyes of most Linux users and system administrators it is essentially a new standardized init process, it provides utilities for a wide array of tasks including device management, login management, network connection management, and event logging (the init component is actually known as the system and service manager). This goes against one of the fundamental principles of the Unix philosophy: "Make each program do one thing well".

Despite the fact that systemd is actually a software suite rather than a single application, many consider it to be bloated. For example, systemd has its own UEFI boot manager called systemd-boot, but most distributions keep using GRUB instead and will most likely continue to do so.

In any case, usage of all the components of the suite is by no means mandatory and every Linux user can choose which ones to use.

Units

Units represent resources managed by systemd. Each unit can declare required or optional dependencies on other units in the system, ensuring that at least all of its required dependencies are up and running before starting it.

Units don't have to be exclusively services, and despite the core component of systemd being an init system, it's not mandatory for all units managed by systemd to be started at boot.

Unit files and their locations

In order to create a unit, you only need to create a text file describing its configuration. Unit files are commonly found under three different locations:

  • /lib/systemd/system: units managed by packages or the OS.
  • /etc/systemd/system: these are usually managed by the sysadmin and can override unit files under /lib (this is the most appropriate place for making changes).
  • /run/systemd/system: non-persistent runtime modifications (remember a filesystem of type tmpfs is mounted under /run).

Naming convention

Unit files are named following a "name.type" convention, e.g.

  • docker.service: the docker service unit.
  • ssh.socket: a unit of type socket for accepting ssh connections.
  • reboot.target: the reboot target-type unit (more on targets later).

These are the three most common types of units, but there are several others like device, mount, swap, path, timer, etc.

Anatomy of a unit file

Unit files are organized in sections denoted by square brackets, e.g. [Section]. The [Unit] section is common to every unit file, and others like [Service] are specific to a type of unit. Sections contain directives in the form Key=Value.

Let's analyze the sections and their directives in a standard unit file for the Nginx web server and reverse proxy:

[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

[Install]
WantedBy=multi-user.target

The Unit section is generally used for defining metadata for the unit and defining its relationship with other units.

  • Description: a short description of the unit, usually including its name.
  • Documentation: it can reference one or more man pages instead of a url if there are any available.
  • After: this unit should be started after all the units referenced in this directive. This does not imply dependency and such must be defined through either the Requires, Wants or BindsTo directives.
  • Wants: establishes a dependency on the units listed here. Unlike Requires, which is more strict, this unit will continue to function if its dependencies are not found or fail to start. Wants is recommended instead of Requires.

The Service section is unique and mandatory for all units of type service.

  • Type: specifies the type of service according to the behavior of its process. In this case, forking means that the service forks into a child process, the parent exits and the child continues to run as the service main process.
  • PIDFile: a path referring to the PID file of the service used in combination with Type=forking so that systemd can reliably identify the main process of the service (whose PID will be stored in the MAINPID environment variable and used later by ExecReload and ExecStop in this case).
  • ExecStart: the command to be executed when the service starts. In this case we can see that it is of course the Nginx binary and that the default configuration file is provided with the -c option.
  • ExecReload: commands to execute to trigger a configuration reload in the service. In this case, the unit is configured to send a SIGHUP signal, which by convention is often used to let know a process that it should re-read its configuration.
  • ExecStop: as you've probably guessed, the command to execute in order to stop the service (by sending a SIGTERM to it).

The Install section is optional and is used to define the behavior or a unit if it is enabled or disabled. Enabling a unit marks it to be automatically started at boot. In essence, this is accomplished by latching the unit in question onto another unit to be started at boot (in this case referencing the multi-user target via the WantedBy directive).

  • WantedBy: this can be seen as the Wants "reverse" directive. It causes a dependency of type Wants= to be added from the listed unit to the current unit. In this case, since Nginx service is enabled, it's "wanted by" the multi-user target.

Targets

As we have already seen, targets are a type of unit. According to systemd's man pages:

A unit configuration file whose name ends in ".target" encodes information about a target unit of systemd, which is used for grouping units and as well-known synchronization points during start-up.
Target units do not offer any additional functionality on top of the generic functionality provided by units. They exist merely to group units via dependencies (useful as boot targets), and to establish standardized names for synchronization points used in dependencies between units. Among other things, target units are a more flexible replacement for SysV runlevels in the classic SysV init system.

In other words, targets can be seen as groups of units, and they are "reached" when all of these units have started successfully. There is an equivalent target for every classic SysV runlevel. These are actually symlinks and can be examined by running
ls -l /lib/systemd/system | grep '.target$' | grep runlevel

runlevel0.target -> poweroff.target
runlevel1.target -> rescue.target
runlevel2.target -> multi-user.target
runlevel3.target -> multi-user.target
runlevel4.target -> multi-user.target
runlevel5.target -> graphical.target
runlevel6.target -> reboot.target

If you are developing your own service, you can even write your own target so that any other services relying on it can know when it's "online", i.e. when all of its dependencies have been fulfilled, e.g. it might depend on network-online.target and postgresql.service. The most common naming pattern for this would be myservice-online.target.

Systemctl

The final important piece of the puzzle is systemctl, the systemd system and service manager control. It can be seen as a CLI to interact with systemd.

As specified in its man pages, its syntax goes along
systemctl [OPTIONS...] COMMAND [UNIT...]

So if you want to check the overall status of the system, you can run:
systemctl status

If you want to check the status of the sshd service in particular:
systemctl status sshd.service

You can also check if targets have been reached:
systemctl status multi-user.target

As mentioned before, systemd keeps a high degree of compatibility with the classic SysV init. If you check the location of the init command under a system managed by systemd (/sbin/init), you will see that it is actually a symlink to /lib/systemd/systemd. If you want to enter runlevel 6, i.e. rebooting your machine, these four commands would be equivalent:

reboot            # /sbin/reboot -> /bin/systemctl*
init 6            # /sbin/init -> /lib/systemd/systemd*
systemctl reboot  # reboot is available as a systemctl command
systemctl isolate reboot.target  # targets can be "isolated"