Otimização de Tempo de Boot no Linux Embarcado

Este artigo é adaptado e atualizado do post Embedded Linux Boot Time Optimization, por Stefan Agner.

Algumas aplicações possuem requerimentos específicos para o tempo de boot do sistema. Geralmente, o sistema não precisa estar imediatamente pronto para todas as suas tarefas, mas deve estar pronto para certas tarefas críticas (por exemplo, aceitar comandos via Ethernet ou mostrar uma interface de usuário). Este artigo fornece algumas metodologias e procedimentos rápidos para melhorar o tempo de boot nos módulos Toradex.

Nota: Algumas das dicas mencionadas neste artigo requerem recompilação do U-Boot, do Kernel ou um rebuild do root file system do zero.

Antes de começar a otimização, precisamos de um método apropriado para mensurar o tempo de boot. Caso seja necessário obter o tempo de boot exato, do começo ao fim, pode até ser preciso envolver o hardware (por exemplo, utilizando GPIO e um osciloscópio). No entanto, na maioria dos casos, monitorar através da porta serial já fornece precisão suficiente. Uma ferramenta bastante utilizada para monitorar a temporização de uma saída serial é o grabserial do Tim Bird. Este utilitário adiciona uma timestamp a cada linha capturada da porta serial, como mostrado abaixo:

O primeiro número representa a timestamp (desde o primeiro caractere recebido), enquanto o segundo número mostra a diferença entre a timestamp atual e a última linha.

De forma geral, este artigo se aplica a todos os módulos Toradex. Todavia, serão apresentadas algumas medições e melhorias utilizando especificamente o módulo da Toradex baseado no Vybrid, da NXP®/Freescale, o Colibri VF61.

Há aproximadamente três fases na inicialização do Linux, que são listadas abaixo e serão examinadas ao curso deste post:

  • Boot loader;
  • Kernel do Linux;
  • User space (sistema de init).

Boot loader

Na verdade, há duas fases antes que o bootloader seja executado: inicialização do hardware e inicialização da ROM. A fase de inicialização do hardware é necessária para preencher os requerimentos de power sequencing e os requerimentos de temporização para o reset de barramentos ou do SoC. Essa fase geralmente é fixa e na faixa de 10-200ms. SoCs ARM inicializam a partir de um firmware gravado na ROM interna. Este firmware carrega o boot loader da mídia de boot. O tempo de execução geralmente é bastante curto e influenciado pelo tamanho do boot loader. Além de diminuir o tamanho do boot loader, outras otimizações são deveras difíceis. O verdadeiro potencial de otimização e flexibilidade estão dentro do bootloader (U-Boot).

Com o release V2.7 Beta 1, o tempo do primeiro caractere ao início do Kernel é de ~1.75 segundos. Isto envolve os seguintes passos:

  • Inicialização do U-Boot (~140ms, medido desde o primeiro caractere recebido);
  • Delay do autoboot (~1s);
  • Inicialização da UBI e montagem do UBIFS (~278ms, graças a um recurso chamado Fastmap. Sem o Fastmap, este processo levaria em torno de 1.6s);
  • Carregamento do Kernel (~328ms);
  • Carregamento e patching da device tree (~4ms);
  • E, finalmente, atinge-se o endereço inicial do Kernel.

Tempo de boot até início do Kernel: ~1750ms.

A otimização mais óbvia é reduzir o delay de Autoboot, que pode ser configurado para zero utilizando:

Essa opção também pode ser configurada como padrão utilizando o símbolo de configuração CONFIG_BOOTDELAY. Nesta versão, porém, com um bootdelay de 0, não há como acessar o console do bootloader diretamente. O U-Boot fornece uma opção chamada CONFIG_ZERO_BOOTDELAY_CHECK que verificará por um caractere mesmo que o bootdelay seja 0. Nós adicionamos esta opção à nossa configuração padrão nas próximas versões.

Tempo de boot até o início do Kernel com esta melhoria: ~781ms.

A saída serial é enviada de forma síncrona. Isso significa que a CPU aguarda até que o caractere seja enviado através da linha serial. Portanto, cada caractere mostrado diminui o desempenho do U-Boot. Como a UBI externa várias mensagens de informação, há potencial para otimização. De fato, existe um símbolo de configuração ‘CONFIG_UBI_SILENCE_MSG’.

Tempo de boot até o início do Kernel com esta melhoria: ~742ms.

Para garantir que o hardware será usado da forma mais eficiente possível, é preciso discernimento sobre a capacidade do hardware e sobre o que está atualmente sendo implementado. Um recurso que faltava até agora era o Cache Level 2 (somente no Colibri VF61 com ‘CONFIG_SYS_L2_PL310’ e ‘CONFIG_SYS_PL310_BASE CA5_L2C_BASE_ADDR’). Após implementar o Cache Level 2, o tempo de boot melhorou em mais de 40ms.

Tempo de boot até o início do Kernel com esta melhoria: ~700ms.

Pode-se remover certos recursos para diminuir os tempos de realocação e inicialização. Removendo o suporte a display (DCU), suporte a EXT3 e EXT4, bem como drivers de periféricos USB como DFU e mass storage, cortamos mais 11ms.

