História do systemd e por que ele venceu
Entenda o que o SysV init não fazia, o que Upstart e launchd tentaram e por que as distribuições migraram para o systemd em quatro anos.
Por que o systemd venceu
A história do systemd começa com duas coisas que o SysV init não conseguia fazer. O SysV init (System V init, o sistema de arranque que o Linux herdou do Unix da AT&T) não tinha forma de descrever de que depende um serviço nem de saber que processos pertencem a um serviço depois de este arrancar. O systemd resolveu ambos os problemas com funcionalidades do kernel que um script de shell não consegue aceder: grupos de controlo para acompanhar processos e sockets de escuta pré-abertos para ordenar o arranque. O resto da história explica como estas duas soluções se espalharam pelo restante espaço de utilizador. É aí que começam as objeções, e várias delas eram legítimas.
O que o SysV init realmente fazia
Num sistema SysV, o PID 1 (ID do processo 1, o primeiro processo iniciado pelo kernel) lia /etc/inittab, escolhia um runlevel e executava os scripts desse runlevel. Os scripts ficavam em /etc/init.d/. As ligações simbólicas em /etc/rc3.d/ determinavam quais scripts eram executados e por que ordem. Por isso, /etc/rc3.d/S20nginx apontava para /etc/init.d/nginx e era chamado com o argumento 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
;;
esacO 20 em S20nginx é uma posição, não uma dependência. Indica que este script é executado depois de S19 e antes de S21. Não indica o motivo dessa ordem. Portanto, nada pode verificá-la, e nenhum mecanismo pode executar em segurança dois scripts não relacionados ao mesmo tempo sem que um administrador determine que isso é seguro.
O programa rc executava cada script sequencialmente e esperava que terminasse. Se um script ficasse bloqueado durante trinta segundos à espera de um endereço de rede, todo o boot ficava bloqueado durante trinta segundos, incluindo serviços que nunca acedem à rede.
O cabeçalho LSB (Linux Standard Base) no início desse script foi uma tentativa de corrigir o problema internamente. Em 2011, o Debian 6.0 tornou insserv a predefinição: o sistema lia Required-Start de cada script, construía um grafo e renumerava as ligações simbólicas. O Debian podia então executar scripts independentes em simultâneo com startpar. Isso ajudou, mas não resolveu o problema mais profundo. A dependência continuava a basear-se na saída de um script. S20nginx com o valor 0 significa que uma função da shell terminou com sucesso. Não significa que o nginx esteja a aceitar ligações.
As cinco coisas que nenhum script de init conseguia corrigir
- Arranque em paralelo. A ordenação por nome de ficheiro estabelece uma ordem total entre todos os serviços da máquina, por isso o boot demora o mesmo que a soma de todas as partes.
- Prontidão. Um script de arranque termina quando cria o processo do daemon, e não quando o daemon já pode responder a pedidos. Por isso, o script seguinte muitas vezes arranca demasiado cedo.
- Supervisão. Um daemon faz fork duas vezes e o processo pai termina. Isto desliga-o do terminal e faz com que seja adotado pelo PID 1. O init deteta a saída de um processo filho, mas não tem uma ligação fiável ao processo que continuou em execução.
- Arranque a pedido. O inetd (o super-servidor da Internet) podia iniciar um daemon quando chegava uma ligação, mas era um sistema separado, com o seu próprio ficheiro de configuração. Também não fazia nada para ordenar os restantes serviços durante o boot.
- Controlo de recursos. Nenhum elemento de um script de init conseguia limitar a memória de um serviço ou a sua quota de CPU. O
ulimitaplicava-se a um único processo e oniceapenas afetava o scheduler. Assim, um processo filho descontrolado de um serviço parecia qualquer outro processo no sistema.
A falha de supervisão era a que mais afetava o trabalho diário. O ficheiro PID era a solução alternativa: o daemon escrevia o seu ID de processo em /run/nginx.pid e a função de paragem lia novamente esse ficheiro. Se o daemon fosse terminado à força, o ficheiro permanecia. O kernel reutilizava então esse número para outro processo, e o start-stop-daemon --stop --pidfile enviava um sinal para o processo que entretanto o tinha recebido. Um ficheiro PID obsoleto é a forma como um script de init termina o processo errado.
o launchd resolveu primeiro o problema dos sockets
A Apple lançou o launchd no Mac OS X 10.4, em 2005, escrito por Dave Zarzycki. Um processo substituiu o init, o rc, o xinetd, o crond e o watchdogd.
A ideia que vale a pena copiar é a ativação por socket. O launchd cria primeiro todos os sockets de escuta e só depois inicia os daemons. Um cliente que se liga a um daemon que ainda não arrancou não recebe uma recusa de ligação, porque o kernel mantém a ligação na fila de pendências desse socket até o daemon chamar accept(). A ordem entre dois daemons deixa de ser algo que um administrador tenha de declarar. O socket trata disso.
O launchd foi desenvolvido sobre o Mach IPC (comunicação entre processos), que faz parte do kernel XNU da Apple e não tem equivalente no Linux. Nunca foi realista portar o código. A ideia, no entanto, acabou por ser adotada.
O Upstart transformou os eventos na unidade de trabalho
O Upstart da Canonical, escrito por Scott James Remnant, foi lançado no Ubuntu 6.10 em outubro de 2006. O Fedora 9 até ao Fedora 14 utilizou-o, tal como o RHEL 6 e o Chrome OS. Substituiu o runlevel por um evento, e um job indicava quais os eventos que o deviam iniciar e parar.
# /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/exampledSurgiram dois problemas à medida que aumentava o número de jobs. O primeiro está relacionado com a direção da dependência. Um job diz "inicia-me quando isto acontecer", pelo que a informação sobre o que depende de quê fica no ficheiro errado: um serviço sabe do que precisa, mas não pode saber quem irá precisar dele no próximo ano. Adicionar um serviço muitas vezes significava editar um job existente para que emitisse um novo evento.
O segundo está relacionado com o acompanhamento. O Upstart acompanhava um daemon que fazia fork contando chamadas fork() com ptrace, configuradas como expect fork ou expect daemon. Se o número de forks estivesse errado, o Upstart supervisionava um processo que já tinha terminado ou aguardava um fork que já tinha ocorrido. O sintoma era initctl start ficar bloqueado sem apresentar um erro, e o ficheiro do job não fornecia qualquer forma de explicar o motivo.
O Upstart também exigia que os contribuidores assinassem o acordo de contribuição da Canonical. Isso não era uma falha de engenharia, mas influenciou quem trabalhava no projeto.
Repensando o PID 1, abril de 2010
Em 30 de abril de 2010, Lennart Poettering publicou um texto chamado "Repensando o PID 1". Kay Sievers trabalhou com ele no projeto. O argumento tinha quatro partes.
- Iniciar menos serviços. Muitos serviços podem esperar até que algo realmente os solicite.
- Deixar de declarar a ordem quando um socket pode indicá-la. Abrir todos os sockets numa única passagem e iniciar tudo ao mesmo tempo.
- Controlar processos com grupos de controlo em vez de ficheiros PID.
- Descrever um serviço num ficheiro declarativo, para que uma única descrição funcione em todas as distribuições.
A primeira versão foi lançada nesse ano. O Fedora 14 disponibilizou o systemd como opção em novembro de 2010, e o Fedora 15 tornou-o o padrão em maio de 2011.
Por que os cgroups tornaram a supervisão confiável
Um cgroup (grupo de controle) é um recurso do kernel para agrupar processos, incorporado ao Linux 2.6.24 em 2008. O systemd coloca cada serviço no seu próprio cgroup. Um processo filho herda o cgroup do processo pai, e um processo sem privilégios não pode sair dele. Fazer fork duas vezes, portanto, não oculta nada: o PID 1 mantém o conjunto exato de processos que pertencem a uma unidade, a qualquer momento. Parar um serviço significa terminar todos os processos do seu cgroup, que é o que o comportamento padrão KillMode=control-group faz.
systemctl status exibe esse grupo:
● 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"Esse bloco é a resposta completa para o ficheiro PID obsoleto. Não existe ficheiro que possa ficar obsoleto, porque a lista faz parte do estado mantido pelo kernel.
A mesma árvore também contém limites, porque os cgroups foram criados para contabilização antes de serem usados para rastreamento. MemoryMax=, CPUQuota= e TasksMax= ocupam uma linha cada. Definir um limite rígido de memória e CPU para um serviço é hoje uma alteração complementar, e em 2009 era um patch para um script de shell que ninguém escreveu.
Por que todas as distribuições mudaram entre 2011 e 2015
- Fedora 15, maio de 2011.
- openSUSE 12.1, novembro de 2011.
- Mageia 2, maio de 2012.
- Arch Linux, padrão para novas instalações a partir de outubro de 2012.
- RHEL 7, junho de 2014.
- SLES 12, outubro de 2014.
- Debian 8, abril de 2015.
- Ubuntu 15.04, abril de 2015.
Os motivos eram, em grande parte, pouco interessantes. Por isso, a mudança foi rápida.
- O mesmo ficheiro de unidade funciona em todas as distribuições. Por isso, os projetos upstream começaram a distribuir um ficheiro
.service, e as distribuições deixaram de manter um script de shell por pacote e por versão. - O acompanhamento das sessões gráficas passou para
systemd-loginddepois de o ConsoleKit deixar de ser mantido por volta de 2012. O GNOME precisava do logind. Por isso, uma distribuição sem systemd tinha de encontrar uma alternativa. Essa alternativa,elogind, é o logind do systemd extraído e mantido separadamente. - O udev, o gestor de dispositivos, foi integrado na árvore de código-fonte do systemd em abril de 2012. As distribuições que distribuíam o udev passaram a acompanhar o repositório do systemd. Em resposta, o Gentoo criou um fork do
eudev. - Os contentores tornaram mais importante acompanhar os processos de forma fiável e aplicar limites por serviço, porque ambos são recursos dos cgroups. A questão de qual supervisor controla um processo de contentor continua relevante sempre que faz uma stack Docker Compose voltar a arrancar depois de um reboot.
A decisão do Debian foi a mais polémica. O Technical Committee votou em fevereiro de 2014. A votação terminou empatada, e o presidente, Bdale Garbee, deu o voto decisivo a favor do systemd. Dias depois, o Ubuntu anunciou que seguiria o Debian em vez de continuar com o Upstart. Um grupo de developers do Debian criou a distribuição Devuan a partir de um fork em novembro de 2014 e lançou o Devuan 1.0 em maio de 2017.
As objeções, apresentadas de forma justa
Âmbito. Um único projeto agora fornece o PID 1, o daemon de logging, a gestão de sessões de login, o gestor de dispositivos, um daemon de configuração de rede, um resolvedor DNS (domain name system), um cliente NTP (network time protocol), um executor de contentores e um boot loader. A defesa habitual, de que estes são binários separados que não é obrigatório instalar, é verdadeira, mas não responde à objeção. Quando um desktop precisa do logind e o logind é separado da árvore do systemd, a escolha deixa de ser livre. Era isso que o acoplamento significava no argumento, e foi o que aconteceu.
O journal binário. O journald escreve num formato binário indexado em vez de texto simples. Obtém funcionalidades que o texto nunca ofereceu: filtragem por unidade e prioridade, campos estruturados e metadados que o programa emissor não pode falsificar, porque o journald regista a unidade e o cgroup por si próprio. journalctl -u nginx -p err --since "-1h" substitui um grep por uma expressão regular de data. O custo também é real. Numa máquina que não arranca, não é possível ler o log com less a partir de uma rescue shell. Aponte o journalctl para o disco montado:
sudo journalctl --directory /mnt/var/log/journal --boot -1 --priority errExiste aqui uma segunda armadilha que apanha as pessoas pelo menos uma vez. O journald mantém os logs em /run/log/journal, que é memória, a menos que /var/log/journal exista. Numa máquina onde esse caminho não existe, journalctl -b -1 não tem nada para mostrar depois de um reboot, exatamente quando seria necessário. Verifique e corrija:
journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journaldjournalctl --disk-usage deve agora indicar journals arquivados em /var/log/journal. Se também quiser texto simples, defina ForwardToSyslog=yes em /etc/systemd/journald.conf e mantenha o rsyslog instalado.
Depuração do boot. Quando uma unidade fica bloqueada, a consola mostra uma linha e nada mais:
[ *** ] A start job is running for Wait for Network to be Configured (1min 32s / no limit)As ferramentas para investigar mais existem: systemctl list-jobs enquanto está bloqueada, systemd-analyze blame e systemd-analyze critical-chain depois, e systemd.log_level=debug na linha de comandos do kernel. A versão justa da crítica é que um init script podia ser lido do princípio ao fim por qualquer pessoa que conhecesse sh, enquanto uma unidade bloqueada exige saber qual de uma dúzia de comandos utilizar. Esse é um custo real. Cada administrador paga-o uma vez, e muitos administradores pagaram-no ao mesmo tempo.
Uma predefinição que muda para todos. O systemd 230, em 2016, alterou a predefinição do logind para que os processos de utilizador restantes fossem terminados no logout. As sessões tmux e screen destacadas terminavam quando acabava a sessão que as tinha iniciado. As distribuições forneceram KillUserProcesses=no em /etc/systemd/logind.conf, e a resposta suportada é loginctl enable-linger <user>. Uma predefinição num projeto mudou um hábito de que milhões de pessoas dependiam. É isso que significa, na prática, ter "demasiado userland num só lugar".
Uma dependência predefinida é uma superfície de segurança. Em março de 2024, o backdoor no xz-utils teve como alvo o sshd no Debian e no Ubuntu. O OpenSSH upstream não faz link com libsystemd. Essas distribuições aplicaram-lhe patches para que o sshd pudesse comunicar a sua prontidão ao systemd, e a libsystemd introduziu a dependência de liblzma, onde estava o backdoor. O próprio protocolo de prontidão consiste num único datagrama enviado para o socket indicado em $NOTIFY_SOCKET, pelo que nunca foi necessária uma biblioteca para o implementar. A resposta do systemd foi carregar as bibliotecas de compressão com dlopen, para que deixassem de ser ligadas por predefinição. Uma classe de bug relacionada mostra a mesma estrutura: em 2017, um valor User= que começava por um dígito era tratado como inválido e a unidade era executada como root em vez de falhar, transformando um erro de digitação numa escalação de privilégios. As versões posteriores recusam iniciar a unidade.
Histórico no seu próprio prompt do systemctl
Cada problema acima agora corresponde a uma diretiva num ficheiro que pode ler.
- O arranque em série passou a ser controlado por
After=eWants=, esystemd-analyze critical-chainmostra o que realmente atrasou o arranque. - A prontidão passou a ser controlada por
Type=notify, onde o serviço escreveREADY=1em$NOTIFY_SOCKETquando já pode atender pedidos.Type=forkingcomPIDFile=ainda existe para daemons antigos, e é o tipo que falha comstart operation timed out. Terminating.quando o ficheiro PID nunca aparece. - A supervisão passou a ser feita pelo cgroup, por isso
Restart=on-failurecomRestartSec=substitui um script wrapper, eStartLimitBurst=impede que um ciclo de falhas seja executado indefinidamente. - O inetd passou a ser uma unidade
.socketcolocada junto da unidade.service. ulimitpassou a serMemoryMax=,CPUQuota=eTasksMax=.- A linha
su - appuser -cnum script de init passou a serUser=,NoNewPrivileges=yeseProtectSystem=strict, pelo que executar um serviço com um utilizador sem privilégios é a configuração padrão de uma unidade, e não trabalho adicional.
[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.targetUma linha desse ficheiro corresponde ao erro que toda a gente comete uma vez. Requires=postgresql.service é um requisito, não uma ordenação: indica que a unidade falha se o Postgres falhar, mas não indica que o Postgres deve arrancar primeiro. Sem After=postgresql.service, ambos arrancam ao mesmo tempo e o seu serviço tenta ligar-se a uma porta onde ainda não existe nenhum processo a escutar. Os dois conceitos são separados de propósito, porque por vezes pretende usar um sem o outro. ProtectSystem=strict monta o sistema de ficheiros como somente leitura para este serviço, razão pela qual StateDirectory= está presente: fornece ao serviço um caminho gravável em /var/lib.
O local mais claro para ver 2005 num servidor de 2026 é o SSH no Ubuntu 24.04, que inclui o systemd 255 em agosto de 2026. ssh.service é ativado por socket por padrão: ssh.socket mantém o socket de escuta, e o sshd arranca quando chega uma ligação. Por isso, Port 2222 em /etc/ssh/sshd_config não tem efeito, porque o sshd não é o processo que abriu a porta. A alteração deve ser feita na unidade do socket.
sudo systemctl edit ssh.socket[Socket]
ListenStream=
ListenStream=2222O ListenStream= vazio limpa o valor herdado da unidade fornecida pelo pacote. Se o deixar de fora, obtém ambas as portas, porque o systemd acrescenta itens a uma lista em vez de a substituir. Em seguida, aplique a alteração e verifique o resultado, mantendo aberta uma segunda sessão SSH durante todo o processo:
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222ss deve apresentar um único socket na porta 2222, pertencente a systemd e não a sshd. Esse é o modelo de funcionamento do launchd, vinte anos depois, no seu VPS. Se preferir o comportamento antigo, sudo systemctl disable --now ssh.socket seguido de sudo systemctl enable --now ssh.service fornece um sshd de execução contínua que volta a ler Port da sua própria configuração.
Os detalhes que encontrará dependem da versão utilizada. Por isso, é útil conhecer a diferença entre uma versão LTS e uma versão interim do Ubuntu antes de planear uma atualização. Em mais de uma máquina, o facto de o ficheiro de unidade ser idêntico em todo o lado é a razão pela qual gerir vários servidores a partir de um único local é agora um problema de configuração, e não de scripts shell. E, quando escreve as suas próprias unidades, o par de serviço e temporizador executa a tarefa que, em 2009, teria dividido entre um script de init e uma linha do cron.
FAQ
Por que as distribuições Linux substituíram o SysV init pelo systemd?
Por 2 razões de engenharia e 1 razão de manutenção. O SysV init ordenava os serviços pelo nome do ficheiro, que representa uma posição e não uma dependência, e perdia o controlo de qualquer daemon que se separasse do processo-pai. Por isso, ficheiros PID obsoletos podiam terminar o processo errado. O systemd resolveu a ordenação com ativação por socket e diretivas de dependência, e resolveu o acompanhamento com grupos de controlo. A razão de manutenção determinou a velocidade: um ficheiro de unidade funciona em todas as distribuições. Assim, os projetos upstream passaram a disponibilizar um ficheiro .service, e os responsáveis pela manutenção das distribuições deixaram de escrever um script shell por pacote. O Fedora 15 mudou em maio de 2011, e o Ubuntu 15.04 foi o último grande resistente, em abril de 2015.
O systemd é um único binário gigante?
Não. A árvore de código-fonte compila muitos programas separados. O PID 1 é o /usr/lib/systemd/systemd, enquanto journald, logind e udevd são processos separados, com os seus próprios binários. Execute ls /usr/lib/systemd/ para os ver no seu sistema. A crítica que permanece está relacionada com o acoplamento das versões, e não com o tamanho do binário: estes programas são lançados em conjunto e partilham interfaces privadas. Por isso, as distribuições tendem a adotá-los como um conjunto, e software como o GNOME passou a esperar especificamente o logind.
Ainda posso executar Linux sem systemd?
Sim. O Devuan disponibiliza sysvinit, o Gentoo usa OpenRC por predefinição, o Void usa runit, o Alpine usa busybox init com OpenRC e o Slackware mantém scripts ao estilo BSD. O custo é o trabalho de compatibilidade. O software de desktop que espera o logind precisa de elogind, que é o logind do systemd mantido como pacote autónomo. Além disso, uma quantidade crescente de software de servidor disponibiliza apenas um ficheiro .service, pelo que terá de escrever e manter o script de arranque manualmente.
Por que o journal é binário em vez de ser um ficheiro de texto simples?
Porque o journald armazena campos estruturados com um índice. Isto permite filtrar por unidade e prioridade, além de guardar metadados que o programa emissor não pode falsificar: o journald regista a unidade, o cgroup e o UID real por conta própria, em vez de confiar na linha do log. O custo é precisar de journalctl para o ler, inclusive a partir de um sistema de recuperação, onde deve indicar o disco montado com journalctl --directory /mnt/var/log/journal. Se também quiser texto, defina ForwardToSyslog=yes em /etc/systemd/journald.conf.
O que substituiu a edição do meu script /etc/init.d?
Ficheiros drop-in. Não edite a unidade em /usr/lib/systemd/system/, porque uma atualização do pacote irá substituí-la. Execute sudo systemctl edit nginx.service, e o systemd criará /etc/systemd/system/nginx.service.d/override.conf, que será combinado com a unidade fornecida pelo pacote. systemctl cat nginx.service mostra o resultado combinado, e systemd-delta lista todas as substituições no sistema. Depois de qualquer edição manual, execute sudo systemctl daemon-reload, caso contrário o comando seguinte irá apresentar Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk.