Daniel Robbins (drobbins@gentoo.org)
President/CEO,
Gentoo Technologies, Inc.
October 2001
С выходом релиза 2.4 Linux появилась возможность использования filesystem с новыми свойствами, таких как Reiserfs, XFS, GFS и других. Эти filesystems еще не достаточно опробованы и имеются вопросы, что именно они могут делать, насколько они хороши и насколько оправдано их использование в промышленной Linux среде. В этой статье Daniel демонстрирует использование init wrapper и конвертацию системы на использование "devfs mode".
Эта статья завершает описание конвертации Linux под использование devfs или Device Filesystem. Для тех, кто только что начал чтение, будут полезны ссылки. В части 4 этой серии статей объяснялось, как devfs решает проблему регистрации устройств на уровне ядра. В части 5 я описал все шаги, необходимые для придания вашей Linux системе свойства devfs-совместимости (без этого переход на devfs невозможен).
Если вы не читали часть 5, очень важно сделать это теперь, прежде чем выполнить описанные ниже команды. Если не сделать предварительную подготовку, есть гарантия, что init wrapper, который будет инсталлирован, правильно работать не сможет. В таком случае вы останетесь с системой, которая не может загружаться и требует больших усилий для своей "реанимации". Однако если вы прочли и сделали все, о чем писалось в части 5, можно двигаться дальше.
Часть 5 этой серии статей завершилась описанием концепции init wrapper с объяснением, почему именно таким способом удобно решать проблему инициализации devfs. Без углубления в детали, все же по шагам "пройдемся" по полной версии init wrapper и "вникнем" в ее части. Начнем с вершины:
The init wrapper, top portion
#!/bin/bash
# Copyright 2001 Daniel Robbins <drobbins@gentoo.org>, Gentoo Technologies, Inc.
# Distributed under the GNU General Public License, version 2.0 or later.
trap ":" INT QUIT TSTP
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
umask 022
if [ $$ -ne 1 ]
then
exec /sbin/init.system $*
fi
Как можно заметить, init wrapper - "правильный" bash script
, что
следует из "магической" первой строки #!/bin/bash
. Самое время
заметить, что этот init wrapper требует версию bash 2.0 или современней.
Введите /bin/bash --version
, чтобы узнать, какая версия
инсталлирована на вашей системе. В системе может быть установлено несколько
версий интерпретатора. При этом может иметься отдельный исполняемый файл
/bin/bash2
. В таком случае скорректируйте первую строку сценария.
Теперь смотрим ниже. Команда trap
защищает сценарий от прерывания
пользователем (например, комбинации CRTL-C в процессе начальной загрузки), если
его выполнение уже началось. Далее, экспортируется path по умолчанию и
устанавливается (по умолчанию) umask 022. Явное назначение umask для использования
в процессе начальной загрузки не помешает, тем более что на ранних ядрах
серии 2.4 имелся дефект, приводивший к установке по умолчанию umask 0.
Ниже - первая условная инструкция, if [ $$ -ne 1 ]
. Интерпретатор
разворачивает $$
в process ID (текущего процесса). Инструкция
читается как "отличается ли ИДЕНТИФИКАТОР текущего процесса от 1?" Что за этим
скрывается? Первый процесс, запущенный ядром при загрузке, всегда получает PID 1.
Именно этот номер зарезервирован для процесса init
. Если PID не
равен 1, можно уверенно сказать, что сценарий запущен из командной строки уже
после загрузки системы. Заметим, второе вполне нормально, так как команда
/sbin/init
имеет двойное назначение и при запуске суперпользователем
на загруженной машине используется для смены runlevel. Во втором случае сценарий
просто запустит через exec
оригинальный /sbin/init
, на
нашей системе переименованный в /sbin/init.system
. Используя
переменную $*
можно любой из параметров, введенный в командной
строке, передать программе init.system.
В случае, когда wrapper запускается ядром при начальной загрузке, сценарий
получает PID равный 1. Первая условная конструкция будет пропущена, а
bash
продолжит выполнение wrapper. Просмотрим следующие строки:
mount -n /proc
devfs="yes"
for copt in `cat /proc/cmdline`
do
if [ "${copt%=*}" = "wrapper" ]
then
parms=${copt##*=}
#parse wrapper option
if [ "${parms/nodevfs//}" != "${parms}" ]
then
devfs="no"
fi
fi
done
Если отрабатывается эта часть кода, значит, идет процесс начальной загрузки.
Первое действие - монтирование /proc к файловой системе root, которая все еще
установлена в режиме read-only. После этого следует большой и сложный кусок
bash
кода, который использует выгоды одной очень удобной особенности
Linux. Вы могли этого и не знать, но ядро позволяет увидеть, какие опции были
ему переданы через LILO или GRUB по содержимому /proc/cmdline
. На
моем development box содержимое /proc/cmdline следующее:
# cat /proc/cmdline
root=/dev/hda6 hda=89355,16,63 mem=524224K
Мы воспользуемся преимуществом существования /proc/cmdline
, сканируя
этот файл для поиска опции начальной загрузки с именем wrapper
.
Если среди kernel boot options появится wrapper=nodevfs
, то сценарий
не станет делать enabled devfs. В случае если такая переменная отсутствует в
/proc/cmdline
, то wrapper bash script
продолжит
devfs инициализацию. Мораль этой истории - вы можете легко отключать инициализацию
devfs, передав ядру в паузе LILO или GRUB параметр wrapper=nodevfs
.
Если сделано так, сценарий переопределит значение переменной devfs в
no
, иначе - оставит yes
.
Wrapping как он есть.
Теперь оставшаяся часть wrapper:
if [ "$devfs" = "yes" ]
then
if [ -e /dev/.devfsd ]
then
clear
echo
echo "The init wrapper has detected that /dev has been automatically mounted by"
echo "the kernel. This will prevent devfs from automatically saving and"
echo "restoring device permissions. While not optimal, your system will still"
echo "be able to boot, but any perm/ownership changes or creation of new compat."
echo "device nodes will not be persistent across reboots until you fix this"
echo "problem."
echo
echo "Fortunately, the fix for this problem is quite simple; all you need to"
echo "do is pass the \"devfs=nomount\" boot option to the kernel (via GRUB"
echo "or LILO) the next time you boot. Then /dev will not be auto-mounted."
echo "The next time you compile your kernel, be sure that you do not"
echo "enable the \"Automatically mount filesystem at boot\" devfs kernel"
echo "configuration option. Then the \"devfs=nomount\" hack will no longer be"
echo "needed."
echo
read -t 15 -p "(hit Enter to continue or wait 15 seconds...)"
else
mount -n /dev /dev-state -o bind
mount -n -t devfs none /dev
if [ -d /dev-state/compat ]
then
echo Copying devices from /dev-state/compat to /dev
cp -ax /dev-state/compat/* /dev
fi
fi
/sbin/devfsd /dev >/dev/null 2>&1;
fi
exec /sbin/init.system $*
Перед нами большая условная инструкция, которая выполняется только тогда, когда
переменная devfs установлена в yes
. Иначе, инициализация devfs
пропускается полностью, а devfs не пытается монтироваться. В таком случае
произойдет традиционная non-devfs boot.
Однако если выбрана установка devfs, сценарий входит внутрь условного выражения.
В этой инструкции выясняется, была ли devfs уже смонтирована ядром. Делается это
через простой "визуальный" контроль наличия символьного устройства
/dev/.devfsd
. При монтировании devfs ядром это устройство создается
автоматически, а будущий devfsd процесс будет использовать его для связи с ядром.
Если devfs уже смонтирована (пользователь выбрал опцию "Automatically mount
devfs at boot" перед компиляцией ядра), происходит распечатка информационного
сообщения. В сообщении говорится, что невозможно повторно установить features of
devfs, так как сделать это можно через wrapper только тогда, когда это еще не
сделано самим ядром.
Если все OK, мы выполняем установку devfs, которая была описана в конце прошлой
статьи. При этом /dev is bind-mounted к /dev-state, а файловая система devfs
монтируется к /dev. Теперь о том, что было пропущено в прошлой статье. Мы
проверяем существование каталога /dev-state/compat
и рекурсивно
копируем его содержимое в /dev. Такая процедура может показаться избыточной
(мы ведь собираемся пользоваться преимуществами devfsd, не так ли?), но как
выясняется необходимая и полезная. Причина, по которой необходим каталог compat
в том, что devfsd's persistence features работают только с devfs-enabled
drivers.
Что случиться, если в системе используется non-devfs kernel module? Казалось бы,
достаточно создать device node в /dev вручную. Проблема в том, что созданный
вручную new device node будет проигнорирован devfsd
, а это означает,
что после очередной перезагрузки он исчезнет и его придется "пересоздавать".
Решение проблемы в том, чтобы иметь каталог /dev-state/compat
. Если
имеется non-devfs module, просто создаете old-style device nodes в
/dev-state/compat
, и они будут добавлены к devfs при начальной
загрузке. Все это благодаря "внимательности" нашего init wrapper.
Осталось запустить devfsd
и выйти из условного выражения. Как
последний штрих, через exec
запускается реальный init
,
или, как он теперь называется, /sbin/init.system
. Происходит
стандартный процесс загрузки системы. Ну, не совсем стандартный. Мы теперь
имеем devfs-enabled system!
Теперь приступим к инсталляции init wrapper. Сначала напишите исходный wrapper.sh и сохраните его где-нибудь в вашей системе. Сделайте следующее:
Installing the init wrapper
# cd /sbin
# cp init init.system
# cp /path/to/wrapper.sh init
# chmod +x init
Теперь init wrapper на месте.
Используя init wrapper, мы уходим от использования сложного компилированного
initscript. Однако полностью избежать всех проблем нам не удастся. Если не
предпринять дополнительных мер, то rc scripts
будут очень много
времени тратить на размонтирование корневой файловой системы (после инсталляции
devfs). Имеется простое решение проблемы. Профильтруйте через grep
ваши rc scripts
на наличие команд umount
, например,
cd /etc/rc.d; grep -r umount *
, или
cd /etc/init.d; grep -r umount *
(в зависимости, где инсталлированы
ваши rc scripts). Далее, в каждом сценарии, где имеется команда umount
,
убедитесь, что она вызывается с ключом -r
.
Ключ -r
для umount
выполняет remount в режим read-only
(при повторной попытке), если unmounting завершился неудачей. Это достаточно
для приведения корневой файловой системы в непротиворечивое состояние, после
чего перезагрузка произойдет без проблем. "Чистое" размонтирование может не
произойти из-за существующего монтирования к /dev, а /dev, в свою очередь,
может оказаться не размонтированным, если открыт какой-либо device node.
Теперь перезагрузка практически подготовлена. Но, прежде, посмотрим на
devfsd
и /etc/devfsd.conf
с позиции, чтобы
compatibility devices и device persistence были enabled. Не волнуйтесь, мы всего
лишь в одном шаге от завершения перехода на devfs.
Загрузите /etc/devfsd.conf
в ваш любимый редактор. Посмотрите на
первые четыре строки рекомендованного мною devfsd.conf:
REGISTER .* MKOLDCOMPAT
UNREGISTER .* RMOLDCOMPAT
REGISTER .* MKNEWCOMPAT
UNREGISTER .* RMNEWCOMPAT
Каждая из этих четырех строк состоит из event (REGISTER
или
UNREGISTER
), регулярного выражения (.*
) и action
(строки *COMPAT
). Что они означают? Первая строка указывает devfsd
исполнять MKOLDCOMPAT
action, когда любое устройство регистрируется
в ядре (регулярное выражение .*
match любому устройству).
MKOLDCOMPAT
action встроенная функция devfsd
и означает
"создавать любые old compatibility devices, соответствующие регулярному выражению,
при его регистрации через devfs". Как несложно догадаться, RM*COMPAT
actions работают при device unregistration, заставляя эти special compatibility
devices волшебно исчезать. В целом, эти четыре строки указывают devfsd
создавать compatibility devices (if any) когда устройство регистрируется и удалять
compatibility devices при unregistered. Благодаря таким строкам, когда
регистрируется драйвер IDE device как
/dev/ide/host0/bus0/target0/lun0/disc
(в devfs-style) самой системой,
то devfs автоматически создает соответствующее /dev/hda
compatibility-style device. Это очень полезно для таких команд, как
mount
и fsck
, которые могут читать /etc/fstab,
содержащий имена old-style device. Вообще, создание compatibility devices
делает переход на devfs "бес проблемным". Следующая строка из моего devfsd.conf:
LOOKUP .* MODLOAD
Эта запись сообщает devfsd
выполнять MODLOAD
action
всякий раз, когда любое устройство (смотри на регулярное выражение) "looked up".
Такое случается, когда приложение ищет конкретное device node. MODLOAD
action заставит выполнить команду modprobe /dev/mydev
(/dev/mydev -
имя устройства, которое внешний процесс пытается найти). Благодаря этой feature
(в сочетании с правильно конфигурированным /etc/modules.conf), становится
возможной автозагрузка драйверов по требованию, например, звуковой карточки, и
другие подобные вещи.
Продолжим разбор строк из devfsd.conf:
devfsd.conf, continued
REGISTER ^pt[sy]/.* IGNORE
CHANGE ^pt[sy]/.* IGNORE
REGISTER .* COPY /dev-state/$devname $devpath
CHANGE .* COPY $devpath /dev-state/$devname
CREATE .* COPY $devpath /dev-state/$devname
В этих нескольких строках предписывается devfsd
использование
/dev-state как архива для любых device permission или ownership изменений, а
также любых new compatibility devices, которые пользователь может создать.
В двух первых строках явно сообщается devfsd
не выполнять специальных
действий при регистрации или смены атрибутов для любых pseudo-terminal devices.
Если такие строки не использовать, то permissions и ownership таких
pseudo-terminals сохранялись бы после перезагрузок. Это не оптимально, так как
мы всегда должны устанавливать новые значения perms для pseudo-terminal devices
после загрузки системы.
Следующие три строки включают /dev-state persistence для всех остальных устройств.
Восстановятся любые атрибуты из /dev-state, когда устройство регистрируется
или стартует сам devfsd
(а также копирование атрибутов для любых
совместимых устройств). Кроме этого, будут немедленно копироваться любые изменения
в атрибутах, а также любые записи для только что созданных совместимых устройств
к /dev-state.
Заканчивается мой devfsd.conf следующими строками:
devfsd.conf, end
REGISTER ^cdrom/cdrom0$ CFUNCTION GLOBAL symlink cdroms/cdrom0 cdrom
UNREGISTER ^cdrom/cdrom0$ CFUNCTION GLOBAL unlink cdrom
REGISTER ^misc/psaux$ CFUNCTION GLOBAL symlink misc/psaux mouse
UNREGISTER ^misc/psaux$ CFUNCTION GLOBAL unlink mouse
Эти четыре строки необязательные, но они достойны упоминания. В то время как
/dev-state persistence работает чудесно для device nodes, к символическим
ссылкам это не относится. Возникает вопрос: как обеспечить, чтобы символические
ссылки, например /dev/mouse или /dev/cdrom, не просто существовали, но и
автоматически "воссоздавались" после перезагрузок? В конфигурациях devfsd
такая возможность предусмотрена. Последние четыре строки (или подобные, все
зависит от специфики настраиваемой системы) решают проблему. Первые две указывают
devfsd
создавать символическую ссылку /dev/cdrom, когда
регистрируется устройство /dev/cdrom/cdrom0. Реализовано это через dynamic call
функций libc, которые в данном случае специфицированы как symlink()
и unlink()
. Последние две строки конфигурационного файла используют
аналогичный подход для создания символических ссылок /dev/mouse, когда устройство
/dev/misc/psaux (в этом примере PS/2 мышь) регистрируется в devfs. "Подгоните"
строки к вашей системе и сохраните конфигурационный файл devfsd.conf.
Предупреждение перед перезагрузкой.
Перед перезагрузкой можно посмотреть Richard Gooch's devfs FAQ. По этой ссылке можно найти информацию о devfs naming scheme, особенно полезную, когда вы только знакомитесь с именами устройств нового стиля (смотри Resources ниже). Я также рекомендую распечатать на принтере из части 5 порядок действий при "emergency bash rescue", если это для вас новость. Помните, если по каким то причинам init wrapper script создаст проблему, вы всегда можете удалить его следующей последовательностью спасательных команд. Загрузитесь "emergency bash rescue", выполните remounting корневой файловой системы в режим read-write и последовательно сделайте:
Откат к статусу pre-wrapper, если необходимо.
# cd /sbin
# mv init wrapper.sh
# mv init.system init
Выполнив эти команды, перемонтируйте файловую систему в режим read-only и перезагрузите систему. Система откатится в pre-wrapper state. Исправьте ошибки, снова перезагрузитесь и наслаждайтесь devfs!