Handbook:SPARC/Working/Initscripts
The contents of this page do not apply to users that chose a systemd profile in Choosing the right profile.
Уровни запуска
Процесс загрузки системы
Когда загружается система по экрану пробегает много текста. Если присмотреться, заметно, что этот текст (обычно) не меняется от загрузки к загрузке. Последовательность всех этих действий называется последовательностью загрузки и в той или иной степени постоянна.
Во-первых, загрузчик размещает в памяти образ ядра, который указан в файле его конфигурации. После этого загрузчик просит процессор запустить ядро. Когда ядро загружено и запущено, оно инициализирует относящиеся к ядру структуры и задания и запускает процесс init.
Этот процесс удостоверяется, что все файловые системы (определённые в /etc/fstab) смонтированы и готовы к использованию. Затем он выполняет несколько сценариев, находящихся в каталоге /etc/init.d/ и запускающих службы, необходимые для успешного запуска системы.
И, наконец, когда все сценарии выполнены, init подключает терминалы (чаще всего просто виртуальные консоли, которые видны при нажатии Alt+F1, Alt+F2 и так далее), прикрепляя к каждой консоли специальный процесс под названием agetty. Этот процесс впоследствии обеспечивает возможность входа в систему через эти терминалы с помощью login.
Сценарии инициализации
Процесс init запускает сценарии из каталога /etc/init.d/ не просто в случайном порядке. Более того, запускаются не все сценарии из /etc/init.d/, а только те, которые предписано исполнять. Решение о запуске сценария принимается в результате просмотра каталога /etc/runlevels/.
Во-первых, init запускает все сценарии из /etc/init.d/, на которые есть символьные ссылки из /etc/runlevels/boot/. Обычно сценарии запускаются в алфавитном порядке, но в некоторых из них имеется информация о зависимостях от других, указывающая системе на необходимость их предварительного запуска.
Когда все сценарии, указанные в /etc/runlevels/boot/, будут выполнены, init переходит к запуску сценариев, на которые есть символьные ссылки из /etc/runlevels/default/. И снова запуск происходит в алфавитном порядке, пока в сценарии не встретится информация о зависимостях; тогда порядок изменяется для обеспечения правильного порядка запуска. Именно по данной причине команды, используемые во время установки Gentoo Linux, используют default
, как в команде rc-update add sshd default.
Как работает init
Конечно, init не принимает решений сам по себе. Ему необходим конфигурационный файл, где описаны необходимые действия. Этот файл — /etc/inittab.
Вспомните последовательность загрузки, описанную чуть ранее: первое действие init — это монтирование всех файловых систем. Это определяется в следующей строке файла /etc/inittab:
si::sysinit:/sbin/openrc sysinit
Этой строкой процессу init предписывается выполнить /sbin/openrc sysinit для инициализации системы. Самой инициализацией занимается сценарий /sbin/openrc, так что можно сказать, что init делает не слишком много — он просто делегирует задачу по инициализации системы другому процессу.
Во-вторых, init выполняет все сценарии, на которые есть символьные ссылки из /etc/runlevels/boot/. Это определяется следующей строкой:
rc::bootwait:/sbin/openrc boot
И снова все необходимые действия выполняются сценарием OpenRC. Заметьте, что параметр, переданный OpenRC (boot), совпадает с названием используемого подкаталога в /etc/runlevels/.
Теперь init проверяет свой конфигурационный файл, чтобы определить, какой уровень запуска нужно запустить. Для этого из /etc/inittab считывается строка:
id:3:initdefault:
В приведенном примере (который подходит для подавляющего большинства пользователей Gentoo) номер уровня запуска — 3. Пользуясь этой информацией, init проверяет, что нужно выполнить для запуска уровня запуска 3:
l0:0:wait:/sbin/openrc shutdown
l1:S1:wait:/sbin/openrc single
l2:2:wait:/sbin/openrc nonetwork
l3:3:wait:/sbin/openrc default
l4:4:wait:/sbin/openrc default
l5:5:wait:/sbin/openrc default
l6:6:wait:/sbin/openrc reboot
В строке, определяющей уровень 3, для запуска служб снова используется сценарий openrc (на этот раз с аргументом default
). Опять-таки, обратите внимание, что аргумент, передаваемый openrc, совпадает с названием подкаталога из /etc/runlevels/.
По окончании работы OpenRC init принимает решение о том, какие виртуальные консоли включить и какие команды выполнить в каждой из них:
c1:12345:respawn:/sbin/agetty 38400 tty1 linux
c2:12345:respawn:/sbin/agetty 38400 tty2 linux
c3:12345:respawn:/sbin/agetty 38400 tty3 linux
c4:12345:respawn:/sbin/agetty 38400 tty4 linux
c5:12345:respawn:/sbin/agetty 38400 tty5 linux
c6:12345:respawn:/sbin/agetty 38400 tty6 linux
Доступные уровни запуска
В предыдущем разделе мы увидели, что init применяет нумерацию для определения уровня запуска, который надо использовать. Уровень запуска — это то состояние, в котором запущена система, он содержит набор сценариев (сценарии уровня запуска или сценарии инициализации), которые следует выполнять, при входе и выходе из определенного уровня запуска.
В Gentoo определено семь уровней запуска: три служебных и четыре определяемых пользователем. Служебные называются sysinit, shutdown и reboot. Действия, совершаемые ими, в точности соответствуют их названиям: инициализация системы, выключение системы и её перезагрузка.
Определяемые пользователем уровни — это те, которым соответствуют подкаталоги в /etc/runlevels/: boot, default, nonetwork и single. Уровень boot запускает все службы, необходимые системе и используемые всеми остальными уровнями. Остальные уровни отличаются друг от друга запускаемыми службами: default используется для повседневной работы, nonetwork - для тех случаев, когда не требуется сеть, а single используется при необходимости восстановления системы.
Работа со сценариями инициализации
Сценарии, запускаемые процессом openrc, называются сценариями инициализации. Каждый сценарий из /etc/init.d/ может запускаться с аргументами start
, stop
, restart
, zap
, status
, ineed
, iuse
, iwant
, needsme
, usesme
или wantsme
.
Для запуска, остановки или перезапуска службы (и всех, зависящих от неё) следует использовать start
, stop
и restart
:
root #
rc-service postfix start
Останавливаются или перезапускаются только те службы, которым необходима данная служба. Остальные зависимые службы (те, которые используют службу, но не нуждаются в ней) эта операция не затрагивает.
Если вы хотите остановить службу, но оставить зависимые от неё работающими, можно использовать опцию --nodeps
вместе с аргументом stop:
root #
rc-service --nodeps postfix stop
Чтобы узнать текущее состояние службы (запущена, остановлена, и так далее), можно использовать аргумент status
:
root #
rc-service postfix status
Если указано, что служба работает, но вы знаете, что это не так, можно сбросить состояние на stopped , используя аргумент zap
:
root #
rc-service postfix zap
Для того, чтобы выяснить зависимости службы, можно использовать аргументы iwant
, iuse
или ineed
. С помощью ineed
можно увидеть те службы, которые действительно необходимы для правильного функционирования интересующей вас службы. С другой стороны, iwant
или iuse
покажет те службы, которые могут использоваться нашей службой, но не обязательны для его корректной работы.
root #
rc-service postfix ineed
Аналогично можно узнать, какие службы нуждаются в данной службе (needsme
) или могут её использовать (usesme
или wantsme
):
root #
rc-service postfix needsme
Обновление уровней запуска
rc-update
Система инициализации Gentoo использует дерево зависимостей для определения служб, которые запускаются в первую очередь. Так как это очень утомительное занятие, и мы бы не хотели, чтобы пользователь занимался этим вручную, мы разработали утилиты, упрощающие управление уровнями запуска и init-скриптами.
Используя rc-update, можно включать и исключать сценарии инициализации из уровней запуска. Из rc-update автоматически запускается сценарий depscan.sh для перестроения дерева зависимостей.
Добавление и удаление служб
В процессе установки Gentoo вы уже добавляли сценарии инициализации в уровень запуска default. Ранее в данном документе было объяснено, что означает default. Кроме уровня запуска, сценарию rc-update требуется второй аргумент, определяющий действие: add
(добавить), del
(удалить) или show
(показать).
Для того, чтобы добавить или удалить сценарии инициализации, просто введите rc-update с аргументом add
или del
, затем название сценария и уровня запуска. Например:
root #
rc-update del postfix default
По команде rc-update -v show выводится список всех доступных сценариев инициализации с указанием списка уровней запуска, на которых они будут выполняться:
root #
rc-update -v show
Вы также можете запустить rc-update show (без -v
) чтобы просто просмотреть включенные сценарии инициализации и их уровни запуска.
Настройка служб
Почему необходима дополнительная настройка
Сценарии инициализации могут быть весьма сложны. Поэтому нежелательно допускать непосредственное редактирование сценариев пользователями, так как это может привнести в систему множество ошибок. Но, с другой стороны, необходимо правильно настроить службу. Например, может понадобиться передать самой службе дополнительные параметры.
Вторая причина, по которой настройки хранятся отдельно от самого сценария — это возможность обновления сценария без опасения, что все пользовательские настройки будут утеряны.
Каталог conf.d
В Gentoo предусмотрен очень простой способ настройки служб: для каждого сценария, предполагающего настройку, в каталоге /etc/conf.d/ есть конфигурационный файл. Например, у сценария, запускающего apache2 (под названием /etc/init.d/apache2) есть конфигурационный файл /etc/conf.d/apache2, где хранятся параметры, передаваемые серверу Apache 2 при запуске:
APACHE2_OPTS="-D PHP5"
Такие конфигурационные файлы содержат только переменные (наподобие /etc/portage/make.conf), облегчая настройку служб. Это также позволяет нам давать больше информации о переменных (в комментариях).
Написание сценариев инициализации
Ещё один полезный ресурс — руководство по написанию сценариев инициализации служб для OpenRC.
Это необходимо?
Нет, написание сценариев инициализации обычно не требуется, так как Gentoo содержит готовые сценарии для всех предоставляемых служб. Однако некоторые пользователи могут установить какую-либо службу, не используя систему Portage; в таком случае, вероятно, придётся создать сценарий самостоятельно.
Не используйте сценарий инициализации, идущий со службой, если он не написан специально для Gentoo: сценарии Gentoo не совместимы со сценариями, используемыми в других дистрибутивах! То есть, если другой дистрибутив не использует OpenRC!
Структура
Основная структура сценария инициализации показана ниже.
#!/sbin/openrc-run
depend() {
# (Информация о зависимостях)
}
start() {
# (Команды, необходимые для запуска службы)
}
stop() {
# (Команды, необходимые для остановки службы)
}
#!/sbin/openrc-run
command=/usr/bin/foo
command_args="${foo_args} --bar"
pidfile=/var/run/foo.pid
name="FooBar Daemon"
description="FooBar is a daemon that drinks"
extra_started_commands="drink"
description_drink="Opens mouth and reflexively swallows"
depend() {
# (Информация о зависимостях)
}
start_pre() {
# (Команды, необходимые для запуска службы)
# Убедимся, что каталог в порядке
checkpath --directory --owner foo:foo --mode 0775 \
/var/run/foo /var/cache/foo
}
stop_post() {
# (Команды очистки после остановки службы)
# Очистка любых остатков
rm -rf /var/cache/foo/*
}
drink() {
ebegin "Starting to drink"
${command} --drink beer
eend $? "Failed to drink any beer :("
}
В любом сценарии инициализации должна быть определена функция start()
или переменная command
. Все остальные разделы необязательны.
Зависимости
Существуют три настройки, работающие с зависимостями, которые можно определить, и они будут влиять на порядок запуска сценария инициализации: want
, use
и need
. Кроме этих, существуют еще два влияющих на порядок загрузки метода, называющихся before
и after
. Последние два не определяют зависимости, они не остановят с ошибкой основной сценарий, если выбранный не должен запускаться (или не смог запуститься).
- Настройка
use
информирует систему init, что данный скрипт использует функциональность некоторого сценария, но строго от него не зависит. Хорошим примером будетuse logger
илиuse dns
. Если эти службы есть, они будут использоваться, но если у вас нет системы журналирования или DNS-сервера, службы все равно будут работать. Если службы существуют, они будут запущены до того, как запустится сценарий, использующий их. - Настройка
want
похожа наuse
с одним исключением.use
учитывает только те службы, которые были добавлены в какой-либо уровень запуска.want
попытается запустить любую доступную службу, даже если она не была добавлена в один из уровней запуска. - Настройка
need
— это жёсткая зависимость. Она означает, что сценарий, которому нужен другой, не запустится, пока другой сценарий не запустится успешно. Также, если другой сценарий будет перезапущен, то этот тоже будет перезапущен. - При использовании
before
, данный сценарий запускается до некоторого сценария, если выбранный сценарий — часть того же уровня инициализации. Так, сценарий инициализации xdm, который определенbefore alsasound
будет запущен до сценария alsasound, но только если alsasound запланирован запуститься на том же уровне инициализации. Если alsasound не запланирован для запуска, то эта конкретная настройка не будет иметь эффекта, и xdm запустится в тот момент времени, который система init посчитает лучшим вариантом. - Похожим образом
after
информирует систему init, что данный сценарий нужно запустить после некоторого сценария, если выбранный сценарий является частью того же уровня инициализации. Если нет, то настройка не имеет эффекта, и сценарий будет запущен системой init в момент времени, который, как она посчитает, будет наилучшим.
Из изложенного должно быть ясно, что need
— это единственная действительная настройка зависимостей, так как она влияет на то, будет ли запущен сценарий или нет. Все остальные являются больше указателями системе init, говорящими в каком порядке сценарии могут (или должны) запускаться.
Теперь, если вы взглянете на существующие сценарии инициализации Gentoo, вы заметите, что некоторые из них имеют зависимости от вещей, которые не являются сценариями. Эти вещи мы называем виртуальными.
Виртуальная зависимость — это зависимость от функций, предоставляемых службой, но не какой-то единственной службой. Сценарий инициализации может зависеть от службы журналирования, но таких достаточно много (metalogd, syslog-ng, sysklogd и тому подобное). Поскольку нельзя нуждаться в каждой из них (ни в одной нормальной системе они не запущены все сразу), мы обеспечили предоставление виртуальной зависимости всеми этими службами.
Например, давайте взглянем на информацию о зависимостях postfix.
depend() {
need net
use logger dns
provide mta
}
Как можно увидеть, служба postfix:
- требует (виртуальную) зависимость в сети (net, которая предоставляется, например, /etc/init.d/net.eth0)
- использует (виртуальную) зависимость logger (которая предоставляется, например, /etc/init.d/syslog-ng)
- использует (виртуальную) зависимость dns (которая, предоставляется, например, /etc/init.d/named)
- предоставляет (виртуальную) зависимость mta (общая для всех почтовых серверов).
Порядок запуска
Как было описано в предыдущем разделе, можно сказать системе init, в каком порядке она должна запускать (или останавливать) сценарии. Этот порядок поддерживается как через настройки зависимостей use и need, так и через настройки порядка before и after. Так как мы описали их ранее, давайте посмотрим на службу Portmap, как на пример такого сценария инициализации.
depend() {
need net
before inetd
before xinetd
}
Также можно использовать знак "*", чтобы охватить все службы данного уровня запуска, хотя это не рекомендуется.
depend() {
before *
}
Если службе необходима возможность записывать данные на локальные диски, она должна потребовать localmount. Если она что-либо поместит в /var/run/, например, PID-файл, она должна запускаться после bootmisc:
depend() {
need localmount
after bootmisc
}
Стандартные функции
Следом за разделом depend()
потребуется определить функцию start()
. В ней содержатся все команды, необходимые для запуска службы. Рекомендуется применять функции ebegin
и eend
для сообщений пользователю о том, что происходит:
start() {
if [ "${RC_CMD}" = "restart" ];
then
# Что-нибудь сделать, если рестарт требует больше, чем просто последовательный запуск stop и start
fi
ebegin "Starting my_service"
start-stop-daemon --start --exec /path/to/my_service \
--pidfile /path/to/my_pidfile
eend $?
}
Как --exec
, так и --pidfile
должны использоваться в функциях start и stop. Если служба не создает PID-файл, то по возможности используйте --make-pidfile
, хотя это может потребовать дополнительного тестирования. В ином случае не используйте PID-файлы. Можно добавить --quiet
к аргументам start-stop-daemon, но это не рекомендуется, если только служба не очень многословная. Использование --quiet
может скрыть отладочную информацию, если служба не может запуститься.
Другой интересной настройкой, используемой в вышеприведенном примере является проверка содержимого переменной RC_CMD. В отличие от предыдущей системы инициализации, новая система OpenRC не поддерживает отдельную функциональность restart для каждого скрипта. Вместо этого сценарий должен проверить содержимое переменной RC_CMD, чтобы проверить, вызывается ли функция (как start()
, так и stop()
) как часть restart, или нет.
Удостоверьтесь, что
--exec
действительно вызывает службу, а не shell-скрипт, который запускает службы и выходит — это должен делать сам init-скрипт.Если нужны дополнительные примеры функции start()
, пожалуйста, прочитайте исходный код сценариев, находящихся в каталоге /etc/init.d/.
Еще одной функцией, которую можно определить (но не обязательно это делать), является stop()
. Система init, применяемая нами, достаточно развита и в состоянии самостоятельно заполнить эту функцию, если используется start-stop-daemon.
stop() {
ebegin "Stopping my_service"
start-stop-daemon --stop --exec /path/to/my_service \
--pidfile /path/to/my_pidfile
eend $?
}
Если служба запускает некоторый другой сценарий (например, на Bash, Python или Perl), и этот сценарий позднее изменяет имя (например, с foo.py на foo), тогда нужно добавить --name
к start-stop-daemon. Нужно определить имя, на которое сценарий изменит свое имя. В приведенном примере, служба запускает foo.py, а потом это имя меняется на foo:
start() {
ebegin "Starting my_script"
start-stop-daemon --start --exec /path/to/my_script \
--pidfile /path/to/my_pidfile --name foo
eend $?
}
У start-stop-daemon есть отличная man-страница, которую можно посмотреть, если нужна дополнительная информация.
user $
man start-stop-daemon
Синтаксис сценариев инициализации, применяемых в Gentoo, основан на оболочке POSIX, поэтому можно свободно использовать внутри своих скриптов sh-совместимые конструкции. Остальные конструкции, вроде тех, которые специфичны только для bash, следует выносить за пределы сценария, чтобы быть уверенным, что сценарий будет работать независимо от того, что Gentoo может сделать со своей системой init.
Добавление дополнительных параметров
Если нужно ввести в сценарий инициализации дополнительные параметры, кроме упоминавшихся, добавьте параметр в одну из следующих переменных и создайте функцию с названием, соответствующим параметру. Например, для поддержки параметра restartdelay
:
- extra_commands — команду можно запустить при любом состоянии службы
- extra_started_commands — команду можно запустить, когда служба запущена
- extra_stopped_commands — команду можно запустить, когда служба остановлена
extra_started_commands="restartdelay"
restartdelay() {
stop
sleep 3 # пауза в 3 секунды перед повторным запуском
start
}
Функция
restart()
не может быть переназначена в OpenRC!Переменные для настройки служб
Для поддержки файлов конфигурации из каталога /etc/conf.d/ ничего дополнительно делать не нужно: при запуске сценария инициализации автоматически подключаются следующие файлы (то есть переменные из них становятся доступны):
- /etc/conf.d/YOUR_INIT_SCRIPT
- /etc/conf.d/basic
- /etc/rc.conf
Кроме того, если сценарий инициализации предоставляет виртуальную зависимость (например, net), то также подключается файл, соответствующий этой зависимости (например, /etc/conf.d/net).
Изменение поведения уровней запуска
Кому это может быть полезным
Большинству пользователей ноутбуков знакома ситуация: дома нужен запуск net.eth0, и наоборот, в дороге запуск net.eth0 не нужен (так как сеть недоступна). В Gentoo можно изменять поведение уровней запуска по своему усмотрению.
Например можно создать второй загружаемый уровень запуска default, в котором будут другие сценарии. Затем, при загрузке, можно выбрать, какой из уровней default следует использовать.
Использование программного уровня (softlevel)
Прежде всего, создайте каталог для второго уровня запуска default. Например, создадим уровень запуска offline:
root #
mkdir /etc/runlevels/offline
Добавьте необходимые сценарии в только что созданный уровень запуска. Например, чтобы получить точную копию уровня default, за исключением net.eth0:
root #
cd /etc/runlevels/default
root #
for service in *; do rc-update add $service offline; done
root #
rc-update del net.eth0 offline
root #
rc-update show offline
(Partial sample Output) acpid | offline domainname | offline local | offline net.eth0 |
Даже несмотря на то, что net.eth0 был удален с уровня запуска offline, udev может попытаться запустить любые устройства, которые найдёт, и запустить соответствующие службы. Данная функциональность называется hotplugging. По умолчанию, Gentoo отключает hotplugging.
Чтобы включить hotplugging, но только для конкретного набора сценариев, используйте переменную rc_hotplug в /etc/rc.conf:
rc_hotplug="net.wlan !net.*"
Для более детальной информации о службах, инициируемых устройствами, просмотрите комментарии в /etc/rc.conf.
Теперь необходимо отредактировать конфигурацию загрузчика, добавив новую запись для уровня запуска offline. В данной записи добавьте softlevel=offline
в качестве параметра загрузки.
Использование загрузочного уровня (bootlevel)
Использование загрузочного уровня полностью аналогично использованию программного уровня. Единственная разница состоит в том, что определяется второй уровень boot вместо второго уровня default.