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

01 - Conceitos (units, targets, sockets, timers)

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

A arquitetura do systemd redefiniu o paradigma de orquestração do espaço de usuário (user space) no Linux ao abandonar o modelo tradicional imperativo — centrado na execução sequencial de scripts shell — em favor de uma abordagem puramente declarativa e orientada a objetos. No núcleo desse modelo está a abstração de Unidades (Units), gerenciadas pelo processo PID 1.

Sob essa perspectiva, o PID 1 opera essencialmente como um compilador e gerenciador de estados em tempo de execução. As configurações armazenadas em disco (arquivos com formato baseado na sintaxe INI) são parseadas e convertidas em estruturas de dados em C na memória do kernel/espaço de usuário. Cada unidade possui um ciclo de vida tipificado, uma máquina de estados finitos bem definida e interfaces expostas via barramento de comunicação IPC (D-Bus).


1. O Conceito Fundamental de Unidade (Unit)

Uma Unidade é a representação abstrata de qualquer recurso que o systemd é capaz de gerenciar, rastrear e orquestrar. Isso transcende a visão clássica de que o sistema de inicialização gerencia apenas "daemons de serviços em background".

As unidades abstraem:

  • Processos de longa duração (.service).
  • Pontos de comunicação de rede e IPC (.socket).
  • Agrupamentos lógicos de estado (.target).
  • Eventos temporais e agendamentos (.timer).
  • Montagens de sistemas de arquivos (.mount e .automount).
  • Dispositivos de hardware reconhecidos pelo kernel e udev (.device).
  • Hierarquias de controle de recursos via cgroups (.slice).
  • Processos externos adotados pelo sistema (.scope).
  • Monitoramento de alterações no sistema de arquivos (.path).
  • Ativação de memória de paginação (.swap).

Cada unidade é identificada universalmente por seu nome e sua extensão (ex: sshd.service, dbus.socket, multi-user.target). A extensão dita o comportamento e a máquina de estados que o PID 1 aplicará àquela unidade específica.

A Máquina de Estados de uma Unidade

Toda unidade dentro do systemd transita por um conjunto estrito de estados rastreáveis na memória:

                  +-------------------+
                  |     inoperative   | (Inexistente na RAM)
                  +-------------------+
                            │
                            ▼ (Carga por demanda / boot)
                  +-------------------+
                  |      loaded       | (Parse do arquivo INI feito)
                  +-------------------+
                            │
            ┌───────────────┴───────────────┐
            ▼                               ▼
  +-------------------+           +-------------------+
  |    activating     |           |   deactivating    |
  +-------------------+           +-------------------+
            │                               │
            ▼                               ▼
  +-------------------+           +-------------------+
  |      active       |           |     inactive      |
  +-------------------+           +-------------------+
            │
            ▼ (Erros de runtime / exit code != 0)
  +-------------------+
  |      failed       |
  +-------------------+

  1. loaded: O arquivo de configuração da unidade foi localizado no caminho de carga (Unit Load Path), parseado com sucesso e compilado na memória.
  2. activating: A unidade está executando seus comandos ou rotinas de pré-inicialização (ex: executando ExecStartPre= ou aguardando resolução de dependências).
  3. active: A unidade alcançou seu estado operacional pleno. Para um .service, significa que o processo está em execução ou enviou a notificação de prontidão (sd_notify). Para um .target, significa que todos os seus nós dependentes foram satisfeitos.
  4. deactivating: A unidade está no processo de encerramento, executando rotinas de desligamento (ExecStop=) ou aguardando encerramento de threads no cgroup.
  5. inactive: A unidade está adormecida, sem processos associados em execução.
  6. failed: A unidade entrou em um estado de erro irrecuperável (ex: o binário principal crashou na inicialização, estourou a cota de reinicializações StartLimitIntervalSec, ou retornou um código de saída não nulo não configurado).

2. Unidades de Alvo (Targets)

As unidades do tipo .target são abstrações lógicas sem comandos de execução direta (não possuem diretivas como ExecStart=). Seu propósito exclusivo é a sincronização e agrupamento de dependências no Grafo Acíclico Dirigido (DAG).

Históricamente, os sistemas SysVinit utilizavam Runlevels numéricos estáticos (0 a 6) para definir o estado do sistema (ex: Runlevel 3 para multiusuário via console, Runlevel 5 para interface gráfica). O systemd substituiu os runlevels por .target units, oferecendo granularidade ilimitada, modularidade e suporte a múltiplos estados ativos simultaneamente.

Estrutura de Agrupamento

Um .target funciona como um "nó de ancoragem". Ao instruir o systemd a alcançar o multi-user.target, o orquestrador expande a árvore de dependências declaradas no arquivo do alvo (e nos diretórios .wants/ e .requires/ associados).

# Exemplo simplificado da estrutura conceitual de um multi-user.target
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target
AllowIsolate=yes

