SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-27

A história do systemd e por que ele venceu

Entenda o que faltava ao SysV init, o que Upstart e launchd tentaram e por que as distribuições migraram para o systemd em quatro anos.

Por que o systemd prevaleceu

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 inicialização que o Linux herdou do Unix da AT&T) não tinha como descrever de que depende um serviço nem como saber quais 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 utilizar: grupos de controlo para monitorizar processos e sockets de escuta pré-abertos para ordenar o arranque. O restante da história mostra como essas duas soluções se espalharam pelo restante do userland, onde começaram as objeções. E várias dessas objeções estavam corretas.

O que o SysV init fazia efetivamente

Num sistema SysV, o PID 1 (ID de processo 1, o primeiro processo iniciado pelo kernel) lia /etc/inittab, escolhia um runlevel e executava os scripts desse runlevel. Os scripts estavam em /etc/init.d/. As ligações simbólicas em /etc/rc3.d/ determinavam quais 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
    ;;
esac

O 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, por isso nada pode verificar essa relação e nenhum sistema pode executar em segurança dois scripts sem relação ao mesmo tempo sem que alguém determine que isso é seguro.

O programa rc executava cada script sequencialmente e esperava que terminasse. Um script que ficasse bloqueado durante trinta segundos à espera de um endereço de rede bloqueava todo o arranque 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 este problema internamente. Em 2011, o Debian 6.0 tornou insserv a predefinição: este lia Required-Start de cada script, criava um grafo e renumerava as ligações simbólicas. Assim, o Debian podia executar scripts independentes ao mesmo tempo com startpar. Isso ajudou, mas não resolveu o problema mais profundo. A dependência continuava a basear-se na conclusão de um script. O retorno 0 de S20nginx significa que uma função de shell terminou com sucesso. Não significa que o nginx está a aceitar ligações.

As cinco coisas que nenhum script de init conseguia corrigir

  • Arranque 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 tanto como a soma de todas as partes.
  • Prontidão. Um script de arranque termina quando cria o processo do daemon, não quando o daemon já consegue responder a um pedido. Por isso, o script seguinte começa muitas vezes demasiado cedo.
  • Supervisão. Um daemon cria dois processos e o processo pai termina. Isto desliga o daemon do terminal e faz com que o PID 1 passe a ser o seu processo pai. 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. Além disso, não fazia nada para ordenar os restantes serviços durante o boot.
  • Controlo de recursos. Um script de init não conseguia limitar a memória de um serviço nem a sua utilização do CPU. ulimit aplicava-se a um único processo e nice apenas alterava o escalonador, por isso um processo filho descontrolado de um serviço parecia um processo como qualquer outro no sistema.

A falha na supervisão era o problema mais frequente no dia a dia. O ficheiro PID era a solução alternativa: o daemon escrevia o seu identificador 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 no sistema. O kernel reutilizava então esse número para outro processo, e start-stop-daemon --stop --pidfile enviava um sinal para o processo que entretanto passara a utilizá-lo. 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 iniciou não recebe uma recusa de ligação, porque o kernel mantém a ligação na fila de backlog desse socket até o daemon chamar accept(). A ordem entre dois daemons deixa de ser algo que um administrador declara manualmente. O socket trata disso.

O launchd foi criado sobre o Mach IPC (comunicação entre processos), que pertence ao kernel XNU da Apple e não tem equivalente no Linux. Fazer o porting do código nunca foi uma opção realista. Ainda assim, a ideia foi adotada.

O Upstart tornou os eventos a 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 usou-o, tal como o RHEL 6 e o Chrome OS. Substituiu o runlevel por um evento, e um job indicava quais eventos o deveriam 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/exampled

Surgiram dois problemas à medida que o número de jobs aumentou. O primeiro é a direção. Um job diz "inicia-me quando isto acontecer", por isso 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 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 é 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 esperava por um fork que já tinha ocorrido. O sintoma era initctl start ficar bloqueado sem erro, e o ficheiro do job não fornecia nenhuma forma de explicar isso.

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 aguardar até que algo realmente os solicite.
  • Evitar declarar uma ordem quando um socket pode indicá-la. Abrir todos os sockets numa única passagem e iniciar tudo ao mesmo tempo.
  • Controlar os processos com grupos de controlo em vez de ficheiros PID.
  • Descrever um serviço num ficheiro declarativo, para que uma 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 a opção predefinida em maio de 2011.

Por que os cgroups tornaram a supervisão confiável

Um cgroup (grupo de controlo) é uma funcionalidade do kernel para agrupar processos, integrada no 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. Por isso, fazer double forking não esconde nada: o PID 1 mantém, a todo o momento, o conjunto exato de processos que pertence a uma unidade. Parar um serviço significa terminar todos os processos no respetivo cgroup, que é o que o comportamento predefinido KillMode=control-group faz.

systemctl status apresenta 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 nenhum ficheiro que possa ficar obsoleto, porque a lista corresponde ao estado mantido pelo kernel.

A mesma árvore também transporta limites, porque os cgroups foram criados para contabilidade 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 consiste hoje num ficheiro drop-in; em 2009, seria um patch para um shell script 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.

A data do RHEL 7 prolongou-se mais do que as outras porque o CentOS 7 a recompilou. Foi nessa versão que a maioria dos administradores contactou pela primeira vez com um ficheiro de unidade, numa etapa de uma história mais longa que vai do Red Hat Linux ao CentOS, Rocky e AlmaLinux.

As razões eram sobretudo pouco interessantes, por isso a mudança foi rápida.

  • Um ficheiro de unidade funciona em todas as distribuições. Por isso, os projetos upstream começaram a incluir um ficheiro .service, e as distribuições deixaram de manter um script shell por pacote e por versão.
  • O acompanhamento das sessões de desktop passou para systemd-logind depois 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 incluíam o udev passaram a acompanhar o repositório do systemd. Como resposta, o Gentoo criou um fork de 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 a ser relevante sempre que faz uma stack do 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 um fork da distribuição, chamado Devuan, 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 fornece agora o PID 1, o daemon de logs, a gestão das 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 é necessário instalar, é verdadeira, mas não responde à objeção. Quando um desktop precisa do logind e o logind é disponibilizado fora da árvore do systemd, a escolha deixa de ser livre. Era esse o significado de acoplamento no argumento, e foi isso que aconteceu.

O journal binário. O journald grava um 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 forjar, porque o journald regista a unidade e o cgroup. 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 pode 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 err

Existe 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 este diretório não existe, journalctl -b -1 não tem nada para mostrar depois de um reboot, exatamente no momento em que precisava dos logs. Verifique e corrija:

journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

journalctl --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.

Capacidade de 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 dos vários comandos usar. 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 utilizadores 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 incluíram KillUserProcesses=no em /etc/systemd/logind.conf, e a resposta suportada é loginctl enable-linger <user>. Uma predefinição num projeto alterou um hábito de que milhões de pessoas dependiam. É isso que significa, na prática, haver "demasiado userland num único lugar".

Uma dependência predefinida é uma superfície de segurança. Em março de 2024, o backdoor no xz-utils tinha 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 que estava pronto ao systemd, e a libsystemd introduziu a liblzma, onde estava o backdoor. O próprio protocolo de prontidão é um único datagrama enviado para o socket indicado em $NOTIFY_SOCKET, pelo que nunca foi necessária uma biblioteca para isso. A resposta do systemd foi carregar as bibliotecas de compressão com dlopen, para que deixassem de ser ligadas por predefinição. Uma classe relacionada de bug tem a mesma forma: 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. Assim, um erro de digitação transformava-se numa elevação de privilégios. As versões posteriores recusam iniciar a unidade.

Histórico no seu próprio prompt do systemctl

