Здесь описаны некоторые приёмы организации встраиваемых ОС GNU/Linux, не являющиеся обязательной частью системы сборки, но применяемой несколькими проектами.
По возможности, иерархия файловой системы встраиваемой ОС GNU/Linux должна придерживаться Filesystem Hierarchy Standard, при необходимости расширяя его.
Однако, не следует бездумно копировать решения, принятые для больших многопользовательских машин с множеством устройств памяти прямого доступа и для бездисковых рабочих станций.
Зависит от особенностей целевой платформы. Как правило, используются особые загрузчики для встраиваемых ОС (например, RedBoot, U-Boot), хотя и занимающих отдельные разделы в постоянной памяти целевой системы, однако не имеющие там структуры общеупотребимой файловой системы. Поэтому каталог /boot, как правило, не нужен.
Содержит основные статические файлы устройств, необходимые для начальной загрузки, например, /dev/mtd*, /dev/console. Затем монтируется на tmpfs и заполняется файлами устройств, чьи драйверы присутствуют в ядре.
Монтируется на tmpfs. Это полностью соответствует роли /tmp в FHS и автоматически решает проблему очистки содержимого при перезагрузке.
Ничего из того, для чего была придумана отдельная иерархия /usr, в типичной встраиваемой ОС не нужно и является избыточным усложнением. Всё содержимое /usr прекрасно укладывается в иерархию корня.
Файлы конфигурации располагаются в /etc, в соответствии с FHS. Файлы конфигурации подвергаются изменениям, отражая настройки пользователя. При обновлении системы заменой образа корневой ФС (основной способ обновления встраиваемых устройств) настройки пользователя потеряются, если содержимое /etc находится в корневой ФС. Наилучшим решением задачи сохранения настроек пользователя после обновления корневой ФС является перенесение файлов конфигурации в отдельный раздел, не затрагиваемый обновлением системы.
Раздел конфигурации монтируется в каталог /var/conf. /etc переименовывается в “/etc_” и содержит начальные, “заводские” настройки. /etc делается символическим линком на /var/conf/etc. При загрузке все новые файлы из “/etc_” копируются в /etc, то есть, в /var/conf/etc.
Поскольку содержимое /etc теперь копируется в новый раздел и может быть сброшено пользователем в “заводские” файлы из “/etc_”, расположение стартовых скриптов в /etc/init.d становится обременительным. Поэтому стартовые скрипты вынесены из /etc/init.d в новый каталог /init.
Основной способ обновления встраиваемых устройств - замена образов ядра и корневой ФС и перезагрузка.
Ядро после загрузки полностью располагается в ОЗУ и никак не связано с образом ядра в разделе ядра. Образ ядра может быть обновлён не прекращая работы системы, а для вступления обновления в силу необходима перезагрузка.
С корневой ФС сложнее,
Если корневая ФС полностью располагается в памяти, как cramfs и другие подобные, то монтированная корневая ФС никак не связана с образом ФС на носителе и образ ФС можно заменить так же, как и ядро.
Но если корневая ФС смонтировна в образ ФС на разделе носителя, то для обновления образа необходимо ФС размонтировать, что невозможно для корневой ФС без прекращения работы системы.
Широко распространено заблуждение, что если корневая ФС смонтирована только для чтения или даже поверх ФС применяется snapshot, позволяющий временно перенаправлять запись в ФС в ОЗУ, оставляя образ ФС неизменным, то можно безопасно заменить образ ФС под работающей системой. Это опасное заблуждение, объяснение причин которого выходит за рамки документа.
Решением проблемы является помещение новой версии образа корневой ФС в раздел конфигурации и прошивка его в носитель при загрузке системы до монтирования корневой ФС.
Это достигается добавлением в ядро образа initramfs, монтируемого в корень на ранней стадии загрузки системы. Стартовый скрипт из образа initramfs временно монтирует раздел конфигурации, проверяет наличие обновления, записывает новые образы ядра и корневой ФС в соответствующие разделы носителя, затем монтирует новый корень и переключается на него.
Наиболее распространённой системой стартовых скриптов является система, придуманная для AT&T UNIX System V.
Эта система предназначена для многопользовательских машин, содержит многочисленные стадии загрузки и остановки системы (уровни исполнения), позволяя системному администратору обслуживать систему, переключая уровни исполнения, тем самым запуская и останавливая группы процессов (системных демонов) и разрешая/запрещая пользователям работать.
Порядок исполнения скриптов определяется буквой уровня исполнения и числом в начале их имени. Отдельные каталоги уровней исполнения наполнены символическими ссылками на файлы скриптов. Добавление нового скрипта требует выбора числа, определяющего порядок исполнения скрипта среди других скриптов.
Все эти навороты совсем не нужны во встраиваемых системах.
Тем не менее, нередки случаи бездумного использования этой системы во встраиваемых ОС.
Для встраиваемой ОС вполне достаточно двух “уровней” исполнения: загрузки и остановки/перезагрузки системы.
Каждый пакет, нуждающийся в выполнении каких-либо действий при загрузке и остановке системы, поставляет свой скрипт, помещаемый в каталог корневой ФС /init.
Скрипт содержит ключевые слова, по которым утилита rcorder, перенесённая из NetBSD, строит список скриптов, упорядоченный в соответствии с зависимостями, записанными в перечисленных ей скриптах.
Полученный список исполняется главным загрузочным скриптом /init/rcS.
При остановке/перезагрузке системы по командам halt или reboot, исполняется скрипт /init/shutdown, который так же запускает rcorder, но исполняет полученный список скриптов в обратном порядке.
Фрагмент главного загрузочного скрипта /init/rcS:
echo "----------------- Starting services -------------------"
echo -ne "${BULLET} Generating rcorder list${DOTS}"
rclist=`/sbin/rcorder -k start /init/*`
res $?
for rc in $rclist; do
if test -x $rc; then
$rc start
fi
done
echo "----------------- Starting services done ---------------"
Пример скрипта /init/openntpd.rc:
#!/bin/sh
# KEYWORD: start stop
# PROVIDE: openntpd net-time-once net-time-cont
# REQUIRE: sysklog network
. /etc/profile
. /init/functions
ntp_opts="-s -f /etc/openntpd.conf"
start()
{
if ! test -e /etc/openntpd.conf; then
/init/openntpd-reconf start
fi
doing "Starting openntpd" \
"mkdir -p /var/run/openntpd && /sbin/openntpd $ntp_opts"
}
stop() { dokillall_wait "Stopping openntpd" openntpd; }
run "$1"