Содержание статьи
Bodhi Linux 6.0 — это несколько своеобразный дистрибутив, но именно на нем я остановил свой выбор. Почему? Во‑первых, он основан на Ubuntu 20.04 LTS, что дает доступ к огромному числу программ в официальных репозиториях Ubuntu. Во‑вторых, у Ubuntu огромное сообщество, опытом которого можно пользоваться. В‑третьих, мне приглянулась легковесная графическая среда Moksha Desktop. Да, у нее есть нюансы в настройке, но разобраться с ними нужно всего один раз. Ну и в‑четвертых, этот дистрибутив минималистичен, но содержит инструменты Build Essential, которые позволяют быстро что‑нибудь скомпилировать.
Итак, для установки Bodhi Linux требуется 768 Мбайт оперативной памяти, объем которой можно уменьшить до 384 Мбайт на работающей системе, и при этом ей все еще можно будет пользоваться. Такая экономичность позволяет создать приличный пул виртуальных машин, например для моделирования сетевых конфигураций.
Если на виртуальной машине подкорректировать объем памяти можно за несколько секунд, то для установки Bodhi Linux на устаревший компьютер с 512 Мбайт ОЗУ придется вместо этого где‑то искать и устанавливать дополнительную планку памяти. Да и в конце концов, почему Bodhi Linux (и многие другие современные дистрибутивы) так прожорливы на этапе установки, хотя могут потом работать в гораздо более скромном окружении?!
Для ответа на этот вопрос надо посмотреть, как происходит загрузка Bodhi Linux с дистрибутивного носителя (на схеме ниже). Первым делом загрузчик операционной системы помещает с носителя в оперативную память ядро Linux /
с упакованным содержимым минимальной файловой системы (МФС) /
, формирует окружение ядра с параметрами командной строки и передает ядру управление. Ядро получает параметры из окружения и принимает их к сведению, определяет и инициализирует основное оборудование компьютера, после чего распаковывает МФС и запускает процесс init, текст сценария которого содержится в файле /
распакованной файловой системы.
Основная задача процесса init на данном этапе — отыскать на носителе образ основной файловой системы /
, переключиться на него с МФС и запустить свое продолжение. Для этого могут потребоваться модули ядра с драйверами устройства, которые обслуживают носитель с дистрибутивом и обеспечивают понимание его файловой системы. Эти драйверы, если они не встроены в ядро, должны быть доступны из МФС.
После переключения на основную файловую систему МФС больше не требуется, и занимаемая ей оперативная память освобождается. Файловая система squashfs позволяет работать с ней в режиме «только чтение» непосредственно из файла с образом. С этого момента оперативная память используется в основном для следующих целей:
- хранения кода выполняющихся процессов и обрабатываемых ими данных;
- хранения изменений, которые происходят в основной файловой системе (это реализуется с помощью механизма overlayfs: дерево каталогов виртуальной файловой системы tmpfs, созданной в оперативной памяти, накладывается на дерево каталогов основной файловой системы squashfs);
- прочих системных кешей и буферов.
На каком же этапе возникает проблема в последовательности этих шагов? Чтобы узнать это, загрузим Bodhi Linux в виртуальной машине с 512 Мбайт ОЗУ. И почти сразу после того, как мы в меню загрузки выберем Try Bodhi или Install Now, получим черный экран смерти со множеством диагностических сообщений. Несмотря на то что заключительный вердикт звучит как «Kernel panic - not syncing: No working init found» («Ядро остановлено — не синхронизировано: не найден рабочий init»), суть проблемы отражает самая первая строка: «Initramfs unpacking failed: write error» («Сбой при распаковке initramfs: ошибка записи»). Это значит, что ядро не смогло распаковать МФС из initrd.
из‑за нехватки оперативной памяти.
Причина проблемы
Почему же памяти не хватило? По большому счету на этом этапе в ОЗУ находятся ядро и заархивированная МФС. Во время загрузки ядро Linux активно выделяет, перемещает и освобождает области памяти. Если ты не разработчик ядра, уследить за всеми выполняемыми действиями довольно сложно. К счастью, когда ситуация стабилизируется, ядро формирует информационное сообщение и узнать «окончательный счет» можно с помощью команды
$ dmesg | grep ']\sMemory'
Ниже приведены результаты ее выполнения для диапазона оперативной памяти от 384 до 1024 Мбайт с шагом 128 Мбайт:
384 Мб: [ ] Memory: 263248K/392760K available (14339K kernel code, 2400K rwdata, 5008K rodata, 2732K init, 4972K bss, 129512K reserved, 0K cma-reserved)
512 Мб: [ ] Memory: 392272K/523832K available (14339K kernel code, 2400K rwdata, 5008K rodata, 2732K init, 4972K bss, 131560K reserved, 0K cma-reserved)
640 Мб: [ ] Memory: 520528K/654904K available (14339K kernel code, 2400K rwdata, 5008K rodata, 2732K init, 4972K bss, 134376K reserved, 0K cma-reserved)
768 Мб: [ ] Memory: 649552K/785976K available (14339K kernel code, 2400K rwdata, 5008K rodata, 2732K init, 4972K bss, 136424K reserved, 0K cma-reserved)
896 Мб: [ ] Memory: 778576K/917048K available (14339K kernel code, 2400K rwdata, 5008K rodata, 2732K init, 4972K bss, 138472K reserved, 0K cma-reserved)
1024 Мб: [ ] Memory: 907600K/1048120K available (14339K kernel code, 2400K rwdata, 5008K rodata, 2732K init, 4972K bss, 140520K reserved, 0K cma-reserved)
На основании этих сведений можно сделать вывод, что объем памяти, занимаемой процессом ядра с сегментами кода (kernel code), констант (rodata), изменяемых неинициализированных (rwdata) и инициализированных (bss) данных постоянно и составляет 29 451 Кбайт или примерно 29 Мбайт. Объем дополнительно резервируемой ядром памяти в некоторой степени зависит от общего объема оперативной памяти компьютера, но в основном определяется размером файла initrd.
. Можно сказать, что значение reserved равно сумме размера процесса ядра (29 Мбайт), файла initrd.
(87 Мбайт) и системных структур (10–20 Мбайт).
Как считать память, если ее не хватило
Чтобы получить журнал сообщений ядра при 384 Мбайт и 512 Мбайт ОЗУ, когда ядро не может распаковать МФС, перенаправь консоль в последовательный порт ttyS0, добавив параметры ядра в командной строке загрузчика:
console=ttyS0 console=tty0 ignore_loglevel
А сам порт перенаправляется в файл настройкой виртуальной машины VirtualBox, как показано на рисунке.
В остальных случаях, когда оперативной памяти достаточно для распаковки МФС, можно назначить прерывание процесса init после загрузки драйверов, достаточных для монтирования накопителей. Для этого к параметрам ядра надо добавить break=mount
. Когда появится командная строка BusyBox, можно вручную примонтировать к МФС основной накопитель и сохранить на него необходимые сведения.
Теперь мы можем посчитать, что объем свободной оперативной памяти на данном этапе составляет 512 – 128 = 384 Мбайт. Это довольно много, если учесть, что перед ядром стоит единственная задача распаковки МФС из сжатого алгоритмом LZ4 архива. Сейчас самое время вспомнить, что в распакованном виде эта файловая система занимает 242 Мбайт, и для ее хранения этот объем надо вычесть из имеющегося свободного пространства: 384 – 242 = 142 Мбайт. Но это все еще приличный запас.
Распаковкой и одновременно формированием МФС занимается конечный автомат, реализованный в модуле init/initramfs.c. Он по мере необходимости резервирует буферную память, которая освобождается полностью только после завершения его работы. Поэтому в описываемых условиях свободная оперативная память исчерпывается в момент формирования 384/2 = 192 Мбайт МФС из 242 Мбайт необходимых.
Парадоксальная идея
Что, если не архивировать cpio-блок с МФС? Идея парадоксальная, но тогда ядру не придется выделять память для распаковки и дистрибутив запустится на системах с 512 Мбайт ОЗУ. Естественно, у такого способа будут недостатки. Во‑первых, размер образа увеличится на 242 – 83 = 159 Мбайт. Из‑за этого может возрасти время загрузки, если носитель с образом не очень производительный, например DVD-ROM или флешка USB 2.0.
Но хуже всего то, что этот способ совершенно неработоспособен, потому что из‑за увеличения файла initrd.
объем свободной памяти сократится на 159 Мбайт, после чего вместо 384 Мбайт останется 225 Мбайт, в которые никак не сможет поместиться 242-мегабайтная МФС. А на самом деле оказывается, что перенос содержимого из cpio-блока в МФС тоже производится через буферную память, что вызывает аварийное прерывание процесса после обработки примерно 112 Мбайт. Поэтому надо искать другие пути.
Изучаем состав минимальной ФС
Единственный объект, модификация которого может помочь сэкономить память на начальном этапе загрузки, — это МФС. Определить, какой объем занимают ее части, нетрудно — достаточно выполнить внутри интересующих каталогов команду
$ du -shm * | sort -rn | head
Она подсчитывает и наглядно показывает размеры подкаталогов в текущем каталоге. В результате получилась карта самых крупных объектов МФС.
Подавляющую долю МФС составляют бинарные файлы, предоставленные производителями устройств, и модули ядра с драйверами устройств. Хорошо бы узнать, из каких пакетов они появились:
$ dpkg-query -S /lib/firmware
wireless-regdb, linux-firmware, intel-microcode, amd64-microcode: /lib/firmware
$ dpkg-query -S /lib/modules
linux-modules-extra-5.4.0-72-generic, linux-modules-5.4.0-72-generic, linux-headers-5.4.0-72-generic: /lib/modules
Оказывается, источники той массы файлов, из‑за которых «распухает» initrd.
, — это всего несколько пакетов. Сгруппировать их по выполняемым функциям и удалить лишние не выйдет. Оставим пока эту проблему и попробуем выяснить, все ли эти пакеты необходимы для начальной загрузки ОС. Для ответа на этот вопрос надо обратиться к сценарию /
, который находится в корне МФС и координирует все выполняемые на этом этапе действия.
Исследование сценария /init
Сначала этот сценарий инициализирует переменную окружения PATH
, чтобы обеспечить себе доступ к системным утилитам:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
После этого он дополняет файловую систему каталогами, чтобы приблизить ее к Filesystem Hierarchy Standard. Некоторые каталоги монтируются специальным образом для отображения в них виртуальных файловых систем:
/dev
/root
/sys <--- mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
/proc <--- mount -t proc -o nodev,noexec,nosuid proc /proc
/tmp
/var/lock
/dev <--- mount -t devtmpfs -o noexec,nosuid,mode=0755 udev /dev
/dev/pts <--- mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts
Затем формируется среда окружения, в которой отметим переменную RUNSIZE
. Она определяет размер оперативной памяти, который будет зарезервирован для виртуальной файловой системы /
. Значение этой переменной извлекается из конфигурационного файла /
и по умолчанию составляет 10%.
После подключения подпрограмм из библиотеки /
запускается цикл разбора параметров, переданных ядру Linux из командной строки загрузчика:
for x in $(cat /proc/cmdline) case $x in ...
ИмяПараметра=*) ИМЯПАРАМЕТРА="${x#ИмяПараметра=}" ;; ...
initramfs.runsize=*) RUNSIZE="${x#initramfs.runsize=}" ;; boot=*) BOOT=${x#boot=} ;; break=*) break=${x#break=} ;; break) break=premount
;; ...
esacdone
В основном он дополняет или изменяет переменные среды. Например, на значение переменной RUNSIZE
можно повлиять, указав параметр ядра initramfs.
. Переменная BOOT — индикатор способа загрузки ОС: local, nfs или casper. В дальнейшем она используется для подключения сценариев монтирования основной файловой системы в команде такого вида:
. /scripts/${BOOT}
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»