Сегодня я расскажу о том, как в Frenzy устроен процесс загрузки. В общих чертах он описан в документации, я же расскажу о тех деталях, которые в документацию не вошли.
Если не вдаваться в подробности, FreeBSD загружается так: сначала с помощью freebsd loader загружается ядро, потом монтируется корневая файловая система, с нее запускается /sbin/init, который в свою очередь запускает /etc/rc, из него выполняется куча скриптов, и в конце концов мы получаем приглашение login. Для нормальной загрузки с LiveCD нам нужно сымитировать этот процесс. Что ж, приступим.
Для загрузки ядра FreeBSD с CD есть специальный файл /boot/cdboot, который мы при сборке ISO-образа записываем в загрузочную область, тут все достаточно просто.
Теперь надо определиться, откуда будем брать корневую файловую систему. Проще всего, конечно, использовать файловую систему самого компакт-диска, с которого мы загружаемся. Но поскольку сменить корневую файловую систему на лету нельзя, загрузить такой LiveCD полностью в оперативную память или из образа с HDD не выйдет. Поэтому в Frenzy я использовал тот же метод, что и в установочном диске обычной FreeBSD - там в качестве корневой файловой системы используется небольшой сжатый gzip-ом файл-образ, который загружается сразу в память.
Добавляем в /boot/loader.conf строчки:
mfsroot_load="YES" mfsroot_type="mfs_root" mfsroot_name="/boot/frenzyroot"
Сама файловая система находится в /boot/frenzyroot.gz. Для экономии памяти туда помещен минимум программ. Кроме init и sh, без которых никак не обойтись, мы добавим туда нужные нам утилиты - cp, echo, test, mdconfig, mount, mount_msdosfs, mount_ntfs, mount_cd9660, mount_nullfs, mount_unionfs, umount и sleep. А для еще большей экономии мы сгенерируем с помощью crunchgen один статический бинарный файл, который будет содержать все эти утилиты, потом на этот файл создадим симлинки с нужными именами.
Итак, у нас есть минимальная корневая файловая система, после ее подключения ядро запустит /sbin/init, и он в свою очередь должен запустить /etc/rc. Однако тут мы делаем первый хак - /etc/rc в нашей корневой файловой системе мы заменили на собственный скрипт. Потом, конечно, мы запустим настоящий /etc/rc, однако предварительно мы должны все подготовить.
Сначала мы ищем устройство, на котором находится сжатый образ frenzy.uzip. Порядок поиска - флешки, CD, UFS и FAT-разделы жестких дисков. Флешки стоят в порядке поиска перед CD для того, чтобы можно было устроить комбинированную загрузку - ядро с CD, а все остальное с флешки (и CD вытащить можно). Пробуем подключать по очереди, как только находим на подключенном устройстве файл образа - продолжаем дальше. Файловая система загрузочного устройства будет смонтирована в /Frenzy/boot.
Небольшое отступление - что такое mount_nullfs и mount_unionfs
В процессе загрузки Frenzy активно используются этим методы монтирования. mount_nullfs позволяет смонтировать один каталог поверх другого, при этом содержимое исходного каталога доступно не будет. В отличие от него, mount_unionfs позволяет смонтировать каталоги друг поверх друга, объединяя содержимое обоих каталогов. В случае совпадения имен файлов приоритетным является тот, который находится в верхнем "слое". Если с файлами в каталоге производятся какие-то действия, то все изменения сохраняются также в верхнем "слое".
Теперь подключаем сжатую файловую систему из образа, монтируется она в каталог /Frenzy/fs. С помощью mount_nullfs мы подключаем каталоги /bin, /sbin, /usr, /var, /etc, /root из сжатой системы в корневую, выглядит это примерно так:
/Frenzy/fs ---- bin ------------ etc ----------- root --- sbin --- usr --- var -- | | | | | | V V V V V V корневая FS -- /bin -- /boot -- /etc -- /mnt -- /root -- /sbin -- /usr -- /var --(на этой и следующих схемах прямыми линиями изображены подключения mount_nullfs, плюсиками - mount_unionfs)
Теперь нам нужно сделать так, чтобы система не подозревала что все у нас только для чтения. Для этого сымитируем возможность записи с помощью mount_unionfs, создав отдельный диск в оперативной памяти и смонтировав каталоги с него поверх имеющихся. Увы, смонтировать RAM-диск сразу поверх всей корневой файловой системы не получится, mount_unionfs сделать это не даст, т.к. получится рекурсия. RAM-диск мы делаем с помощью mdconfig и монтируем его в /Frenzy/mfs. После всех этих операций получаем вот такой "бутерброд":
/Frenzy/mfs -------------------- etc --- mnt --- root ------------ usr --- var -- + | + + + V | V V V /Frenzy/fs ---- bin ------------ etc -----|----- root --- sbin --- usr --- var -- | | | | | | | V V V V V V V корневая FS -- /bin -- /boot -- /etc -- /mnt -- /root -- /sbin -- /usr -- /var --
Каталог mnt мы монтируем через mount_nullfs, т.к. в /mnt у нас изначально нет файлов, и делать mount_unionfs было бы излишне. Записываемый /mnt нам нужен для того, чтобы потом создавать каталоги точек монтирования. А вот каталог /usr на этом этапе мы пока записываемым не делаем, потому что впереди нас ждет подключение модулей FEM.
Тепер немного отвлечемся и рассмотрим, как реализована загрузка в память и загрузка из образа на жестком диске. Процессы эти в общем-то идентичны - нам нужно сменить файловую систему в /Frenzy/fs, подключив ее из другого frenzy.uzip - скопированного в память либо уже лежащего на одном из разделов жесткого диска. Поскольку у нас пока нет процессов, которые блокируют файловую систему, препятствуя ее размонтированию, дело обстоит достаточно просто - мы размонтируем верхние два "слоя" нашего бутерброда, после чего монтируем их снова, но уже взяв другой frenzy.uzip в качестве базы.
И только теперь мы готовы подключать модули. Модуль представляет собой сжатый образ каталога /usr, содержащий файлы из одного или нескольких пакаджей, который в дальнейшем будет подключен поверх уже имеющейся /usr. Но поскольку в пакаджах содержатся еще и install-скрипты (например, некоторые пакеты после установки добавляют в систему нового пользователя), простого монтирования будет недостаточно. Поэтому после подключения всех модулей будет вызвана процедура fem_postinstall, отвечающая за запуск install-скриптов из пакаджей.
Файловые системы модулей монтируются в каталоги, создаваемые в /Frenzy/ramdisk/fem. После этого они по очереди с помощью mount_unionfs друг поверх друга монтируются в /usr. И наконец, после того как все модули будут подключены, мы делаем /usr записываемым, подключая поверх него каталог usr из RAM-диска. В итоге выходит вот такая структура файловой системы:
/Frenzy/mfs -------------------- etc --- mnt --- root ------------ usr --- var -- + | + + + + | + + + + | + модуль2 + + | + + + + | + модуль1 + + | + + + V | V V V /Frenzy/fs ---- bin ------------ etc -----|----- root --- sbin --- usr --- var -- | | | | | | | V V V V V V V корневая FS -- /bin -- /boot -- /etc -- /mnt -- /root -- /sbin -- /usr -- /var --
Предполагаю, что к этому моменту у вас в голове вертится мысль "Нафига так сложно???". Ответ таков: в FreeBSD, увы, нет функции под названием pivot_root, которая существует в Linux. А функция эта позволяет переключить корневую файловую систему на уровне ядра (не как chroot, который меняет корневую FS для запущенного в нем процесса). Тогда весь этот процесс выглядел бы на порядок проще.
Ну вот и все, файловую систему мы подготовили, можно запускать системный /etc/rc. Вернее, можно было бы. Дело в том, что rc-скрипты среди всего прочего выполняют монтирование файловых систем из /etc/fstab и запуск на них fsck. Нам это совсем не нужно, поскольку сломает всё, что мы так долго строили. Так что приходится делать еще один хак и ряд скриптов, отвечающих за эти процессы, слегка модифицировать, добавив в их начало код "если мы загружаемся с CD, то выполнение команд пропустить".
Вот собственно и все. Посмотреть на скрипты, которые отвечают за процесс загрузки, можно в SVN-репозитории.
Продолжение, возможно, последует (хотелось бы узнать из комментариев, что еще вас интересует)