Todos os problemas acima são agora uma diretiva num ficheiro que pode consultar.

  • O arranque sequencial tornou-se After= e Wants=, e systemd-analyze critical-chain mostra o que efetivamente bloqueou o arranque.
  • A prontidão tornou-se Type=notify, onde o serviço escreve READY=1 em $NOTIFY_SOCKET quando pode aceitar pedidos. Type=forking com PIDFile= continua disponível para daemons antigos, e é o tipo que falha com start operation timed out. Terminating. quando o ficheiro PID nunca aparece. Escolher o tipo errado faz com que uma unidade comunique o estado active enquanto o daemon que iniciou já terminou. Por isso, vale a pena saber qual Type= corresponde à forma como o seu daemon efetivamente inicia antes de escrever essa linha.
  • A supervisão passou a ser feita pelo cgroup. Assim, Restart=on-failure com RestartSec= substitui um script wrapper, e StartLimitBurst= impede que um ciclo de falhas seja executado indefinidamente.
  • O inetd passou a ser uma unidade .socket ao lado da unidade .service.
  • ulimit tornou-se MemoryMax=, CPUQuota= e TasksMax=.
  • A linha su - appuser -c num script init tornou-se User=, NoNewPrivileges=yes e ProtectSystem=strict. Assim, executar um serviço como um utilizador sem privilégios é a configuração padrão de uma unidade, e não uma tarefa 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.target

Uma linha nesse ficheiro contém o erro que todos cometem uma vez. Requires=postgresql.service é um requisito, não uma regra de ordenação: indica que a sua unidade falha se o Postgres falhar, mas não indica que o Postgres deve iniciar primeiro. Sem After=postgresql.service, ambos iniciam ao mesmo tempo, e o seu serviço tenta ligar-se a uma porta onde ainda não existe nenhum processo à escuta. Os dois conceitos são separados de propósito, porque por vezes é necessário usar um sem o outro. ProtectSystem=strict monta o sistema de ficheiros como só de leitura para este serviço. Por isso, StateDirectory= existe: fornece ao serviço um caminho com permissões de escrita dentro de /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 predefinição: ssh.socket mantém o socket à escuta, e o sshd inicia quando chega uma ligação. Assim, 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=2222

O ListenStream= vazio limpa o valor herdado da unidade fornecida pelo pacote. Se o omitir, obtém ambas as portas, porque o systemd acrescenta valores a uma lista em vez de os substituir. Em seguida, aplique a alteração e confirme-a, mantendo uma segunda sessão SSH aberta durante todo o processo:

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222

ss deve apresentar um 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, vale a pena conhecer a diferença entre uma versão LTS e uma versão interim do Ubuntu antes de planear uma atualização. Em mais do que uma máquina, o facto de um 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 scripting de shell. Ao escrever as suas próprias unidades, o par de serviço e timer desempenha a tarefa que, em 2009, teria dividido entre um script init e uma linha do cron.

FAQ

Por que as distribuições Linux substituíram o SysV init pelo systemd?

Por dois motivos de engenharia e um motivo 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 deixava de acompanhar qualquer daemon que criasse um processo filho e 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. O motivo de manutenção determinou a velocidade da mudança: um ficheiro de unidade funciona em todas as distribuições, por isso os projetos upstream passaram a fornecer um ficheiro .service e os responsáveis pela manutenção das distribuições deixaram de escrever um script de 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 vários programas separados. O PID 1 é /usr/lib/systemd/systemd, enquanto journald, logind e udevd são processos separados, cada um com o seu próprio binário; execute ls /usr/lib/systemd/ para os ver no seu sistema. A crítica que permanece está relacionada com o acoplamento entre versões, 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 tratá-los como um conjunto, e software como o GNOME passou a esperar especificamente o logind.

Ainda posso executar Linux sem systemd?

Sim. O Devuan fornece sysvinit, o Gentoo usa o OpenRC por predefinição, o Void usa runit, o Alpine usa o busybox init com OpenRC, e o Slackware mantém scripts ao estilo BSD. O custo é o trabalho de compatibilidade. Software de desktop que espera o logind precisa de elogind, que é o logind do systemd mantido como um pacote autónomo. Além disso, uma quantidade crescente de software de servidor fornece apenas um ficheiro .service, pelo que terá de escrever e manter o script de arranque manualmente.

Por que motivo o journal é binário em vez de ser um ficheiro de texto simples?

Porque o journald armazena campos estruturados com um índice. Isso permite filtrar por unidade e prioridade, além de fornecer metadados que o programa que envia a mensagem não consegue falsificar. O journald regista a unidade, o cgroup e o UID real por conta própria, em vez de confiar na linha do log. A desvantagem é 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 a substitui. Execute sudo systemctl edit nginx.service e o systemd criará /etc/systemd/system/nginx.service.d/override.conf, que será aplicado sobre 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 apresentará Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk.

#systemd#linux#init#sysvinit#history