Исследуем процесс загрузки Linux
(C) В.А.Костромин, 2007 г.
Оглавление
Предисловие
Вот уже восьмой год я использую ОС Linux на персональных компьютерах дома и на работе (хотя, признаюсь, и от Windows полностью отказаться пока не могу). За это время сменилось несколько поколений ядра этой операционной системы, существенно изменилось (в лучшую сторону) программное обеспечение, система стала вполне пригодной для использования на персональном компьютере. Однако ее настройка и оптимизация до сих пор остаются для меня сферой темной и непонятной. Но всегда было и до сих пор имеется желание разобраться в этих вопросах. Вспоминаются времена доброй старой ДОС, когда поневоле приходилось изучить все возможности конфигурации системы путем редактирования config.sys и autoexec.bat, чтобы впихнуть все нужные приложения в тесные рамки 640 Кбайт оперативной памяти. Хочется так же свободно ориентироваться и в конфигурационных файлах и в процедурах загрузки Линукс. Конечно, современные компьютеры не так стеснены в объемах ОП, да и процедуры загрузки Linux куда как сложнее организованы, и все же, и все же. В настоящей статье я попытаюсь изложить все те знания по вопросу организации процесса загрузки Linux, которые мне удалось собрать из различных источников. Цель статьи состоит не только в том, чтобы понять, как происходит начальная загрузка Linux, но и научиться влиять на этот процесс Ведь конфигурация системы во многом (а может быть, и полностью) определяется в процессе начальной загрузки.
Настоящая статья представляет собой конспект, составленный из всех найденных мной источников, дополненный результатами различных экспериментов и проверок на моем домашнем компьютере с несколькими разными дистрибутивами Linux. (Cписок использованных источников приведен в конце данной статьи; он не претендует на полноту, это только те статьи, которые мне стали каким-то образом доступны.) Очень краткое, но емкое описание процесса начальной загрузки приведено в великолепной статье Эрика Реймонда [2], которую я очень рекомендую всем прочитать. Более подробное описание имеется в специальном HOWTO Грега О’Кифи (Greg O’Keefe) “От включения питания до приглашения Bash” [3], но на мой взгляд, даже там изложение слишком краткое и лишенное интересных подробностей и деталей. А вся суть, как известно, в деталях! Кроме того, О’Кифи заканчивает на появлении приглашения Bash, а мне хочется рассмотреть и вопросы оптимизации работы пользователя в системе. Это та цель, которую я продекларировал еще в своей первой книге [1], но которая не была мной достигнута. В частности, я постараюсь проследить, как процессы загрузки отображаются на экране монитора и протоколах работы системы.
Я буду очень благодарен, если после прочтения статьи вы пришлете мне свои замечания и предложения по ее доработке на адрес kos @ rus-linux dot net. Я намереваюсь некоторое время дорабатывать содержание этих заметок и постараюсь учесть все замечания и дополнения, которые мне будут присланы. Поэтому следите за появлением изменений и корректировок. Чтобы вам было удобнее отслеживать внесение поправок, каждый раздел заметок снабжается ссылкой на дату последней корректировки раздела.
На тот случай, если вы захотите повторить мои действия, следует сделать несколько предварительных замечаний. Для выполнения действий по конфигурированию и настройке системы в большинстве случаев необходимо иметь права суперпользователя. А, как известно, любое неосторожное действие, выполненное с такими полномочиями, может привести к катастрофическим для системы последствиям. Поэтому, традиционный отказ от отвественности. Имейте в виду, что все, что вы будете делать со своей системой, вы делаете на свой страх и риск. Предупреждаю, что даже небольшая ошибка при редактировании конфигурационных файлов может привести к тому, что ваша система перестанет загружаться. Так что любые эксперименты на первых этапах лучше проводить на компьютере, где нет ценных для вас файлов, или сохранить такие файлы на резервном носителе. Я все эксперименты проводил на старом компьютере, который использую только для тестирования. Впрочем, во многих случаях можно исправить свои ошибки, загрузив систему в однопользовательском режиме (об этом будет рассказано в одном из последних разделов данной статьи) или с помощью одного из так называемых LiveCD – загрузочных CD-ROM с операционной системой (описание которых уже выходит за рамки настоящей статьи). А еще лучше выполнять такого рода эксперименты в виртуальном компьютере.
Исследуем процесс загрузки Linux
(C) В.А.Костромин, 2007
(версия файла от 16.09.2007 г.)
Приложение 1. Пример скрипта rc.sysinit
В листинге 11 приведено содержимое файла rc.sysinit с моего компьютера, на котором установлен дистрибутив Mandriva Free 2007 Spring. Поскольку файл этот достаточно большой, листинг разбит на отдельные фрагменты, каждый из которых будет разбираться и комментироваться по отдельности.
Листинг 11-1. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 1. В первых строках содержится обычный для bash-скриптов заголовок «#!/bin/bash», небольшой комментарий и устанавливаются значения нескольких переменных.
Листинг 11-2. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 2. Квадратные скобки в языке скриптов эквивалентны оператору test, который проверяет условие и возвращает значение «истина» или «ложь». В первом условном операторе if проверяется, что файл /etc/sysconfig/network существует и является обычным файлом. Если это действительно так, то этот файл выполняется (точка является синонимом команды source, которая читает и выполняет команды из указанного файла (в данном случае это /etc/sysconfig/network) и возвращает статус, определяемый последней командой из этого файла. В моем случае в файле /etc/sysconfig/network содержится всего одна строка «NETWORKIG=yes».
Следующие два условных оператора выполняют аналогичные действия с файлами /etc/sysconfig/usb и /etc/sysconfig/system. Заглянув в эти файлы я увидел, что там просто задаются значения нескольких переменных (видимо, они будут использованы где-то в дальнейшем).
Затем, уже безусловно, исполняется файл /etc/init.d/functions. Этот файл содержит определения функций, которые будут использоваться большинством скриптов из каталога /etc/init.
Последний условный оператор в этом фрагменте проверяет, что переменная HOSTNAME задана и имеет ненулевую длину. Если это именно так (то есть HOSTNAME не задана), то этой переменой присваивается значение «localhost».
Листинг 11-3. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 3. В этом фрагменте монтируются файловые системы /proc и /sys.
Листинг 11-4. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 4. В этом, довольно большом фрагменте задается системный шрифт. Как сообщается в строках комментария, это необходимо сделать именно сейчас, на начальном этапе конфигурирования системы, поскольку многие сообщения переведены на язык пользователя (про аврору не понял). Файл шрифта и другие необходимые файлы должны находиться в каталоге /etc/sysconfig/console, если они там не обнаружены, переменной DELAYED_FONT будет присвоено значение «yes», благодаря чему фонт будет загружаться позже, после того, как будет смонтирован раздел /usr.
Листинг 11-5. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 5. Содержимое командной строки загрузки ядра записывается в переменную cmdline. Эта переменная затем будет использоваться для того, чтобы посмотреть какое значение задано для того или иного параметра.
Листинг 11-6. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 6. Размонтируется виртуальный диск, проверив предварительно некоторые условия. Вся ветка каталога /initrd/dev перемещается в /dev.
Листинг 11-7. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 7. Утилита udev вызывается для создания nodes блоковых устройств. Это необходимо сделать уже сейчас, чтобы обеспечить возможность работать с клавиатурой для получения ответов пользователя на различные запросы.
Листинг 11-8. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 8. Тут что-то делается с SELinux. Не разбирался, скорее всего запускается, но, возможно, выполняются только какие-то подготовительные операции.
Листинг 11-9. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 9. Пока без комментариев.
Листинг 11-10. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 10. Выводятся приветствия системы и предложение перейти в интерактивный режим работы (для чего надо нажать клавишу ).
Листинг 11-11. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 11. Монтируются псевдо-файловые системы /dev/pts и /dev/shm, и подключается терминал Брайля (если существует файл /bin/brltty).
Листинг 11-12. Файл /etc/rc.d/rc.sysinit системы Mandriva Free 2007 Spring. Часть 12. Пара к закрывающей фигурной скобке стояла во фрагменте 10.
Команда wait приводит к тому, что система ждет, пока не закончатся все активные процессы.
Исследуем процесс загрузки Linux
(C) В.А.Костромин, 2007
(версия файла от 27.07.2007 г.)
Этап 1: BIOS
Когда вы включаете питание компьютера, в его оперативной памяти еще нет никакой программы, поэтому управление компьютером может осуществляться только аппаратным обеспечением. На Intel-овских платформах начальная загрузка операционной системы осуществляется посредством так называемой “базовой системы ввода/вывода” или BIOS. Достаточно подробное описание того, как это происходит, я нашел в статьях [4,5,6,7]. Я не буду приводить здесь это описание полностью (заинтересованные читатели могут обратиться к первоисточникам), перечислю только кратко основные этапы.
После включения компьютера блок питания проверяет все необходимые уровни напряжений. Если все уровни напряжений соответствуют номинальным, то на материнскую плату поступит сигнал PowerGood. До появления этого сигнала на вход процессора подается сигнал RESET, который удерживает процессор в сброшенном состоянии. Но после получения сигнала PowerGood от блока питания сигнал RESET будет снят и процессор начнет выполнять свои первые инструкции. При этом процессор стартует от вполне известного состояния: командный регистр CS содержит 0xFFFF, указатель команд (региcтр IP) содержит 0, сегментные регистры данных и стека содержат 0. Таким образом, после снятия RESET процессор в реальном режиме выполняет инструкции, размещающиеся в области ROM BIOS, начинающейся с адреса FFFF:0000 (физический адрес, соответственно, — 0xFFFF0). Размер этой области, очевидно, составляет 16 байт, вплоть до конца максимально адресуемого адресного пространства в реальном режиме — 0xFFFFF. По этому адресу располагается инструкция перехода на реально исполняемый код BIOS.
По соображениям снижения стоимости код BIOS в современных материнских платах хранится в постоянной памяти (ПЗУ) в сжатом виде. Только небольшая его часть, используемая на самых первых этапах загрузки, является непосредственно исполняемой. Поэтому первая задача, которая решается сразу после включения питания, заключается в том, чтобы инициализировать контроллер DRAM, декомпрессировать основной код BIOS и загрузить его в ту область оперативной памяти (RAM), которую именуют «теневой» (shadow RAM). Эта область затем защищается от записи и управление передается на записанный в нее исполняемый код BIOS. Теневая память в ходе дальнейшей работы отдана в полное владение чипсета материнской платы; операционная система к ней доступа не имеет. Но аппаратными средствами обеспечивается отображение теневой памяти на те области, которые в реальном режиме работы доступны для старых операционных систем типа MS-DOS, так что последние обнаруживают код BIOS именно там, где ожидают его найти.
Исполняемый код BIOS вначале реализует функцию начального самотестирования (POST — Power-On Self Test). При этом тестируются процессор, память и системные средства ввода/вывода, а также производится конфигурирование программно-управляемых аппаратных средств компьютера. Кроме того производится поиск и обнаружение периферийных устройств. При этом производится сравнение установок, записанных в CMOS (Complementary Metal Oxide Semiconductor) с тем, что реально обнаружено в системе. Некоторые несовпадения, например, различие типов флоппи-дисковода, могут быть допустимы и процесс загрузки продолжится. Другие ошибки, например, отсутствие видеокарты, приводят к невозможности дальнейшей загрузки. Но все сообщения о выявленных на этом этапе ошибках сводятся только к тому, что раздастся несколько коротких звуковых сигналов.
Можно еще отметить, что некоторые типы периферийных устройств могут содержать расширения BIOS в собственных ПЗУ. В таком случае устанавливаются соответствующие ссылки на эти расширения. Основная причина, по которой это необходимо, заключается в том, что по историческим причинам размер первичного загрузчика (загрузчика первого этапа, как мы его будем дальше называть) на персональных компьютерах ограничен величиной 446 байт. Этого явно недостаточно для того, чтобы включить в этот загрузчик драйверы всех периферийных устройств (например, дисплея и устройств хранения данных), которые могут понадобиться на этапе начальной загрузки системы. Тем более, что драйверы могут различаться для однотипных устройств от разных производителей. Поэтому функция загрузки некоторых драйверов возлагается на BIOS.
В составе исполняемого кода BIOS имеется утилита Setup, которая позволяет выполнить некоторые действия по конфигурированию аппаратных средств компьютера. Утилиту Setup обычно можно вызвать, если во время процесса самотестирования нажать указанную на экране клавишу. Параметры конфигурирования, установленные с помощью этой утилиты, запоминаются в энергонезависимой памяти, питаемой от миниатюрного аккумулятора, размещенного на материнской плате. Утилиту Setup можно использовать для некоторых настроек, но мы на этом не задерживаемся. Для интересующихся приведу несколько ссылок на материалы, в которых этот вопрос освещается подробнее.
После завершения процедуры самотестирования та часть кода BIOS, которая реализует процедуры самотестирования (POST), удаляется из оперативной памяти за ненадобностью. Оставшаяся часть BIOS, реализующая функции BIOS (runtime services), остается в ОП. Она будет доступна в последующем для загруженной операционой системы.
Первой задачей той части BIOS, которая осталась в ОП, является поиск активного загрузочного устройства. Список устройств, которые могут являться загрузочными, хранится в энергонезависимой памяти компьютера (CMOS), а порядок просмотра этого списка является одним из настраиваемых параметров BIOS. Загрузочным устройством может быть дискета, CD-ROM, раздел жесткого диска, сетевое устройство или даже USB-устройство (флеш-диск). Поиск загрузочного устройства осуществляется путем вызова прерывания INT19h BIOS.
Процедура обработки прерывания INT19h состоит в том, что считывается сектор с координатами Cylinder:0 Head:0 Sector:1 на очередном устройстве, его содержимое помещается в ОП по адресу 0000:7С00h, после чего осуществляется проверка, является ли этот сектор загрузочным, то есть содержит ли он код первичного загрузчика. Загрузочные сектора помечаются «волшебным» числом 0x55AA в позиции 0x1FE = 510. Это последние два байта сектора. Наличие (или отсутствие) такого кода в последних байтах сектора позволяет программе BIOS решить, является ли данное устройство загрузочным.
Как только программа обработки прерывания обнаружит, что загруженный в память сектор содержит это самое «магическое число» 0x55AA , управление передается на начало этого сектора (по абсолютному адресу 0000:7С00h). Дальнейшие события зависят от того, где обнаружен загрузочный сектор — на жестком диске или на одном из других устройств: дискете, CD или flash-диске (на всех этих устройствах обычно создается образ загрузочной дискеты).
Если при проверке загрузочный сектор не обнаружен ни на одном устройстве, вызывается прерывание INT18h. Когда-то (в первых персональных компьютерах, производимых компанией IBM) это прерывание служило для вызова интерпретатора ROM-BASIC, который дальше управлял работой компьютера. Клоны IBM-PC не имеют BASIC в ROM-памяти и теперь это прерывание используют для организации загрузки по сети. Но мы не будем рассматривать эту ветку развития событий и вернемся к тому случаю, когда прерывание INT19h обнаружило загрузочный диск и передало управление находящейся в нем программе.
Примечание: Как следует из приведенного выше описания, BIOS выполняет массу работы по тестированию системы. Как мы увидим чуть позже, ядро Linux потом повторно проделывает всю эту работу. Как правило, после загрузки ядра большинство функций BIOS не используется (хотя есть некоторые исключения) и, тем не менее, этот уже бесполезный код BIOS сохраняется в «теневой» памяти компьютера, отнимая часть драгоценного ресурса у работающей системы.
