Processo de arranque alpino no Raspberry Pi

Hoje vamos dar uma olhada no processo de inicialização do Alpine Linux em um Raspberry Pi com algum detalhe.

clip_image001

A imagem mostra o conteúdo de um cartão SD "virgem" com a imagem alpina, que ainda não foi inicializado.

clip_image003

Esta imagem mostra o conteúdo da pasta de arranque.

Etapas iniciais do boot no Raspberry Pi

Consulte este link para um guia detalhado em diferentes mídias de inicialização:

https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/bootflow.md

O Raspberry Pi não tem uma "BIOS" clássica como nos computadores compatíveis com IBM PC baseados em x86. A sua CPU deve ser pensada como um add-on para o VideoCore IV para efeitos de arranque, em vez do contrário.

Assim, inicialmente o VideoCore IV, a GPU, tem controle. Ele parte de sua própria ROM (primeira etapa), e verifica as fontes de inicialização para um arquivo chamado bootcode.bin. Para os nossos propósitos, vamos assumir que este arquivo é carregado a partir do cartão SD.

bootcode.bin (51 KB) é carregado no cache local de 128K L2 da GPU. Ele habilita a RAM e carrega o terceiro estágio: start.elf (2759 KB)

start.elf lerá config.txt e definirá as configurações apropriadas para o VideoCore IV. config.txt é um arquivo de parâmetros para o Raspberry Pi onde você pode definir, por exemplo, o modo de vídeo, overscan, opções de áudio, codecs MPEG, configurações de overclocking, etc. - é "como" as configurações da BIOS parte do computador tradicional. start.elf em si é o firmware para a GPU, que é o seu próprio sistema operacional chamado "VideoCore OS".

NB: Existem diferentes versões de start.elf, incluindo start_x.elf que inclui drivers de câmera, e start_cd.elf - que é uma versão reduzida de start.elf, a ser usada quando a memória da GPU é reduzida ao máximo: gpu_mem=16. Esta última versão pode ser interessante para sistemas embarcados. Consulte este site para mais informações: https://www.raspberrypi.org/documentation/configuration/boot_folder.md

config.txt é opcional. Se você usar o mesmo cartão SD (ou imagem) para vários Pi's, você pode definir filtros condicionais para aplicar a certos cenários; você pode até aplicar uma condição baseada na série do Raspberry Pi's, ou sobre o estado de uma GPIO. Há também a possibilidade de incluir arquivos adicionais no config.txt. Em conjunto, você obtém um sistema bastante poderoso para a configuração básica do Raspberry Pi.

Claro que também existem opções de inicialização no config.txt, que controlam como a continuação da inicialização.

start.elf então carrega o kernel (como especificado por config.txt / default), e o lança com cmdline.txt como linha de comando do kernel .

opções de inicialização config.txt

dê uma vista de olhos:

https://www.raspberrypi.org/documentation/configuration/config-txt/boot.md

para ler mais sobre as opções de inicialização disponíveis.

O config.txt padrão da Alpine

