Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

04 - Sistema de Inicialização (Init System)

video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

NOTA DA PASTA: Este diretório documenta a espinha dorsal do Espaço de Usuário (User Space). Enquanto o subsistema anterior (Kernel) governa o hardware e o Ring 0, o Sistema de Inicialização (Init System) é o primeiro software executado em Ring 3. O foco desta pasta abrange desde a semântica estrita do processo ancestral (PID 1), passando pelo legado imperativo do SysVinit, até a complexa arquitetura baseada em grafos de dependência e ativação por sockets do systemd, incluindo a orquestração de daemons e a preparação do ambiente interativo de rede e terminal.


1. A Fronteira Arquitetural: O Hand-off do Kernel

A transição da inicialização do núcleo para a execução utilitária do sistema operacional é marcada por um momento singular de inflexão arquitetural. Ao final da execução da função C start_kernel() e de suas rotinas subsequentes (como a liberação da memória de inicialização - init memory), o kernel Linux alcança um estado de repouso estrutural. O hardware está mapeado, a MMU está ativa, o escalonador (scheduler) está tickando, mas não há absolutamente nada "útil" para o usuário acontecendo.

Para dar vida à máquina, o kernel, através da kernel thread inicial, executa uma chamada de sistema interna kernel_execve(). O alvo desta chamada é um arquivo binário residente no sistema de arquivos raiz recém-montado (frequentemente com a assistência do initramfs e da operação switch_root). Historicamente, o kernel busca este binário em uma matriz estrita de caminhos: /sbin/init, /etc/init, /bin/init e, como último recurso de falha, /bin/sh.

Ao executar este binário, a thread do kernel sofre uma metamorfose definitiva: ela abandona o nível de privilégio Ring 0, assume o espaço de endereçamento de Ring 3 e consolida-se como o Primeiro Processo do Espaço de Usuário. A partir desse exato ciclo de processador, o kernel passa a atuar exclusivamente de forma reativa — respondendo a interrupções de hardware ou solicitações de syscalls —, enquanto a orquestração ativa da máquina (montagem de discos secundários, inicialização de rede, serviços de log e terminais) é inteiramente delegada a este único processo executável: o Init System.

2. A Semântica do PID 1 e a Árvore de Processos

O processo init é instanciado com o Identificador de Processo 1 (PID 1). No padrão POSIX e na arquitetura Unix-like, o PID 1 não é apenas um número de ordem; ele confere propriedades mágicas e responsabilidades excepcionais, inseridas diretamente no código-fonte do kernel.

A Imunidade a Sinais (Signal Immunity)

Para garantir a sobrevivência do sistema, o kernel concede ao PID 1 imunidade absoluta contra o envio de sinais não tratados. Se o usuário root executar um comando catastrófico como kill -9 1 (que envia o sinal imperdoável SIGKILL), o kernel simplesmente intercepta e descarta a chamada. O processo init só processará sinais para os quais ele tenha explicitamente registrado um handler de tratamento no seu próprio código (como interceptar um SIGTERM para iniciar o desligamento ordenado da máquina). Se o PID 1 morrer (seja por falha de segmentação, bug ou invocação maliciosa interna), o kernel considera que a árvore de espaço de usuário foi decapitada e invoca um Kernel Panic imediato (Kernel panic - not syncing: Attempted to kill init!).

O Problema do Ceifador (The Reaper Problem)

A responsabilidade mais vital do PID 1 em tempo de execução é o papel de "Ceifador de Zumbis" (Zombie Reaper). Quando um processo pai cria um processo filho (fork), e esse filho termina a execução (exit), o kernel libera a memória do filho, mas mantém sua entrada na tabela de processos (estado Zombie ou Z) para que o pai possa consultar o código de saída (exit status) do filho através da chamada wait().
Se o processo pai falhar ou terminar abruptamente antes de chamar wait(), o processo filho zumbi fica "órfão".
Por definição arquitetural do Linux, o kernel realiza um reparenting (readoção) automático: todo processo órfão é imediatamente transferido para a guarda do PID 1. Portanto, o sistema de init deve rodar um loop infinito que continuamente execute a chamada de sistema wait() (ou waitpid()) para recolher os códigos de saída de processos órfãos moribundos, expurgando-os da tabela do kernel. Se o init falhar nesta tarefa, a tabela de processos eventualmente esgotará (PID Exhaustion), e o sistema não poderá instanciar novos programas, exigindo um reinício físico.

3. As Responsabilidades de Orquestração Inicial

A mera existência do PID 1 não torna a máquina útil. O binário init age como o arquiteto do sistema e deve seguir uma "Máquina de Estados" para levar o sistema de um disco mudo para um servidor acessível na rede. As responsabilidades em fases são tipicamente:

  1. Montagem do Sistema de Arquivos Secundário: O kernel só montou o / (frequentemente em modo apenas leitura). O init deve ler a tabela /etc/fstab, verificar (com fsck) e montar montagens vitais como /usr, /var, /home, pseudo-filesystems remanescentes e ativar o arquivo/partição de swap.
  2. Inicialização de Infraestrutura Base: Ativação do daemon de coleta de logs do sistema (historicamente o syslogd, modernamente o systemd-journald), essencial para que serviços subsequentes possam relatar erros de inicialização. Ativação do barramento de comunicação IPC (como o dbus-daemon).
  3. Configuração de Rede e Segurança: Invocação de utilitários como iptables/nftables para selar as portas da máquina antes que as interfaces de rede sejam subidas, seguido da configuração de rotas e IPs via gerenciadores de rede estáticos ou dinâmicos.
  4. Inicialização de Daemons (Serviços): Orquestração sequencial ou paralela de servidores web (Nginx), bancos de dados (PostgreSQL) e servidores de acesso remoto (SSHD).
  5. Provisionamento de TTYs: Para uso local, o init invoca binários como o getty em terminais virtuais (/dev/tty1), que limparão a tela e apresentarão o prompt de login:. Em sistemas gráficos, o init dispara o Display Manager (ex: GDM, LightDM) na porta de vídeo principal.

4. Evolução do Design: Do Sequencial ao Concorrente

A engenharia em torno do Init System sofreu uma das fraturas arquiteturais mais intensas da história do Linux, dividindo-se entre o paradigma clássico e os supervisores modernos baseados em grafos de dependência.

A Era Imperativa (SysVinit)

Por décadas, o padrão Unix-like (herdado do System V) baseou-se em uma lógica estritamente imperativa. O init era um programa C simplório cujo único trabalho era ler o arquivo /etc/inittab e executar extensos e complexos scripts Shell (em /etc/init.d/) de forma rigorosamente sequencial (lexicográfica).

  • Limitações: Por ser sequencial, o tempo de inicialização era lento (um timeout de rede atrasaria todo o boot). A resolução de dependências dependia de numeração rígida nos nomes dos scripts (ex: S20network antes de S50apache). Pior, a gestão de estado era frágil: para rodar um processo em background (daemonize), o serviço realizava a técnica de double-fork (o pai cria um filho, o filho cria um neto, o filho morre para enganar o terminal e o neto fica rodando). Com isso, o SysVinit perdia o controle e o rastreamento do PID real, não podendo parar processos ou coletar métricas confiavelmente.

A Era Baseada em Eventos e Grafos (systemd e Upstart)

