Waarom is systemd de standaard geworden in Linux?
Ontdek de geschiedenis van systemd en waarom het SysV init verving. Wij analyseren de technische tekortkomingen van SysV, de rol van cgroups en de terechte kritiek op systemd.
Waarom systemd heeft gewonnen
De geschiedenis van systemd begint bij twee zaken die SysV init niet kon. SysV init (System V init, het opstartsysteem dat Linux heeft geërfd van AT&T Unix) had geen mogelijkheid om de afhankelijkheden van een service te beschrijven, en geen manier om te achterhalen welke processen bij een service hoorden zodra deze draaide. systemd beantwoordde beide punten met kernel-functies die een shell-script niet kan bereiken: control groups voor het volgen van processen, en vooraf geopende listening sockets voor het bepalen van de volgorde. De rest van het verhaal gaat over hoe die twee antwoorden zich verspreidden naar de rest van de userland, waar de bezwaren begonnen, en verschillende van die bezwaren waren terecht.
Wat SysV init daadwerkelijk deed
Op een SysV-systeem las PID 1 (process ID 1, het eerste proces dat de kernel start) /etc/inittab, koos een runlevel en voerde de scripts voor dat runlevel uit. De scripts stonden in /etc/init.d/. Symbolische koppelingen in /etc/rc3.d/ bepaalden welke scripts werden uitgevoerd en in welke volgorde; zo wees /etc/rc3.d/S20nginx naar /etc/init.d/nginx en werd deze aangeroepen met het argument start.
#!/bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO
case "$1" in
start)
start-stop-daemon --start --quiet --pidfile /run/nginx.pid \
--exec /usr/sbin/nginx
;;
stop)
start-stop-daemon --stop --quiet --retry TERM/30/KILL/5 \
--pidfile /run/nginx.pid
;;
esacHet 20 in S20nginx is een positie, geen afhankelijkheid. Het geeft aan dat dit script wordt uitgevoerd na S19 en vóór S21. Het vermeldt niet waarom, waardoor niets dit kan controleren en niets veilig twee ongerelateerde scripts tegelijk kan uitvoeren zonder dat een mens beslist of dit veilig is.
Het programma rc voerde elk script na elkaar uit en wachtte tot het was afgesloten. Een script dat dertig seconden blokkeerde terwijl het wachtte op een netwerkadres, blokkeerde de gehele opstartprocedure voor dertig seconden, zelfs voor services die het netwerk nooit gebruiken.
De LSB (Linux Standard Base) header bovenaan dat script was een poging om dit van binnenuit op te lossen. Debian 6.0 maakte in 2011 insserv de standaard: het las Required-Start uit elk script, bouwde een graaf en hernummerde de symlinks. Debian kon daarna onafhankelijke scripts tegelijkertijd uitvoeren met startpar. Dat hielp, maar het loste het dieperliggende probleem niet op. De afhankelijkheid bleef gebaseerd op het afsluiten van een script. Dat S20nginx de waarde 0 teruggeeft, betekent slechts dat een shell-functie is voltooid. Het betekent niet dat nginx verbindingen accepteert.
De vijf zaken die geen enkel init-script kon oplossen
- Parallel opstarten. Het ordenen op bestandsnaam dwingt een totale volgorde af voor elke service op de machine, waardoor het opstartproces net zo traag is als de som van de onderdelen.
- Gereedheid. Een startscript stopt zodra het de daemon heeft geforkt, niet wanneer de daemon daadwerkelijk verzoeken kan afhandelen, waardoor het volgende script vaak te vroeg start.
- Supervisie. Een daemon fork-t tweemaal en de ouder stopt, waardoor deze losgekoppeld wordt van de terminal en opnieuw wordt toegewezen aan PID 1. init ziet een kindproces stoppen en heeft geen betrouwbare koppeling met het proces dat is blijven draaien.
- Starten op aanvraag. inetd (de internet super-server) kon een daemon starten wanneer er een verbinding binnenkwam, maar dit was een afzonderlijk systeem met een eigen configuratiebestand en het deed niets aan de volgorde van alle andere processen tijdens het opstarten.
- Resourcebeheer. Niets in een init-script kon het geheugengebruik of het CPU-aandeel van een service begrenzen.
ulimitwas van toepassing op één proces ennicebeïnvloedde alleen de scheduler, waardoor een op hol geslagen kindproces van een service eruitzag als elk ander proces op de machine.
Het supervisiegat was hetgeen dat dagelijks voor problemen zorgde. Het PID-bestand was de workaround: de daemon schreef zijn proces-ID naar /run/nginx.pid en de stop-functie las het bestand weer uit. Als de daemon hard werd beëindigd, bleef het bestand achter. De kernel hergebruikte dat nummer vervolgens voor iets anders en start-stop-daemon --stop --pidfile stuurde een signaal naar het proces dat het nummer op dat moment bezat. Een verouderd PID-bestand is de manier waarop een init-script het verkeerde proces beëindigt.
launchd lost het socket-probleem als eerste op
Apple bracht launchd uit in Mac OS X 10.4 in 2005, geschreven door Dave Zarzycki. Eén proces verving init, rc, xinetd, crond en watchdogd.
Het idee dat het kopiëren waard was, was socket-activatie. launchd maakt eerst elke luisterende socket aan en start daarna pas de daemons. Een client die verbinding maakt met een daemon die nog niet is gestart, krijgt geen 'connection refused', omdat de kernel de verbinding in de backlog-wachtrij van die socket vasthoudt totdat de daemon accept() aanroept. De volgorde tussen twee daemons hoeft niet langer handmatig te worden ingesteld. De socket regelt dit.
launchd was gebouwd op Mach IPC (inter-process communication), dat onderdeel is van Apple's XNU-kernel en geen Linux-equivalent heeft. Het porten van de code was nooit realistisch. Het idee vond desondanks zijn weg.
Upstart maakte van events de werkeenheid
Canonical's Upstart, geschreven door Scott James Remnant, werd in oktober 2006 uitgebracht in Ubuntu 6.10. Fedora 9 tot en met Fedora 14 maakten er gebruik van, evenals RHEL 6 en Chrome OS. Het verving het runlevel door een event, waarbij een job aangaf welke events deze moesten starten en stoppen.
# /etc/init/example.conf
start on filesystem and net-device-up IFACE!=lo
stop on runlevel [!2345]
respawn
respawn limit 10 5
exec /usr/local/bin/exampledEr ontstonden twee problemen naarmate het aantal jobs toenam. Het eerste is de richting. Een job zegt "start mij wanneer dit gebeurt", waardoor de kennis over afhankelijkheden in het verkeerde bestand staat: een service weet wat deze zelf nodig heeft, maar kan niet weten wie er in de toekomst van de service afhankelijk zal zijn. Het toevoegen van een service betekende vaak dat een bestaande job moest worden aangepast zodat deze een nieuw event uitstuurde.
Het tweede probleem is de tracering. Upstart volgde een forking daemon door fork()-aanroepen te tellen met ptrace, wat u configureerde als expect fork of expect daemon. Als u het aantal forks verkeerd inschat, superviseert Upstart een proces dat al is afgesloten, of wacht het op een fork die al heeft plaatsgevonden. Het symptoom is dat initctl start blijft hangen zonder foutmelding, waarvoor het jobbestand geen verklaring biedt.
Upstart vereiste bovendien dat bijdragers de contributor agreement van Canonical ondertekenden. Dat was geen technisch mankement, maar het bepaalde wel wie er aan de software werkte.
Heroverweging van PID 1, april 2010
Op 30 april 2010 publiceerde Lennart Poettering een bericht genaamd "Rethinking PID 1". Kay Sievers werkte samen met hem aan het project. Het argument bestond uit vier onderdelen.
- Start minder. Veel services kunnen wachten totdat er daadwerkelijk om gevraagd wordt.
- Stop met het declareren van volgordes waar een socket dit kan impliceren. Open alle sockets in één keer en start daarna alles tegelijk.
- Volg processen met control groups in plaats van met PID-bestanden.
- Beschrijf een service in een declaratief bestand, zodat één beschrijving op elke distributie werkt.
De eerste release volgde datzelfde jaar. Fedora 14 leverde systemd als optie in november 2010, en Fedora 15 maakte het de standaard in mei 2011.
Waarom cgroups supervisie betrouwbaar maakten
Een cgroup (control group) is een kernel-functionaliteit voor het groeperen van processen, die in 2008 werd toegevoegd aan Linux 2.6.24. systemd plaatst elke service in een eigen cgroup. Een child-proces erft de cgroup van zijn parent, en een proces zonder privileges kan zichzelf hier niet uit verplaatsen. Double forking verbergt daarom niets: PID 1 beheert op elk moment exact de set processen die bij een unit hoort. Het stoppen van een service betekent het beëindigen van alles in de bijbehorende cgroup, wat de standaardactie is van KillMode=control-group.
systemctl status toont die groep:
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-08-11 09:14:22 UTC; 3min ago
Main PID: 1042 (nginx)
Tasks: 3 (limit: 4653)
Memory: 6.1M (peak: 7.4M)
CGroup: /system.slice/nginx.service
├─1042 "nginx: master process /usr/sbin/nginx"
├─1043 "nginx: worker process"
└─1044 "nginx: worker process"Dat blok is het volledige antwoord op het probleem van verouderde PID-bestanden. Er is geen bestand dat verouderd kan raken, omdat de lijst de status van de kernel zelf is.
Dezelfde boomstructuur bevat ook limieten, omdat cgroups oorspronkelijk werden gebouwd voor accounting voordat ze werden gebruikt voor tracking. MemoryMax=, CPUQuota= en TasksMax= bestaan elk uit één regel. Het instellen van een harde limiet voor geheugen en CPU op een service is tegenwoordig een drop-in bestand, terwijl het in 2009 een patch voor een shell-script was die niemand schreef.
Waarom elke distributie tussen 2011 en 2015 overstapte
- Fedora 15, mei 2011.
- openSUSE 12.1, november 2011.
- Mageia 2, mei 2012.
- Arch Linux, standaard voor nieuwe installaties vanaf oktober 2012.
- RHEL 7, juni 2014.
- SLES 12, oktober 2014.
- Debian 8, april 2015.
- Ubuntu 15.04, april 2015.
De redenen waren grotendeels saai, wat verklaart waarom de overstap zo snel verliep.
- Eén unit-bestand werkt op elke distributie, waardoor upstream-projecten begonnen met het meeleveren van een
.service-bestand en distributies stopten met het onderhouden van een shell-script per pakket per release. - Het bijhouden van desktopsessies verplaatste naar
systemd-logindnadat het onderhoud aan ConsoleKit rond 2012 stopte. GNOME had logind nodig, dus een distributie zonder systemd moest een vervanger vinden. Die vervanger,elogind, is de logind-component van systemd, afgesplitst en afzonderlijk onderhouden. - udev, de device manager, werd in april 2012 samengevoegd met de broncode van systemd. Distributies die udev meeleverden, volgden vanaf dat moment de repository van systemd. Gentoo splitste als reactie hierop
eudevaf. - Door containers werden betrouwbare procesbewaking en limieten per service belangrijker, aangezien beide functies van cgroups zijn. De vraag welke supervisor eigenaar is van een containerproces blijft actueel wanneer u een Docker Compose-stack laat herstellen na een reboot.
Het besluit van Debian was het meest opvallend. De Technische Commissie stemde in februari 2014, de stemmen staakten, en de voorzitter, Bdale Garbee, bracht de beslissende stem uit voor systemd. Ubuntu kondigde enkele dagen later aan Debian te volgen in plaats van door te gaan met Upstart. Een groep Debian-ontwikkelaars splitste de distributie in november 2014 af als Devuan en bracht in mei 2017 Devuan 1.0 uit.
De bezwaren, eerlijk verwoord
Omvang. Eén project levert nu PID 1, de logging-daemon, beheer van inlogsessies, de device-manager, een netwerkconfiguratie-daemon, een DNS (domain name system) resolver, een NTP (network time protocol) client, een container-runner en een bootloader. Het gebruikelijke verweer, dat dit afzonderlijke binaries zijn die u niet hoeft te installeren, is waar, maar het beantwoordt het bezwaar niet. Zodra een desktopomgeving logind vereist, en logind is losgekoppeld van de systemd-broncode, is de keuze niet langer vrij. Dat is wat koppeling in dit argument betekende, en het is gebeurd.
Het binaire journal. journald schrijft een geïndexeerd binair formaat in plaats van platte tekst. U krijgt functionaliteit die tekst nooit bood: filteren per unit en per prioriteit, gestructureerde velden en metadata die het verzendende programma niet kan vervalsen, omdat journald de unit en cgroup zelf registreert. journalctl -u nginx -p err --since "-1h" vervangt een grep met een datum-reguliere expressie. De kosten zijn echter ook reëel. Op een machine die niet opstart, kunt u het logboek niet lezen met less vanuit een rescue-shell. U wijst journalctl in plaats daarvan naar de aangekoppelde schijf:
sudo journalctl --directory /mnt/var/log/journal --boot -1 --priority errEr is hier een tweede valstrik waar mensen één keer in trappen. journald bewaart logs in /run/log/journal, wat het geheugen is, tenzij /var/log/journal bestaat. Op een systeem waar dit niet het geval is, heeft journalctl -b -1 niets te tonen na een reboot, precies het moment waarop u het nodig heeft. Controleer en herstel dit:
journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journaldjournalctl --disk-usage zou nu gearchiveerde journals onder /var/log/journal moeten rapporteren. Als u ook platte tekst wilt, stel dan ForwardToSyslog=yes in /etc/systemd/journald.conf in en houd rsyslog geïnstalleerd.
Debugbaarheid van het opstartproces. Wanneer een unit blijft hangen, toont de console slechts één regel en niets anders:
[ *** ] A start job is running for Wait for Network to be Configured (1min 32s / no limit)De tools om verder te kijken bestaan wel: systemctl list-jobs terwijl het proces vastloopt, systemd-analyze blame en systemd-analyze critical-chain achteraf, en systemd.log_level=debug op de kernel-commandoregel. De eerlijke versie van de klacht is dat een init-script van boven naar beneden kon worden gelezen door iedereen die sh kende, terwijl een vastgelopen unit vereist dat u weet naar welk van de twaalf commando's u moet grijpen. Dat zijn reële kosten. Ze worden één keer per beheerder betaald, en ze werden door veel beheerders tegelijkertijd betaald.
Een standaardinstelling die voor iedereen verandert. systemd 230 veranderde in 2016 de standaardinstelling van logind, waardoor achtergebleven gebruikersprocessen werden beëindigd bij het uitloggen. Losgekoppelde tmux en screen sessies stierven wanneer de sessie die ze startte werd beëindigd. Distributies leverden KillUserProcesses=no in /etc/systemd/logind.conf, en het ondersteunde antwoord is loginctl enable-linger <user>. Eén standaardinstelling in één project veranderde een gewoonte waar miljoenen mensen op vertrouwden; dat is wat "te veel userland op één plek" in de praktijk betekent.
Een standaardafhankelijkheid is een aanvalsoppervlak. In maart 2024 richtte de backdoor in xz-utils zich op sshd op Debian en Ubuntu. De upstream OpenSSH linkt niet aan libsystemd. Die distributies hebben het erin gepatcht zodat sshd gereedheid kon rapporteren aan systemd, en libsystemd trok liblzma aan, waar de backdoor zich bevond. Het gereedheidsprotocol zelf is een enkel datagram dat naar de socket wordt gestuurd die in $NOTIFY_SOCKET is benoemd, dus daarvoor was nooit een bibliotheek vereist. De reactie van systemd was om compressiebibliotheken te laden met dlopen, zodat ze niet langer standaard worden gelinkt. Een gerelateerde klasse van bugs toont hetzelfde patroon: in 2017 werd een User=-waarde die met een cijfer begon als ongeldig behandeld en draaide de unit als root in plaats van te falen, waardoor een typefout een privilege-escalatie werd. Latere versies weigeren de unit te starten.
Geschiedenis bij uw eigen systemctl-prompt
Elk probleem hierboven is nu een instructie in een bestand dat u kunt lezen.
- Seriële boot werd
After=enWants=, ensystemd-analyze critical-chaintoont wat uw boot daadwerkelijk ophield. - Readiness werd
Type=notify, waarbij de serviceREADY=1naar$NOTIFY_SOCKETschrijft wanneer deze gereed is voor verzoeken.Type=forkingmetPIDFile=bestaat nog steeds voor oude daemons, en dit is het type dat faalt metstart operation timed out. Terminating.wanneer het PID-bestand nooit verschijnt. - Supervisie werd de cgroup, dus
Restart=on-failuremetRestartSec=vervangt een wrapper-script, enStartLimitBurst=voorkomt dat een crash-loop eeuwig doorgaat. - inetd werd een
.socket-unit naast de.service-unit. ulimitwerdMemoryMax=,CPUQuota=enTasksMax=.- De
su - appuser -c-regel in een init-script werdUser=,NoNewPrivileges=yesenProtectSystem=strict, waardoor een service draaien als een niet-geprivilegieerde gebruiker de standaardvorm van een unit is in plaats van extra werk.
[Unit]
Description=Example API
Wants=network-online.target
After=network-online.target postgresql.service
Requires=postgresql.service
[Service]
Type=notify
ExecStart=/usr/local/bin/exampled
Restart=on-failure
RestartSec=2
MemoryMax=512M
CPUQuota=50%
TasksMax=128
User=exampled
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
StateDirectory=exampled
[Install]
WantedBy=multi-user.targetEén regel in dat bestand is de fout die iedereen wel eens maakt. Requires=postgresql.service is een vereiste, geen volgorde: het stelt dat uw unit faalt als Postgres faalt, en het zegt niet dat Postgres eerst moet starten. Zonder After=postgresql.service starten beide op hetzelfde moment, en uw service probeert verbinding te maken met een poort waarop nog niets luistert. De twee zijn bewust gescheiden, omdat u soms de een zonder de ander wilt. ProtectSystem=strict koppelt het bestandssysteem alleen-lezen voor deze service, en daarom is StateDirectory= aanwezig: het geeft de service één schrijfbaar pad onder /var/lib.
De duidelijkste plek om 2005 op een 2026-server te zien is SSH op Ubuntu 24.04, dat systemd 255 bevat sinds augustus 2026. ssh.service wordt standaard geactiveerd via een socket: ssh.socket houdt de luisterende socket vast, en sshd start wanneer er een verbinding binnenkomt. Daarom heeft Port 2222 in /etc/ssh/sshd_config geen effect, omdat sshd niet het proces is dat de poort heeft geopend. De wijziging hoort in de socket-unit thuis.
sudo systemctl edit ssh.socket[Socket]
ListenStream=
ListenStream=2222De lege ListenStream= wist de waarde die is overgenomen van de meegeleverde unit. Laat u dit weg, dan krijgt u beide poorten, omdat systemd toevoegt aan een lijst in plaats van deze te vervangen. Pas daarna de wijzigingen toe en controleer dit, terwijl u de hele tijd een tweede SSH-sessie openhoudt:
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222ss hoort één socket op poort 2222 te tonen, eigendom van systemd, niet van sshd. Dat is het launchd-ontwerp, twintig jaar later, op uw VPS. Als u de voorkeur geeft aan het oude gedrag, geeft sudo systemctl disable --now ssh.socket gevolgd door sudo systemctl enable --now ssh.service u een langlopende sshd die weer Port uit zijn eigen configuratie leest.
Welke van deze details u tegenkomt, hangt af van de release die u draait. Het is daarom de moeite waard om het verschil tussen een LTS- en een interim Ubuntu-release te kennen voordat u een upgrade plant. Over meerdere machines heen is het feit dat een unit-bestand overal identiek is de reden dat meerdere servers vanaf één plek beheren nu een configuratieprobleem is in plaats van een shell-scriptingprobleem. En wanneer u uw eigen units schrijft, doet het service- en timer-paar het werk dat u in 2009 zou hebben verdeeld tussen een init-script en een cron-regel.
FAQ
Waarom hebben Linux-distributies SysV init vervangen door systemd?
Om twee technische redenen en één onderhoudsreden. SysV init ordende services op basis van bestandsnaam, wat een positie is in plaats van een afhankelijkheid. Bovendien verloor het het overzicht over daemons die zich afsplitsten van hun ouderproces, waardoor verouderde PID-bestanden het verkeerde proces konden beëindigen. systemd loste de volgorde op met socket-activatie en afhankelijkheidsrichtlijnen, en loste het volgen van processen op met control groups. De onderhoudsreden bepaalde de snelheid: één unit-bestand werkt op elke distributie. Hierdoor leveren upstream-projecten een .service-bestand aan en hoeven distributiebeheerders niet langer per pakket een shell-script te schrijven. Fedora 15 schakelde over in mei 2011 en Ubuntu 15.04 was de laatste grote achterblijver in april 2015.
Is systemd één gigantisch binair bestand?
Nee. De broncode bouwt vele afzonderlijke programma's. PID 1 is /usr/lib/systemd/systemd, terwijl journald, logind en udevd afzonderlijke processen zijn met hun eigen binaire bestanden; voer ls /usr/lib/systemd/ uit om ze op uw eigen systeem te zien. De kritiek die blijft bestaan gaat over de koppeling van releases in plaats van de grootte van de binaire bestanden: deze programma's worden samen uitgebracht en delen private interfaces. Distributies nemen ze daarom meestal als een set af, en software zoals GNOME is specifiek afhankelijk geworden van logind.
Kan ik nog steeds Linux draaien zonder systemd?
Ja. Devuan levert sysvinit, Gentoo gebruikt standaard OpenRC, Void gebruikt runit, Alpine gebruikt busybox init met OpenRC en Slackware behoudt scripts in BSD-stijl. De prijs hiervan is extra werk voor compatibiliteit. Desktopsoftware die logind verwacht, heeft elogind nodig; dit is de logind van systemd, onderhouden als een zelfstandig pakket. Daarnaast levert een groeiend aantal serverapplicaties alleen nog een .service-bestand aan, waardoor u het opstartscript zelf moet schrijven en onderhouden.
Waarom is de journal binair in plaats van een plat tekstbestand?
Omdat journald gestructureerde velden met een index opslaat. Dit maakt filteren per unit, filteren op prioriteit en metadata mogelijk die het verzendende programma niet kan vervalsen: journald registreert zelf de unit, de cgroup en de werkelijke UID in plaats van de logregel te vertrouwen. De prijs hiervan is dat u journalctl nodig heeft om het te lezen, ook vanuit een rescuesysteem, waar u het met journalctl --directory /mnt/var/log/journal naar de aangekoppelde schijf verwijst. Als u ook tekst wilt, stelt u ForwardToSyslog=yes in binnen /etc/systemd/journald.conf.
Wat is de vervanging voor het bewerken van mijn /etc/init.d script?
Drop-in bestanden. Bewerk de unit in /usr/lib/systemd/system/ niet, omdat een pakket-upgrade deze overschrijft. Voer sudo systemctl edit nginx.service uit en systemd maakt /etc/systemd/system/nginx.service.d/override.conf aan, dat over de verpakte unit heen wordt samengevoegd. systemctl cat nginx.service toont het samengevoegde resultaat en systemd-delta somt elke override op de machine op. Voer na elke handmatige wijziging sudo systemctl daemon-reload uit, anders geeft het volgende commando Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. weer.