- Explica passo a passo como criar uma “microdistribuição Linux” compilando o kernel Linux manualmente e montando um espaço de usuário mínimo
- Aborda desde o básico o papel do kernel do sistema operacional, os componentes de uma distribuição Linux e a relação entre kernel e espaço de usuário
- Usa como exemplo a arquitetura RISC-V (máquina
riscv64 virt do QEMU), mas os mesmos princípios podem ser aplicados a outras arquiteturas, como x86
- Constrói um ambiente Linux mínimo e executável diretamente, incluindo o processo
init, initramfs e um shell simples escrito em Go
- Por fim, apresenta como usar o projeto
u-root para criar uma microdistribuição realmente útil e conclui como um guia introdutório para entender a estrutura geral de sistemas Linux
O que é o kernel de um sistema operacional
- O kernel é o componente central do sistema operacional responsável por gerenciar recursos de hardware e controlar a execução de programas
- Mesmo em um ambiente de núcleo único, ele oferece funções de gerenciamento de multitarefa que fazem vários programas parecerem executar ao mesmo tempo
- O kernel abstrai o controle dos dispositivos de entrada e saída, para que os aplicativos não precisem lidar diretamente com endereços de hardware ou valores de registradores
- Por exemplo, um programa simplesmente pede para “escrever uma mensagem na saída padrão”, e o kernel cuida da interação com o hardware real
- Ele fornece uma forma de acesso a dados em alto nível por meio da interface de sistema de arquivos
- Um arquivo não é apenas dado em disco, mas funciona como uma interface lógica para comunicação com o kernel
- O kernel fornece isolamento entre processos e um modelo de comunicação, permitindo que cada aplicação rode de forma independente ou cooperativa
- O kernel Linux é open source, pode rodar em várias arquiteturas e é um dos kernels mais usados no mundo
O que é uma distribuição Linux
- Só com o kernel Linux o usuário não consegue executar um navegador web ou aplicativos com GUI; é necessária uma infraestrutura de software em várias camadas sobre o kernel
- Configuração de rede, atribuição de IP, gerenciamento de VPN etc. são responsabilidade de programas de espaço de usuário em camadas superiores, não do kernel
- Portanto, uma distribuição Linux é definida como a combinação de kernel + infraestrutura de espaço de usuário
- A distribuição inclui, sobre as funções básicas oferecidas pelo kernel, pacotes, ferramentas, configurações e processo de inicialização (
init)
- A complexidade varia bastante, indo de configurações mínimas como o Arch Linux até opções mais amigáveis ao usuário como o Ubuntu
Infraestrutura fora do kernel: espaço de usuário e processo init
- Quando o kernel termina o boot, ele executa primeiro o processo
init, de PID 1
- O
init é o ancestral de todos os processos do espaço de usuário e executa em sequência os serviços e ferramentas do sistema
- O conjunto de processos e ferramentas executados pelo
init constitui a parte prática de uma distribuição Linux
- À medida que a distribuição fica mais complexa, ela pode acumular recursos desnecessários e receber críticas por ser “inchada”
- Em contrapartida, ao criar uma microdistribuição customizada, é possível montar um sistema leve com apenas o mínimo necessário
Compilando o kernel Linux para RISC-V
- Em um ambiente
x86, compila-se o kernel para RISC-V usando uma toolchain de cross-compilation
- Baixa-se o código-fonte
linux-6.5.2.tar.xz em kernel.org e executa-se make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
- É possível editar visualmente as configurações do kernel com
menuconfig
- Depois da compilação paralela com
make -j16, é gerado arch/riscv/boot/Image
- No QEMU, o boot é feito com
qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Image
- Nos logs de boot, é possível verificar mensagens como detecção da camada SBI, inicialização da UART e ativação do printk
Primeiro obstáculo: sem sistema de arquivos raiz
- Durante o boot do kernel, ocorre kernel panic com o erro
VFS: Unable to mount root fs
- Causa: não foi fornecido um sistema de arquivos raiz (
initramfs)
- Um sistema de arquivos pode ser montado não só em disco, mas também em RAM (
initramfs)
- O
initramfs é empacotado no formato cpio e, no QEMU, pode ser carregado com a opção -initrd
Montando o initramfs e executando “Hello world”
- O requisito mínimo é a presença do binário
/init
- Escreve-se
init.c e compila-se com linkagem estática (-static)
- O empacotamento é feito com
cpio -o -H newc < file_list.txt > initramfs.cpio
- Ao rodar no QEMU, a mensagem “Hello world” é exibida, mas ao encerrar o
init ocorre novamente um kernel panic
- Solução: adicionar um loop infinito para que o
init não termine
Adicionando um shell simples escrito em Go
- O
init executa /little_shell usando fork e execl
little_shell.go é um shell simples que recebe entrada do usuário e repete os comandos digitados
- A compilação para RISC-V é feita com
GOOS=linux GOARCH=riscv64 go build little_shell.go
- Tanto
init quanto little_shell compartilham a saída pela UART
- A entrada e saída padrão são gerenciadas como descritores de arquivo e herdadas durante o
fork
- Como resultado, forma-se um ambiente Linux básico, em que “Hello from init” e a entrada do shell aparecem de forma intercalada
Resumo do papel do kernel
- Abstração de hardware: programas de usuário conseguem imprimir saída sem conhecer detalhes da UART ou dos dispositivos
- Fornecimento de interfaces de alto nível: acesso a outros binários (
little_shell) por meio do sistema de arquivos
- Isolamento de processos:
init e o shell executam em espaços de memória independentes
- O kernel fornece uma base de execução estável e portável sobre hardware complexo
Definição de sistema operacional
- Há quem considere apenas o kernel como sistema operacional, e há quem considere a distribuição inteira como o sistema operacional
- O importante é entender os limites de papel e a estrutura de interação entre kernel e espaço de usuário
Criando uma microdistribuição realmente útil com u-root
- O projeto u-root fornece um conjunto de ferramentas de espaço de usuário baseado em Go
- O
u-root inclui um bootloader de espaço de usuário e um ambiente de shell executados sobre o kernel Linux
- Após a instalação, o comando
GOOS=linux GOARCH=riscv64 u-root gera automaticamente um initramfs
- O arquivo
/tmp/initramfs.linux_riscv64.cpio pode ser executado no QEMU
- Durante o boot, é exibido o banner “Welcome to u-root!” junto com um prompt básico de shell
- Há suporte a comandos básicos como
ls, pwd e echo, além de autocompletar com tab
Prática de conexão de rede
- Adicionam-se ao QEMU os dispositivos
virtio-net-device e virtio-rng-pci
- Usa-se a opção
-device virtio-net-device,netdev=usernet -netdev user,id=usernet
- Com o
dhclient do u-root, o IP é atribuído automaticamente via DHCP
- Exemplo: atribuição de
10.0.2.15/24 à interface eth0
- Com
wget http://google.com, confirma-se acesso à rede externa e o download de index.html
A importância do gerenciador de pacotes e do init
- Distribuições comuns usam um gerenciador de pacotes para instalar e atualizar software dinamicamente
- Neste exercício, a abordagem é mais próxima de sistemas embarcados, em que é preciso recompilar a imagem inteira
- O
init não é apenas um executor simples de processos, mas um componente central da inicialização de dispositivos, gerenciamento de serviços e controle do boot do sistema
- Pelo código-fonte do
init do u-root, é possível observar vários processos de configuração de dispositivos como /dev
Repositório no GitHub
- Todo o código e os exemplos deste guia estão disponíveis em popovicu/linux-micro-distro
- É possível compilar a imagem
initramfs e reproduzir a prática
1 comentários
Comentários do Hacker News
Há alguns meses venho criando minha própria microdistribuição Linux
O modo de usuário é composto por um único binário estático, com apenas alguns arquivos para dar suporte a contêineres microVM confidenciais
A estrutura do initramfs é especialmente interessante. O processo em que o kernel descompacta o arquivo cpio, entra no tmpfs e executa o /init parece mágica
Também é possível concatenar vários arquivos cpio, cada um com compressão própria, e eles são sobrepostos em sequência
Graças a esse design simples e elegante, aprendi muito escrevendo meu próprio código de desempacotamento
Recentemente, o qemu passou a oferecer suporte a uftrace nas principais arquiteturas
Quando especialistas perguntam “como depurar isso?”, essa é exatamente a resposta
Mais detalhes podem ser vistos nesta thread
Eu também estou tocando um projeto parecido — azathos
Ele inclui um toy init, um shell e alguns utilitários
Coloquei o GNU coreutils para depuração e agora estou focado em desenhar janelas no framebuffer
Esse projeto é muito legal. Me lembra da época em que eu fazia uma “distribuição” baseada em disquete em 98 para criar imagens de PCs com Windows via broadcast UDP
“make bzimage”, erros em scripts de init, reboot infinito… muitas lembranças
É interessante como o jeito de fazer hoje não é tão diferente. Portar isso para o Raspberry Pi parece algo divertido e educativo. Talvez eu mesmo tente
No fim, um amigo gravou o conteúdo do sftp em um CD e isso resolveu, mas naquela época só dava para gravar em 2x
Fico curioso sobre o quão difícil seria executar isso como uma imagem de nuvem (por exemplo, Vultr, DigitalOcean) ou subir uma GUI para rodar o Firefox
Também dá para inicializar com outra distribuição e depois executar seu próprio kernel com kexec para instalar tudo em memória
Para um exemplo real de implementação, dá para consultar o nixos-anywhere
É um trabalho mais simples do que parece
Se existisse uma versão desse projeto voltada para Raspberry Pi, seria realmente muito interessante
Eu estava me perguntando por que alguém faria isso manualmente, em vez de simplesmente explorar Linux com Gentoo
Dá para customizar o espaço do usuário, mas não é ideal para aprender Linux em si
Só o stage3 tarball já é praticamente uma “mini distribuição”
Para aprendizado, isso é realmente ótimo, e se a ideia for terminar rápido, buildroot é uma boa escolha
Aprendi muito com este post. Obrigado por ser um post tão rico em informação