Pular para o conteúdo

Bootloaders

Um bootloader é o software responsável pela lacuna entre o firmware cedendo o controle e um kernel estar em um estado em que consegue rodar: localizar uma imagem de kernel no disco, carregá-la na memória, realizar quaisquer transições de modo de CPU que o kernel espera que já tenham acontecido, e repassar informação (um mapa de memória, argumentos de boot, localizações de módulos) de que o kernel precisa mas não tem como reunir sozinho. A decisão de escrever um, ou depender de um já existente, está entre as primeiras escolhas práticas que um projeto de kernel hobby faz.

O GRUB2 é o bootloader mais amplamente usado na comunidade de desenvolvimento de SO hobby, implementando tanto boot BIOS legado quanto UEFI, Multiboot (ambas as versões), e drivers de sistema de arquivos capazes de ler um kernel diretamente de sistemas de arquivos comuns sem que o kernel precise de nada dessa lógica por conta própria. Um kernel visando o GRUB precisa apenas de um header Multiboot corretamente posicionado e um build que produza um binário que o GRUB consiga carregar, sem código de boot sector, sem driver de disco e sem parser de sistema de arquivos, ao custo de depender de uma ferramenta externa e de propósito geral cujo comportamento e padrões o kernel não controla. O Limine, uma alternativa mais nova, implementa de forma semelhante protocolos da família Multiboot (e seu próprio protocolo de boot Limine, mais moderno) com menos bagagem legada e configuração geralmente mais simples, e se tornou comum em projetos mais novos especificamente por esse motivo.

Escrever um bootloader do zero significa implementar, no mínimo, o código de boot sector mais inicial que o próprio firmware carrega (para BIOS, um MBR de 512 bytes atendendo aos requisitos de carregamento e assinatura descritos em BIOS vs. UEFI), uma forma de carregar mais código do disco (comumente dependendo dos serviços de disco int 0x13 da BIOS inicialmente, já que implementar um driver de disco real nesse estágio é, por si só, um empreendimento substancial), e as transições de modo que o kernel de outra forma precisaria realizar por conta própria. Esse caminho oferece controle completo e, para alguns, é ele mesmo o objetivo do exercício (entender cada passo do processo de boot em primeira mão, em vez de tratá-lo como infraestrutura já resolvida), mas duplica trabalho que um bootloader já existente e bem testado já faz corretamente, e é consideravelmente mais propenso a erros de acertar durante o estágio mais inicial e menos diagnosticável da execução de um kernel.

O limite de 512 bytes de um boot sector raramente é suficiente para implementar tudo que um bootloader precisa, razão pela qual bootloaders, sejam o GRUB ou um escrito à mão, são convencionalmente divididos em estágios. Um estágio 1 mínimo, restrito ao próprio boot sector, faz o menor trabalho possível: o suficiente para localizar e carregar um estágio 2 maior a partir de uma localização conhecida no disco, e saltar para ele. O estágio 2, não mais restrito pelo limite de 512 bytes, realiza o trabalho mais substancial (ler um kernel de um sistema de arquivos, analisar seu header Multiboot, reunir o mapa de memória, e realizar transições de modo) antes de finalmente passar o controle para o próprio kernel. Essa estrutura em estágios é exatamente o que o próprio GRUB implementa internamente, abstraída de um desenvolvedor de kernel que escolhe depender dele em vez de escrevê-lo.

O que um bootloader precisa fazer, no fim das contas

Seção intitulada “O que um bootloader precisa fazer, no fim das contas”

Independentemente de ser escrito à mão ou uma ferramenta já existente, as responsabilidades de todo bootloader convergem para a mesma lista curta: localizar uma imagem de kernel (em uma localização bruta de disco, ou dentro de um sistema de arquivos), carregá-la na memória física no endereço que ela espera, reunir qualquer informação de ambiente que o protocolo de carregamento do kernel exija (um mapa de memória sendo o exemplo mais universal), realizar qualquer transição de modo de CPU que o protocolo garanta em nome do kernel, e transferir o controle com a CPU e quaisquer estruturas de informação no estado que aquele protocolo promete. Um kernel que escolhe escrever seu próprio bootloader está, na prática, escolhendo implementar essa lista por conta própria, em vez de delegá-la; um kernel que escolhe GRUB ou Limine está escolhendo confiar que a ferramenta já a implementa corretamente, em troca de um subsistema grande e crítico para o boot a menos para escrever e depurar do zero.

Um kernel que espera eventualmente suportar tanto um caminho de boot escrito à mão quanto um bootloader já existente se beneficia ao projetar seu ponto de entrada em torno de uma estrutura de informação comum (uma representação interna normalizada do mapa de memória e parâmetros de boot, populada de forma diferente dependendo de qual caminho de fato carregou o kernel) em vez de escrever código de inicialização de kernel que assume diretamente o formato de dados de um carregador específico, o que de outra forma precisa ser duplicado ou totalmente refeito se o caminho de carregamento algum dia mudar.

  1. ^ GNU, GRUB Manual: documenta a própria arquitetura interna em estágios do GRUB e os protocolos de boot suportados.
  • Multiboot: o protocolo que a maioria dos bootloaders já existentes, GRUB incluído, usa para passar o controle a um kernel.
  • The Boot Process: a sequência completa da qual um bootloader é um estágio.
  • Escrevendo um Bootloader UEFI: um caminho inteiramente diferente, carregado e rodado diretamente pelo firmware sem handoff em estágios algum.