Есть мно­го задач, которые про­ще решить в GNU/Linux, чем где‑то еще, поэто­му вир­туал­ки с Linux в наше вре­мя — это обыч­ное дело. И чем мень­ше будет занимать на дис­ке и в памяти такая вир­туал­ка, тем луч­ше. В этой статье я возь­му за осно­ву дис­три­бутив Bodhi Linux и покажу, как умень­шить его тре­бова­ния к опе­ратив­ной памяти, что­бы он зарабо­тал на машинах с 512 Мбайт вмес­то офи­циаль­но необ­ходимых 768 Мбайт. Опи­сан­ные рекомен­дации акту­аль­ны для всех сов­ремен­ных вер­сий Ubuntu.

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 /casper/vmlinuz с упа­кован­ным содер­жимым минималь­ной фай­ловой сис­темы (МФС) /casper/initrd.gz, фор­миру­ет окру­жение ядра с парамет­рами коман­дной стро­ки и переда­ет ядру управле­ние. Ядро получа­ет парамет­ры из окру­жения и при­нима­ет их к све­дению, опре­деля­ет и ини­циали­зиру­ет основное обо­рудо­вание компь­юте­ра, пос­ле чего рас­паковы­вает МФС и запус­кает про­цесс init, текст сце­нария которо­го содер­жится в фай­ле /init рас­пакован­ной фай­ловой сис­темы.

Использование ОЗУ при загрузке Bodhi Linux
Ис­поль­зование ОЗУ при заг­рузке Bodhi Linux

Ос­новная задача про­цес­са init на дан­ном эта­пе — отыс­кать на носите­ле образ основной фай­ловой сис­темы /casper/filesystem.squashfs, перек­лючить­ся на него с МФС и запус­тить свое про­дол­жение. Для это­го могут пот­ребовать­ся модули ядра с драй­верами устрой­ства, которые обслу­жива­ют носитель с дис­три­бути­вом и обес­печива­ют понима­ние его фай­ловой сис­темы. Эти драй­веры, если они не встро­ены в ядро, дол­жны быть дос­тупны из МФС.

Пос­ле перек­лючения на основную фай­ловую сис­тему МФС боль­ше не тре­бует­ся, и занима­емая ей опе­ратив­ная память осво­бож­дает­ся. Фай­ловая сис­тема 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.gz из‑за нех­ватки опе­ратив­ной памяти.

 

Причина проблемы

По­чему же памяти не хва­тило? По боль­шому сче­ту на этом эта­пе в ОЗУ находят­ся ядро и заар­хивиро­ван­ная МФС. Во вре­мя заг­рузки ядро 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.gz. Мож­но ска­зать, что зна­чение reserved рав­но сум­ме раз­мера про­цес­са ядра (29 Мбайт), фай­ла initrd.gz (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.gz объ­ем сво­бод­ной памяти сок­ратит­ся на 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.gz, — это все­го нес­коль­ко пакетов. Сгруп­пировать их по выпол­няемым фун­кци­ям и уда­лить лиш­ние не вый­дет. Оста­вим пока эту проб­лему и поп­робу­ем выяс­нить, все ли эти пакеты необ­ходимы для началь­ной заг­рузки ОС. Для отве­та на этот воп­рос надо обра­тить­ся к сце­нарию /init, который находит­ся в кор­не МФС и коор­диниру­ет все выпол­няемые на этом эта­пе дей­ствия.

 

Исследование сценария /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. Она опре­деля­ет раз­мер опе­ратив­ной памяти, который будет зарезер­вирован для вир­туаль­ной фай­ловой сис­темы /run. Зна­чение этой перемен­ной извле­кает­ся из кон­фигура­цион­ного фай­ла /conf/initramfs.conf и по умол­чанию сос­тавля­ет 10%.

Пос­ле под­клю­чения под­прог­рамм из биб­лиоте­ки /script/functions запус­кает­ся цикл раз­бора парамет­ров, передан­ных ядру 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
;;
...
esac
done

В основном он допол­няет или изме­няет перемен­ные сре­ды. Нап­ример, на зна­чение перемен­ной RUNSIZE мож­но пов­лиять, ука­зав параметр ядра initramfs.runsize. Перемен­ная BOOT — инди­катор спо­соба заг­рузки ОС: local, nfs или casper. В даль­нейшем она исполь­зует­ся для под­клю­чения сце­нари­ев мон­тирова­ния основной фай­ловой сис­темы в коман­де такого вида:

. /scripts/${BOOT}

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии