Estou tentando entender como o kernel do linux sabe onde está o rootfs desejado na inicialização.
Li este documento:
https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
Uma parte de interesse diz:
Todos os kernels 2.6 do Linux contêm um arquivo compactado no formato "cpio", que é extraído no rootfs quando o kernel inicializa ... o código mais antigo para localizar e montar uma partição raiz
Nosso kernel é 4.X, mas acho que isso ainda se aplica? Parece que todos os kernels têm um rootfs "cpio" embutido.
E, de fato, como lemos, diz:
O processo de compilação do kernel 2.6 sempre cria um arquivo initramfs no formato cpio compactado e o vincula ao binário do kernel resultante. Por padrão, este arquivo está vazio ... A opção de configuração CONFIG_INITRAMFS_SOURCE ... pode ser usada para especificar uma fonte para o arquivo initramfs
Isso levanta mais algumas questões:
- Portanto, se eu quiser que meus rootfs estejam na RAM, preciso definir
CONFIG_INITRAMFS_SOURCE
para apontar para meus rootfs (presumivelmente no formato cpio).
Mas isso não significa que meu kernel e rootfs agora são inseparáveis? E se eu quiser fazer um pequeno ajuste no RootFS sem reconstruir? E se eu quiser meu rootfs armazenado separado do kernel? Como eu digo ao kernel a localização do meu rootfs?
- Além disso, e se eu quiser que meus rootfs estejam no armazenamento físico (como eMMC, pen drive, etc.) e não na RAM?
Disse anteriormente que:
Se o rootfs não contiver um programa init depois que o arquivo cpio incorporado for extraído para ele, o kernel passará pelo código mais antigo para localizar e montar uma partição raiz
Mas como? Como ele sabe onde localizar o rootfs? Se estiver no eMMC, preciso dizer isso ao kernel de alguma forma, certo?
O bootloader que estou usando é o U-boot. Eu verifiquei as variáveis de ambiente do U-boot para ver se estava de alguma forma passando o local rootfs para o kernel como um argumento de inicialização, mas não parece ser o caso ...
Editar:
Conforme apontado nos comentários, a localização do rootfs é passada para o kernel via boot arg. No meu caso, o u-boot está passando root=/dev/mmcblk0p4 rw
como um argumento de inicialização para o kernel. Então, isso responde a uma das minhas perguntas - você pode passar o local para qualquer rootfs descompactado como um argumento de inicialização.
Ainda não estou claro como, dado alguns rootfs.tar.gz
que são separados do kernel, como dizer ao kernel para descompactar isso na RAM e usá-lo como rootfs. Talvez isso não seja possível e eu só precise usar CONFIG_INITRAMFS_SOURCE
? De qualquer forma, vou ler os documentos 4.X.
Em primeiro lugar, não se assuste com as referências a "2.6" nos documentos do kernel. Os kernels atuais ainda são membros da linha "2.6", mas passaram por duas rodadas de renumeração apenas para "fins de marketing" (então 2.6.40 se tornou 3.0 e 3.20 se tornou 4.0). Seu kernel 4.19 normalmente seria rotulado como 2.6.79.
Parece que há alguma confusão acontecendo aqui sobre o significado de "rootfs". O "rootfs" é um sistema de arquivos especial baseado em RAM usado internamente pelo kernel. É exatamente o mesmo que os sistemas de arquivos "tmpfs" que são comumente montados em locais como
/run
,/dev/shm
, ou às vezes/tmp
. (Bem, a menos que o recurso "tmpfs" não seja compilado no kernel, caso em que um "tmpfs" simplificado chamado "ramfs" é usado.) Esses sistemas de arquivos apenas vivem no pagecache, não há nenhum dispositivo de apoio para ramfs, enquanto o tmpfs é suportado por swap, se disponível.O kernel, portanto, não precisa se preocupar em "encontrar" o "rootfs", mas precisa preenchê-lo de alguma forma, porque todo o cache de página está vazio na inicialização. É aí que o "arquivo initramfs" entra em ação.
cpio
Isso é apenas um arquivo (compactado) (nãotar
pelas razões mencionadas nos documentos) que é descompactado pelo kernel no "rootfs" vazio. Este arquivo pode ser incorporado diretamente na imagem do kernel, definindoCONFIG_INITRAMFS_SOURCE
durante a compilação, ou fornecido pelo bootloader (é isso que ainitrd
opção no GRUB faz). Esse arquivo geralmente é criado usando ferramentas de espaço do usuário comodracut
ou (confusamente)mkinitrd
.Se a imagem cpio não estiver disponível ou não contiver um executável
/init
, o kernel volta para um método diferente onde ele examina oroot=
argumento da linha de comando, interpreta-o como a localização de um sistema de arquivos raiz real, monta-o/
e executainit
diretamente. No entanto, isso raramente é usado hoje em dia, pois requer todos os drivers de armazenamento e sistema de arquivos necessários compilados diretamente no kernel. Na maioria dos sistemas, oroot=
argumento não é usado pelo kernel, mas processado pelo/init
initramfs. Isso/init
(que geralmente é umsystemd
ou um script de shell) cuida de carregar os módulos necessários, montar o sistema de arquivos raiz real e alternar para ele.Há muito tempo, um mecanismo diferente chamado "initrd" foi usado em vez do moderno "initramfs". O "initrd" ("init ramdisk") era um dispositivo de bloco baseado em RAM que foi inicializado com um sistema de arquivos real (como ext2), cuja imagem foi fornecida exatamente como o
cpio
arquivo moderno. É por isso que muitos lugares ainda usam o nome "initrd" para se referir a essas coisas de inicialização antecipada.Essa é uma maneira de fazer isso, sim, mas não é a única maneira.
Se você tem um bootloader que pode ser configurado para carregar o kernel e o initramfs como arquivos separados, você não precisa usá-lo
CONFIG_INITRAMFS_SOURCE
durante a construção do kernel. É suficiente terCONFIG_BLK_DEV_INITRD
definido na configuração do kernel. (Antes do initramfs havia uma versão mais antiga da técnica chamadainitrd
, e o nome antigo ainda aparece em alguns lugares.) O carregador de inicialização carregará o arquivo initramfs e, em seguida, preencherá algumas informações sobre sua localização e tamanho de memória em uma estrutura de dados em um local específico da imagem do kernel já carregada. O kernel possui rotinas internas que usarão essas informações para localizar o initramfs na RAM do sistema e descompactá-lo.Ter o initramfs como um arquivo separado permitirá que você modifique o arquivo initramfs mais facilmente, e se o seu carregador de inicialização puder aceitar a entrada do usuário, talvez especifique outro arquivo initramfs a ser carregado em vez do arquivo normal no momento da inicialização. (Isso é muito útil se você tentar criar um initramfs personalizado e errar algumas coisas. Estive lá, fiz isso.)
Para um sistema x86 tradicional baseado em BIOS, você encontrará informações sobre esses detalhes em (fonte do kernel)/Documentation/x86/boot.txt . Os sistemas baseados em UEFI fazem isso de maneira um pouco diferente (também descrito no mesmo arquivo), e outras arquiteturas como ARM têm seus próprios conjuntos de detalhes sobre como passar informações do carregador de inicialização para o kernel.
Em sistemas não embarcados regulares, o initramfs normalmente conterá apenas a funcionalidade suficiente para ativar os subsistemas essenciais. Em um PC comum, esses geralmente seriam os drivers para o teclado, a tela e o driver do controlador de armazenamento para o sistema de arquivos raiz, além de quaisquer módulos e ferramentas do kernel necessários para ativar subsistemas como LVM, criptografia de disco e/ou RAID de software, se você usar esses recursos.
Assim que os subsistemas essenciais estiverem ativos e o sistema de arquivos raiz estiver acessível, o initramfs normalmente fará uma
pivot_root(8)
operação para alternar do initramfs para o sistema de arquivos raiz real. Mas um sistema embarcado, ou um utilitário especializado como o DBAN , pode empacotar tudo o que precisa no initramfs e nunca fazer apivot_root
operação.Normalmente, os scripts e/ou ferramentas dentro do initramfs obterão as informações necessárias para localizar o sistema de arquivos raiz real a partir das opções na linha de comando do kernel. Mas você não precisa fazer isso: com um initramfs personalizado, você pode fazer algo como alternar para um sistema de arquivos raiz diferente se uma tecla ou botão do mouse específico for pressionado em um momento específico na sequência de inicialização.
Com uma configuração de armazenamento complexa (por exemplo, LVM criptografado em cima de um RAID de software, em um sistema que usa armazenamento SAN redundante de vários caminhos), todas as informações necessárias para ativar o sistema de arquivos raiz podem não caber na linha de comando do kernel, então você pode incluir o pedaços maiores em initramfs.
As distribuições modernas geralmente usam um gerador initramfs para construir um initramfs adaptado para cada kernel instalado. Diferentes distribuições costumavam ter seus próprios geradores initramfs: RedHat usado
mkinitrd
enquanto Debian tinhaupdate-initramfs
. Mas após a introduçãosystemd
dele parece que muitas distribuições estão padronizandodracut
como um gerador initramfs.Um arquivo initramfs moderno pode ser uma concatenação de vários
.cpio
arquivos e cada parte pode ou não ser compactada. Um arquivo initramfs típico em um sistema x86_64 moderno pode ter um arquivo de "atualização antecipada de microcódigo" como primeiro componente (geralmente apenas um único arquivo em um arquivo cpio descompactado, pois o arquivo de microcódigo é normalmente criptografado e, portanto, não é muito compactável. o conteúdo regular do initramfs, como um.cpio
arquivo compactado.Para obter uma compreensão mais profunda do seu sistema, recomendo que você extraia um arquivo initramfs para um diretório temporário e examine seu conteúdo. No Debian, existe uma
unmkinitramfs(8)
ferramenta que pode ser usada para extrair um arquivo initramfs de maneira direta. No RedHat 7, você pode precisar usar/usr/lib/dracut/skipcpio <initramfs file>
para pular o arquivo de atualização de microcódigo e, em seguida, canalizar a saída resultantegzcat
paracpio -i -d
extrair o conteúdo do initramfs para o diretório de trabalho atual. O Ubuntu pode usarlzcat
no lugar dogzcat
.