Как создаётся и работает слоистая файловая система Puppy.

Немного истории.

Самые первые Puppy работали по принципу «ядро в рамдиске» (vmlinuz + initrd). Посмотреть можно на немецком сайте MicroMuppy. По такому же принципу работает вполне современный TinyCore Linux размером 12Mb, обсуждение здесь. Но такая система на базе Puppy оказалась негибкой и Барри Каулер применил во второй версии Puppy unionfs а впоследствии aufs. Этот революционный шаг и сделал Puppy столь популярным.

Как это делается

Всё изложенное здесь относится к загрузке FRUGAL. При загрузке FULL производится монтирование раздела Puppy в корень как в любом обычном Linux, при этом initrd.gz не нужен (Во всяком случае так было со старыми версиями Puppy)

После загрузки и распаковки ядра (vmlinuz) и образа рамдиска (initrd.gz) мы имеем вполне работоспособную, но изолированную в оперативной памяти Linux-систему. Возможности этой системы сильно урезаны для уменьшения объёма и выполняет она одну задачу - создание слоистой файловой системы Puppy. Руководит этим процессом скрипт init. Первое, что делает init, определяет глобальные переменные и следом за этим «ищет связи с внешним миром», говоря технически устанавливает драйверы, необходимые для доступа к носителям информации (hd, sd, sr, flash).

Следующим этапом является поиск файлов Puppy. Он может производиться на всех носителях или, для ускорения загрузки с заданного раздела (например pdev1=sda2) и заданного каталога (например psubdir=puppy217). Имена файлов для поиска берутся из файла /DISTRO_SPECS (впоследствии сохраняется как /etc/DISTRO_SPECS). Полученные данные записываются в файл /tmp/PUPPYFILES (впоследствии сохраняется как /initrd/tmp/PUPPYFILES) и на основе их анализа выбирается способ загрузки Puppy - переменная PUPMODE (впоследствии записывается в /etc/rc.d/PUPSTATE). PUPMODE может принимать следующие значения:

  • PUPMODE=2 - Загрузка FULL (даже при отсутствии initrd.gz)
  • PUPMODE=5 - Первая (чистая) загрузка FRUGAL. Все изменения в системе сохраняются в tmpfs, по окончании сессии предлагается сохранить данные в файл сохранения.
  • PUPMODE=12 - Обычная загрузка FRUGAL. Все изменения в системе сразу записываются в файл сохранения.
  • PUPMODE=13 - Загрузка FRUGAL для флешек. Все изменения в системе сохраняются в tmpfs, по окончании заданного времени (по умолчанию 30 мин.) или окончании сессии все изменения в системе записываются в файл сохранения.
  • PUPMODE=77 - Загрузка FRUGAL для мультисессионного CD-RW. По окончании сессии все изменения в системе записываются в каталог на диске.

Примечание - имя файла сохранения настроек обязательно содержит $DISTRO_FILE_PREFIX (берётся из /etc/DISTRO_SPECS), _save, произвольную часть задаваемую пользователем и расширения .2fs .3fs .4fs в зависимости от внутренней ФС файла сохранения. Например pupm_save-mysavefile.3fs.

Подробнее

После загрузки драйверов носители информации становятся доступными для монтирования. Командой init_probepart -k получается список разделов и циклом производится их последовательное монтирование к /mnt/data. Командой find производится последовательный поиск файлов Puppy с записью каждого найденного файла в соответствующую этому файлу переменную. Если в строке загрузки указан параметр psubdir, то поиск производится только внутри указанного каталога. По окончании цикла переменные записываются в /tmp/PUPPYFILES. Если указан параметр pdev1, то командой grep удаляются все строки, не соответствующие шаблону $PDEV1
Примечание. $PDEV1 это указанное значение параметра pdev1. Например если в строке загрузки записано pdev1=sda2, то значением переменной $PDEV1 будет sda2
В зависимости от наличия и состава найденных файлов, а так же от параметра pmedia выбирается режим загрузки PUPMODE (значения смотрите выше), а в зависимости от числового значения $PUPMODE задаётся шаблон монтирования в виде набора переменных. Каждую переменную из набора обрабатывает свой участок скрипта init по соответствующему сценарию. В результате мы получаем набор каталогов /pup_rw, /pup_ro2 и т. д. (зависит от $PUPMODE) с примонтированными к ним файлами Puppy. При значениях PUPMODE=5 или 13 к /pup_rw монтируется tmpfs. Далее весь набор pup_r* согласно очереди монтируется к /pup_new командой