Propriedades Fundamentais dos Targets:

  • Composição Modular: Diferente dos runlevels mutuamente exclusivos do SysVinit, múltiplos targets podem estar ativos simultaneamente. É perfeitamente válido ter multi-user.target, network.target, graphical.target e timers.target no estado active ao mesmo tempo.
  • Isolamento (AllowIsolate=yes): Se a diretiva AllowIsolate=yes estiver presente no arquivo do alvo, o administrador pode chavear o estado do sistema em tempo de execução via systemctl isolate <alvo>.target. O systemd calcula a diferença entre as unidades ativas e as requeridas pelo novo alvo, encerrando todas as unidades que não pertençam à nova árvore de dependência (simulando a troca tradicional de runlevel).
  • Targets Especiais:
  • sysinit.target: Inicialização do espaço de sistema inicial (montagem de sistemas de arquivos vitais, inicialização de swap, carga de regras de segurança).
  • basic.target: Ponto de sincronização onde todos os serviços básicos de infraestrutura, sockets IPC e timers foram iniciados.
  • multi-user.target: Estado de operação do sistema em modo texto com rede e múltiplos usuários.
  • graphical.target: Ponto de consolidação que traciona o ambiente de interface gráfica (Display Manager) sobre o multi-user.target.
  • emergency.target: Modo de recuperação mínimo onde o PID 1 dispara um shell de emergência no console raiz com o sistema de arquivos montado apenas em modo leitura.

3. Unidades de Socket (Socket Activation)

A unidade .socket é a implementação da arquitetura de Ativação por Socket no Linux, inspirada na filosofia do launchd do macOS e evoluindo o antigo conceito do inetd/xinetd.

A premissa da ativação por socket é separar a infraestrutura de comunicação IPC/Rede do ciclo de vida da aplicação que processa esses dados.

A Mecânica da Ativação por Socket

Tradicionalmente, um daemon ao ser iniciado executa a sequência de chamadas de sistema:

formula
$$\text{socket()} \longrightarrow \text{bind()} \longrightarrow \text{listen()} \longrightarrow \text{accept()}$$

Se múltiplos daemons precisarem ser iniciados durante o boot, eles precisam ser encadeados sequencialmente para garantir que os daemons de suporte já estejam escutando nas portas corretas antes que os clientes tentem se conectar.

Com unidades .socket, a responsabilidade das chamadas socket(), bind() e listen() passa a ser do PID 1:

1. Early Boot:
   systemd (PID 1) ──► socket(), bind(), listen() ──► Abre portas e UNIX Sockets

2. Chegada do Primeiro Pacote / Conexão:
   Cliente ──► Conecta no Socket (Gerenciado pelo Kernel / systemd)
   systemd (PID 1) ──► Bloqueia a conexão no buffer do Kernel
   systemd (PID 1) ──► Instancia o app.service associado via fork()/execve()
   systemd (PID 1) ──► Passa o File Descriptor (FD 3) para o app.service

3. Execução:
   app.service ──► herda o FD 3 ──► executa accept() diretamente sem abrir porta

Exemplo de Estrutura de Arquivo .socket:

[Unit]
Description=Sockets de Escuta do Servidor Web
Documentation=man:nginx(8)

[Socket]
ListenStream=80
ListenStream=0.0.0.0:443
ListenDatagram=123
SocketUser=www-data
SocketMode=0660

[Install]
WantedBy=sockets.target

Vantagens Arquiteturais da Ativação por Socket:

  1. Paralelização Agressiva: Todos os sockets do sistema (como /run/dbus/system_bus_socket, portas HTTP, SSH, bancos de dados) são abertos pelo systemd no milissegundo 1 do boot. Todos os serviços correspondentes podem ser disparados simultaneamente em paralelo. Se um serviço A tentar se comunicar com o serviço B via socket, o Kernel retém os dados no buffer do soquete de escuta até que o serviço B termine sua inicialização, sem falhas de conexão ou timeouts do cliente.
  2. Ativação Sob Demanda (Lazy Loading): Serviços que consomem muita RAM podem permanecer inativos (no estado inactive). A unidade .socket permanece escutando na porta. No momento em que o primeiro pacote TCP atinge a porta, o systemd acorda a unidade .service correspondente e transfere o tráfego.
  3. Reinicializações Transparentes: Se a unidade .service associada crashar ou for reiniciada para um upgrade de código, o socket não é fechado. O kernel continua aceitando conexões e mantendo-as na fila de escuta enquanto o daemon é recarregado, eliminando a perda de pacotes durante a janela de manutenção.

4. Unidades de Temporizador (Timers)

As unidades do tipo .timer são a solução moderna do systemd para o agendamento de tarefas temporais, atuando como o substituto nativo dos daemons tradicionais de cron (cron, crond, anacron).