Vamos dar uma olhada no config.txt que navega com a versão 3.8.1 do ARMHF da Alpine:

  • disable_splash=1
    • isto irá desactivar a tela do arco-íris no início (puramente cosmética)
  • boot_delay=0
    • não espere em start.elf antes de carregar o kernel. (um atraso de inicialização pode ser necessário para que alguns cartões SD "se preparem")
  • gpu_mem=256
    • define a divisão da memória entre a GPU e a CPU. A memória da Pi é compartilhada. Aqui definimos 256 MB para a GPU, você pode querer reduzir isso em sistemas incorporados que não conduzem monitores.
  • gpu_mem_256=64
    • define a divisão de memória para Pi's com apenas 256 MB de memória total (apenas algumas Pi 1B de geração antiga)
  • [pi0]
    • declaração condicional para Pi Zero; isto abrange Pi Zero e Pi Zero W
    • todas as declarações até ao próximo condicional só serão aplicadas se corrermos com um Pi Zero / Zero W
    • o Pi Zero / Zero W ainda usa uma CPU ARMv6 ISA mais antiga (ARM11 @ 1 GHz)
    • vamos usar um kernel compilado para o ARMv6
  • kernel=boot/vmlinuz-rpi
    • isto especifica o kernel a carregar
    • este deve ser um ficheiro de imagem de kernel não comprimido
    • Os kernels de 32 bits são carregados para o endereço 0x8000, por padrão.
  • initramfs boot/initramfs-rpi
    • "o comando initramfs especifica tanto o nome do arquivo ramfs quanto o endereço de memória para o qual ele deve ser carregado".
    • initramfs = sistema de ficheiros RAM inicial
    • este arquivo pode ser extraído com 7Zip, por exemplo (extrair duas vezes)
    • NOTA: este parâmetro de configuração é especificado sem o "=".
  • [pi1]
    • isto aplicará as seguintes linhas de configuração a todas as Raspberry Pi 1 - não será igual em Pi 2, etc.
  • kernel=boot/vmlinuz-rpi
  • initramfs boot/initramfs-rpi
  •  [pi2]
  • kernel=boot/vmlinuz-rpi2
    • repare como usamos um kernel diferente, já que aqui temos uma CPU que pode executar o código ARMv7
  • initramfs boot/initramfs-rpi2
    • e um initramas diferente...
  • [pi3]
    • kernel=boot/vmlinuz-rpi2
    • initramfs boot/initramfs-rpi2
  • [pi3+]
    • kernel=boot/vmlinuz-rpi2
    • initramfs boot/initramfs-rpi2
  • [todos]
    • a(s) seguinte(s) linha(s) será(ão) novamente aplicada(s) a todos os Pi's
  • incluir usercfg.txt
    • incluirá um arquivo usercfg.txt na configuração, que ainda não existe na imagem alpina padrão.
    • aqui você pode colocar a configuração personalizada adicional que quiser, por exemplo, a resolução HDMI, etc.
    • alternativamente, você poderia simplesmente colá-lo abaixo no config.txt

Nota: a configuração utilizada no Alpine não especifica um segundo parâmetro para os initramfs (endereço para a montagem dos initramfs); portanto, não é aconselhável copiar este 1:1. Dito isto, parece funcionar, provavelmente por omissão, para um padrão seguro!

Leia mais sobre o config.txt:

O Kernel e os Initramfs

O núcleo

Dependendo das configurações do config.txt, o kernel correto será selecionado e carregado na RAM. Uma vez carregado, o(s) núcleo(s) do(s) processador(es) ARM (são/estão) liberados do reset para que (ele/eles) possa(m) inicializar o kernel.

Será passado o conteúdo do cmdline.txt como linha de comando do kernel. start.elf também passará parâmetros adicionais próprios, por exemplo, a definição de canais DMA, o endereço MAC do chip SMSC LAN, etc.

Obtenha o cmdline.txt:

gato /proc/cmdline

dmesg | grep "Linha de Comando

cmdline.txt

vamos dar uma olhada no cmdline.txt padrão da Alpine:

modules=loop,squashfs,sd-mod,usb-storage quiet dwc_otg.lpm_enable=0 console=tty1

Isto carrega os módulos

  • etapa
    • o dispositivo de laço é usado para montar um arquivo como um sistema de arquivo
  • squashfs
  • sd-mod
    • Controlador de disco SCSI / driver de bloco
    • aparentemente copiado da configuração padrão alpina, por exemplo, aqui: https://wiki.alpinelinux.org/wiki/Create_a_Bootable_Compact_Flash
  • usb-storage
    • Driver de armazenamento em massa USB Linux

NB: os nomes dos módulos podem conter ambos - e _, estes símbolos podem aparentemente ser intercambiáveis.

tranquilo

  • define o nível de log padrão do kernel para KERN_WARNING, que suprime todas as mensagens de log, exceto as muito sérias, durante o boot (fonte: documentação do Raspberry Pi)

dwc_otg.lpm_enable=0

consola=tty1

  • define o console serial

Leitura adicional:

initramfs

” O comando initramfs especifica tanto o nome do arquivo ramfs quanto o endereço de memória para o qual ele deve ser carregado. Ele executa as ações do ramfsfile e do ramfsaddr em um parâmetro. O endereço também pode ser seguido pelo kernel (ou 0) para colocá-lo na memória após a imagem do kernel. Os valores de exemplo são: initramfs initramf.gz 0x00800000 ou initramfs initramfs init.gz followkernel. NOTA: Esta opção usa uma sintaxe diferente de todas as outras opções, e você não deve usar um = caractere aqui". – Documentação Raspberry Pi