Tempo de boot até o início do Kernel com esta melhoria: ~671ms.

De acordo com as timestamps, a maior parte do tempo é gasta anexando a UBI e montando o UBIFS, bem como carregando o kernel (~329ms). Obviamente, o tamanho do Kernel e o tempo de carregamento correlacionam-se linearmente, de modo que otimizar o tamanho do kernel ajudará a melhorar ainda mais o tempo de boot.

Kernel

Para mensurar somente o tempo de boot do kernel, a função “match” do grabserial pode ser usada para resetar a timestamp na última mensagem mostrada pelo bootloader:

O fim do tempo de boot é difícil de ser determinado, uma vez que o kernel continua a inicializar o hardware mesmo após o root file system ser montado e o primeiro processo do user space (init) começar a rodar (inicialização atrasada). A string “Freeing unused kernel memory” é a última mensagem emitida antes do início do processo init e, portanto, marca o fim do procedimento de inicialização “linear” do kernel (ver kernel_init no init/main.c). Utilizaremos a timestamp desta mensagem para comparar os tempos de inicialização. O kernel padrão tem um tamanho compactado de 4778kB e um tempo de boot de ~3.25 segundos.

Tempo de boot do Kernel até o começo do Init: ~3.25s.

De forma semelhante ao U-Boot, o kernel do Linux envia todas as mensagens de forma síncrona ao console serial. O comportamento exato depende do console serial utilizado, mas a LPUART (o driver utilizado para o console do Vybrid) espera de forma síncrona até que o caractere seja enviado pela porta serial. A vantagem é que quando o kernel trava, todas as mensagens até aquele ponto são visíveis. Se as mensagens fossem enviadas de forma assíncrona, a última mensagem visível não indicaria o ponto de travamento.

O kernel possui um argumento para minimizar a quantidade de mensagens mostradas: “quiet”. Porém, isto também silencia nossa referência para a medição do tempo de boot (“Freeing unused kernel memory”). A maneira mais fácil de recuperar esta mensagem é elevar o nível de log para esta declaração em particular. Ela está localizada em ‘mm/page_alloc.c’ – procure por “Freeing %s memory”. Elevando a mensagem para ‘pr_alert’, a medição mostrou uma melhoria de 1.7 segundos, que é uma melhora de mais do que o dobro!

Tempo de boot do kernel até o começo do Init com essa melhoria: ~1.16s.

A maneira mais fácil de obter mais melhorias é removendo recursos. O Yocto project possui um prático script chamado ksize.py (localizado em layers/openembedded-core/scripts/tiny/ksize.py) que precisa ser inicializado de dentro de um diretório de build do kernel. A ferramenta mostra tabelas identificando o tamanho de partes individuais do kernel. A primeira tabela mostra uma visão geral em alto nível (execute make clean antes de realizar a build para obter uma visão mais precisa):

Os recursos a serem removidos com segurança são, obviamente, específicos a cada aplicação. Passando por cada diretório de nível mais alto, verifica-se rapidamente os candidatos mais promissores à remoção. Para este artigo, removemos vários sistemas de arquivo (cifs, nfs, ext4, ntfs), o subsistema de áudio, suporte a multimídia, suporte a USB e adaptadores de rede sem fio. O kernel ficou com aproximadamente 3.613 KB, por volta de 1 MB menor que antes. O tempo de carregamento do kernel no bootloader também diminuiu em aproximadamente ~160ms.

Tempo de boot do kernel ao começo do Init com esta melhoria: ~1.00s.

Outra ideia para melhoria pode ser avaliar diferentes algoritmos de compressão, embora o algoritmo padrão atual na nossa configuração do kernel seja o LZO, que já é bastante elaborado.

User Space

No user space do Linux, a inicialização é feita pelo sistema de init. As imagens da Toradex utilizam o sistema de init padrão do Ångström, que é o systemd. O systemd, init system padrão de facto nos desktops Linux hoje em dia, é cheio de recursos e especialmente projetado com sistemas dinâmicos em mente. O systemd também influencia o tempo de boot. Vários daemons são inicializados simultaneamente (lançando mão dos sistemas multi-núcleos atuais). A ativação de sockets permite o carregamento atrasado de serviços num ponto posterior no tempo e a ativação de dispositivos permite a inicialização de serviços on demand. Ademais, o daemon de logging journald economiza espaço devido a arquivos de log encapsulados em binários e a um gerenciamento sofisticado de arquivos de log.

Dependendo da aplicação, um sistema embarcado pode ser bastante estático. Assim, os recursos dinâmicos do systemd não são realmente necessários. Infelizmente, o systemd não é muito modular, ou os módulos individuais teriam dependências interligadas. Isto faz com que seja difícil reduzir o systemd ao mínimo absoluto. Esta seção é separada em duas partes: a primeira parte apresenta técnicas de otimização de tempo de boot para o systemd; a segunda parte aborda o System V e outras alternativas.

Em ambas as partes utilizaremos a mensagem “Freeing unused kernel memory” como base para medição de tempo:

./grabserial -d /dev/ttyUSB1 -t -m “^\[ *[]0-9.]* Freeing unused kernel memory.*”

systemd

Para este post, definiremos o shell de login no console serial como uma task crítica. O shell de login é definido como “Type=Idle”, o que significa que, por definição, ele iniciará somente após todos os outros serviços terem sido iniciados.

Para inicializar uma aplicação headless ou baseada em framebuffer, geralmente cria-se um novo serviço. O systemd permite definir certos requerimentos que um serviço necessita antes que seja inicializado (por exemplo, Rede com “Wants=network-online.target”) e automaticamente garante que os serviços sejam inicializados tão logo os requerimentos sejam satisfeitos. Todavia, uma vez que os serviços são inicializados em paralelo, os recursos da CPU são compartilhados entre eles. Mesmo assim, a aplicação provavelmente já estará rodando antes que o console serial esteja disponível, portanto os números a seguir podem ser uma estimativa para cima.

Tempo de boot do user space até o login sem melhorias: ~13.48s.

O argumento quiet do kernel também é captado pelo systemd. Esta mudança já fornece um efeito positivo no tempo de boot do systemd, cortando aproximadamente 1.24s no processo.

Tempo de boot do user space até o login com esta melhoria: ~12.24s.

O systemd fornece um utilitário chamado systemd-analyze que apresenta uma lista de serviços e seus tempos de início quando inicializado com o argumento “blame”. Isto permite encontrar facilmente os culpados pelo tempo de boot; porém, os valores podem ser enganosos uma vez que o tempo é medido de acordo com o horário real. Um serviço listado pode estar somente em sleep enquanto a CPU trabalha em outros processos. Portanto, o serviço no topo da lista pode não ser o maior culpado pelo tempo de boot, especialmente num sistema single-core.

Serviços podem ser desabilitados utilizando o comando disable. Alguns serviços (especialmente aqueles fornecidos pelo próprio systemd) podem necessitar do comando mask para serem desabilitados. Alguns ainda podem ser necessários para que o sistema opere; portanto, deve-se desabilitar os serviços com cuidado e um de cada vez. Para este artigo, os seguintes serviços foram desabilitados:

Tempo de boot do user space até o login com esta melhoria: ~11.88s.

O systemd vem com seu próprio daemon de logging chamado journald. Este é um dos componentes que não deve ser desabilitado completamente. Durante a inicialização, o daemon de logging precisa gerenciar e deletar arquivos de log antigos no disco, bem como escrever novos logs. Desabilitar o armazenamento de logs no disco já melhora o tempo de boot, com a desvantagem, é claro, dos arquivos de log não serem gravados. Use Storage=none no /etc/systemd/journald.conf para desabilitar a parte de armazenamento de logs.

Tempo de boot do user space até o login com esta melhoria: ~11.62s.

System V init e outras alternativas

Por muitos anos o SysV foi o sistema de init padrão também no Linux. Por ser um sistema de init baseado em script, o SysV é bastante modular e relativamente fácil de ser enxuto. Especialmente para sistemas relativamente estáticos, onde a ativação de dispositivos e sockets do systemd não é necessária, o SysV é uma boa alternativa.

A distribuição de referência do Yocto project, “poky” (ver The Yocto Project’s Reference Distribution “Poky” on Toradex Hardware), utiliza SysV por padrão. Utilizando a imagem ‘core-image-minimal’ e uma configuração de IP estático, o tempo de boot medido no Colibri VF61 foi de ~2.5s.

Tempo de boot do user space até o shell com System V: ~2.5s.

A camada meta-yocto também fornece a ‘poky-tiny’, que usa apenas um shell script como sistema de init. Basta substituir a distribuição por “poky-tiny” e fazer normalmente o build de uma imagem do Yocto, como ‘console-image-minimal’.

A distribuição é feita para ser utilizada como um initramfs; porém, removendo MACHINE_ESSENTIAL_EXTRA_RDEPENDS, IMAGE_FSTYPES e PREFERRED_PROVIDER_virtual/kernel do arquivo conf/distro/poky-tiny.conf, é possível fazer build de uma imagem UBIFS funcional.

Para “reconfigurar” corretamente a distribuição para obter um root file system “flashável”, deve-se criar uma nova camada de distribuição e copiar o arquivo de configuração da distribuição. O “tempo de boot” até o shell é, obviamente, muito rápido (220ms), permitindo a execução de um simples comando com um tempo de boot total de pouco menos de 2 segundos. Todavia, não há nenhuma outra funcionalidade senão montar o root file system, suporte básico a virtual file system e um shell.

Ainda, dependendo da quantidade de recursos necessária num projeto, este pode ser um bom ponto de partida.

Tempo de boot do user space até o shell somente com shell script: ~0.2s.

Saiba mais

Interrupção de GPIO no Linux

Executando aplicações .NET no Linux Embarcado em processadores ARM com Mono

Comandos Básicos no Terminal Linux

Referências

http://free-electrons.com/doc/training/boot-time/boot-time-slides.pdf
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Linux Embarcado » Otimização de Tempo de Boot no Linux Embarcado
Comentários:
Talvez você goste:

Séries

Menu