Um arquivo .timer não executa comandos diretamente no seu arquivo; ele é um gatilho de ativação que monitora relógios internos e, ao atingir o parâmetro temporal programado, envia um sinal de ativação para uma unidade .service de mesmo nome (ou definida pela diretiva Unit=).

Tipos de Relógios e Temporizadores

O systemd suporta dois modelos primários de contagem temporal:

1. Temporizadores Monotônicos (Monotonic Timers)

Baseiam-se em relógios monotônicos do kernel (que contam o tempo corrido a partir de um evento fixo e não são afetados por alterações manuais de relógio ou sincronização NTP).

  • OnActiveSec=: Dispara $N$ segundos/minutos após o próprio arquivo .timer ter sido ativado na RAM.
  • OnBootSec=: Dispara $N$ tempo após a inicialização inicial do kernel.
  • OnStartupSec=: Dispara $N$ tempo após o PID 1 ter iniciado o espaço de usuário.
  • OnUnitActiveSec=: Dispara $N$ tempo após a última vez que a unidade .service atrelada transitou para o estado active.
  • OnUnitInactiveSec=: Dispara $N$ tempo após a unidade atrelada ter finalizado e retornado ao estado inactive.

2. Temporizadores de Tempo Real (Realtime / Calendar Timers)

Baseiam-se no relógio de parede (Wall Clock, tempo Unix real). Utilizam uma sintaxe de calendário expressiva através da diretiva OnCalendar=.

[Unit]
Description=Rotina de Backup Noturno do Banco de Dados

[Timer]
# Dispara todos os dias às 02:30:00 AM
OnCalendar=*-*-* 02:30:00
# Mantém o controle se o disparo foi perdido devido ao sistema estar desligado
Persistent=true
# Adiciona um atraso aleatório para evitar picos de I/O sincronizados em servidores virtuais
RandomizedDelaySec=15m

[Install]
WantedBy=timers.target

A Sintaxe da Diretiva OnCalendar=

A expressão de calendário do systemd segue o formato:
DiaDaSemana Ano-Mês-Dia Hora:Minuto:Segundo

Exemplos de expressões sintaticamente válidas:

  • OnCalendar=Fri *-*-* 18:00:00: Toda sexta-feira às 18:00.
  • OnCalendar=*-*-01 00:00:00: No primeiro dia de cada mês, à meia-noite.
  • OnCalendar=Mon..Fri *-*-* 08,12,16:00:00: De segunda a sexta, às 08:00, 12:00 e 16:00.
  • OnCalendar=hourly: Atalho para *-*-* *:00:00 (no início de cada hora).
  • OnCalendar=daily: Atalho para *-*-* 00:00:00 (à meia-noite).

Vantagens Arquiteturais dos Timers sobre o Cron:

  1. Integração Nativa com o Journald: Todos os logs gerados pelas execuções do serviço disparado pelo timer são capturados automaticamente no journalctl, vinculados diretamente à unidade .service executada, eliminando a necessidade de redirecionar saídas para arquivos de texto manuais ou gerenciar envios de e-mail locais via mailx.
  2. Controle Granular de Recursos: Como a tarefa agendada é executada dentro de um .service real, ela herda todas as diretivas de isolamento de cgroups (ex: limitar o uso de CPU com CPUQuota=30% ou RAM com MemoryMax=1G durante a rotina de backup).
  3. Persistência e Atrasos Flexíveis (Persistent=true): Se a máquina estiver desligada no exato momento em que um evento OnCalendar deveria ter sido disparado, a flag Persistent=true instrui o systemd a salvar o estado da última execução em /var/lib/systemd/timers/. No próximo boot, o systemd detecta o disparo perdido e executa a tarefa pendente imediatamente.
  4. Desordem Aleatória de Processos (RandomizedDelaySec=): Em ambientes de infraestrutura em nuvem com milhares de instâncias virtuais rodando no mesmo hipervisor, timers sincronizados para a meia-noite geram tempestades de I/O no armazenamento. O atraso aleatório pulveriza a carga de execução dentro de uma janela definida sem quebrar a lógica de agendamento.

5. Matriz Comparativa entre os Tipos de Unidades Abordados

Tipo de Unidade Sufixo Função Primária Exemplo de Uso Mecanismo de Execução
Service .service Gerenciamento do ciclo de vida de processos e daemons. sshd.service, nginx.service Invoca binários via execve() e monitora o cgroup.
Target .target Ponto de sincronização e agrupamento lógico no DAG. multi-user.target, graphical.target Sem comandos. Agrupa dependências Wants= e Requires=.
Socket .socket Abertura preventiva de pontos de comunicação IPC/Rede. dbus.socket, docker.socket Invoca chamadas de soquete no kernel e passa o File Descriptor.
Timer .timer Agendamento temporal de tarefas efêmeras ou periódicas. logrotate.timer, fstrim.timer Monitora timers do kernel e dispara um .service associado.