Haverá uma mensagem do kernel como esta "[ 0.143709] Tentando descompactar a imagem rootfs como initramfs..." no dmesg, mostrando que a imagem está sendo descompactada.

O initramfs permite ao kernel Linux adicionar módulos e realizar outras tarefas na preparação para a montagem do sistema de arquivos real. Assim, ele faz um sistema de inicialização muito versátil, sem a necessidade de recompilar o kernel / adicionar sobrecarga ao kernel. Por exemplo, para utilizadores com um sistema de ficheiros encriptado, o initramfs irá pedir a frase-chave antes de poder desencriptar e montar os sistemas de ficheiros.

Dependendo da CPU do seu Raspberry Pi, serão utilizados ficheiros initramfs diferentes, conforme definido em config.txt:

  • boot/initramfs-rpi para Pi 1 / 1B+ e Pi Zero / Zero W, e Módulo de Cálculo 1
  • boot/initramfs-rpi2 para todos os outros Pi's (2, 3B, 3B+, ...)

O arquivo initramfs pode ser extraído usando, por exemplo, no Windows 7Zip. (Você tem que extraí-lo duas vezes: o primeiro arquivo contém um segundo arquivo gzip)

imagens initramfs podem estar em diferentes formatos, dependendo de quais algoritmos foram compilados estaticamente no kernel (por exemplo, gzip / bzip2 / LZMA / XZ / LZO / LZ4).

clip_image005

Este segundo arquivo empacota uma pasta e uma estrutura de arquivos, que você reconhecerá se já usou o Linux antes. Ele está em um formato especial, "cpio", que é semelhante ao alcatrão.

clip_image007

se estiveres curioso, podes ler mais sobre o cpio aqui:

https://en.wikipedia.org/wiki/Cpio

Isto é o que o arquivo contém:

clip_image008

no caixote do lixo, contém:

  • busybox
  • sh

em alguém, ele contém

o init é lançado pelo kernel do Linux como primeiro processoe, basicamente, estabelece a Alpine.

Aqui está a ferramenta utilizada para gerar os initramas para Alpine:

https://github.com/alpinelinux/mkinitfs

init

esta é uma sequência excerto do que o roteiro do initramfs faz:

  • cria diretórios /usr/bin, /usr/sbin, /proc, /sys, /dev, /sysroot , /media/cdrom, /media/usb, /tmp, /run recursively
    • alguns desses diretórios parecem já estar presentes nos initramas
  • instala o busybox (configura os links simbólicos para os comandos embutidos do busybox)
  • estabelece o PATH para /usr/bin:/bin:/usr/sbin:/sbin
  • cria /dev/null usando mknod
  • montagens proc a /proc
    • proc é um sistema de pseudo-arquivo de informação de processos
    • você pode obter informações sobre o kernel lendo e configurando certas coisas, escrevendo em alguns arquivos deste diretório
    • as entradas numeradas são diretórios contendo informações sobre o processo com esta identificação de processo em particular
    • https://www.tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html
  • ... e sysfs para /sys
  • analisando as opções passadas via cmdline.txt, são reconhecidas as seguintes:
    • alpine_dev autodetect autoraid chart cryptroot cryptroot cryptrootheader cryptoffset
    • cryptdiscards cryptkey debug_init dma init_args keep_apk_new modules ovl_dev
    • pkgs raiz_size pkgs quiet root usbdelay ip alpine_repo apkovl alpine_start splash
    • blacklist overlaytmpfs rootfstype rootflags nbd resume s390x_net dasd ssh_key

Dica: tente mudar "tranquilo" em cmdline.txt para "noquiet"para obter uma bota mais verbosa, e ver muitas mensagens do init

  • monta devtmpfs a /dev
  • carrega os condutores para poder montar o modloop mais tarde
  • se o parâmetro nbd estiver presente e configurado (dispositivo de bloco de rede), tenta trazer um endereço ip e configura o dispositivo de bloco de rede usando nbd-client
  • se o parâmetro raiz estiver definido
    • se [ -n "$KOPT_root" ]; então
    • n avalia se o comprimento do "$KOPT_root" é diferente de zero
    • então o nlplug-findfs é executado
    • opcionalmente, o overlaytmpfs é tratado
    • os pontos de montagem atuais são migrados para /sysroot
    • switch_root é executado e o controle é passado para /sbin/init
    • (este é um ponto de entrada para instalar o Alpine Linux em modo regular, não em modo somente leitura)

caso contrário:

  • nlplug-findfs é executado para encontrar a mídia de inicialização,
  • se o parâmetro apkovl está pronto,
    • e está vazio - e /tmp/apkovls existe (ou seja, foram encontrados arquivos por nlplug-findfs) - o ovl é definido desde a primeira linha de /tmp/apkovls
    • e começa com http:// / https:// / ftp:// - tenta-se uma conexão de rede para puxar o apkovl
    • caso contrário o ovl está definido para a corda na opção apkovl
  • se a apkovl existir,
    • e é um arquivo .gz, o apkovl é desempacotado tar -C "$dest" -zxvf "$ovl" > $ovlfiles e /tmp/ovlfiles é preenchido com a produção de alcatrão
  • há código para uma tela de respingo, fbsplash é uma das funções incluídas no busybox - ele exibe arquivos fbsplash*.ppm
  • se [ -z "$ALPINE_REPO" ] -> significa, se a string $ALPINE_REPO estiver vazia
    • em algumas circunstâncias são adicionados serviços de inicialização padrão, para o runlevel sysinit, boot, shutdown e padrão (por exemplo, o serviço firstboot)
  • Os repositórios da apk são adicionados e expandidos
    • o pacote base alpina depende de baselayout alpino, alpine-conf e alguns outros pacotes
      • também inclui o arquivo /etc/os-release -> que mostra o nome, versão e bonito nome do Alpine Linu
    • alpine-baselayout é o layout do diretório base
    • alpine-conf inclui, entre outros, o script lbu e os scripts de configuração
    • busybox inclui o binário busybox
    • busybox-initscripts inclui alguns initscripts
  • finalmente, o diretório raiz é trocado, e o /sbin/init é iniciado
    • isto irá iniciar o OpenRC e executar os níveis de execução
    • /sbin/init é um link simbólico para /bin/busybox

Atenção: O apkovl é montado antes do modloop e antes de instalar os pacotes. Isto permite ao apkovl configurar quais os pacotes a serem instalados, entre outras coisas.

Runlevels OpenRC

sysinit -> boot -> default -> shutdown

https://hoverbear.org/2014/09/29/init-runlevels-and-targets/

Sistema de runlevel sysinit:

clip_image010

modloop será iniciado após o dev-mount, e antes do checkfs, fsck, hwdriver, módulos, hwclock, dev, sysfs

hwdrivers serão iniciados após o modloop. hwdrivers irão carregar os drivers usando o modprobe.

Bota de nível de corrida:

clip_image012

Nível de execução padrão:

clip_image014

Modloop

Existe também o ficheiro modloop (modloop-rpi ou modloop-rpi2).

como ele é montado

O ficheiro certo para o seu sistema é determinado no script /etc/init.d/modloop na função find_modloop()

O resultado de uname -r é verificado em relação aos módulos de directório/ - o subdirectório devidamente nomeado tem de estar nele.

Por exemplo: 4.14.69-0-rpi2

Desta forma são utilizados os módulos certos para o seu kernel (uname -r imprime a liberação do kernel).

modloop é montado no diretório /.modloop

clip_image015

Os Symlinks são criados a partir de /lib/firmware para /módulos/módulos/firmwaree /lib/módulos está ligado simbolicamente a /.modloop/módulos

Dica: se você quiser corrigir o firmware Raspberry Pi 3B+ WiFi, você tem que colocá-lo em modloop.

Conteúdo do Modloop

Para explorar o conteúdo do modloop, você pode usar (testado em um sistema de menta):

apt-get install squashfs-tools

unsquashfs modloop-rpi2

Ele contém firmware e drivers.

clip_image016

clip_image017

Referências:

leitura adicional: