Насколько маленьким может быть ядро linux?
Некоторое время назад я научился конвертировать виртуальные машины в oracle cloud из ubuntu 20.04 в gentoo. Машины предоставляемые в рамках always free tier весьма маломощны. Это в частности приводит к тому, что перекомпиляция ядра превращается в достаточно длительный процесс. У исходного ядра ubuntu 20.04 в конфиге было 7904 параметра. После того, как я сделал
число параметров уменьшилось до 1285. Мне стало интересно попробовать выбросить из ядра все лишнее и посмотреть, что получится.
Я буду компилировать ванильное ядро 5.4.0, потому что именно эта версия используется на моей установке gentoo. Для ускорения процесса я компилирую ядро на своей рабочей машине (i7, 8 cores, 64Gb RAM, tmpfs). Готовое ядро я копирую на машину в oracle cloud. Начинать процесс надо с команды
В результате у вас появится файл .config для самого минимального ядра текущей архитектуры. В моем случае в этом файле оказалось 284 непустых строк, не являющихся комментариями. Давайте скомпилируем его и посмотрим на размер ядра:
Итак, наше ядро будет 64-х битным, мы активируем вывод диагностики ядра, включаем поддержку терминала, конфигурируем последовательный порт и консоль на нем:
Машина в oracle cloud загрузиться с этим ядром не сможет, вывода на консоль не будет. Как оказалось, надо добавить поддержку EFI и ACPI, являющуюся ее зависимостью. Скрипт ./scripts/config не реализует логику добавления обратных зависимостей т.е. если добавить только CONFIG_EFI, то make выкинет этот параметр из конфига. Также стоит отметить, что включение опций часто включает нижележащие опции. Так в случае с включением CONFIG_ACPI автоматически включается, к примеру, поддержка кнопки включения/выключения.
Собираем ядро
Это ядро по прежнему не может завершить процесс загрузки, но по крайней мере способно сообщить об этом:
Давайте добавим необходимые параметры:
Собираем ядро:
И загружаемся. На этот раз ядро успешно находит корневой диск, но в консоли мы видим ошибки:
Пытаемся залогиниться:
Тоже не получается, но зато нам недвусмысленно подсказывают какой параметр надо добавить. Добавляем его и остальные необходимые параметры:
Собираем ядро:
На этот раз нам удается залогиниться:
Ура! Ssh тоже работает. Тем не менее в консоли опять есть ошибки:
А в dmesg находим еще:
Добавляем поддержку часов реального времени, файловой системы vfat и inotify:
Собираем ядро:
Хммм, vfat диск по прежнему не может подмонтироваться:
А вот и ответ почему в dmesg заодно с еще одной ошибкой:
Добавляем параметры:
Собираем ядро:
Загружаемся, в консоли ошибок нет, но появилась новая ошибка в dmesg:
Исправляем:
Собираем ядро:
Перезагружаемся и на этот раз не видим новых ошибок:)!
make localmodconfig && make localyesconfig
число параметров уменьшилось до 1285. Мне стало интересно попробовать выбросить из ядра все лишнее и посмотреть, что получится.
Я буду компилировать ванильное ядро 5.4.0, потому что именно эта версия используется на моей установке gentoo. Для ускорения процесса я компилирую ядро на своей рабочей машине (i7, 8 cores, 64Gb RAM, tmpfs). Готовое ядро я копирую на машину в oracle cloud. Начинать процесс надо с команды
make tinyconfig
В результате у вас появится файл .config для самого минимального ядра текущей архитектуры. В моем случае в этом файле оказалось 284 непустых строк, не являющихся комментариями. Давайте скомпилируем его и посмотрим на размер ядра:
$ 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
$
Это ядро абсолютно бесполезно. Оно не только не может загрузиться, но даже не имеет возможности сообщить о возникших проблемах. Давайте это исправим. Для активации параметров я буду использовать командуscript/config -e config_parameter_name
Итак, наше ядро будет 64-х битным, мы активируем вывод диагностики ядра, включаем поддержку терминала, конфигурируем последовательный порт и консоль на нем:
./scripts/config -e CONFIG_64BIT -e CONFIG_PRINTK -e CONFIG_TTY -e CONFIG_SERIAL_8250 -e CONFIG_SERIAL_8250_CONSOLE
Машина в oracle cloud загрузиться с этим ядром не сможет, вывода на консоль не будет. Как оказалось, надо добавить поддержку EFI и ACPI, являющуюся ее зависимостью. Скрипт ./scripts/config не реализует логику добавления обратных зависимостей т.е. если добавить только CONFIG_EFI, то make выкинет этот параметр из конфига. Также стоит отметить, что включение опций часто включает нижележащие опции. Так в случае с включением CONFIG_ACPI автоматически включается, к примеру, поддержка кнопки включения/выключения.
./scripts/config -e CONFIG_ACPI -e CONFIG_EFI -e CONFIG_EFI_STUB
Собираем ядро
$ 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
$
Это ядро по прежнему не может завершить процесс загрузки, но по крайней мере способно сообщить об этом:
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. ]---
Давайте добавим необходимые параметры:
# 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
Собираем ядро:
$ 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
$
И загружаемся. На этот раз ядро успешно находит корневой диск, но в консоли мы видим ошибки:
The futex facility returned an unexpected error code.
...
* Call to flock failed: Function not implemented
Пытаемся залогиниться:
(none) login: root
process 182 (login) attempted a POSIX timer syscall while CONFIG_POSIX_TIMERS is not set
Password:
setgid: Function not implemented
Тоже не получается, но зато нам недвусмысленно подсказывают какой параметр надо добавить. Добавляем его и остальные необходимые параметры:
./scripts/config -e CONFIG_POSIX_TIMERS -e CONFIG_FUTEX -e CONFIG_FILE_LOCKING -e CONFIG_MULTIUSER
Собираем ядро:
$ 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
$
На этот раз нам удается залогиниться:
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:~#
Ура! Ssh тоже работает. Тем не менее в консоли опять есть ошибки:
* 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
А в dmesg находим еще:
[ 2.910198] udevd[360]: inotify_init failed: Function not implemented
Добавляем поддержку часов реального времени, файловой системы vfat и inotify:
./scripts/config -e CONFIG_RTC_CLASS -e CONFIG_DNOTIFY -e CONFIG_INOTIFY_USER -e CONFIG_VFAT_FS
Собираем ядро:
$ 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
$
Хммм, vfat диск по прежнему не может подмонтироваться:
* Some local filesystem failed to mount
А вот и ответ почему в dmesg заодно с еще одной ошибкой:
[ 3.782884] udevd[527]: error creating signalfd
[ 4.107698] FAT-fs (sda15): codepage cp437 not found
Добавляем параметры:
./scripts/config -e CONFIG_SIGNALFD -e CONFIG_NLS_CODEPAGE_437 -e CONFIG_NLS_ISO8859_1
Собираем ядро:
$ 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
$
Загружаемся, в консоли ошибок нет, но появилась новая ошибка в dmesg:
[ 2.756136] udevd[360]: error creating epoll fd: Function not implemented
Исправляем:
./scripts/config -e CONFIG_EPOLL
Собираем ядро:
$ 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
$
Перезагружаемся и на этот раз не видим новых ошибок:)!
На моей рабочей машине (i7, 8 cores, 64gb RAM, tmpfs) финальная конфигурация собирается за 1m 16s. В oracle cloud с двумя ядрами и на обычном диске этот же процесс занимает 19m 51s.
Получившееся ядро не является абсолютно минимальным. Так, например, включение поддержки сети добавляет кучу разных сетевых адаптеров. Я не стал заниматься перфекционизмом и вычищать абсолютно все, что не нужно. Кроме того хочу предупредить, что хотя загрузка и быстрое тестирование не выявило проблем с отсутствием дополнительных важных компонент ядра скорее всего таковые существуют. Так что если вдруг вы решите переиспользовать мой конфиг пожалуйста тщательно протестируйте ядро для вашего конкретного случая и при необходимости добавьте нужные параметры ядра.