Quão pequeno pode ser um kernel do Linux?

Algum tempo atrás eu aprendi como converter máquinas virtuais em nuvem oracle do ubuntu 20.04 para o gentoo. As máquinas fornecidas no nível sempre gratuito são muito fracas. Isso, em particular, leva ao fato de que a recompilação do kernel se torna um processo bastante demorado. O kernel original do ubuntu 20.04 tinha 7904 parâmetros em sua configuração. Depois que eu fiz:



make localmodconfig && make localyesconfig
      
      





o número de parâmetros diminuiu para 1285. Tornou-se interessante para mim tentar jogar fora todas as coisas desnecessárias do kernel e ver o que acontece.



Estarei compilando o kernel vanilla 5.4.0 porque essa é a versão usada na minha instalação do gentoo. Para acelerar o processo, eu compilo o kernel na minha máquina de trabalho (i7, 8 núcleos, 64 Gb RAM, tmpfs). Copio o kernel finalizado para a máquina em nuvem oracle. Você precisa iniciar o processo com o comando:



make tinyconfig
      
      





Isso lhe dará um arquivo .config para o núcleo mínimo da arquitetura atual. No meu caso, este arquivo contém 284 linhas não em branco que não são comentários.



Vamos compilá-lo e ver o tamanho do kernel:



$ yes ""|make -j$(nproc)
$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 441872 Jan 31 18:09 arch/x86/boot/bzImage
284
$

      
      





Este kernel é completamente inútil. Não apenas não inicializa, como também não tem a capacidade de relatar problemas. Vamos consertar isso. Para ativar os parâmetros, usarei o comando:



script/config -e config_parameter_name
      
      





Portanto, nosso kernel será de 64 bits, ativamos a saída de diagnóstico do kernel, habilitamos o suporte ao terminal, configuramos a porta serial e o console nele:



./scripts/config -e CONFIG_64BIT -e CONFIG_PRINTK -e CONFIG_TTY -e CONFIG_SERIAL_8250 -e CONFIG_SERIAL_8250_CONSOLE
      
      





Uma máquina em nuvem oracle não será capaz de inicializar com este kernel, não haverá saída para o console. Como se viu, você precisa adicionar suporte para EFI e ACPI, que é sua dependência. O script ./scripts/config não implementa a lógica para adicionar dependências reversas, ou seja, se você adicionar apenas CONFIG_EFI, make irá remover este parâmetro da configuração. Também é importante notar que as opções de ativação geralmente incluem as opções abaixo. Portanto, no caso de habilitar CONFIG_ACPI, ele é habilitado automaticamente, por exemplo, suporte para o botão liga / desliga.



./scripts/config -e CONFIG_ACPI -e CONFIG_EFI -e CONFIG_EFI_STUB
      
      





Juntando o núcleo:



$ yes ""|make -j$(nproc)
$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 1036960 Jan 31 18:13 arch/x86/boot/bzImage
409
$
      
      





Este kernel ainda não pode completar o processo de inicialização, mas pelo menos é capaz de relatá-lo:



Kernel panic - not syncing: No working init found.  Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance.
Kernel Offset: 0x22000000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
---[ end Kernel panic - not syncing: No working init found.  Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance. ]---
      
      





Vamos adicionar os parâmetros necessários:



# virtual guest support and pci
./scripts/config -e CONFIG_PCI -e CONFIG_VIRTIO_PCI -e CONFIG_VIRTIO -e CONFIG_VIRTIO_MENU -e CONFIG_PARAVIRT -e CONFIG_HYPERVISOR_GUEST  
# disk support
./scripts/config -e CONFIG_BLOCK -e CONFIG_SCSI -e CONFIG_BLK_DEV_SD -e CONFIG_SCSI_VIRTIO
# filesystems
./scripts/config -e CONFIG_EXT4_FS -e CONFIG_PROC_FS -e CONFIG_SYSFS -e CONFIG_DEVTMPFS -e CONFIG_DEVTMPFS_MOUNT
# executable formats
./scripts/config -e CONFIG_BINFMT_ELF -e CONFIG_BINFMT_SCRIPT
# network
./scripts/config -e CONFIG_NET -e CONFIG_VIRTIO_NET -e CONFIG_PACKET -e CONFIG_UNIX -e CONFIG_INET -e CONFIG_NET_CORE -e CONFIG_NETDEVICES -e CONFIG_VIRTIO_NET
      
      





Juntando o núcleo:

$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 1950368 Jan 31 18:18 arch/x86/boot/bzImage
616
$
      
      





E estamos carregados. Desta vez, o kernel encontra com sucesso o disco raiz, mas vemos erros no console:



The futex facility returned an unexpected error code.
...
 * Call to flock failed: Function not implemented
      
      





Estamos tentando fazer login:

(none) login: root
process 182 (login) attempted a POSIX timer syscall while CONFIG_POSIX_TIMERS is not set
Password:
setgid: Function not implemented
      
      





Também não funciona, mas somos indagados de forma inequívoca sobre qual parâmetro adicionar. Adicione-o e outros parâmetros necessários:



./scripts/config -e CONFIG_POSIX_TIMERS -e CONFIG_FUTEX -e CONFIG_FILE_LOCKING -e CONFIG_MULTIUSER
      
      





Juntando o núcleo:



$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 1979040 Jan 31 18:25 arch/x86/boot/bzImage
623
$
      
      





Desta vez, conseguimos fazer o login:



instance-20210124-1735 login: root
Password:
Last login: Mon Feb  1 02:25:10 UTC 2021 from 73.239.106.74 on ssh
root@instance-20210124-1735:~#
      
      





Hooray! Ssh também funciona. No entanto, existem erros no console novamente:



 * Some local filesystem failed to mount
...
hwclock: Cannot access the Hardware Clock via any known method.
hwclock: Use the --verbose option to see the details of our search for an access method.
 * Failed to set the system clock

      
      





E no dmesg também encontramos:



[    2.910198] udevd[360]: inotify_init failed: Function not implemented
      
      





Adicionamos suporte para relógio em tempo real, sistema de arquivos vfat e inotify:



./scripts/config -e CONFIG_RTC_CLASS -e CONFIG_DNOTIFY -e CONFIG_INOTIFY_USER -e CONFIG_VFAT_FS
      
      





Juntando o núcleo:



$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 2015904 Jan 31 18:36 arch/x86/boot/bzImage
643
$
      
      





Hmmm, a unidade vfat ainda não consegue montar:



 * Some local filesystem failed to mount
      
      





E aqui está a resposta por que em dmesg junto com mais um erro:



[    3.782884] udevd[527]: error creating signalfd
[    4.107698] FAT-fs (sda15): codepage cp437 not found
      
      





Adicione parâmetros:



./scripts/config -e CONFIG_SIGNALFD -e CONFIG_NLS_CODEPAGE_437 -e CONFIG_NLS_ISO8859_1
      
      





Juntando o núcleo:



$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 2015904 Jan 31 18:41 arch/x86/boot/bzImage
646
$
      
      





Nós carregamos, não há erros no console, mas um novo erro apareceu no dmesg:



[    2.756136] udevd[360]: error creating epoll fd: Function not implemented
      
      





Nós consertamos:



./scripts/config -e CONFIG_EPOLL
      
      





Juntando o núcleo:



$ ll arch/x86/boot/bzImage && grep -v ^# .config|grep -c .
-rw-rw-r-- 1 kvt kvt 2020000 Jan 31 19:13 arch/x86/boot/bzImage
647
$
      
      





Reinicializamos e desta vez não vemos novos erros!



Na minha máquina de trabalho (i7, 8 núcleos, 64 gb RAM, tmpfs), a configuração final é construída em 1m 16s. Na nuvem oracle com dois núcleos e em um disco normal, o mesmo processo leva 19m 51s.



O kernel resultante não é absolutamente mínimo. Assim, por exemplo, habilitar o suporte à rede adiciona vários adaptadores de rede diferentes. Não me envolvi no perfeccionismo e limpei absolutamente tudo o que não é necessário. Além disso, quero avisá-lo que, embora o carregamento e o teste rápido não revelem problemas com a ausência de outros componentes importantes do kernel, eles provavelmente existem. Então, se de repente você decidir reutilizar minha configuração por favor, teste o kernel completamente para o seu caso particular e adicione os parâmetros de kernel necessários, se necessário.



All Articles