mount -t aufs -o udba=reval,diropq=w,dirs=${UMNTMAIN}${ZLAYER}${UMNTRO} unionfs /pup_new

где ${UMNTMAIN} то, что грузится в верхние слои, например 1 и 2 /pup_rw=rw:/pup_ro2=ro:
${ZLAYER} (если есть) = /pup_ro3=rw: , ${UMNTRO} - всё остальное с 4 слоя и ниже. Обычно это дополнительные модули.

Последним выполняется exec switch_root /pup_new /sbin/init (смена корня) и мы уже в полноценной файловой системе Puppy. Дальнейшей загрузкой руководит /etc/rc.d/sysinit.

Как это может быть сделано

Здесь описывается попытка создания своего скрипта init по более простому сценарию.

Если внимательно присмотреться к логике секций «Поиск файлов Puppy» и «Подключение файлов Puppy» то видно, что это «сетевое планирование», всеохватывающее и громоздкое. Кроме того создаётся множество «одноразовых» переменных.

Объединив поиск и подключение в одном цикле удалось избавиться от лишнего кода (450 строк против прежних 1500) и ускорить загрузку.

Что сделано:

  • Для получения списка разделов вместо скриптового probepart_init -k применён fdisk -l, работающий гораздо быстрее, и применяется только при отсутствии $PDEV1
fdisk -l | grep ^/ | grep -v swap | cut -f 1 -d ' '
  • Для исключения лишних монтирований $PDEV1 монтируется сразу к /mnt/dev_base и в случае обнаружения на разделе базового файла цикл прерывается командой break, отмонтирование не производится.
if [ "$PBASE" = "" ]; then
umount /mnt/dev_base
continue
else
echo "$PBASE" > /tmp/PUPPYFILES
break
fi
  • Монтирование найденных файлов производится по мере их нахождения и, благодаря новому busybox, производится одной командой (Приведён пример подключения базового файла)
mount -o loop /mnt/dev_base$PSUBDIR/${DISTRO_FILE_PREFIX}-${DISTRO_VERSION}*.sfs /pup_ro2
  • Переменная ${UMNTMAIN} создаётся в процессе монтирования.
  • Для монтирования дополнительных модулей применена обрабатываемая циклом переменная $EXTRASFSLIST, которая формируется из:
  1. переменной $modules, которая берётся из строки загрузки
  2. списка модулей из каталога, указанного в переменной $PFSDIR
EXTRASFSLIST="$modules $(find /mnt/dev_base/${PFSDIR} -name *\.pfs -exec basename {} \;)"
  • Переменная ${UMNTRO} создаётся в процессе работы цикла обработки $EXTRASFSLIST

Не реализован пока поиск save-файла, если он находится на другом разделе. Так же исключена загрузка по сети, сильно увеличивает размер initrd.gz из-за добавления драйверов, но это легко вернуть.

Как это работает

Работает этот «слоистый бутерброд» довольно просто. Обращение к файлам происходит по полным путям (например /initrd/pup_ro2/etc/… или /mnt/.opera-12.11/…) согласно «очереди» aufs. Поиск идёт до первого совпадения. Поэтому одноимённые файлы из верхних слоёв закрывают файлы из нижних. При изменении файла он полностью записывается в верхний слой. При удалении файла, находящегося не в первом слое создаётся «тень» - скрытый файл .wh.filename, при удалении каталога - .wh..wh.dirname. Эти файлы прерывают поиск и файл или каталог как бы не существует в корне системы, хотя по полному пути его можно найти.

Для тех, кто хочет подробнее

Очень подробно ФС Puppy рассмотрена в статье Фарватера "Архитектура файловой системы Puppy Linux".

Печать/экспорт