systemd auf Embedded-Systemen: was sich lohnt und was schadet
systemd im Embedded-Einsatz: Units und Abhängigkeiten richtig schneiden, Boot-Zeit statt Reihenfolge optimieren, Dienste per Watchdog überwachen, das Journal vom Flash fernhalten und mit read-only Root-Dateisystem arbeiten.
Kurz gesagt: systemd ist auf Embedded-Systemen kein Selbstzweck, aber auch nicht der Ballast, für den es oft gehalten wird. Der Gewinn liegt weniger im schnelleren Start als in den Dingen, die man sonst mühsam in Shell-Skripten nachbaut: Abhängigkeiten zwischen Diensten, definiertes Verhalten im Fehlerfall, Watchdog-Überwachung, Ressourcengrenzen. Der Schaden entsteht dort, wo Voreinstellungen aus der Serverwelt unverändert auf ein Gerät mit Flash-Speicher treffen.
Was systemd auf einem Gerät tatsächlich beiträgt
Die verbreitete Begründung „schnellerer Boot” greift zu kurz. Interessanter sind vier andere Punkte:
Abhängigkeiten werden explizit. In einer Unit steht, was vorher laufen muss und was danach kommt. In einer gewachsenen Sammlung von Init-Skripten steckt dieselbe Information in Nummernpräfixen und sleep-Aufrufen – und niemand traut sich, daran etwas zu ändern.
Fehlerverhalten ist konfiguriert, nicht programmiert. Restart=on-failure mit RestartSec und StartLimitBurst beschreibt in drei Zeilen, was sonst eine Watchdog-Schleife im Skript wäre, die man beim nächsten Problem erst wieder verstehen muss.
Ressourcen sind begrenzbar. Über die cgroup-Anbindung lassen sich Speicher und CPU-Anteil pro Dienst deckeln. Auf einem Gerät, das nicht swappen kann, ist das der Unterschied zwischen einem Dienst, der abstürzt, und einem System, das stehen bleibt.
Der Hardware-Watchdog wird bedient. systemd kann sowohl den Watchdog der Plattform füttern als auch von einzelnen Diensten regelmäßige Lebenszeichen verlangen – ohne dass dafür eigener Code entsteht.
Units schneiden, ohne sich zu verrennen
Eine typische Anwendungs-Unit ist kürzer, als viele erwarten:
[Unit]
Description=Device control application
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/devicectl
Restart=on-failure
RestartSec=2s
WatchdogSec=30s
MemoryMax=128M
[Install]
WantedBy=multi-user.target
Zwei Details entscheiden über das Verhalten im Feld. Type=notify bedeutet, dass der Dienst per sd_notify(READY=1) selbst meldet, wann er arbeitsfähig ist – erst dann starten Units, die von ihm abhängen. Ohne das gilt ein Dienst als bereit, sobald sein Prozess existiert, was bei allem mit Initialisierungsphase zu Startproblemen führt, die sich sporadisch anfühlen.
WatchdogSec verlangt vom Dienst ein regelmäßiges sd_notify(WATCHDOG=1). Bleibt es aus, greift die konfigurierte Reaktion – vom Neustart des Dienstes bis zum Reboot des Geräts. Das ersetzt den handgeschriebenen Überwachungsprozess, den es in fast jedem gewachsenen System gibt.
Boot-Zeit: messen statt vermuten
Wer die Startzeit verkürzen will, sollte nicht raten. systemd-analyze blame listet die Units nach Dauer, systemd-analyze critical-chain zeigt die Kette, die den Start tatsächlich verlängert. Der Unterschied ist wesentlich: Eine Unit kann acht Sekunden brauchen und trotzdem irrelevant sein, weil sie parallel zu etwas Längerem läuft.
Die üblichen Funde sind unspektakulär und wiederholen sich: ein Dienst, der auf network-online.target wartet, obwohl er das Netz erst später braucht; ein sleep aus der Init-Skript-Ära, das jemand vorsichtshalber übernommen hat; ein Dienst in der kritischen Kette, der eigentlich per Socket-Aktivierung starten könnte, sobald ihn zum ersten Mal jemand anspricht. Mehr zum Gesamtbild steht im Artikel zur Boot-Zeit-Optimierung – systemd ist dort nur einer von mehreren Abschnitten, und selten der größte.
Die zwei Voreinstellungen, die Geräten schaden
Das Journal auf dem Flash. Standardmäßig schreibt journald nach /var/log/journal, sofern das Verzeichnis existiert. Auf einem Server ist das richtig. Auf einem Gerät mit eMMC oder SD-Karte bedeutet es kontinuierliche Schreibzugriffe über Jahre. Storage=volatile hält das Journal im RAM; wo persistente Logs nötig sind, gehören SystemMaxUse und eine bewusst gewählte Partition dazu. Verschlissene Flash-Speicher nach drei Jahren Feldeinsatz lassen sich oft bis auf diese eine Voreinstellung zurückführen.
Ein beschreibbares Root-Dateisystem. Ein Gerät, das jederzeit die Spannung verlieren kann, fährt mit read-only Root deutlich sicherer. systemd kommt damit gut zurecht: /etc lässt sich per Overlay beschreibbar halten, /var als tmpfs führen, und was wirklich persistent sein muss, liegt auf einer eigenen Datenpartition. Der Aufwand entsteht einmal beim Aufsetzen des Yocto-Images und spart die Klasse von Fehlern, bei denen ein Gerät nach einem Stromausfall nicht mehr hochkommt.
Wann BusyBox init die bessere Wahl bleibt
Nicht jedes Gerät braucht das. Ein Sensorknoten mit 32 MB RAM, drei Prozessen und einer festen Startreihenfolge fährt mit BusyBox init einfacher, kleiner und leichter zu überblicken. Die Grenze verläuft weniger an der Speichergröße als an der Komplexität: Sobald Dienste voneinander abhängen, im Fehlerfall definiert reagieren sollen und überwacht werden müssen, baut man sonst Stück für Stück ein schlechteres systemd nach.
Wenn Sie vor dieser Entscheidung stehen oder ein bestehendes System aufräumen wollen, sehen wir uns das gern gemeinsam an – im Rahmen unserer Embedded-Linux-Entwicklung oder als Schulung, in der Units, Abhängigkeiten und Boot-Analyse an Ihrer eigenen Plattform durchgegangen werden.
Häufige Fragen
- Ist systemd für kleine Embedded-Systeme nicht zu schwergewichtig?
- Das kommt auf die Klasse an. Auf einem Gerät mit wenigen Megabyte RAM und einer Handvoll Dienste ist BusyBox init schlanker und völlig ausreichend. Sobald aber Abhängigkeiten zwischen Diensten, Neustarts im Fehlerfall, Watchdog-Überwachung oder Ressourcenbegrenzung gefragt sind, schreibt man diese Dinge sonst in Shell-Skripten nach – meist schlechter als systemd es kann. Ab etwa 64 MB RAM und einem Dutzend Diensten spricht wenig dagegen.
- Wie verhindere ich, dass das Journal den Flash-Speicher verschleißt?
- Mit `Storage=volatile` in der `journald.conf` bleibt das Journal im RAM und überlebt keinen Neustart – für viele Geräte genau richtig, weil Logs ohnehin an einen Server gehen. Wird persistentes Logging gebraucht, begrenzt man es über `SystemMaxUse` und `RuntimeMaxUse` und legt es bewusst auf eine Partition, deren Verschleiß eingeplant ist. Unbegrenztes Schreiben auf eine eMMC ist einer der häufigsten Gründe für Ausfälle nach Jahren im Feld.
- Warum startet mein Dienst zu früh, obwohl After= gesetzt ist?
- `After=` regelt nur die Reihenfolge, nicht die Bereitschaft. Der vorherige Dienst gilt als gestartet, sobald sein Prozess läuft – nicht, sobald er arbeitsfähig ist. Wer echte Bereitschaft braucht, nutzt `Type=notify` und lässt den Dienst per `sd_notify` melden, wann er so weit ist. Für Netzwerkabhängigkeiten ist `network-online.target` das richtige Ziel, nicht `network.target`.
- Bringt systemd wirklich eine kürzere Boot-Zeit?
- Nicht automatisch, aber es macht die Boot-Zeit analysierbar und parallelisierbar. `systemd-analyze blame` und `critical-chain` zeigen, welche Unit tatsächlich die Kette verlängert – oft eine ganz andere als vermutet. Der eigentliche Gewinn entsteht durch Socket-Aktivierung und das Entfernen unnötiger Abhängigkeiten, nicht durch den Wechsel des Init-Systems an sich.
Geschäftsführer, bitshift dynamics
Entwickelt mit seinem Team hardwarenahe Software für Embedded-Produkte – C++, Qt/QML, Embedded Linux und das Yocto Project. bitshift dynamics arbeitet seit 2005 in diesem Feld.