Руководство по "продвинутым" файловым системам, часть 6


Первоисточник : http://www-106.ibm.com/developerworks/library/l-fs6.html


Переход на devfs (с использованием init wrapper)

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, можно двигаться дальше.

The init wrapper

Первые строки.

Часть 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.

Kernel boot options

В случае, когда wrapper запускается ядром при начальной загрузке, сценарий получает PID равный 1. Первая условная конструкция будет пропущена, а bash продолжит выполнение wrapper. Просмотрим следующие строки:

More of the init 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 следующее:

Contents of /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:

Rest of the init 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 только тогда, когда это еще не сделано самим ядром.

Device persistence

Если все 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.

Теперь приступим к инсталляции 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 на месте.

Tweaking umount

Используя 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.

devfsd.conf

Загрузите /etc/devfsd.conf в ваш любимый редактор. Посмотрите на первые четыре строки рекомендованного мною devfsd.conf:

devfsd.conf, top portion 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:

Module auto-loading

devfsd.conf, continued LOOKUP .* MODLOAD

Эта запись сообщает devfsd выполнять MODLOAD action всякий раз, когда любое устройство (смотри на регулярное выражение) "looked up". Такое случается, когда приложение ищет конкретное device node. MODLOAD action заставит выполнить команду modprobe /dev/mydev (/dev/mydev - имя устройства, которое внешний процесс пытается найти). Благодаря этой feature (в сочетании с правильно конфигурированным /etc/modules.conf), становится возможной автозагрузка драйверов по требованию, например, звуковой карточки, и другие подобные вещи.

Device persistence

Продолжим разбор строк из 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.

CFUNCTION and symlinks

Заканчивается мой 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!

Resources

About the author


authorResiding in Albuquerque, New Mexico, Daniel Robbins is the President/CEO of Gentoo Technologies, Inc., the creator of
Gentoo Linux, an advanced Linux for the PC, and the Portage system, a next-generation ports system for Linux. He has also served as a contributing author for the Macmillan books Caldera OpenLinux Unleashed, SuSE Linux Unleashed, and Samba Unleashed. Daniel has been involved with computers in some fashion since the second grade, when he was first exposed to the Logo programming language as well as a potentially dangerous dose of Pac Man. This probably explains why he has since served as a Lead Graphic Artist at SONY Electronic Publishing/Psygnosis. Daniel enjoys spending time with his wife, Mary, and his daughter, Hadassah. You can contact Daniel at drobbins@gentoo.org.

Перевод: Владимир Холманов