A resposta moderna para as limitações imperativas são sistemas de init como o systemd. O paradigma muda radicalmente:

  • Declaração vs Imperação: Em vez de ditar "como" executar (através de código bash), o administrador declara "o que" o serviço é em um arquivo .service. O init cuida do comportamento complexo internamente em linguagem C compilada.
  • Topological Sort (Paralelismo): O systemd lê todas as unidades, entende as diretivas de dependência (Requires=, After=) e constrói um grafo acíclico dirigido na memória. Ele então dispara todos os serviços que não dependem de nada simultaneamente, maximizando o uso dos núcleos da CPU e minimizando drasticamente o tempo de boot.
  • Ativação por Socket (Socket Activation): Uma inovação crucial emprestada do macOS (launchd). O sistema de init cria o socket de rede (ex: porta 80 para web) imediatamente, mas não inicia o daemon web. Se outro serviço tentar se conectar à porta 80, o kernel enfileira a requisição no buffer e notifica o systemd. Neste momento, sob demanda, o init inicia o daemon (ex: nginx), e passa o descritor de arquivo do socket para ele processar a conexão já existente. Isso remove a dependência de ordem: todos os sockets são criados no milissegundo 1 do boot.
  • Isolamento via Cgroups: O init moderno coloca cada daemon em seu próprio Control Group (recurso interno do kernel). Mesmo que o processo faça milhares de forks, ele nunca escapa de seu cgroup correspondente. Se o administrador pedir para parar o serviço, o init dizimará todas as threads atreladas àquele grupo, eliminando daemons renegados.

A Abordagem Minimalista (runit e s6)

Como contraponto filosófico ao escopo hipertrofiado do systemd, o ecossistema também possui supervisores de processos estritos (usados em Alpine Linux ou infraestruturas em contêineres). O foco não é gerenciar montagens ou sockets, mas simplesmente assegurar o estado do processo: uma árvore de diretórios serve como mapa; para cada diretório com um script executável, o supervisor (ex: runsv) roda o processo em "foreground" constante, interceptando seus fluxos de saída padrão (stdout/stderr) para um sistema de log atrelado e, criticamente, reiniciando o serviço em milissegundos caso ele morra ou crashe.

5. O Fechamento (Shutdown e Halt)

A responsabilidade arquitetural do Init System vai além do nascimento do espaço de usuário; ela estende-se até sua morte inevitável. Quando o encerramento do sistema é solicitado, a máquina de estados deve rodar de ré para garantir a integridade de dados (especialmente evitar que os journals dos discos sejam corrompidos em um power-off sujo).

A rotina de desligamento ditada pelo PID 1 exige a seguinte orquestração macro:

  1. Aviso aos Logados: Emissão de aviso broadcast a todos os terminais virtuais conectados.
  2. Sinalização Elegante (SIGTERM): O init emite o sinal POSIX SIGTERM (15) para absolutamente todos os outros processos na tabela do kernel, dizendo essencialmente: "salvem seu estado em disco, fechem os soquetes, encerrem".
  3. Janela de Tolerância: O init entra num breve sleep temporizado (usualmente 5 a 90 segundos dependendo da arquitetura e configuração), aguardando o colapso gradual da árvore de processos.
  4. A Guilhotina (SIGKILL): Ao fim do timeout, a diplomacia é encerrada. O init envia um sinal de interrupção de hardware virtual (SIGKILL - 9) para toda a tabela (exceto si mesmo). Tudo que estava rodando é destruído pelo kernel instantaneamente, sem aviso, garantindo a liberação de I/O de disco.
  5. Desmontagem Inversa: Liberação de partições de swap e execução de chamadas umount para todos os sistemas de arquivos (ou remontagem forçada da raiz / como apenas-leitura ro caso o umount direto falhe por restrição do executável principal).
  6. Hand-off Final (ACPI Poweroff): Em seu último suspiro arquitetural, o PID 1 invoca a chamada de sistema mágica reboot(LINUX_REBOOT_CMD_POWER_OFF). Com isso, ele abdica formalmente do controle em Ring 3, devolvendo o sistema ao Ring 0, onde o kernel aciona os sinais elétricos na interface ACPI da placa-mãe para desenergizar fisicamente o processador e cortar o fluxo de energia da Fonte de Alimentação ATX, finalizando o ciclo de vida computacional da sessão.