Содержание
Как создаётся и работает слоистая файловая система 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, которая формируется из:
- переменной $modules, которая берётся из строки загрузки
- списка модулей из каталога, указанного в переменной $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".