Без init’а Linux невозможен…
Сергей ГАВРИЛЕНКО
N 9 (336) 28.02.2005
За более чем два с половиной года, которые я читаю журнал, в нем не упоминалась тема загрузки Linux’а (я имею в виду не LILO и GRUB, а загрузку именно системы). Настало время осветить этот вопрос.
О загрузчиках вы можете прочитать в статье Сергея ЯРЕМЧУКА «Как грузят пингвины» (МК № 46, 48 (217, 219)). Так что мы начнем сразу с загрузки ядра. Оно, по сути, является программой, которая после запуска захватывает все ресурсы компьютера и потом раздает их по мере необходимости другим программам. Загрузчики при загрузке ядра могут передавать ему параметры, руководствуясь которыми, ядро выполняет то или иное действие. Знание этих параметров может помочь правильно сконфигурировать ядро и наладить загрузку Linux. Поэтому я приведу наиболее применяемые из них, за исключением специфических или редко используемых.
Параметр root задает корневую файловую систему. Ядро при компиляции запоминает устройство, на котором оно собиралось, и считает его корневым. Но так как ядро собирается на одном компьютере, а используется на других, то и устройства могут не совпасть. Например, собирали ядро на устройстве /dev/hda2, а при установке корневая ФС оказалась на /dev/hda5 - в таком случае ядро будет пытаться найти ФС на /dev/hda2 и, не найдя, устроит панику (panic), отказываясь грузить систему. Задав параметр root=/dev/hda5, можно указать ядру, где будет находиться корневая ФС. Значением параметра root могут быть не только IDE-устройства, но и SCSI-диски. Есть еще особый случай - загрузка бездисковых компьютеров по локальной сети. В этом случае параметр будет выглядеть так: root=/dev/nfs (nfs - Network File System). При таком раскладе нужно указать еще один параметр nfsroot. Его значение имеет вид nfsroot=///.
Следующие два параметра - ro и rw. Вернее, даже не два, а варианты одного: ro говорит ядру, что корневая ФС должна быть смонтирована в режиме «только для чтения», а rw, соответственно, «чтение-запись».
Параметр mem используется в том случае, если ядро не видит всей памяти, установленной в системе. Эта ситуация касается компьютеров с объемом ОЗУ более 64 мегабайт. Используя этот параметр, нужно просто указать объем памяти, установленной в системе. Например, если ядро видит 64 мегабайта, а на самом деле их 256, то просто напишите в конфигурационном файле загрузчика mem=256 (в зависимости от загрузчика это может быть в разных местах конфига, смотрите справку по вашему загрузчику), но здесь нужно быть осторожным - нельзя указывать в этом параметре памяти больше, чем ее есть, иначе система будет виснуть при запуске первой же программы или даже при загрузке. Дело тут вот в чем: Linux распределяет память не с нижних адресов, как Windows или DOS, а с верхних (то есть не с начала, а с конца). Таким образом удалось справиться с проблемой первых 640 килобайт. И если указать памяти больше, чем есть на самом деле, то ядро, выделяя память для программ, будет обращаться к несуществующим адресам и, не найдя их, «виснуть».
Если ядро не определяет какие-то устройства или выдает ошибку при загрузке, стоит воспользоваться параметром debug, который даст возможность ядру выводить на консоль всю информацию и сообщения. Если же проблемы настолько серьезные, что ядро выдает «Kernel panic» - может помочь параметр panic. Суть вот в чем: ядро «в панике» ждет, когда человек перезагрузит компьютер, если же указать параметр panic=, то ядро само перезагрузит машину через n секунд.
Еще одна проблема связана с программой init - единственным процессом Linux’а, который работает наряду с ядром, и имеет самый высокий статус среди программ в системе. Ядро после загрузки пытается запустить init, чтобы передать ему бразды правления загрузкой оставшейся части системы. Но может случиться, что исполняемый файл init поврежден или находится не там, где его ищет ядро. В таком случае может помочь параметр загрузки init - в качестве его аргумента можно указать место, где расположен резервный файл программы или указать другую программу, которая исполнится вместо init.
В ядрах, которые не используют devfs, для некоторых устройств нужно указать особые параметры - их достаточно большое количество, но один нужно знать. Это параметр для CD-RW приводов, который имеет вид hd*=ide-scsi, где hd* - ваш RW’шник, причем hd* может принимать значения от hda до hdd в зависимости от шины и расположения на ней. В основном это hdc (Master на вторичной шине), хотя я видел и hdd (slave на вторичной шине). Один совет: не сажайте CD-RW на slave, после двух-трех выброшенных болванок пожалеете.
После чтения параметров ядро монтирует временную корневую ФС, которая называется initial ram disk или initrd, и содержит модули ядра и программы необходимые ядру для монтирования настоящей корневой файловой системы. Initrd - это упакованный образ, распаковывающийся в участок памяти, выделенный ему ядром при старте системы. Далее следует определение ядром аппаратной части компьютера, монтирование настоящей корневой ФС и запуск журналирования ядра. Последняя операция, которую ядро выполняет непосредственно в процессе загрузки - запуск программы init. Программа init является центральной программой системы и выполняется при любых условиях. Init может легально завершить работу только в одном случае - при завершении работы системы. В остальных случаях, если init перестала работать, ядро перезапускает ее. Init берет на себя основную работу по контролю за системой и работой программ.
При запуске init читает свой конфигурационный файл /etc/inittab. В этом файле содержатся настройки init’a и действия, которые необходимо выполнить при запуске. Файл /etc/inittab состоит из строк такого формата: id:runlevel:action:process, где id - номер строки (не должно быть двух одинаковых id); runlevel - уровень запуска системы (читайте ниже); process - команда для выполнения; action - действие, которое нужно выполнить над process. Разновидности action строго ограничены: wait - ждать завершения process; once - запустить process один раз; respawn - перезапустить process в случае завершения; off - завершить process; boot - запускать process при запуске системы.
Далее по регламенту (пишу под мерный писк системы «Рада» и народных депутатов :-)), поговорим об уровне запуска, или runlevel. У Linux и Unix систем есть так называемые уровни запуска - нечто вроде расписания в мире людей. Например: в понедельник утром мы идем на работу и соответственно выполняем подготовительные процедуры, в пятницу вечером, готовясь к уходу домой и выходным, нам приходится делать совершенно иные процедуры, а, уезжая в командировку, мы собираем вещи, берем билет на поезд, и т.д. Так и в Linux: при запуске системы необходимо выполнить одни программы, при останове - другие. Вот и определили для Linux (описываю для Red Hat-клонов, так как они у нас самые популярные) шесть уровней запуска: 0 - останов системы; 1 - однопользовательский режим без поддержки сети; 2 - многопользовательский режим без сети; 3 - многопользовательский консольный с сетью; 4 - не используется; 5 - многопользовательский графический с сетью; 6 - перезагрузка компьютера. Для определения уровня запуска по умолчанию в файле /etc/inittab есть строка id:X: initdefault, где X - уровень запуска по умолчанию. То есть, если вам необходима голая консоль, то эта строка будет выглядеть так: id:3:initdefault, а в случае необходимости загрузки графики при старте - id:5:initdefault. Для уровней запуска в Red Hat создана иерархия каталогов /etc/rc.d, где каждому уровню запуска соответствует свой каталог. Выглядит это так: в /etc/rc.d присутствуют подкаталоги с именами от /etc/rc.d/rc0.d до /etc/rc.d/rc6.d, где цифра в имени каталога соответствует уровню запуска. В этих каталогах содержатся ссылки на скрипты запуска-останова служб (демонов), сами же скрипты находятся в каталоге /etc/rc.d/init.d. Ссылки имеют вид S(K)XXимя_скрипта, где XX - стартовый номер службы. Каждая служба может либо запускаться, либо останавливаться, и именно для этой цели используются буквы S и K в имени ссылки. А именно: чтобы запустить службу на определенном уровне запуска, в имени ссылки нужно поставить букву S, а чтобы остановить - соответственно, букву K. Стартовый номер демона используется для порядка при их запуске или остановке. Номера присваиваются произвольно, но при этом нужно учитывать, что некоторые демоны должны запускаться раньше остальных, а останавливаться позже других, и наоборот. Еще один момент - если служба запущена, то при перезагрузке или выключении компьютера она должна быть остановлена. Поэтому если вы создали ссылку для запуска службы, то должны создать и ссылку для ее остановки в каталогах /etc/rc.d/rc0.d и /etc/rc.d/rc6.d, причем негласным правилом стало, что стартовые номера для запуска и остановки службы должны в сумме давать 99. Например: если служба wine запускается ссылкой S98wine, то в каталогах /etc/rc.d/rc0.d и /etc/rc.d/rc6.d должна быть ссылка K1wine, то есть если служба запускается позже других, то останавливаться должна раньше всех. Чтобы научиться разбираться в этом вопросе, советую открыть каталог /etc/rc.d и хорошенько его исследовать, а также почитать скрипты в /etc/rc.d/init.d, это поможет вам узнать больше не только о процессе запуска служб, но и о написании скриптов (скажу честно, скрипты незаменимая штука, научитесь их писать - не пожалеете).
Хотя тема демонов довольно интересна, но все же продолжим разговор о процессе загрузки. Init, определив уровень запуска, передает управление скрипту /etc/rc.sysinit. Функций у этого скрипта очень много, приведу только основные: установка переменной PATH (установка путей); установка имени хоста (вашего компьютера в сети (в Linux всегда есть сеть, если не реальная, то виртуальная)); чтение конфига сети /etc/sysconfig/network; монтирование псевдофайловой системы /proc; установка часов; установка системного шрифта; активизация раздела подкачки (SWAP); подключение USB устройств; проверка корневой файловой системы утилитой fsck; настройка устройств Plug’n’Play; перемонтирование корневой ФС в режим чтения-записи; обновление файлов fstab и mtab (читайте ниже); монтирование локальных, не корневых, ФС; очистка временного каталога /tmp; включение подкачки; запрос на вход в интерактивную загрузку. Этот файл является лучшим примером скриптов.
Далее init передает управление скрипту /etc/rc. Этот скрипт определяет уровень выполнения, на котором в данный момент находится система, и уровень, на который нужно перейти, а также запрашивает подтверждение на интерактивную или неинтерактивную загрузку. Затем он, в соответствии с полученными параметрами, выполняет запуск или остановку служб. Но, согласитесь, довольно глупо запускать уже запущенную службу или останавливать остановленную. Поэтому в Linux используют простую, но эффективную систему флагов для контроля состояния служб. Ее смысл состоит в следующем: при запуске службы создается так называемый флаг - файл вида /var/lock/имя_службы/${имя службы}.init. Скрипт rc проверяет наличие таких флагов: если флага нет, то запуск службы имеет смысл, и наоборот.
После окончания работы скриптов init запускает процесс getty или mgetty. Далее у пользователя запрашивается его имя в системе (login), а затем и пароль. После их ввода программа login проверяет правильность логина и пароля, сравнивая их с записями в файле /etc/shadow. Если все правильно, login запускает программу-оболочку, которой в основном является bash (запускаемую оболочку можно поменять в файле /etc/passwd). После запуска bash читает конфигурационный файл пользователя c именем .profile, и выводит приглашение для ввода команд. Пользователь после всего этого оказывается в своем домашнем каталоге, для обычного пользователя это /home/имя_пользователя, а для root’а - /root.
Также хочу сказать несколько слов о конфигурационных файлах, которые принимают участие в загрузке. Три из них уже упомянуты выше, это /etc/passwd, /etc/shadow и .profile. Кроме того, есть файл /etc/bashrc, в котором находятся настройки программы bash, и файл /etc/fstab, в котором записаны все файловые системы, зарегистрированные в системе.