Практическая загрузка
Изучите процесс загрузки Linux, Windows и Unix
Йогеш Бабар
Кембриджский университет, 2020
перевод В.Айсин
Эта книга в другом переводе — с подстветкой синтаксиса кода — выложена здесь:
http://onreader.mdl.ru/HandsonBooting/content/index.html
Эта книга посвящена Red Hat. Его удивительная культура работы доказала, что делиться – значит заботиться.
Об авторе
Йогеш Бабар работает в Red Hat последние десять лет. В настоящее время он является главным инженером технической поддержки в области ядра Linux. Он специализируется на устранении неполадок и настройке производительности корпоративных серверов Linux. Процесс загрузки Linux — его сильная сторона, и он регулярно выступает на конференциях и форумах по открытому коду. Он также проводит семинары по операционным системам для студентов-инженеров.
О техническом рецензенте
Марк Сандаски — инженер встраиваемого программного обеспечения с 28-летним опытом низкоуровневого программирования. Он работал в таких отраслях, как BIOS для ПК, медицинское оборудование и оборона. У него есть опыт работы со встроенными ОС (Linux, Windows Embedded Compact), RTOS (uCOS/II, FreeRTOS) и физическими системами. В настоящее время он живет в южной Калифорнии со своей женой и тремя детьми. Вы можете связаться с ним по адресу marc_sandusky@outlook.com
или www.linkedin.com/in/marc-sandusky-67852b2/
.
Благодарности
Я хотел бы поблагодарить Харальда Хойера за написание dracut и Леннарта Пёттеринга за написание systemd. Харальд, ты проявил огромное терпение, отвечая на мои вопросы.
Также спасибо: Шиталу, Раме и Шумику, которые посоветовали мне задокументировать процедуру загрузки; Партх Госвами, который помог мне написать об этом краткую статью; Рангану и Рагвендре Паю за регулярную просьбу предоставлять обновленную информацию; и Гохале Сир за то, что зажег во мне искру, а также за то, что показал мне, в чем я действительно хорош.
Спасибо всей команде Apress, особенно редактору по закупкам Селестине Джон, координатору проекта Адите Мираши и редактору по развитию Мэтью Муди, которые приложили огромные усилия для разработки этой книги. Особая благодарность Марку Сандаски за техническую рецензию книги. Поскольку это была моя первая книга, я допустил много ошибок, но вся команда Apress поддерживала меня на протяжении всего процесса.
И последнее, но не менее важное: спасибо моей красивой, сильной и удивительной жене. Даршана, какое терпение ты проявила! Иногда я удивляюсь, как тебе удалось остаться с кем-то вроде меня, который всегда гонится за каким-то проектом.
Введение
В первую неделю я работал на новой работе и увидел, как один из наших клиентов просил помощи по проблеме «невозможно загрузиться». Я был новичком и неопытным. Я хотел помочь, но не смог. Заказчик был в панике, поскольку это привело к остановке производства. Для них была на счету каждая минута, потому что тысячи пользователей не могли получить доступ к этой системе, поскольку она не загружалась. Все были в панике. В конце концов некоторые из наших старших инженеров решили проблему. Им потребовалось почти пять часов, чтобы вернуть систему в эксплуатацию. В конце концов все сложилось хорошо, но эта напряженная ситуация породила во мне что-то — желание учиться. Я решил изучить всю последовательность загрузки.
Когда я начал искать книги и статьи в Интернете, меня постигло разочарование. По операционным системам доступны тысячи книг и бесчисленное количество статей, но я не смог найти ни одной книги, которая подробно объясняла бы всю последовательность загрузки.
В мире открытого исходного кода есть поговорка: если вы что-то ищете, но оно недоступно, создайте это. Итак, я решил изучить последовательность загрузки самостоятельно. Мне потребовались годы, чтобы понять всю последовательность загрузки. Лучшее, что я делал в своем путешествии, — это вел записи, а также начал преподавать то, чему научился, другим. В конце концов, делиться — значит заботиться. Мои сессии загрузки стали популярны среди студентов-инженеров и системных администраторов. Некоторые из них действительно подтолкнули меня к написанию настоящей книги по этой теме. Я обратился в Apress, и идея им понравилась, так что сегодня у вас в руках первая книга по загрузке.
В этой книге использован уникальный подход. Сначала я расскажу, почему вам следует изучить загрузку. Другими словами, почему это важно? Далее я объясню, как работают разные загрузчики, установив почти 100+ операционных систем на один компьютер. О загрузчике Linux есть отдельная глава. Фактически, для каждого компонента, участвующего в последовательности загрузки, есть отдельные главы. Далее я объясню роль ядра в последовательности загрузки. Ядро играет жизненно важную роль наряду с systemd. Поскольку systemd — это первый процесс, запускаемый ядром, в конечном итоге он берет на себя всю последовательность загрузки. В нескольких главах рассматривается systemd, поэтому эта книга — хороший ресурс для тех, кто хочет прочитать о systemd. Я также рассмотрел наиболее распространенные сценарии «не могу загрузить» в Linux. Это делает книгу отличным ресурсом и для системных администраторов. Это не значит, что эта книга предназначена только для экспертов по Linux. Если вы знаете основы Linux, то эта книга для вас. Книга — отличный мост между новичками и экспертами Linux. Надеюсь, вам понравятся эти усилия.
Есть старая поговорка: ни одна книга не идеальна. Если вы обнаружите какие-либо ошибки в этой книге или просто хотите связаться со мной, напишите мне по адресу yogeshbabar420@gmail.com
.
Спасибо,
Йогеш Бабар
Глава 1
Введение
Не все знают Fedora. Однажды кто-то задал мне вопрос:
Студент: Что такое Fedora?
Я: Fedora — это Linux.
Студент: Что такое Linux?
Я: Linux — это операционная система.
Студент: Что такое операционная система?
Я: Она управляет компьютерами.
Студент: Что такое компьютер?
Я: Компьютеры помогают пользователям.
Студент: Что такое пользователь?
Я: Пользователь такой же, как я.
Студент: Кто ты, черт возьми, такой?
Я: Ну, меня зовут Йогеш Бабар. Я работаю в Red Hat последние десять лет и люблю рассказывать о том, как загружаются операционные системы.
Зачем?
Всем известно, что загрузка операционной системы занимает примерно 20–30 секунд. Итак, почему я написал 486-страничную книгу о 30-секундной загрузке? Ответ прост.
-
Не существует подходящего документа/статьи/книги, объясняющей полную последовательность загрузки. Вы найдете сотни хороших книг по операционным системам, но ни одной о том, как загружается система.
-
Вы можете решить проблемы с загрузкой, только если знаете, как загружается система.
-
Если вы системный администратор и приходите на собеседование, интервьюеры спросят, как загружается Linux.
-
Проблемы «невозможно загрузиться» всегда имеют самую высокую степень серьезности, поскольку из-за них выходит из строя вся производственная система. Если система работает медленно, производство все еще работает; хотя оно и затронуто, по крайней мере, оно все еще работает. Сервер, на котором работают 10 000 пользователей, но он не может загрузиться, означает, что вся производственная система не работает. В этом важность загрузки, и, как я уже сказал, вы не сможете решить проблемы с загрузкой, если не знаете, как загружается система.
-
Интересно понять процедуру загрузки.
-
Изучив все это, вы обретете огромное счастье.
Что?
Итак, что именно загружается? Технически процесс копирования ядра с жесткого диска в память и последующего его выполнения называется загрузкой (booting). Но это определение на самом деле не вдохновляет нас на изучение загрузки.
Я скажу своими словами: мать — это супермножество, а ее новорожденный ребенок — ее подмножество. Точно так же операционная система — это надмножество, а загрузка — его подмножество. Подмножество принадлежит своему надмножеству.
Теперь рассмотрим такое утверждение: «Ребенок рождает мать».
Технически это неправильно, но представьте себе, что пока у женщины не родится ребенок, она женщина; в тот момент, когда у нее рождается ребенок, женщина становится матерью. Итак, ребенок рождает мать.
То же самое происходит и в компьютерах. Технически загрузка — это часть операционной системы, и операционная система должна порождать загрузку, но все наоборот. Именно загрузка рождает операционную систему. Следовательно, мы можем сказать, что загрузка — это процедура, в результате которой возникает операционная система.
Цель этой книги
В книге объясняется процедура загрузки настольной или серверной системы на базе архитектуры x86, а также рассматривается процедура загрузки различных операционных систем. Основное внимание уделяется углубленному анализу процедуры загрузки Linux, а вторичное внимание уделяется другим популярным операционным системам, таким как Windows и UNIX. Как вы знаете, существует огромное количество дистрибутивов Linux. Некоторые из них предназначены для пользователей настольных компьютеров, некоторые — для корпоративных клиентов, некоторые — исключительно для игровых целей, а некоторые доступны для пользователей, которые предпочитают использовать подход «сделай сам». Практически невозможно описать последовательность загрузки каждого дистрибутива. Поэтому я решил выбрать дистрибутив Linux, который является первым выбором для корпоративных клиентов, а именно Red Hat Enterprise Linux (RHEL).
RHEL основан на Fedora Linux. Fedora развивается быстро (цикл выпуска составляет шесть месяцев), тогда как RHEL — это медленный дистрибутив (цикл выпуска составляет два-три года). Это означает, что Fedora принимает новейшие разработки, как только команда QE (Quality Engineering) дает им зеленый свет. Поскольку Fedora является испытательной площадкой для популярных корпоративных дистрибутивов Linux, все, что доступно в Fedora, в конечном итоге становится частью RHEL. systemd — лучший пример этого. Вот почему я выбрал Fedora Linux для объяснения последовательности загрузки Linux.
Источник питания
Все начинается, когда вы нажимаете кнопку питания. При нажатии кнопки питания питание поступает на материнскую плату. Материнская плата отправляет сигнал на ваш источник питания (SMPS/PSU), который отвечает за исправность источника питания, и в результате материнская плата пытается запустить процессор.
Процессор
Когда процессор на базе архитектуры x86 запускается, он очищает старые данные из всех регистров и начинается с этого:
IP 0xfff0
CS selector 0xf000
CS base 0xffff0000
0xffff0000 + 0xfff0 = 0xffffff0
. Это ячейка памяти, в которой CPU ожидает найти первую команду для выполнения. В этой ячейке содержится команда перехода, указывающая на точку входа в BIOS. Другими словами, именно так запускается BIOS или процессор переходит в BIOS/прошивку.
После этого прошивка и загрузчик являются следующим этапом процедуры загрузки. Задача прошивки — запустить загрузчик операционной системы. В следующей главе я расскажу, что происходит в прошивке и как она выполняет загрузчик.
Глава 2
Мультизагрузка
Разобраться в загрузчике и прошивке сложно. Это не обязательно сложно, но тема может быть сложной. Чтобы читателям было легче усвоить эту книгу, я буду использовать три тестовые системы.
Номер системы | Имя системы | Цель |
---|---|---|
1 | BIOS | Для демонстрации BIOS |
2 | UEFI | Для демонстрации UEFI |
3 | Jarvis | Для мультизагрузочного проекта со 100+ ОС |
Поскольку загрузчики и прошивка тесно взаимодействуют друг с другом, я начну с установки определенного списка операционных систем в каждой системе и при этом объясню связь между загрузчиком и прошивкой. Такой подход сделает сложные темы более понятными, интересными и увлекательными. Короче говоря, я объясню загрузчик и прошивку (BIOS/UEFI) вместе, хотя это разные понятия.
Список операционных систем
Мы будем устанавливать следующие операционные системы в нашу первую систему BIOS, то есть в систему, в которой установлено встроенное ПО BIOS:
Sun OpenSolaris 2009
Fedora Linux 15
PC-BSD 9.0
Windows 7
Red Hat Enterprise Linux 6.0
Windows Server 2003 (2k3)
Windows XP
Я знаю, что эти операционные системы довольно старые, но я выбрал их не просто так.
Видите ли, BIOS сам по себе является устаревшей прошивкой, поэтому, если вы хотите разобраться в BIOS, вам придется использовать только старые операционные системы. Помните, вы сможете понять UEFI (текущую прошивку), только если разбираетесь в BIOS. Это похоже на то, что вы лучше поймете Java, если хорошо знаете C. Кроме того, использование этих старых операционных систем даст мне возможность коснуться загрузчиков Windows и Unix. Кроме того, это даст мне возможность объяснить устаревший загрузчик Linux GRUB (GRUB Legacy).
Идея заключается в мультизагрузке нашей системы BIOS со всеми операционными системами, упомянутыми ранее. Для этого нам нужно следовать правилам и предписаниям каждой операционной системы.
OS | Правила |
---|---|
Unix | Операционные системы Unix (OpenSolaris и BSD) необходимо устанавливать только в основной раздел. |
Linux | В Linux нет каких-либо правил установки. Его можно установить в любой основной или логический раздел. |
Windows | Операционную систему Windows можно установить в любой раздел (основной или логический), но предшественник семейства Windows должен присутствовать в первом основном разделе. Это означает, что вы можете установить Windows 7 в логический раздел, но ее предшественник, XP или win2k3, должен присутствовать в первом основном разделе. Также нельзя нарушить последовательность установки операционной системы Windows. Например, нельзя сначала установить Windows 7, а затем более старую версию Win2k3 или XP. Должно быть в такой последовательности: 98, потом 2000, потом XP. |
Потратьте некоторое время и попытайтесь подготовить последовательность установки вашей ОС. Проверьте последовательность загрузки сейчас.
Окончательная последовательность установки операционной системы показана здесь:
- Windows XP
- Sun OpenSolaris 2008
- PC-BSD 9.0
- Windows Server 2003
- Windows 7
- Red Hat Enterprise Linux 6
- Fedora 15
Установка операционных систем
Теперь поговорим об установке операционных систем.
Первичные(основные)/логические разделы
С помощью BIOS мы можем создать только четыре раздела. Но, конечно, вы, вероятно, видели, что используется больше разделов. Итак, позвольте мне немного изменить мое утверждение. В системе на базе BIOS вы можете создать на диске только четыре первичных (основных) (primary) раздела. Если вам нужно больше, вам нужно сделать четвертый основной раздел дополнительным (secondary) (также называемым расширенным (extended)) разделом. Расширенный раздел будет работать как контейнер, и внутри этого контейнера вы можете создать столько логических (logical) разделов, сколько захотите. Почему эти разделы называются логическими, ведь они не видны BIOS? Кроме того, почему BIOS может создать только четыре основных раздела? Ответы на эти вопросы будут даны при обсуждении главной загрузочной записи (MBR).
Разбиение на разделы
Давайте сначала разобьем жесткий диск системы BIOS. Для этого мы будем использовать Live CD GParted. GParted — это инструмент сообщества GNU. Это бесплатный live-образ ISO на базе Debian Linux с открытым исходным кодом. На рисунке 2-1 показано расположение разделов нашей системы BIOS.
Рисунок 2-1. Структура разделов BIOS в GParted
Операция GParted по разделению жесткого диска проста. Мы создадим структуру разделов, показанную на рисунке 2-2, на 75 ГБ дискового пространства.
Рисунок 2-2. Структура раздела, созданного Gparted
Для получения дополнительной информации о том, как использовать GParted для разделения жесткого диска, обратитесь к документации GParted по адресу https://gparted.org/articles.php.
На рисунке 2-3 вы можете увидеть имя диска, размер раздела, используемую файловую систему и связанные с ней флаги (если есть).
Рисунок 2-3. Структура файловой системы, созданная GParted
Давайте установим нашу первую операционную систему в первый основной раздел.
Первая установка ОС: XP
На рисунке 2-4 вы можете увидеть структуру разделов, показанную установщиком Windows XP.
Рисунок 2-4. Схема разделов, показанная установщиком XP
Мы устанавливаем XP на первый основной раздел. С точки зрения Windows это диск C:, как показано на рисунке 2-4. После завершения установки и перезагрузки системы на нашем экране появится Windows XP (рисунок 2-5).
Рисунок 2-5. XP после успешной установки
Пришло время разобраться, как загружалась Windows XP, но перед этим нам необходимо разобраться с загрузочным сектором. Загрузочный сектор (boot sector) — это первый сектор каждого жесткого диска (512 байт) плюс 31 КБ пространства; другими словами, это первые 63 сектора загрузочного носителя (от 0 до 62). Или вы можете учесть, что в загрузочном секторе некоторое пространство (512 байт + 31 КБ) каждого раздела будет зарезервировано для хранения информации, связанной с загрузчиком. Это пространство (опять же 512 байт + 31 КБ) не будет показываться пользователям ОС. Фактическое хранение данных в разделе начинается после этого зарезервированного пространства. Обратитесь к рисунку 2-6 для лучшего понимания этого.
Рисунок 2-6. Схема диска в системе на базе BIOS
Загрузочный сектор
На санскрите есть одна удивительная поговорка, которая звучит так: "एकम सत वगाः बहु्ाः वदन्ति सत्य". Это означает, что есть только одна истина, но есть разные способы достичь ее. Как показано на рисунке 2-7, загрузочный сектор называется по-разному, но в конечном итоге концепция остается той же. Люди называют эту структуру следующими именами:
Главная загрузочная запись (MBR) (Master boot record)
Загрузочная запись (Boot record)
Загрузочный сектор (Boot sector)
Загрузчик (Bootloader)
Рисунок 2-7. Загрузочный сектор
В этой книге мы будем называть его загрузочным сектором (boot sector), поскольку жесткий диск (HDD) всегда разделен на сектора, и каждый сектор имеет размер либо 512 байт, либо 4 КБ. Большинство жестких дисков имеют размер сектора 512 байт.
В системе на базе BIOS каждый поставщик ОС (неважно, Windows, Unix или Linux) должен разделить загрузчик на три части. Часть-1 загрузчика будет храниться в первых 440 байтах кода первого сектора диска (это Bootstrap). Часть-2 будет храниться в разделе загрузчика размером 31 КБ (т.е. в следующих 62-х секторах диска, это Bootloader), а последняя часть-3 будет храниться внутри фактического раздела, где установлена конкретная ОС. Проще говоря, всякий раз, когда устанавливается операционная система (в нашем случае это Windows XP), она делит свой загрузчик New Technology Loader (NTLDR) на три части.
Расположение | Размер | Часть | Информация |
---|---|---|---|
Bootstrap (MBR, stage 1) | 440 байт | NTLDR, часть-1 | Самая маленькая часть |
Bootloader (post-MBR gap, stage 1.5) | 31 КБ | NTLDR, часть-2 | Больше по сравнению с частью-1 |
Внутри реального раздела ОС, stage 2 | Нет ограничений по размеру | NTLDR, часть-3 | Самая большая часть |
Но почему загрузчик разделен на три части?
Это обусловлено историческими причинами. BIOS имеет технические ограничения: он не может получить доступ к более чем 512 байтам или читать дальше первого сектора. Итак, очевидно, что когда BIOS завершает свою задачу, он переходит к первым 512 байтам жесткого диска, и что бы там ни находилось, он просто запускает этот код. К счастью, эта программа будет нашим загрузчиком bootstrap (440 байт). Поскольку загрузчик bootstrap крошечный по размеру, он делает только одну вещь: переходит на большее пространство, которое является загрузчиком bootloader второй части. Его размер составляет 31 КБ. Эти 31 КБ опять же очень крохотные, и bootloader'у предстоит найти еще больший размер. Он перейдет к части-3, которая находится уже внутри раздела. Этот файл части-3 будет находиться на диске C:
под именем NTLDR
. Файл части-3 загрузчика XP показан на рисунке 2-8.
Рисунок 2-8. Файл части-3 загрузчика XP
Как видите, размер файла намного больше (245 КБ). Этот файл (он знает, где находится ядро XP) будет выполнять тяжелую работу загрузчика, а именно копировать ядро Windows XP под названием winload.exe
из C:\windows
в память. Как только ядро будет скопировано в память, работа загрузчика считается завершенной и он исчезает. Помните, OS==kernel==OS
. Как только ядро окажется в памяти, оно позаботится об остальной части последовательности загрузки. Вы можете увидеть последовательность загрузки XP на рисунке 2-9.
Рисунок 2-9. Последовательность загрузки Windows XP
Я знаю, что у вас, вероятно, много вопросов. Но продолжайте читать, и на все ваши вопросы будут даны ответы. Давайте продолжим и обсудим поля загрузочного сектора, которые я еще не объяснил. Для этого вы можете обратиться к рисунку 2-10.
Рисунок 2-10. Загрузочный сектор
Поле сигнатуры производителя предназначено для поставщиков жестких дисков. Упомянутые здесь данные говорят нам, какой поставщик изготовил этот жесткий диск, например Seagate, Western Digital, Samsung и т. д. Таким образом, по сути, они содержат информацию о производителе жесткого диска.
NULL имеет только 2 байта пространства. NULL означает NULL. Если это значение не NULL, то BIOS во время процедуры POST будет считать этот жесткий диск неисправным/поврежденным, и загрузка будет остановлена. Итак, оно должно быть NULL. Всякий раз, когда ОС внезапно перезагружается или сама ОС или жесткий диск обнаруживает поврежденный сектор или какое-либо серьезное повреждение, это поле будет помечено как ненулевое.
Поле MBR (как уже упоминалось, чаще всего его называют PT (Partititon Table)) может быть самым популярным разделом из всех этих полей. MBR означает «master boot record» и имеет размер 64 байта. MBR далее разделен на четыре части. Размер каждой части составляет 16 байт, и каждая часть содержит информацию об одном разделе.
Размер | Часть | Хранит |
---|---|---|
16 байт | Часть-1 | Информация первого раздела |
16 байт | Часть-2 | Информация второго раздела |
16 байт | Часть-3 | Информация третьего раздела |
16 байт | Часть-4 | Информация четвертого раздела |
Это означает, что 64 байта MBR могут содержать только четыре записи раздела, и именно по этой причине вы можете создать только четыре основных раздела в системе на базе BIOS.
Сигнатура fdisk
также называется флагом загрузки (boot flag); некоторые люди называют его просто звездочкой *
, или в стиле Windows его еще называют флагом активности/неактивности (active/inactive flag). fdisk
важен в случае мультизагрузки разных операционных систем, о чем мы сейчас говорить не будем.
А пока я хочу, чтобы вы запомнили эти два правила:
Логический раздел не может быть активным.
ОС не может загружаться с логического раздела.
На данный момент эти два правила не будут иметь для вас никакого смысла, но мы обсудим их в подходящее время. На рисунке 2-11 показана полная последовательность загрузки Windows XP.
Рисунок 2-11. Последовательность загрузки Windows XP
Теперь мы установим и загрузим новую ОС, а именно OpenSolaris 2008.
OpenSolaris 2008
На рисунке 2-12 показан экран при загрузке с установочного носителя OpenSolaris 2008.
Рисунок 2-12. Экран приветствия установочного носителя OpenSolaris 2008
Нам нужно установить OpenSolaris на второй раздел. На рисунке 2-13 вы можете видеть, что мы выбрали второй основной раздел для установки.
Рисунок 2-13. Структура диска, показанная установщиком OpenSolaris 2008
Но, как вы можете видеть на рисунке 2-14, установка завершается неудачей с некоторыми сообщениями об ошибках.
Рисунок 2-14. Установка завершается неудачей с появлением некоторых сообщений об ошибках
Сообщения об ошибках связаны с файловой системой. Итак, подготовим файловую систему вручную с помощью утилиты fdisk
; однако перед этим вы должны знать, какое имя жесткого диска было назначено OpenSolaris. Вывод команды pfexec format
(показанный на рисунке 2-15) покажет нам имя жесткого диска.
Рисунок 2-15. Имя жесткого диска, назначенное OpenSolaris
Итак, имя назначенного жесткого диска — c4d1
. Нам нужно передать это имя устройства утилите fdisk
. Полную команду смотрите на рисунке 2-16.
Рисунок 2-16. Команда fdisk
Имя диска указывает номер контроллера 4, номер диска 1 и номер раздела 0. С помощью утилиты fdisk
мы сначала удалили второй раздел (который был родным для ext3/Linux) и создали новый раздел с файловой системой Solaris2. Новый раздел становится разделом номер 4. Кроме того, он автоматически становится активным разделом (см. рисунок 2-17). Мы еще не говорили об «активности или fdisk-сигнатуре», но поговорим об этом в ближайшее время.
Рисунок 2-17. Изменения, внесенные с помощью команды fdisk
Возвращаясь к нашей установке, давайте перезапустим установку, и, как вы можете видеть на рисунке 2-18, на этот раз мы выбрали раздел, отформатированный в файловой системе OpenSolaris, для установки нашего OpenSolaris 2008.
Рисунок 2-18. Установка OpenSolaris в раздел файловой системы OpenSolaris
На этот раз установка завершится удачно (см. рисунок 2-19), и OpenSolaris 2008 будет установлен.
Рисунок 2-19. Программа установки не завершится удачно
После установки мы перезагрузим нашу систему BIOS. Как вы думаете, какая ОС будет загружаться?
Windows XP?
OpenSolaris?
XP и OpenSolaris вместе?
Ни одна из них?
Потратьте некоторое время и подумайте, прежде чем продолжить...
На рисунке 2-20 показано, что мы видим на экране после перезагрузки.
Рисунок 2-20. Экран приветствия после перезагрузки
Итак, здесь загружается ОС OpenSolaris, и она также дает нам возможность загрузить XP. Давайте прольем свет на то, что произошло на заднем плане. OpenSolaris увидел, что он устанавливается в свой собственный раздел (второй), но в первом разделе доступна другая операционная система — Windows (или, по крайней мере, «non-Unix ОС»).
Но как OpenSolaris узнал, что в первом основном разделе установлена другая ОС?
Когда OpenSolaris был установлен в отдельный раздел, он увидел, что сигнатура fdisk установлена в первом основном разделе. (Опять же, подпись fdisk также называется флагом активности или просто флагом *
.) Как мы видели и ранее на диаграмме спецификации загрузочного сектора (рисунок 2-21), каждый раздел имеет 512 байт + 31 КБ пространства, зарезервированного для целей загрузки, и это пространство скрыто от пользователя.
Рисунок 2-21. Загрузочный сектор
Другими словами, когда мы создали структуру разделов с помощью GParted, инструмент создал следующие отсеки для каждого раздела:
Загрузчик Bootstrap
Сигнатура производителя
NULL
MBR (PT)
Сигнатура fdisk (признак активности)
Загрузчик Bootloader
Но он заполнял данные только в полях сигнатуры производителя и MBR (PT). Поле сигнатуры производителя будет содержать данные производителя жесткого диска, тогда как в случае поля MBR (PT) данные будут следующими:
Начало и конец первого основного раздела
Начало и конец второго основного раздела
Начало и конец третьего основного раздела
Начало и конец четвертого основного раздела
По сути, будет четыре записи, и каждая запись будет занимать 16 байт. Остальные поля, кроме подписи поставщика и MBR (PT), будут пустыми. Также обратите внимание, что GParted подготовит все отсеки (512 байт + 31 КБ), но заполнит только поля подписи поставщика и MBR для первого основного раздела.
Возвращаясь к полю сигнатуре fdisk, при установке Windows XP было установлено следующее:
Часть-1 NTLDR в загрузчике bootstrap
Часть-2 NTLDR в загрузчике bootloader
Часть-3 NTLDR внутри первого основного раздела
Затем он установил сигнатуру fdisk (признак активности раздела) в свой раздел (2 байта).
Итак, структура диска будет примерно такой, как показано на рисунке 2-22.
Рисунок 2-22. Структура диска после установки XP
OpenSolaris нашел эту структуру диска. Когда установка OpenSolaris была завершена и он захотел установить свой загрузчик (GRUB), он увидел звездочку (*) на первом основном разделе и тогда понял, что уже установлена операционная система Windows. Теперь у GRUB (загрузчика OpenSolaris) есть два варианта.
Установить часть-1 (загрузчик bootstrap) и часть-2 (загрузчик bootloader) GRUB'a в первый основной раздел, а часть-3 GRUB'a в отдельный раздел (второй раздел, где установлен OpenSolaris).
Или установить часть-1 (загрузчик bootstrap) в первые 512 байт своего собственного раздела, часть-2 в 31 КБ своего собственного раздела, а часть-3 также в свой собственный раздел; затем поместить *
в свой собственный второй раздел (см. рисунок 2-23).
Рисунок 2-23. Структура диска в GParted после установки OpenSolaris
Обратите внимание, что флаг загрузки вернулся к разделу OpenSolaris. Кроме того, GParted не понимает раздел Solaris2; следовательно, в качестве имени файловой системы отображается ext3.
Если OpenSolaris выберет вариант 1, то ему придется очистить часть-1 и часть-2 загрузчика Windows XP. Это также означает, что будет загружаться только OpenSolaris, а XP загрузиться не сможет. Следовательно, OpenSolaris выбирает вариант 2, предоставляющий равные возможности для загрузки Windows XP. OpenSolaris также создает запись Windows XP в одном из своих файлов (об этом файле мы поговорим позже в этой главе). Всякий раз, когда OpenSolaris начинает загружаться, GRUB обращается к этому файлу и находит в нем запись Windows, которая отображается на экране. На рисунке 2-24 показан экран приветствия OpenSolaris.
Рисунок 2-24. Экран приветствия OpenSolaris
Итак, полная последовательность загрузки OpenSolaris выглядит следующим образом:
-
Включение системы.
-
Процессор переходит в BIOS.
-
BIOS запускает процедуру POST.
-
Возвращаемся в BIOS.
-
BIOS какой-то туповатый; он проверит приоритет загрузки, установленный пользователем.
-
Когда я говорю приоритет загрузки (boot priority), я имею в виду устройство, через которое будет загружаться система.
-
Это может быть CDROM, USB, HDD, PXE и т. д.
-
-
BIOS переходит к первым 512 байтам HDD или к первому сектору загрузочного устройства.
-
Загрузочным устройством может быть что угодно, но на данный момент мы рассматриваем HDD.
-
-
BIOS передаст управление тому двоичному коду, который присутствует в загрузчике bootstrap.
-
Как вы думаете, кто там? Загрузчик Windows (NTLDR) или OpenSolaris (GRUB)? Подумайте немного, а затем продолжайте.
-
Загрузочный сектор, хранящийся в первых 512 байтах, представляет собой NTLDR Windows XP.
-
Вы, должно быть, заметили, что 440 байт пространства загрузчика bootstrap очень малы, и ни один код не может загрузить из него ОС. Следовательно, часть-1 NTLDR (загрузчик bootstrap) просто переходит в большее пространство, то есть в часть-2 (загрузчик bootloader / 31 КБ / виртуальную загрузочную запись). Часть-2 проверяет MBR (PT) (64 байта) и находит в нем четыре записи. Это означает, что на диске четыре основных раздела. Но здесь есть проблема: на каком из четырех основных разделов установлена ОС? Вы, конечно, скажете, что это первый и второй раздел, но откуда загрузчик bootloader знает, где находится ОС? И какая из них должна загружаться? Это реальный вопрос, и для решения этой проблемы было создано поле сигнатуры fdisk. В каком бы разделе ни были заполнены или установлены эти 2 байта, в этом разделе есть операционная система. Итак, когда устанавливалась Windows XP или OpenSolaris, эта ОС обязана заполнить 2 байта поля сигнатуры fdisk или установить
*
на своем собственном разделе, чтобы загрузчик bootloader знал, на каком разделе находится ОС. В нашем случае*
находится на втором разделе (OpenSolaris сохранил ее во время установки). Таким образом часть-2 NTLDR узнает, что ей нужно перейти на второй раздел.
-
-
Часть-2 NTLDR переходит ко второму разделу, что означает просто переход к части-1 загрузчика GRUB во втором разделе (загрузчик bootstrap).
-
Часть-1 GRUB (загрузка bootstrap / 440 байт) снова крошечная, поэтому она снова перейдет в большее пространство, которое является частью-2 GRUB (загрузчик bootloader).
-
Часть-2 знает, где находится часть-3. Местоположение части-3 будет жестко запрограммировано в части-2, поэтому она просто перейдет к части-3. Часть-3 прочитает текстовый файл
/rpool/boot/grub/menu.lst
(см. рисунок 2-25); это тот же файл, который был создан OpenSolaris при обнаружении XP на первом основном разделе.Рисунок 2-25. Файл
menu.lst
OpenSolaris -
Часть-3 GRUB прочитает этот текстовый файл и распечатает все, что написано после переменной
title
, и именно так мы доберемся до экрана, показанного на рисунке 2-26.Рисунок 2-26. Экран приветствия OpenSolaris.
На рисунке 2-27 показана полная последовательность загрузки OpenSolaris.
Рисунок 2-27. Последовательность загрузки OpenSolaris
Если пользователь выбирает для загрузки вариант OpenSolaris, то часть-3 OpenSolaris GRUB знает, где находится ядро OpenSolaris — в каталоге /boot
. GRUB скопирует ядро из /boot
в память и передаст управление ядру. На этом задача загрузчика GRUB заканчивается и он исчезает. Теперь ядро OpenSolaris позаботится об остальной части загрузки. Мы поговорим о ядре в главе 4.
Если пользователь выбирает вариант загрузки Windows XP, то часть-3 OpenSolaris GRUB вернется к части-1 NTLDR (загрузчик bootstrap). Часть-1 NTLDR перейдет в часть-2 NTLDR. Часть-2 перейдет в часть-3. Часть-3 NTLDR загрузит winload.exe
в память. Файл winload.exe
знает, где находится ядро XP. В конечном итоге NTLDR скопирует или загрузит его в память. Как только ядро окажется в памяти, работа NTLDR будет завершена (помните, kernel=OS=kernel
). Поскольку ядро XP находится в памяти, оно позаботится об остальной части последовательности загрузки.
PC-BSD 9.0
Звездочка *
или флаг загрузки находится в разделе OpenSolaris, поэтому теперь мы установим PC-BSD 9.0. На рисунке 2-28 программа установки PC-BSD показывает количество разделов, на которые можно установить PC-BSD 9.0.
Рисунок 2-28. Количество разделов
Как видите, соглашение об именах жестких дисков в BSD отличается от более ранних ОС. Нам нужно установить BSD на третий раздел — ada0s2
. Это означает «Адаптер номер ноль и номер слайса 2». Слайс можно рассматривать как раздел. На рисунке 2-29 показана структура диска и соглашения об именовании дисков.
Рисунок 2-29. Структура диска и соглашения об именовании дисков
Назначьте пространство ada0s2
как /
(корневая файловая система). На рисунке 2-30 показана структура разделов PC-BSD 9.0. Вы также заметите, что файловая система BSD — UFS
, то есть файловая система Unix.
Рисунок 2-30. Структура разделов PC-BSD 9.0
После установки система перезагрузится. Теперь потратьте немного времени и подумайте, какая ОС будет загружаться.
Какой из нижеперечисленных вариантов это будет?
OpenSolaris, который даст возможность загружать Windows и BSD.
Будет ли это PC-BSD, который даст возможность загружать две другие ОС?
Будет ли это только PC-BSD?
Будет ли это только Windows XP?
Будет ли это только OpenSolaris?
Или ни одна операционка не загрузится?
Ознакомьтесь с блок-схемами загрузки более ранних операционных систем и попытайтесь придумать собственную последовательность загрузки.
Как вы можете видеть на рисунке 2-31, будет загружаться ОС OpenSolaris, что позволит загрузить дополнительно только Windows.
Рисунок 2-31. PC-BSD не загружается
PC-BSD не загружается. Прежде чем перейти к следующей странице, снова выделите немного времени и подумайте о том, что произошло.
Вы правы: есть вероятность, что PC-BSD не сохранила сигнатуру *
флага активности для своего собственного раздела. Посмотрим, так ли это. Мы загрузимся с помощью GParted (рисунок 2-32) и проверим нашу теорию.
Рисунок 2-32. Экран приветствия GParted
Как вы можете видеть на рисунке 2-33, PC-BSD не имеет установленного значения *
в собственном разделе.
Рисунок 2-33. Структура диска в GParted
Итак, последовательность загрузки выглядит так, как показано на рисунке 2-34.
Рисунок 2-34. Последовательность загрузки и почему PC-BSD не может загрузиться
Это означает, что OpenSolaris не знает, что BSD установлен в третьем разделе. Следовательно, запись PC-BSD не относится к OpenSolaris. Что, если мы сохраним флаг загрузки на разделе BSD? Будет ли он загружаться? Но как нам сохранить флаг загрузки на третьем разделе? Все просто — GParted дает нам такую возможность. Щелкните правой кнопкой мыши третий раздел и выберите флаг загрузки, как показано на рисунке 2-35.
Рисунок 2-35. Установка флага загрузки на PC-BSD
На рисунке 2-36 показано, как выглядит структура диска после установки флага загрузки в третьем разделе BSD.
Рисунок 2-36. Схема диска
Как вы думаете, какая ОС будет загружаться?
Только PC-BSD?
PC-BSD, который даст возможность загружать любую другую ОС?
Опять OpenSolaris, который позволит загружать Windows?
Только OpenSolaris?
Только Windows XP?
На рисунке 2-37 показан ответ; после перезагрузки загружается только PC-BSD, и он не дает возможности загрузить какую-либо другую ОС.
Рисунок 2-37. Экран приветствия PC-BSD
Давайте попробуем понять, как удалось загрузиться PC-BSD.
-
Включение системы.
-
BIOS выполняет процедуру POST. POST проверяет состояние оборудования и подает звуковой сигнал, если все в порядке, и возвращается в BIOS.
-
BIOS тупой, и он просто перескакивает на первый сектор всего HDD, который является загрузчиком bootstrap Windows XP.
-
XP часть-1 (NTLDR) переходит в большее пространство, которое является частью-2 NTLDR (загрузчик bootloader). Загрузчик проверяет MBR и обнаруживает, что существует четыре основных раздела, но какой из них активен? Чтобы это проверить, загрузчик проверяет сигнатуру fdisk первого основного раздела, которая не установлена, а затем проверяет флаг загрузки второго раздела, который также не установлен. Следовательно, он переходит к третьему разделу, где обнаруживает установленный флаг загрузки. Загрузчик bootloader (часть-2) NTLDR переходит к разделу BSD и запускает загрузчик bootstrap загрузчика bootloader BSD. Загрузчиком bootloader BSD является BTX, что означает Boot Extended. BTX переходит ко второй части и, в конечном итоге, к третьей части. Третья часть BTX знает, где находится ядро BSD. Часть-3 BTX копирует образ ядра BSD в память, и на этом этапе BTX останавливается, а PC-BSD начинает загружаться и показывает нам экран приветствия. На рисунке 2-38 показана блок-схема последовательности загрузки PC-BSD.
Рисунок 2-38. Последовательность загрузки PC-BSD
Интересная часть загрузки BSD заключается в том, что при установке PC-BSD флаг загрузки был обнаружен на втором разделе, то есть разделе OpenSolaris. И у BSD есть три варианта:
-
Сохранить флаг загрузки в собственном третьем разделе.
-
Сохранить флаг загрузки в собственном третьем разделе и сделать запись OpenSolaris в некоторых его файлах.
-
Сохранить флаг загрузки на прежнем месте, на втором разделе.
Если BSD выберет первый вариант (а), то только BSD сможет загрузиться, и это будет несправедливо по отношению к другим установленным операционным системам. Мы хотим, чтобы BSD выбрала второй вариант (b), поскольку это позволит загружать любую другую ОС, но BTX — это старый загрузчик, и он не имеет возможности мультизагрузки других операционных систем. Следовательно, BSD выбирает третий вариант (c). Таким образом, загружается только OpenSolaris, и он предоставляет возможность загрузки XP. Помните, XP сразу не загружается. Загружается только OpenSolaris, и, прочитав файл menu.lst
, он дает возможность загрузить XP. Это также означает, что сам BSD решил не загружаться.
Что, если мы вернемся и сохраним флаг загрузки на первом разделе Windows XP? Тогда какая ОС будет загружаться? На рисунке 2-39 мы добились этого.
Рисунок 2-39. Последовательность загрузки PC-BSD
Будет загружаться только Windows XP, и последовательность загрузки проста. На рисунке 2-40 показано, как загружается Windows XP.
Рисунок 2-40. Последовательность загрузки Windows XP
Перед установкой новой ОС нам необходимо переместить флаг загрузки из третьего раздела BSD во второй раздел OpenSolaris. На рисунке 2-41 показано изменение флага загрузки с раздела XP на раздел OpenSolaris.
Рисунок 2-41. Структура диска из GParted
После этого изменения начнет загружаться OpenSolaris, а вместе с этим будет загружаться и Windows XP, но BSD загрузиться не сможет. Означает ли это, что каждый раз, когда мы загружаем BSD, нам нужно возвращать флаг загрузки в раздел BSD? На данный момент да, но мы автоматизируем все это с помощью загрузчиков.
Windows Server 2003
Как вы можете видеть на рисунке 2-42, мы установим Windows Server 2003 (win2k3) в первый логический раздел. Для win2k3 это диск D:
.
Рисунок 2-42. Схема диска, показанная установщиком win2k3
Как вы думаете, после установки какая ОС будет загружаться?
Только Win2k3?
Будет ли win2k3 предоставлять возможность загрузки любой другой ОС?
win2k3 и OpenSolaris?
PC-BSD?
Только XP?
Win2k3 и XP?
Прежде чем продолжить, подумайте немного и придумайте свой ответ.
Как вы можете видеть на рисунке 2-43, будет загружаться операционная система win2k3.
Рисунок 2-43. Экран приветствия win2k3 после перезагрузки
И win2k3 дает возможность загружать Windows XP. Это означает, что загружается только семейство операционных систем Windows. Кроме того, вот несколько вопросов, которые нам следует рассмотреть:
-
Где сейчас находится флаг загрузки?
-
Какая ОС загрузится, если мы сохраним флаг загрузки на втором разделе?
-
Какая ОС загрузится, если мы сохраним флаг загрузки на третьем разделе?
-
Какая ОС будет загружаться, если мы сохраним флаг загрузки на логическом разделе (разделе win2k3)?
-
Есть ли способ загрузить только Windows XP?
Все ответы на эти вопросы вы получите в следующем обсуждении.
Здесь ясно одно: win2k3 — единственная загружающаяся ОС. Прежде чем обсуждать, как она может загружаться, нам нужно проверить, какой сценарий win2k3 создал на диске для успешной загрузки.
Когда win2k3 устанавливался, он увидел, что он устанавливается в логический раздел и что флаг загрузки находится в разделе OpenSolaris (см. рисунок 2-44).
Рисунок 2-44. Структура диска при установке Win2k3
Для загрузки win2k3 должен установить флаг загрузки в свой собственный раздел, установив часть-1 и часть-2 своего загрузчика (опять же NTLDR) в свои собственные 512 байт + 31 КБ. Но здесь есть проблема. Помните правила, которые мы видели во время установки Windows XP?
Логический раздел не может быть активным.
ОС не может загружаться с логического раздела.
Из-за этих двух правил win2k3 не может сохранять флаг загрузки на своем собственном разделе и, в конечном итоге, не может загружаться с логического раздела. На рисунке 2-45 показана последовательность загрузки, объясняющая, почему Win2k3 не может загрузиться с логического раздела. Но в чем причина таких правил?
Рисунок 2-45. Последовательность загрузки Win2k3, если он пытается загрузиться из логического раздела
Все просто: в MBR всего четыре записи, а именно:
Первый основной = sda1
Второй основной = sda2
Третий основной = sda3
Четвертый основной = расширенный раздел (не логический раздел) = sda4
Раздел win2k3 — sda5. Другими словами, это SATA диск a
(первый) и раздел номер 5. Поскольку в MBR нет записи для логического раздела, часть-2 NTLDR XP не знает, что доступен пятый раздел. Таким образом, даже если win2k3 сохраняет флаг загрузки в своем собственном разделе, NTLDR XP не может его увидеть. Следовательно, win2k3 никогда не загрузится. Теперь почему в MBR не может быть более пяти записей? Это потому, что 64 байта могут хранить только четыре записи. Почему бы не увеличить размер MBR? На самом деле, даже если разработчики захотят увеличить размер MBR, они просто не смогут. Вы поймете причину, когда мы поговорим о прошивке UEFI далее в этой главе.
Теперь для Win2k3 это стало проблемой курицы и яйца. Он хочет загрузиться, но для этого ему необходимо сохранить флаг загрузки на своем собственном разделе, но если он это сделает, то BIOS не сможет увидеть этот раздел. Как нам решить эту проблему?
Некоторые замечательные разработчики решили эту проблему, а тот, кто придумал эту идею, просто легенда. win2k3 переносит свой загрузчик NTLDR на первый основной раздел, что означает часть-1, часть-2 и часть-3. Это также означает, что win2k3 удалит все части XP NTLDR, поскольку пространство (512 байт + 31 КБ) очень маленькое и оба загрузчика туда не поместятся. (Здесь есть некая точка, называемая VBR, которая выходит за рамки этой книги.) Однако при удалении загрузчик XP win2k3 вносит запись XP в один из своих текстовых файлов и сохраняет ее в первом основном разделе. Файл называется boot.ini
, как показано на рисунке 2-46.
Рисунок 2-46. Файл boot.ini
При этом win2k3 сохраняет флаг загрузки только для первого основного раздела. Итак, вот как загружается win2k3:
-
Включение системы.
-
Процессор переходит в BIOS. BIOS запускает POST.
-
POST проверяет, оборудование подает звуковой сигнал и возвращается в BIOS.
-
BIOS переходит к первым 512 байтам первого основного раздела.
-
Начнется загрузка, которая является частью NTLDR для Win2k3.
-
Часть-1 находит часть-2 NTLDR.
-
Часть-2 проверяет MBR и подпись fdisk.
-
Сигнатура fdisk устанавливается на первом первичном разделе, что означает, что часть-2 перейдет внутрь первого основного раздела XP и будет запускать часть-3 NTLDR Win2k3. Чтобы дать вам представление: часть-3 — это новая версия, а не старый NTLDR XP. Здесь я привожу два изображения.
-
Обратите внимание на размер NTLDR (часть-3) на рисунке 2-47. Это когда мы установили Windows XP.
Рисунок 2-47. Размер файла NTLDR части-3 для Windows XP
-
На рисунке 2-48 обратите внимание на размер NTLDR (часть-3) после установки win2k3.
Рисунок 2-48. Размер файла NTLDR части-3 для win2k3
Как видите, часть-3 NTLDR в Windows XP составляла 245 КБ, но теперь с win2k3 она составляет 291 КБ.
-
-
Часть-3 NTLDR (win2k3) будет читать файл
boot.ini
из того же раздела (первого основного) и напечатает все, что написано в кавычках. На рисунке 2-49 показано, что будет напечатано на экране.Рисунок 2-49. Экран приветствия, отображаемый win2k3
-
Если пользователь выбирает вариант Windows Server 2003, Enterprise, то третья часть NTLDR Win2k3 знает, где находится ядро Win2k3. Это пятый раздел, где установлена Win2k3. Она копирует ядро в память, и NTLDR win2k3 исчезает.
-
Если пользователь выбирает вариант Microsoft Windows XP Professional, то часть-3 NTLDR также знает, где находится ядро Windows XP. Это первый основной раздел. Сначала запускается
winload.exe
; в конечном итогеwinload.exe
копирует ядро XP в память, и NTLDR исчезает. На рисунке 2-50 показана полная последовательность загрузки Windows XP.
Рисунок 2-50. Последовательность загрузки Windows XP
Итак, вот как могут загружаться Windows XP и win2k3. Давайте вернемся к обсуждению сигнатур fdisk; поскольку загружается только Win2k3, а другие ОС не могут загружаться, у меня есть несколько вопросов:
Можем ли мы загрузить только Windows XP?
Что, если мы сохраним флаг загрузки в OpenSolaris?
Что, если мы сохраним флаг загрузки в PC-BSD?
Что, если мы нигде не сохраним флаг загрузки?
Не торопитесь, подумайте, просмотрите блок-схемы и придумайте свой ответ.
Готовы? Мы не можем загрузить только Windows XP. Это просто невозможно, поскольку в загрузчиках Windows XP все части заменены на NTLDR Win2k. Кроме того, теперь только Win2k3 знает, где находится XP, и только Win2k3 может загружать Windows XP. Это также означает, что если часть-1 загрузчика Win2k3 будет повреждена или удалена, мы потеряем XP навсегда. Но если мы сохраним флаг загрузки на PC-BSD, то он загрузится как обычно. На рисунке 2-51 показана последовательность загрузки PC-BSD.
Рисунок 2-51. Последовательность загрузки PC-BSD
Если мы не сохраним флаг загрузки ни на одном из разделов, то никто просто не загрузится. Это похоже на ситуацию, которую мы обсуждали, говоря о том, что произойдет, если на логическом разделе будет установлен флаг загрузки. На рисунке 2-52 показана последовательность загрузки, объясняющая, почему ни одна из операционных систем не может загрузиться.
Рисунок 2-52. Последовательность загрузки, показывающая, почему ни одна из ОС не может загрузиться
Установка флага загрузки в логическом разделе так же эффективна, как и отсутствие установки флага загрузки где-либо еще.
Теперь главный вопрос: что, если мы сохраним флаг загрузки на разделе OpenSolaris? OpenSolaris не сможет загрузиться. Загрузчик OpenSolaris, которым является GRUB, выдаст сообщение об ошибке, показанное на рисунке 2-53.
Рисунок 2-53. GRUB удален по запросу
Но почему? Он должен загрузиться, верно? В OpenSolaris ничего не менялось (512 байт + 31 КБ). Просто win2k3 перенесла флаг загрузки с раздела OpenSolaris на первый основной. Итак, в идеале он должен загрузиться, но не получается, и причина в поведении win2k3. Когда устанавливалась ОС win2k3, она столкнулась с ситуацией, аналогичной той, с которой столкнулись OpenSolaris и PC-BSD. Другими словами, флаг загрузки находится на другом разделе, и на этом разделе установлена другая ОС. Что OpenSolaris сделал в этой ситуации, так это переместил флаг загрузки из раздела XP на свой второй раздел, но, поскольку это сделает XP незагружаемой, он великодушно сделал запись для XP в своем собственном файле (menu.lst
). OpenSolaris каждый раз читает этот файл и дает XP равные шансы на загрузку.
В случае PC-BSD он обнаруживает, что флаг загрузки установлен на OpenSolaris, и если его переместить в отдельный раздел, OpenSolaris станет незагружаемым. Следовательно, BSD великодушно решила не ставить флаг загрузки на свой раздел, чтобы другая ОС не стала незагружаемой. Но win2k3 не настолько щедр. Когда win2k3 устанавливался, он увидел, что флаг загрузки находится в ОС, отличной от Windows. Таким образом, он переместил флаг загрузки OpenSolaris, но, поскольку это операционная система, отличная от Windows, он не создал запись в boot.ini
. Идя дальше, win2k3 даже повредил/удалил первую часть OpenSolaris GRUB. Следовательно, OpenSolaris сейчас не может загрузиться.
Позже win2k3 пошел дальше и очистил загрузчик XP, но он сделал запись для XP в boot.ini
, поскольку это операционная система Windows. Вот почему я сказал, что win2k3 не обладает той щедростью, которую демонстрируют OpenSolaris и PC-BSD. Но мы исправим OpenSolaris в разделе «Настройка GRUB» этой главы.
Windows 7
Как вы можете видеть на рисунке 2-54, мы устанавливаем Windows 7 в пятый раздел.
Рисунок 2-54. Схема диска, показанная установщиком Windows 7
Windows не показывает расширенный раздел, чтобы избежать путаницы для простых пользователей настольных компьютеров.
Как вы думаете, какая ОС после установки загрузится? Как обычно, не торопитесь и придумайте свой ответ, прежде чем перейти к рисунку 2-55.
Рисунок 2-55. Экран приветствия в Windows 7
Вы угадали: Windows 7 загрузится. Ниже приведена полная последовательность загрузки Windows 7:
-
Включение системы.
-
Процессор переходит в BIOS.
-
После процедуры POST BIOS переходит к первому сектору всего жесткого диска.
-
Когда устанавливалась Windows 7, символ
*
был на первом основном диске, а Windows 7 устанавливалась в логический раздел. Итак, Windows 7 сталкивается с теми же проблемами, что и Win2k3. -
Чтобы сделать себя загружаемой, Windows 7 пойдет по тому же пути, что и win2k3. Windows 7 установит часть-1, часть-2 и часть-3 в первый основной раздел. Часть-3 не обязательно устанавливать на первом основном разделе, поскольку в части-2 жестко запрограммировано расположение части-3, но именно так работает семейство Windows.
-
В процессе установки части-1 и части-2 на первом основном разделе, очевидно, что Windows 7 должна удалить NTLDR win2k3 (часть-1 и часть-2), но при удалении файлов Windows 7 распознает это. win2k3 — операционная система семейства Windows; следовательно, загрузчик Windows 7, называемый Boot Configuration Data (BCD), делает запись для win2k3 в своем собственном файле, который можно увидеть в
bcdedit.exe
. Посмотрите на рисунок 2-56, чтобы увидеть выходные данныеbcdedit.exe
.«Windows Legacy OS Loader» на рисунке 2-56 означает win2k3.
Рисунок 2-56. Вывод
bcdedit.exe
-
Итак, возвращаясь к последовательности загрузки, она выглядит следующим образом: BIOS ➤ POST ➤ BIOS ➤ первый сектор жесткого диска.
-
Первые 440 байт загрузчика bootstrap — это часть-1 загрузчика BCD Windows 7. Он будет искать большее пространство, которое является частью-2 BCD.
-
Часть-2 BCD прочитает MBR и узнает, что на этом жестком диске есть четыре основных раздела, но чтобы проверить, какой из них активен, она начнет проверять сигнатуру fdisk каждого раздела, но обнаружит, что сам же первый основной раздел и активен.
-
Часть-2 перейдет внутрь первого основного раздела, где хранится часть-3 загрузчика BCD Windows 7. Часть-3 прочитает файл конфигурации загрузчика через
bcdedit.exe
и выведет список записей, упомянутых перед переменной описания. На рисунке 2-57 показано, что появится на экране.Рисунок 2-57. Экран приветствия в Windows 7
-
Если пользователь выбирает Windows 7, то, как вы можете видеть в
bcdedit.exe
, часть-3 BCD вызоветwinload.exe
изC:\windows\systemd32
. Помните, что здесьC:
означает раздел Windows 7, который является шестым логическим разделом. -
Файл
winload.exe
знает расположение ядра Windows 7. Начнется загрузка ядра в память, и как только это будет сделано, ядро Windows 7 позаботится о дальнейшей последовательности загрузки. Вы можете увидеть анимацию, показанную Windows 7 после начала загрузки, на рисунке 2-58.Рисунок 2-58. Анимация, отображаемая Windows 7 во время загрузки
На рисунке 2-59 показана полная блок-схема последовательности загрузки Windows 7.
Рисунок 2-59. Последовательность загрузки Windows 7
-
Если пользователь выберет более раннюю версию Windows, то часть-3 BCD вызовет часть-3 NTLDR, которая находится только в первом основном разделе, и последовательность загрузки продолжится, что мы видели в Win2k3. Рисунок 2-60 объясняет последовательность загрузки win2k3 и XP.
Рисунок 2-60. Последовательность загрузки win2k3 и XP
Red Hat Enterprise Linux 6 (RHEL 6)
Имя установщика RHEL — Anaconda. Установщик Anaconda используется всеми дистрибутивами на базе Fedora. На рисунке 2-61 мы начали установку RHEL 6.
Рисунок 2-61. Экран приветствия загрузочного носителя RHEL 6
На рисунке 2-62 показано текущее расположение разделов.
Рисунок 2-62. Схема разделов, показанная установщиком Anaconda
Как показано на рисунке 2-63, нам нужно назначить root (/
) разделу sda7 и переформатировать его в ext4, который является выбором файловой системы по умолчанию для RHEL 6.
Рисунок 2-63. Схема разделов, которую реализует Anaconda
Как видно на рисунке 2-64, RHEL 6 (или Anaconda) обнаружил некоторую ОС и пытается предоставить равные возможности для загрузки другой ОС (указанной как Other). Есть две записи ОС, которые загрузчик RHEL 6 (GRUB) покажет во время загрузки.
Рисунок 2-64. Anaconda обнаруживает другую ОС
Согласно RHEL 6, другая ОС будет загружаться с sda5. Это означает следующее:
sda1 = XP
sda2 = Solaris
sda3 = PC BSD
sda4 = Extended partition
sda5 = Win win2k3 <<<-----------
Если во время загрузки пользователь выбирает вариант Other, предполагается, что загрузится win2k3. Какая ОС загрузится после выбора варианта Other? Не торопитесь и придумайте свою собственную последовательность загрузки.
Перезагрузим систему и посмотрим, какая ОС загружается. Как вы можете видеть на рисунке 2-65, загружается RHEL 6, которая дает вам возможность загрузить другую ОС.
Рисунок 2-65. Экран приветствия RHEL 6
Вот как загружается RHEL 6:
-
Когда система включается, она переходит в BIOS, затем из BIOS в POST и из POST обратно в BIOS.
-
В конечном итоге BIOS попадает в первый сектор всего жесткого диска и запускает загрузку bootstrap.
-
Когда RHEL 6 устанавливался, флаг активного раздела
*
был на первом основном разделе. -
Проблема, с которой столкнулись Win2k3 и Windows 7, также встречается и в RHEL 6. RHEL 6 устанавливается в логический раздел, к которому BIOS не может получить доступ или увидеть. Итак, чтобы решить эту проблему, RHEL 6 должен разместить часть-1 и часть-2 загрузчика (GRUB) в первый основной раздел. Помните, что Windows также разместила часть-3 в первый основной раздел, но RHEL (и вообще любая ОС Linux) разместит только первые две части в первый основной раздел, а часть-3 GRUB будет сохранена в собственном разделе; в нашем случае это sda-7.
-
При замене части-1 и части-2 первого основного раздела RHEL заметил, что уже установлена какая-то другая ОС, и, чтобы дать ей равные шансы на загрузку, он сделал запись для нее в файле конфигурации с именем
/boot/grub/grub.conf
своего собственного раздела. На рисунке 2-66 показан файлgrub.conf
.Рисунок 2-66. Файл
grub.conf
Как вы можете видеть, все, что написано после переменной
title
, будет напечатано на экране. -
Возвращаясь к последовательности загрузки, загрузка bootstrap в первом основном разделе взята из RHEL.
-
Часть-1 GRUB RHEL перейдет к части-2.
-
В части-2 GRUB жестко запрограммировано расположение части-3 GRUB. Часть-3 GRUB находится в разделе RHEL, sda7.
-
Часть-3 GRUB прочитает файл
grub.conf
из каталога/boot/grub
, и все, что написано после заголовка, будет напечатано на экране. Рисунок 2-67 показывает это.Рисунок 2-67. Экран приветствия, отображаемый GRUB в RHEL 6
-
Если пользователь выбирает первую запись, то есть Red Hat Enterprise Linux 6, то третья часть GRUB знает, где находится ядро RHEL. На рисунке 2-68 показан файл
grub.conf
.Рисунок 2-68. Файл
grub.conf
RHEL 6 -
Двоичный файл ядра будет находиться в
/boot/vmlinuz
. (Обратите внимание на переменнуюkernel
на рисунке 2-68.) По сути, тот же файлgrub.conf
сообщает расположение ядра третьей части GRUB. Он скопирует ядро (vmlinuz
) в память, и работа загрузчика GRUB будет завершена. Ядро RHEL позаботится о дальнейшей последовательности загрузки. Тем временем, когда система загружается, на экране появится красивая анимация, как показано на рисунке 2-69.Рисунок 2-69. Анимация для скрытия сложных сообщений журнала
На рисунке 2-70 показана блок-схема полной последовательности загрузки RHEL 6.
Рисунок 2-70. Последовательность загрузки RHEL 6
-
Если же пользователь выберет Other, будет вызываться все, что присутствует в разделе sda5. Как вы можете видеть на рисунке 2-71, sda5 находится в разделе win2k3.
Рисунок 2-71. Другая ОС находится в разделе 5
-
Когда была установлена win2k3, она переместила все части своего загрузчика на первый основной раздел. Это означает, что в разделе win2k3 нет загрузчика, поэтому, конечно, никакая ОС не загрузится. На рисунке 2-72 показано сообщение об ошибке, появляющееся на экране при попытке загрузить другую ОС.
Рисунок 2-72. Сообщение об ошибке
Теперь у меня есть пара вопросов:
-
Где сейчас признак активности раздела
*
? -
Если я оставлю
*
на втором разделе, какая ОС загрузится? -
Если я оставлю
*
на третьем разделе, какая ОС загрузится? -
Если я оставлю
*
на пятом (логическом) разделе, какая ОС загрузится? -
Если я не оставлю
*
ни на одном из разделов, какая ОС загрузится?
Во всех этих сценариях будет загружаться только одна ОС — RHEL 6 (рисунок 2-73).
Рисунок 2-73. Экран рабочего стола RHEL 6
Независимо от того, где вы установите *
или даже если вы не установите ни на одном разделе, постоянно будет загружаться только RHEL. Причина проста, но она совершенно меняет последовательность загрузки. Загрузчик Red Hat Enterprise Linux, то есть GRUB, не следует за *
и не проверяет, какой раздел активен, перед вызовом части-3 своего загрузчика bootloader. На самом деле ни одна из ОС Linux не удосуживается проверять активный раздел. Они просто пропускают этот шаг. Таким образом, последовательность загрузки становится следующей:
-
Сначала система переходит в BIOS, затем POST, затем обратно в BIOS и, наконец, в загрузчик bootstrap первого основного раздела.
-
Часть-1 RHEL GRUB переходит к части-2 GRUB, которая (после пропуска части сигнатуры fdisk) переходит к части-3 GRUB.
-
Часть-3 GRUB переходит в
/boot/grub.conf
, где печатаются записи ОС. -
Если пользователь выбирает RHEL, то ядро загружается из
/boot/vmlinuz
в память. -
Ядро само позаботится о дальнейшей загрузке ОС, что подробно описано в остальной части книги.
Это также означает, что в данный момент загружается только одна ОС — RHEL 6. Это плохо! Следовательно, нам нужно настроить GRUB для загрузки остальных операционных систем.
Настройка GRUB
Лучшей особенностью GRUB является то, что он может загружать любую другую ОС, независимо от того, основана она на Linux или нет. Трюк с загрузкой другой ОС, используемой GRUB, прост, но удивителен. Чтобы любой загрузчик мог загрузить ОС, вам не нужно ничего делать, кроме загрузки ядра соответствующей ОС в память. GRUB знает, где находится ядро ОС Linux (/boot/vmlinuz). Но GRUB не знает, где находится ядро Windows или PC-BSD. Хитрость в том, что соответствующие загрузчики этих операционных систем знают расположение своих ядер. Итак, GRUB просто вызывает соответствующие загрузчики; например, если GRUB хочет загрузить BSD, это происходит в третьем основном разделе. Чтобы лучше это понять, обратитесь к рисунку 2-74, на котором показано расположение разделов.
Рисунок 2-74. Структура разделов BIOS
BSD установила свой загрузчик bootloader в зарезервированные 512 байт + 31 КБ своего раздела. Итак, GRUB вызовет часть-1 BTX. Это называется цепной загрузкой (chainloading). Часть-3 загрузчика GRUB будет загружать часть-1 BTX. Часть-1 BTX знает, что делать дальше: искать часть-2. Часть-2 перейдет к части-3 и загрузит ядро BSD в память, после чего BSD начнет загружаться. Чтобы добиться такой цепной загрузки, нам нужно сообщить GRUB расположение первой части BTX через файл grub.conf
. Это будет номер жесткого диска 1 и номер раздела 3, но GRUB начинает отсчет с 0, поэтому местом будет номер жесткого диска 0 и номер раздела 2. Запись в /boot/grub/grub.conf
выглядит следующим образом:
title pc-bsd <<<---- the os entry title
rootnoverify (hd0,2) <<<---- location of BTX
chainloader +1 <<<---- grub will chainload the BTX
Как вы можете видеть на рисунке 2-75, остальные записи операционной системы аналогичны BSD; изменится только номер раздела.
Рисунок 2-75. Измененный файл grub.conf
RHEL 6
После перезагрузки GRUB покажет упомянутые записи title
. См. рисунок 2-76.
Рисунок 2-76. Экран приветствия GRUB, отображаемый RHEL 6
Если пользователь выберет Windows, он вызовет часть-2 BCD, которая находится в пространстве размером 31 КБ первого основного файла. Это пространство размером 31 КБ также называется volume boot record (загрузочная запись тома) (VBR). Я намеренно пропустил объяснение VBR, поскольку оно излишне создаст путаницу. Итак, в случае с цепной загрузкой Windows просто имейте в виду, что вместо части-1 будет вызываться часть-2. Для тех, кому нужна дополнительная информация о VBR, MBR — это основная загрузочная запись жесткого диска, расположенная в первом секторе жесткого диска. Каждый том (например, раздел) имеет свою собственную загрузочную запись, называемую VBR, в качестве первого сектора раздела. Два названия для двух похожих вещей.
Таким образом, часть-2 BCD вызовет часть-3 BCD, которая находится в первом основном разделе. Она прочитает записи ОС BCD (bcdedit.exe
), как показано на рисунке 2-77, и выведет их на экран.
Рисунок 2-77. Записи ОС, отображаемые загрузчиком BCD
Если пользователь выберет более раннюю версию Windows, как мы видели ранее (во время загрузки Windows 7), он запустит часть-3 NTLDR, которая снова находится в первом основном разделе. Как показано на рисунке 2-78, NTLDR прочитает файл boot.ini
с диска C и распечатает записи ОС.
Рисунок 2-78. Записи ОС, отображаемые NTLDR Win2k3
Если пользователь выбирает XP, часть-3 NTLDR знает, где находится ядро XP. Вместо этого пользователь выбирает win2k3, а затем тот же NTLDR загрузит в память ядро win2k3.
Обратитесь к рисунку 2-79, который представляет собой главный экран загрузки, предоставляемый RHEL, если пользователь выбирает OpenSolaris.
Рисунок 2-79. Записи ОС, показанные RHEL
Ниже приведены инструкции, которым будет следовать GRUB:
title Solaris
rootnoverify (hd0,1)
chainloader +1
Итак, часть-3 RHEL GRUB передаст управление загрузчику bootstrap второго основного раздела, но помните, что win2k3 очистил часть-1 OpenSolaris GRUB. Следовательно, как видно на рисунке 2-80, он не сможет загрузиться.
Рисунок 2-80. OpenSolaris не удалось загрузиться
Это означает, что сначала нам нужно исправить загрузчик OpenSolaris. Чтобы это исправить, нам нужно загрузиться с образа Live CD OpenSolaris, который мы использовали для установки OpenSolaris, и после загрузки установить часть-1 и часть-2 (часть-2 не обязательна, но ее можно переустановить) GRUB. с live-диска на зарезервированные 512 байт раздела OpenSolaris + 31 КБ. Команда, которую мы будем использовать, — installgrub
. Как следует из названия, команда скопирует часть-1 (этап 1) и часть-2 (этап 2) GRUB из live-образа и поместит их в пространство раздела OpenSolaris размером 512 байт + 31 КБ. На рисунке 2-81 показано действие команды.
# installgrub /boot/grub/stage1 /boot/grub/stage2 /dev/rdsk/c4d0s0
Рисунок 2-81. Команда installgrub
После перезагрузки RHEL снова покажет те же записи ОС (рисунок 2-82), поскольку для RHEL ничего не изменилось.
Рисунок 2-82. Записи ОС, показанные RHEL
Если на этот раз мы выберем OpenSolaris, то часть-3 RHEL GRUB загрузит часть-1 OpenSolaris GRUB из второго раздела. Часть-1 вызовет часть-2 и, в конечном итоге, вызовет часть-3 из фактического раздела OpenSolaris. Третья часть OpenSolaris GRUB будет читать файл /rpool/boot/grub/menu.lst
и, как показано на рисунке 2-83, выводить заголовки на экран.
Рисунок 2-83. Записи ОС, показанные OpenSolaris
Если пользователь выберет OpenSolaris, то третья часть OpenSolaris GRUB загрузит ядро из /boot
. Если пользователь выбирает Windows, то третья часть OpenSolaris GRUB будет следовать этим инструкциям из /rpool/boot/grub/menu.lst
:
title Windows
rootnoverify (hd0,0)
chainloader +1
Теперь мы знаем, что появится на экране (см. рисунок 2-84).
Рисунок 2-84. Записи ОС, показанные в формате BCD
История продолжится, если пользователь выберет более раннюю версию Windows, о которой мы уже говорили. Возвращаясь к исходному списку ОС, на рисунке 2-85 показано, что представлено GRUB RHEL.
Рисунок 2-85. Записи ОС, показанные RHEL
Если пользователь решит загрузить BSD, вы точно знаете, что произойдет. Часть-3 GRUB RHEL будет загружать часть-1 BTX из третьего основного раздела. Часть-1 BTX вызовет часть-2, а часть-2 вызовет часть-3 BTX. В части-3 BTX отобразится экран приветствия, как показано на рисунке 2-86.
Рисунок 2-86. Экран приветствия PC-BSD
После выбора загрузки часть-3 BTX загрузит ядро BSD Unix в память. Итак, все операционные системы, какую бы мы ни установили до сих пор, теперь могут загружаться, и не имеет значения, какой раздел активен. Но можем ли мы взломать загрузчики Windows и заставить их загружать операционные системы Linux и Unix из нашего списка? Да, можем, и это то, что мы сейчас сделаем.
Взлом загрузчиков Windows
На самом деле обмануть загрузчики Windows довольно легко. Как мы видели ранее, загрузчики выполняют цепную загрузку; например, часть-1 вызывает часть-2 своего загрузчика и так далее. Чтобы понять суть, давайте возьмем в качестве примера BSD. Часть-1 BCD вызывает свою часть-2 BCD, но если мы укажем части-1 BCD загрузить по цепочке часть-1 RHEL, то часть-1 RHEL запустится и в конечном итоге будет следовать своей собственной последовательности загрузки. Часть-1 GRUB (RHEL) вызовет часть-2 GRUB и в конечном итоге загрузит часть-3 GRUB, поскольку адрес блока части-3 жестко запрограммирован в части-2. Это означает, что как только запустится часть-1 любого загрузчика, он начнет следовать своей собственной последовательности загрузки, и мы воспользуемся этим поведением.
Для этого нам нужно получить часть-1 каждого загрузчика, отличного от Windows, и поместить ее в файловую систему Windows. Итак, файловая система может быть FAT32 или NTFS. Очевидно, что размещение части-1 каждого загрузчика, отличного от Windows, в первом основном разделе имеет наибольшее преимущество, поскольку каждая операционная система Windows устанавливает свои соответствующие загрузчики в первый основной раздел. Итак, с помощью команды dd мы скопируем первые 512 байт (даже первых 440 байт достаточно) каждой ОС, отличной от Windows, и поместим их в раздел XP. Давайте смонтируем первый основной раздел, как показано на рисунке 2-87.
Рисунок 2-87. Команда монтирования
Давайте скопируем первые 512 байт и поместим их в раздел sda1. Для этого обратитесь к рисунку 2-88.
Рисунок 2-88. Передача первых 512 байт в первый основной раздел
Теперь мы снова загрузимся в XP и, как показано на рисунке 2-89, добавим записи файлов части-1 в файл boot.ini
. Файл boot.ini
читается обоими загрузчиками Windows: BCD и NTLDR Win2k3.
Рисунок 2-89. Добавление записей в файл boot.ini
Ниже приведены записи, которые мы добавили:
c:\RHEL.out="RHEL"
c:\SOLARIS.out="SOLARIS"
c:\BSD.out="BSD"
Как и в случае с файлом grub.conf
, все, что написано в двойных кавычках в boot.ini
, будет считаться заголовком записи ОС. Теперь давайте перезагрузим систему и выберем запись ОС Windows из списка ОС RHEL (см. рисунок 2-90).
Рисунок 2-90. Список ОС, отображаемый RHEL
Как мы дошли до этого экрана, легко понять.
-
Система переходит сначала к BIOS, затем к POST, затем к BIOS, затем к первым 512 байтам, а затем к загрузке bootstrap (часть-1) RHEL (GRUB).
-
Затем следует часть-1 GRUB, которая переходит к части-2 GRUB, которая переходит к части-3 GRUB, которая переходит в
/boot/grub.conf
, где печатаются заголовки ОС. -
Пользователь выбирает Windows, поэтому далее идет часть-1 BCD из первого основного раздела, а затем часть-2 BCD.
-
Наконец, процесс переходит к части-3, затем к файлу
bcd.exe
, который читает файлboot.ini
, и все, что записано в двойных кавычках, будет напечатано на экране.
Список ОС показан на Рисунке 2-91.
Рисунок 2-91. Записи ОС, отображаемые в Windows 7 (BCD)
Если пользователь выберет более раннюю версию Windows, то часть-3 BCD вызовет часть-3 NTLDR Win2k3. NTLDR снова прочитает файл boot.ini
и распечатает список ОС, как показано на рисунке 2-92.
Рисунок 2-92. Записи ОС, отображаемые NTLDR Win2k3
Если пользователь выбирает OpenSolaris, то часть-3 NTLDR запустит файл SOLARIS.out
из C:
(первый основной раздел). Файл SOLARIS.out
— это не что иное, как часть-1 загрузчика OpenSolaris из второго раздела. Часть-1 загрузчика OpenSolaris будет вызывать часть-2 и, в конечном итоге, часть-3 GRUB. Часть-3 прочитает файл menu.lst
и распечатает список ОС (рисунок 2-93).
Рисунок 2-93. Записи ОС, отображаемые OpenSolaris GRUB
Если пользователь снова выберет Windows, то часть-3 OpenSolaris вызовет часть-2 BCD из первого основного раздела (rootnoverify (hd0,0)
). (Часть-2 BCD будет находиться в разделе VBR первого основного раздела. Мы не будем рассматривать VBR в этой книге.) Часть-2 BCD будет вызывать часть-3 BCD. Она прочитает записи ОС через bcdedit.exe
и из boot.ini
и распечатает записи ОС. Записи ОС, напечатанные на экране, показаны на рисунке 2-94.
Рисунок 2-94. Записи ОС, отображаемые в Windows 7 (BCD)
Вот как мы создали цикл загрузчика (см. рисунок 2-95 и рисунок 2-96).
Рисунок 2-95. Для загрузки выбрана запись RHEL
Рисунок 2-96. Записи ОС, показанные в GRUB RHEL
Как вы можете видеть, Linux загружает Windows, Linux загружает Unix, Unix загружает Windows, Windows загружает Windows и Windows загружает Linux, но одной вещи все еще не хватает, а именно Linux загружает Linux. Для этого мы установим последнюю ОС из нашего списка — Fedora 15.
Fedora 15
Как показано на рисунке 2-97, мы устанавливаем Fedora 15 на sda8.
Рисунок 2-97. Установщик Fedora
По умолчанию Fedora попытается установить свой загрузчик на первую основной раздел, но если мы разрешим это, нам снова нужно будет добавить запись обо всех остальных ОС в ее grub.conf
. Вместо этого мы будем следовать другому подходу. Мы установим загрузчик Fedora (GRUB) в отдельный раздел (sda8) вместо sda1. См. рисунок 2-98.
Рисунок 2-98. Выбор устройства загрузчика
Это означает, что после перезагрузки Fedora никак не сможет загрузиться, поскольку GRUB RHEL не знает об этой новой ОС, поэтому нам нужно добавить запись Fedora в grub.conf
RHEL. Для этого давайте смонтируем sda8, как показано на рисунке 2-99.
Рисунок 2-99. Монтирование раздела Fedora
Скопируйте записи Fedora (см. рисунок 2-100) из файла grub.conf
Fedora GRUB: /mnt/boot/grub/grub.conf
.
Рисунок 2-100. Файл grub.conf
Fedora 15
Записи простые. Всякий раз, когда вызывается часть-3 Fedora, она загружает ядро Fedora из /boot/vmlinuz-2.6.38.6-26.rc1.fc15.x86_64
в память. После этого она загружает initramfs
из /boot/initramfs-2.6.38.6-26.rc1.fc15.x86_64.img
в память.
На рисунке 2-101 показан файл /boot/grub/grub.conf
RHEL после копирования записи Fedora из /mnt/boot/grub/grub.conf
.
Рисунок 2-101. Файл grub.conf
RHEL
После перезагрузки мы получим запись Fedora (рисунок 2-102).
Рисунок 2-102. Записи ОС, показанные RHEL
Когда пользователь выбирает Fedora для загрузки, согласно записи в файле grub.conf
RHEL, часть-3 GRUB RHEL загружает ядро из восьмого раздела (sda8 Fedora), а также загружает initramfs
из того же места (мы будем поговорим об initramfs
в главе 5), и загрузчик исчезнет.
Полная блок-схема
На рисунке 2-103 показана полная блок-схема каждой установленной нами ОС.
Рисунок 2-103. Полная блок-схема всех операционных систем
Надеюсь, теперь вы понимаете, как загрузчики загружают операционные системы в системах на базе BIOS. Теперь пришло время разобраться с новой прошивкой — Unified Extensible Firmware Interface (единый интерфейс расширяемой прошивки) (UEFI).
Единый интерфейс расширяемой прошивки (UEFI)
Вот ограничения BIOS, которые вы наблюдали до сих пор:
У вас может быть только четыре основных раздела.
BIOS не может прочитать логические разделы.
BIOS какой-то тупой; он просто переходит к первому сектору вашего жесткого диска.
Максимальный размер раздела в системе на базе BIOS составляет 2,2 ТБ.
Почему у него такие ограничения? Прошивка BIOS была разработана в 1982 году для IBM PC-5150 (рисунок 2-104), который раньше имел следующую конфигурацию:
CPU = 8088 — 16bit x86 processor
Memory = upto 256KB max
OS = MS-DOS
Рисунок 2-104. IBM PC-5150
Как видите, BIOS для этого ПК был разработан много лет назад. За это время операционные системы выросли с гибких дисков до дисков NVME и от текстового режима до блестящих графических интерфейсов. Аппаратные устройства перешли от драйверов к технологии Plug and Play, но BIOS остался прежним: изначально он имел 16-битный набор инструкций, а на более поздних этапах начал использовать 32-битный набор инструкций. Сегодня у нас есть 64-битные процессоры, но BIOS по-прежнему состоит из 32-битных инструкций. В силу некоторых исторических причин мы до сих пор не обновили BIOS до 64-битной версии. Когда все работает, зачем что-то переписывать? Так или иначе, эту философию приняла компьютерная индустрия. В то время как процессор перешел с 16-битного (8088) на 64-битный (i9) режим, BIOS оставался либо 16-битным, либо 32-битным, поскольку на момент ранних этапов загрузки не было необходимости иметь 64-битный процессор, и именно поэтому у нас есть режимы процессора (реальный, защищенный и длинный).
В реальном режиме процессор будет ограничен 16 битами. В этом режиме будут запускаться программы, подобные старому BIOS, которые содержат 16-разрядные инструкции. Эти программы не могут работать ни в каком другом режиме. Позже CPU переключится из реального режима в защищенный. Защищенный режим составляет 32 бита, и в наши дни программы, такие как BIOS, с 32-битным набором инструкций, будут работать в этом режиме, а позже CPU будет переведен в длинный режим, который составляет 64 бита. Помните, что эти режимы не реализуются процессором; скорее, они реализуются прошивкой, такой как BIOS. Это означает, что если мы удалим тот же CPU из системы с включенным реальным режимом и поместим его в систему, у которой нет реального режима, то тот же CPU сразу запустится в защищенном режиме. Мы еще поговорим об этих режимах в главе 4.
Поскольку BIOS работает в защищенном режиме, доступное для BIOS адресное пространство составляет всего 4 ГБ. Если в системе 20 ГБ памяти, BIOS сможет адресовать только до 4 ГБ. Хотя в системе установлен 64-битный процессор i9, BIOS все равно сможет использовать только 32 его бита. Из-за этих аппаратных проблем BIOS имеет ограничения.
Ограничения BIOS
Вот некоторые ограничения BIOS:
-
BIOS может перейти только к первому сектору размером 512 байт.
- MBR (PT) размером 64 байта является частью первого загрузочного сектора. Если мы увеличим размер MBR, он выйдет за рамки 512 байт; следовательно, мы не можем увеличить размер MBR, поэтому BIOS может предоставить только четыре основных раздела.
-
BIOS не может генерировать хорошую графику/графический интерфейс.
-
Это общее утверждение, которое используется в сравнении с UEFI. Некоторые производители BIOS реализовали веб-браузеры вне ОС, но такие реализации редко можно увидеть на обычном настольном оборудовании.
-
Кроме того, в Phoenix некоторые реализации BIOS содержат драйвер FAT32, с помощью которого удается отображать иконки внутри настроек BIOS.
-
-
Вы не можете использовать мышь в BIOS.
-
Многие производители BIOS поддерживают мышь, но, опять же, ее редко можно найти в обычных настольных системах.
-
-
Максимальный размер раздела составляет 2,2 ТБ.
-
BIOS использует и поддерживает таблицу разделов MS-DOS, которая довольно старая и имеет свои недостатки, такие как максимальный размер раздела 2,2 ТБ.
-
-
BIOS тупой, потому что не понимает ни загрузчик, ни ОС.
-
Он медленный из-за аппаратных ограничений.
-
Что касается скорости загрузки, BIOS медленный, поскольку для инициализации оборудования требуется время.
-
BIOS требуется почти 30 секунд, чтобы начать фактическую загрузку на уровне ОС.
-
-
Он изо всех сил пытается инициализировать аппаратные устройства нового поколения.
-
BIOS имеет ограниченные инструменты предварительной загрузки.
-
По сравнению с прошивкой UEFI, в BIOS очень мало инструментов предварительной загрузки, таких как удаленная диагностика оборудования и т. д.
-
Итак, чтобы преодолеть все эти ограничения BIOS, Intel в 1998 году запустила инициативу под названием Intel Boot Initiative (IBI); позже он стал интерфейсом расширяемой прошивки (EFI). К Intel присоединились все возможные поставщики ОС и оборудования (HP/Apple/Dell/Microsoft/IBM/Asus/AMD/American Megatrends/Phoenix Technologies). Для этого проекта они создали форум с открытым исходным кодом, и в конце концов он стал унифицированным интерфейсом расширяемой прошивки (UEFI).
Открытый исходный код подписан под лицензией BSD, но базовый код Intel по-прежнему является проприетарным. UEFI — это, по сути, платформа с открытым исходным кодом, и поставщики создают на ее основе свои приложения на основе спецификации, предоставленной UEFI.org
. Например, компания American Megatrends создала APTIO, а Phoenix Technologies создала прошивку SecureCore UEFI. Apple была первой, кто осмелился запустить системы с прошивкой UEFI. Все недостатки BIOS связаны с его 16-битным набором команд. Поскольку этот 16-битный набор инструкций ограничивает использование аппаратного обеспечения BIOS до 1 МБ адресного пространства, UEFI нацелился на это ограничение и устранил его.
Преимущества UEFI
UEFI поддерживает 64-битные процессоры; следовательно, он не сталкивается с какими-либо аппаратными ограничениями, с которыми сталкивается BIOS.
-
UEFI может использовать весь процессор. В отличие от BIOS (который использует 16 бит процессора), UEFI может использовать до 64 бит.
-
UEFI может использовать полный модуль оперативной памяти. В отличие от 1 МБ адресного пространства BIOS, UEFI может поддерживать и использовать терабайты оперативной памяти.
-
Вместо 64 байт крошечной MBR UEFI использует таблицу разделов GPT (GUID), которая предоставляет бесконечное количество разделов, и все они будут основными. На самом деле, здесь не существует понятия первичного и логического разделов.
-
Максимальный размер раздела составляет 8 зеттабайт.
-
В UEFI есть инструменты корпоративного управления.
-
Вы сможете починить компьютер удаленно.
-
Вы сможете пользоваться Интернетом внутри прошивки UEFI.
-
Вы сможете изменить поведение/настройки прошивки UEFI из ОС.
-
Чтобы изменить настройки BIOS, нам необходимо перезагрузить систему, поскольку ОС работает в длинном режиме, тогда как BIOS работает в реальном режиме, а реальный режим возможен только во время загрузки.
-
-
-
UEFI — небольшая ОС.
-
У вас будет полный доступ к аудио и видео устройствам.
-
Вы сможете подключиться к Wi-Fi.
-
Вы сможете использовать мышь.
-
Что касается графического пользовательского интерфейса, UEFI предоставит богатый графический интерфейс.
-
У UEFI будет собственный магазин приложений, как у нас для телефонов Android и Apple.
-
Вы сможете загружать и использовать приложения из магазина приложений UEFI, как на телефонах Android и Apple. Доступны сотни приложений, таких как календари, почтовые клиенты, браузер, игры, оболочки и т. д.
-
UEFI может запускать любой двоичный файл, имеющий формат исполняемого файла EFI.
-
UEFI безопасно загружает операционные системы с помощью функции безопасной загрузки (Secure Boot). Мы подробно обсудим эту функцию позже.
-
UEFI обратно совместим, что означает, что он поддерживает «способ загрузки BIOS». Другими словами, операционные системы, не имеющие поддержки UEFI, также смогут загружаться с помощью UEFI.
-
Графический интерфейс UEFI
На рисунке 2-105 показана реализация графического интерфейса ASUS.
Рисунок 2-105. Реализация ASUS UEFI
Вот некоторые вещи, на которые стоит обратить внимание:
-
Богатый графический интерфейс.
-
Указатель мыши
-
Значки, кнопки, параметры прокрутки, анимация, графики, раскрывающиеся списки и т. д.
Конечно, вам понадобится дорогая материнская плата, чтобы получить такую богатую реализацию UEFI, но даже базовые реализации UEFI намного лучше, чем реализации BIOS.
Реализация UEFI
Форум UEFI публикует спецификацию UEFI. Текущая спецификация UEFI на момент написания этой книги была 2.8, и ее можно загрузить по адресу https://uefi.org/specifications. Текущая спецификация состоит из 2551 страницы, и каждый поставщик (материнская плата, операционная система, разработчик UEFI и т. д.) должен с ней согласиться. Спецификация устанавливает правила, которым должен следовать каждый поставщик. Ниже приведены некоторые основные правила UEFI.
Системный раздел EFI (ESP)
Каждый поставщик ОС должен создать один раздел EPS, и загрузчик должен быть установлен только в этом разделе. Нет необходимости создавать ESP в качестве первого раздела; его можно создать где угодно, но ESP должен иметь файловую систему FAT16/32 (предпочтительно FAT32). Рекомендуемый размер ESP составляет минимум 256 МБ. Поставщик ОС должен создать следующую структуру каталогов в ESP:
EFI System Partition
├── EFI
│ ├── <OS_vendor_name>
│ │ ├── <boot_loader_files>
После создания этой структуры ОС должна установить загрузчик только внутри местоположения /EFI/<os_vendor_name>/
. На рисунке 2-106 показана структура UEFI.
Рисунок 2-106. Структура UEFI
Это означает, что, подобно 512 байтам + 31 КБАЙТ пространства, зарезервированного для загрузчиков, точно так же у нас есть минимальное выделенное пространство в 256 МБ для загрузчиков в UEFI. Раздел ESP будет смонтирован в Linux в точку монтирования /boot/efi
.
EFI
Каждый поставщик ОС обязан записывать файлы загрузчика в исполняемом формате EFI. Кроме того, файлы должны иметь расширение .efi
.
Безопасная загрузка
Одной из лучших функций UEFI является Secure Boot. Эта функция была предложена Microsoft и позже добавлена в спецификацию UEFI. Microsoft впервые использовала функцию безопасной загрузки в Windows 8. Мы подробно поговорим о безопасной загрузке, как только ознакомимся с тем, как работает UEFI.
Таблица разделов
Рекомендуемая таблица разделов — GPT, которая представляет собой таблицу разделов GUID, тогда как BIOS использует таблицу разделов MS-DOS.
Для лучшего понимания UEFI воспользуемся тем же подходом, который мы использовали с BIOS. Мы будем использовать новую систему под названием UEFI, на которой установлена прошивка UEFI, и установим в нее пару ОС.
Список операционных систем
Как вы знаете, UEFI использует таблицу разделов GPT; следовательно, не существует концепции первичного или вторичного/логического раздела. Это также означает, что установка операционных систем не имеет особого приоритета. Вы можете устанавливать операционные системы любым удобным для вас способом. Мы будем устанавливать операционные системы в следующем порядке:
Ubuntu 18
Windows 10
Fedora 31
Ubuntu 18.04 LTS
У нас почти 64,4 ГБ жесткого диска. Нет необходимости использовать инструмент, подобный GParted, для создания структуры разделов, как мы использовали в BIOS. Вместо этого мы будем использовать дисковую утилиту по умолчанию, предоставляемую Ubuntu. См. рисунок 2-107.
Рисунок 2-107. Структура диска, предоставляемая Ubuntu
Как показано на рисунке 2-108, сначала мы создадим раздел ESP размером 3 ГБ.
Рисунок 2-108. Создание раздела ESP
После создания ESP мы создадим еще один раздел (10 ГБ) для корневой файловой системы Ubuntu. На рисунке 2-109 показана окончательная структура расположения разделов Ubuntu.
Рисунок 2-109. Структура разделов Ubuntu
После установки на рисунке 2-110 вы можете видеть, что ESP смонтирован в /boot/efi
, а корневая файловая система смонтирована в sda2.
Рисунок 2-110. Точки монтирования
Кроме того, согласно спецификации UEFI, Ubuntu создала структуру каталогов /EFI/ubuntu
в точке монтирования /boot/efi
(sda1) и установила в нее загрузчик GRUB. См. рисунок 2-111.
Рисунок 2-111. Каталог EFI в Ubuntu
Также обратите внимание на расширения .efi
файлов загрузчика. Ниже приведена последовательность загрузки Ubuntu в системе UEFI:
-
Включение системы.
-
Переход в прошивку UEFI. UEFI запускает POST.
-
POST проверяет оборудование и подает звуковой сигнал, если все в порядке.
-
POST возвращается в UEFI.
-
UEFI умный; вместо перехода к первым 512 байтам UEFI находит раздел ESP.
-
Переходит в ESP. Опять же, UEFI умный, и он понимает загрузчик. На экране отображается название загрузчика. В случае Ubuntu он видит файл
grubx64.efi
; следовательно, он отображает имя Ubuntu в приоритете загрузки UEFI. Пожалуйста, обратитесь к рисунку 2-112, где вы можете увидеть записьubuntu
в меню приоритета загрузки UEFI.Рисунок 2-112. Окно приоритета загрузки UEFI
-
Помните, что загрузчик еще не был вызван или запущен UEFI. Раньше BIOS показывал вам только имена доступных загрузочных устройств, таких как CD-ROM, HDD и PXE, но UEFI заходит внутрь устройства, чтобы проверить наличие раздела ESP, и напрямую показывает имя ОС.
-
В тот момент, когда пользователь выбирает опцию Ubuntu, UEFI запустит
grubx64.efi
из раздела ESP. Абсолютный путь будет/boot/efi/EFI/ubuntu/grubx64.efi
. Далееgrubx64.efi
прочитаетgrub.cfg
, который находится в том же каталоге, и, как показано на рисунке 2-113, напечатает заголовок записи.Рисунок 2-113. Экран приветствия Ubuntu
С BIOS раньше были такие скачки:
-
Заходим в сигнатуру fdisk, заходим в часть-1 загрузчика, и переходим в часть-2 загрузчика.
-
Переходим к части-3 загрузчика, а затем переходим к файлу конфигурации загрузчика, например
menu.lst
илиgrub.cfg
. -
Распечатываем названия.
В UEFI переход (a) пропускается. UEFI напрямую переходит к (b). Раньше в BIOS загрузчик был разделен на три части из-за нехватки места, но UEFI не имеет никаких ограничений по пространству. Следовательно, весь загрузчик доступен в одном двоичном файле. Например, в случае Ubuntu grubx64.efi
содержит первую, вторую и третью части, добавленные в один двоичный файл, которым является grubx64.efi
.
Файл grubx64.efi
в конечном итоге загрузит ядро (vmlinuz
) и initramfs
из /boot
в память, после чего работа загрузчиков GRUB Ubuntu будет завершена. На рисунке 2-114 показана блок-схема последовательности загрузки Ubuntu.
Рисунок 2-114. Последовательность загрузки Ubuntu
Windows 10
Как вы можете видеть на рисунке 2-115, раздел 1 — это ESP, а раздел 2 — это корень (/
) Ubuntu.
Рисунок 2-115. Структура разделов, показанная в Windows 10
Теперь мы создадим новый раздел для Windows. При создании нового раздела Windows зарезервирует место для инструмента восстановления Windows под названием MSR (Microsoft Recovery, раздел 3). См. рисунок 2-116.
Рисунок 2-116. Резервирование пространства MSR
Как показано на рисунке 2-117, на вновь созданный раздел 4 мы установим Windows 10.
Рисунок 2-117. Установка Windows 10 в раздел 4
Windows по умолчанию обнаружит раздел ESP и, следуя спецификации UEFI, создаст в нем каталог с именем Microsoft и установит в него загрузчик (BCD). Если Windows не найдет ESP, то она создаст его для нас. Поскольку Windows в основном предназначен для пользователей настольных компьютеров, она не покажет нам раздел ESP (см. рисунок 2-118), как его показывает Ubuntu.
Рисунок 2-118. ESP скрыт
Вот как Windows 10 будет загружаться в системе на базе UEFI:
-
Включение системы: сначала UEFI, затем POST, затем UEFI и затем ESP.
-
Как показано на рисунке 2-119, распечатыаются записи ОС согласно каталогам, найденным в ESP (
/boot/efi/EFI
).Рисунок 2-119. Записи ОС внутри UEFI
-
В тот момент, когда пользователь выбирает диспетчер загрузки Windows, UEFI запустит файл
bootmgfw.efi
из каталогаEFI/Microsoft
. В системе на базе Linux абсолютный путь к тому же файлу будет/boot/efi/EFI/Microsoft/bootmgfw.efi
. -
bootmgfw.efi
в конечном итоге загрузит ядро Windows изC:\windows\system32\
. -
Ядро Windows позаботится о дальнейшей загрузке, и при этом пользователям будет показана знаменитая анимация, показанная на рисунке 2-120.
Рисунок 2-120. Знаменитый экран загрузки Windows
-
Как вы можете видеть на рисунке 2-121, на данный момент загружается только одна ОС, и это Windows 10. Но не волнуйтесь, поскольку Windows 10 обязана следовать спецификации UEFI, поэтому она не затронула каталог Ubuntu и, конечно же, не добавила запись Ubuntu в собственный файл загрузчика.
Рисунок 2-121. Последовательность загрузки Windows 10
Fedora 31
Последняя операционная система, которую мы установим, — это Fedora 31. Как показано на рисунке 2-122, мы снова создадим стандартный раздел sda5
и смонтируем /dev/sda1
(ESP) в /boot/efi
.
Рисунок 2-122. Установка Fedora
Помните, не форматируйте sda1
, который является ESP. Потеря ESP означает потерю загрузчиков Windows и Ubuntu. После установки GRUB Fedora предоставит нам список ОС (рисунок 2-123).
Рисунок 2-123. Записи ОС, показанные Fedora
При установке GRUB установщик Fedora Anaconda обнаружил другие операционные системы из ESP. Чтобы предоставить им равные возможности загрузки, Fedora добавила записи Ubuntu и Windows в grub.cfg
. Ниже приведена последовательность загрузки Fedora:
-
Включение системы: сначала UEFI, затем POST, затем UEFI.
-
UEFI переходит в ESP.
-
UEFI переходит в каталог ESP и выбирает ОС для загрузки, проверив приоритет загрузки. На данный момент приоритет загрузки установлен на Fedora. Посмотрите на рисунок 2-124.
Рисунок 2-124. Запись Fedora внутри UEFI
-
Поскольку приоритет загрузки установлен на Fedora, UEFI войдет в каталог
/boot/efi/EFI/fedora
(см. рисунок 2-125) и запустит файлgrubx64.efi
.Рисунок 2-125. Каталог Fedora EFI
-
grubx64.efi
прочитает файлgrub.cfg
и распечатает записи ОС на экране. Это показано на рисунке 2-126.Рисунок 2-126. Записи ОС, показанные Fedora
-
В тот момент, когда пользователь выбирает Fedora, тот же
grubx64.efi
загрузитvmlinuz
иinitramfs
Fedora из/boot
(sda4) в память. Ядро Fedora позаботится об остальной части загрузки. См. блок-схему на рисунке 2-127. Шаги, предпринимаемые ядром, будут более подробно обсуждаться в главе 4.Рисунок 2-127. Последовательность загрузки Fedora
UEFI Shell
UEFI — небольшая операционная система. Как и обычные операционные системы, UEFI предоставляет необходимую среду для запуска приложений. Конечно, UEFI не сможет запускать все двоичные файлы, но двоичные файлы, созданные в формате исполняемого файла EFI, смогут легко работать. Одним из лучших таких приложений (двоичных файлов), предоставляемых UEFI, является оболочка. Как показано на рисунке 2-128, ее можно найти в настройках UEFI в разделе Boot Manager.
Рисунок 2-128. Встроенная оболочка UEFI
Если реализация UEFI вашей системы не предоставляет оболочку, вы можете загрузить приложение оболочки с сайта проекта TianoCore или со страницы EDK-II GitHub.
https://www.tianocore.org/
https://github.com/tianocore/edk2/blob/UDK2018/ShellBinPkg/UefiShell/X64/Shell.efi
Отформатируйте USB-устройство в файловой системе FAT32 и поместите на него загруженный файл Shell.efi
. Загрузитесь снова с того же устройства, и UEFI представит вам оболочку UEFI в окне приоритета загрузки. См. рисунок 2-129.
Рисунок 2-129. Оболочка UEFI, загруженная с USB
Здесь следует отметить удивительную вещь: UEFI не показал, что к системе подключено USB-устройство. Скорее, UEFI зашел внутрь USB-устройства и увидел файловую систему FAT32. Он увидел файл Shell.efi
и понял, что это не обычное приложение EFI; скорее, оно предоставляет оболочку пользователю. Если бы это был BIOS, он показывал бы эту систему только как подключенный USB-диск, но здесь UEFI показывает, что у вас есть оболочка внутри подключенного USB-диска.
В тот момент, когда вы выберете опцию Launch EFI Shell from USB drives, UEFI запустит файл Shell.efi
и предоставит вам оболочку (рисунок 2-130), когда операционная система отсутствует. Это замечательно.
Рисунок 2-130. Оболочка UEFI
Записи blk*
— это имена устройств, тогда как fs*
— это соглашение об именах файловой системы. Поскольку оболочка UEFI может читать файловую систему FAT32 (раздел ESP), мы можем просматривать каталог ESP, как показано на рисунке 2-131.
Рисунок 2-131. Просмотр каталога EFI
fs0
означает номер файловой системы 0. Это внутренняя команда оболочки, которую мы можем использовать для изменения раздела. Как вы можете видеть на рисунках 2-132 и 2-133, fs2
— это наш ESP.
Рисунок 2-132. Каталог EFI
Рисунок 2-133. Каталог загрузчика Ubuntu
Мы можем просто запустить файл grubx64.efi
через оболочку, и GRUB появится на экране. См. рисунок 2-134.
Рисунок 2-134. GRUB Ubuntu
Для оболочки UEFI grubx64.efi
— простое приложение. Аналогичным образом, как показано на рисунке 2-135, мы также можем запустить загрузчик Windows. См. также рисунок 2-136.
Рисунок 2-135. Запуск загрузчика Windows из оболочки UEFI
Рисунок 2-136. Знаменитая анимация Windows
Оболочка может быть полезна при разрешении сценариев «невозможно загрузиться». Рассмотрим сценарий, показанный на рисунке 2-137, где система выдает ошибку в командной строке GRUB.
Рисунок 2-137. Система не может загрузиться
Используя оболочку UEFI, мы можем проверить, присутствуют ли файлы, связанные с GRUB, или нет.
Заблуждения об UEFI
Ниже приведены некоторые заблуждения относительно UEFI.
Заблуждение 1: UEFI — это новый BIOS или UEFI — это BIOS
Люди продолжают говорить, что UEFI — это новый BIOS. Фактически, когда вы заходите в прошивку UEFI, сама прошивка говорит, что это UEFI BIOS. Посмотрите на рисунок 2-138.
Нет, UEFI — это не BIOS и не новый BIOS. UEFI здесь, чтобы заменить BIOS. UEFI — это совершенно новая прошивка, и вы не можете использовать BIOS и UEFI в одной системе. У вас либо UEFI, либо BIOS.
Рисунок 2-138. UEFI — это не BIOS
Определить, есть ли у вас BIOS или UEFI, довольно просто. Если вы можете использовать мышь внутри прошивки, значит, у вас есть UEFI, и если вы видите богатый графический интерфейс, значит, у вас есть UEFI. Правильный способ проверки — использовать команду, подобную efibootmgr
.
# efibootmgr -v
Fatal: Couldn't open either sysfs or procfs directories for accessing EFI variables.
Try 'modprobe efivars' as root.
Если вы получаете такой вывод от команды efibootmgr в системе Linux, значит, у вас BIOS. Если у вас получилось что-то подобное, значит у вас UEFI:
# efibootmgr -v
BootCurrent: 0005
Timeout: 2 seconds
BootOrder: 0005,0004,0003,0000,0001,0002,0006,0007,000A
Boot0000* EFI VMware Virtual SCSI Hard Drive (0.0) PciRoot(0x0)/Pci(0x15,0x0)/Pci(0x0,0x0)/SCSI(0,0)
Boot0001* EFI VMware Virtual SATA CDROM Drive (1.0) PciRoot(0x0)/Pci(0x11,0x0)/Pci(0x4,0x0)/Sata(1,0,0)
Это правильный способ определить, какая прошивка установлена в вашей системе. Возвращаясь к нашему обсуждению UEFI BIOS, поставщики используют термины UEFI и BIOS вместе, потому что большинство пользователей не понимают термин UEFI. Например, статья, в которой говорится «измените параметры в вашем UEFI» может сбить с толку большинство пользователей, но фраза «измените параметры в вашем BIOS» будет хорошо понята всеми. Следовательно, производители используют термин UEFI/BIOS просто для понимания, но помните, что одновременно можно использовать только одну прошивку, а не обе.
Заблуждение 2: Microsoft — это зло
Как мы видели, UEFI — это форум, частью которого являются производители операционных систем, включая Microsoft. Чтобы сделать загрузку более безопасной, Microsoft предложила функцию безопасной загрузки в UEFI. Безопасная загрузка остановит выполнение неавторизованных или скомпрометированных двоичных файлов во время загрузки. Это решает следующие три проблемы:
-
Это гарантирует, что
grubx64.efi
, который будет запущен, получен из подлинного источника. -
Это гарантирует, что в BCD нет бэкдора.
-
Это останавливает выполнение чего-либо, если оно несанкционировано.
Вот как работает безопасная загрузка:
-
Microsoft генерирует пару ключей (открытый и закрытый).
-
Microsoft ставит цифровую подпись своему загрузчику или файлам с помощью закрытого ключа.
-
Открытый ключ Microsoft будет храниться внутри прошивки UEFI.
-
Цифровая подпись, созданная на шаге 2, будет восстановлена с помощью открытого ключа Microsoft, который присутствует внутри UEFI.
-
UEFI разрешит выполнение файла
*.efi
только если цифровая подпись совпадает. -
Если цифровая подпись не совпадает, то UEFI посчитает, что это вредоносная программа или, по крайней мере, она не поставляется Microsoft, UEFI прекратит выполнение.
Довольно хорошая реализация от Microsoft, не так ли? Да, это так. Но проблема возникнет, когда функция безопасной загрузки включена и вы выбираете Linux для загрузки. UEFI извлечет открытый ключ Microsoft и сгенерирует цифровую подпись grubx64.efi
. Сгенерированная цифровая подпись, конечно, не будет совпадать с файлами загрузчика Microsoft, поэтому она будет считаться неавторизованной программой, и UEFI остановит ее выполнение. Другими словами, Linux или любая другая операционная система, отличная от Windows, никогда не сможет загрузиться. Итак, каково решение этого вопроса? Простое: UEFI должен предоставить возможность отключить функцию безопасной загрузки, что он и делает. См. рисунок 2-139. Фактически, возможность отключения функции безопасной загрузки должна присутствовать в прошивке UEFI. Это предусмотрено спецификацией UEFI.
Рисунок 2-139. Отключение функции безопасной загрузки
Но Microsoft ясно заявила, что сертифицированы будут только те системы, у которых включена безопасная загрузка. Это означает, что если вы являетесь поставщиком оборудования и хотите, чтобы ваша система была сертифицирована для Windows, в ней должна быть включена безопасная загрузка. Некоторые лидеры отрасли сочли этот шаг «злым», поскольку операционные системы, отличные от Windows, не смогут загружаться на том же оборудовании. К обсуждению того, является ли Microsoft злом или нет, мы вернемся позже, но сначала давайте посмотрим, какие возможности есть у ОС, отличных от Windows.
Поставщики Linux должны создавать свою собственную пару ключей
Да, каждый поставщик ОС Linux должен создать свою собственную пару ключей, а затем подписать свои загрузчики своим закрытым ключом и сохранить открытый ключ в прошивке UEFI. Всякий раз, когда пользователь выбирает Windows для загрузки, UEFI будет использовать открытый ключ Windows, а всякий раз, когда пользователь выбирает Linux для загрузки, UEFI будет использовать открытый ключ Linux для регенерации цифровой подписи файлов загрузчика Linux. Кажется, что это простое решение, но оно не сработает. На рынке существует более 200+ активных дистрибутивов Linux, и новые версии обычно выпускаются каждые шесть месяцев. Это означает, что почти каждые шесть месяцев на рынке будет появляться новая версия дистрибутива Linux. Грубо говоря, это означает, что поставщики Linux будут иметь почти 400 ключей в год, поэтому очевидно, что вы не сможете разместить такое количество ключей в UEFI. Даже если бы вы могли, это помешает одному из главных девизов дизайна UEFI — быстрой загрузке. Короче говоря, это не может быть решением.
Все поставщики Linux должны создавать только одну пару ключей
Это также не может быть решением. Существует более 200 активных дистрибутивов Linux, а их офисы разбросаны по всему миру. Если бы все поставщики Linux объединились и создали только одну пару ключей, то эту пару ключей пришлось бы рассылать через Интернет разработчикам по всему миру. Это был бы кошмар безопасности. Короче говоря, его будет трудно поддерживать; следовательно, это не решение.
Отключить функцию безопасной загрузки UEFI.
Кажется, это единственный действенный подход. UEFI предоставляет возможность отключения функции безопасной загрузки, и Microsoft не возражает против предоставления такой возможности. Например, предположим, что у вас есть система с двойной загрузкой, в которой установлены Windows 10 и Fedora 31. Если вы хотите загрузить Windows, то в UEFI необходимо включить безопасную загрузку, а если в следующий раз вы захотите загрузить Linux, вам придется зайти в UEFI и изменить включенную безопасную загрузку на отключенное состояние. Вы можете считать это обходным путем, но это непрактично; следовательно, его нельзя рассматривать как решение.
Итак, как Linux может воспользоваться преимуществами безопасной загрузки? Есть только одно решение: использовать закрытый ключ Microsoft для цифровой подписи файлов загрузчика Linux, и угадайте, что: Microsoft согласилась на это. Итак, на данном этапе Linux может обеспечить безопасную загрузку с помощью пары ключей Microsoft, и, следовательно, Microsoft определенно не является злом. Он просто хотел обеспечить безопасность своей последовательности загрузки.
Но в этой схеме есть одна проблема; разработка GRUB будет зависеть от пары ключей Microsoft. Если в GRUB вносится какое-либо новое изменение, нам необходимо повторно подписать его, используя ключ Microsoft. Ubuntu сначала решила эту проблему, представив загрузчик меньшего размера под названием shim. Предполагается, что этот загрузчик должен быть подписан ключом Microsoft, а затем его задача — вызвать настоящий загрузчик, которым является GRUB. Благодаря такому подходу мир Linux преодолел зависимость от подписи Microsoft. Поскольку shim никогда не изменится (по крайней мере, это будет редко), разработка GRUB продолжится в том же духе.
Итак, если включена безопасная загрузка, то последовательность загрузки Linux будет следующей:
-
Включение системы: сначала UEFI, затем POST, а затем UEFI.
-
ESP перечисляет операционные системы и доступные загрузочные устройства.
-
Если пользователь выбирает Linux, процесс загрузки восстанавливает цифровую подпись файла
shim.efi
, используя открытый ключ Microsoft. -
Если цифровая подпись совпадает, то разрешается выполнение
shim.efi
. -
shim.efi
вызывает исходный загрузчикgrubx64.efi
. -
grubx64.efi
прочитывает файлgrub.cfg
из ESP и предоставляет список доступных ОС. -
Если пользователь снова выбирает Linux, то тот же файл
grubx64.efi
начнет загрузку ядра иinitramfs
в память.
Обратитесь к рисунку 2-140, чтобы увидеть список файлов, участвующих в этой последовательности загрузки.
Рисунок 2-140. Файлы, участвующие в описанной последовательности загрузки
Заблуждение 3: Отключение UEFI
Одно из самых больших заблуждений заключается в том, что можно отключить UEFI и запустить BIOS. Нет, вы не можете отключить прошивку вашей системы; также нельзя иметь две прошивки в одной системе. У вас либо UEFI, либо BIOS. Когда люди говорят «отключить UEFI», это означает, что они хотели бы сказать: позволить UEFI загружаться с помощью BIOS или устаревшим способом. Одной из самых важных особенностей UEFI является его обратная совместимость, то есть он понимает способ загрузки BIOS, то есть подход 512 байт + 31 КБ. Таким образом, когда вы меняете настройки UEFI с способа UEFI на устаревший способ, это означает лишь то, что UEFI не будет следовать способу загрузки ESP. Скорее всего, прошивка будет следовать способу загрузки BIOS, но это не означает, что вы отключаете прошивку UEFI. Когда вы загружаете систему UEFI способом BIOS, вы теряете все функции, предоставляемые UEFI.
Поскольку теперь вы лучше понимаете прошивку и принцип работы загрузчиков, сейчас самое время углубиться в загрузчик GRUB.
Глава 3
Загрузчик GRUB
В настоящее время системы Linux используют загрузчик GRUB версии 2. Первый стабильный выпуск GRUB 2 вышел в 2012 году, но в Linux корпоративного уровня он начал появляться в 2014 году с Centos 7 и RHEL 7. После 2015 года он получил широкое распространение почти во всех популярных дистрибутивах Linux. Обычно, когда пользователи сообщают об ошибках или просят добавить новые функции, разработчики прислушиваются к отзывам, расставляют приоритеты в работе и в конечном итоге запускают новую версию кода. Однако в случае с GRUB это работало по-другому. Разработчики решили изменить всю структуру GRUB 2, когда пользователи были довольны GRUB Legacy (версия 1).
— Часто задаваемые вопросы по GNU GRUB (https://www.gnu.org/software/grub/grub-faq.html)
Вот некоторые функции, которые GRUB 2 предоставляет или находится в разработке:
-
Полная поддержка USB.
-
Поддержка единого установочного ключа Linux (LUKS). LUKS — это стандарт шифрования жесткого диска Linux.
-
Причудливая реализация меню с анимацией, красочными эффектами, таблицами стилей и т. д.
-
Инструмент «parted» будет добавлен в загрузчик. Когда это будет добавлено, пользователи смогут редактировать конфигурацию диска во время загрузки.
В этой главе будет рассмотрено следующее:
Как GRUB 2 реализован для прошивки BIOS и UEFI.
Структурные изменения в GRUB 2, специфичные для прошивки.
Функция спецификации загрузчика GRUB 2
Функция безопасной загрузки UEFI и ее реализация в GRUB 2.
Несколько проблем, связанных с загрузчиком, и способы их устранения
Реализация GRUB 2
Как мы уже видели, GRUB берет на себя управление прошивкой. Это означает, что ему приходится иметь дело не только с BIOS, но и с UEFI. Давайте сначала посмотрим, как GRUB 2 был реализован в системах на базе BIOS.
GRUB 2 в системах на базе BIOS
GRUB 2 в системе на базе BIOS хранит все свои файлы в трех разных местах.
/boot/grub2/
/etc/default/grub
/etc/grub.d/
В случае Ubuntu номер версии 2 не используется в имени GRUB, поэтому это будет /boot/grub/
вместо /boot/grub2/
, grub-install
вместо grub2-install
или grub-mkconfig
вместо grub2-mkconfig
.
Давайте обсудим местоположения и их содержание.
/boot/grub2
Это место, куда будет установлен GRUB 2. Как вы можете видеть на рисунке 3-1, в этом каталоге хранятся основные файлы загрузчика.
Рисунок 3-1. Файлы в каталоге /boot/grub2
device.map
GRUB не понимает имена дисков, такие как sda или vda, поскольку эти соглашения об именах дисков были созданы драйверами SCSI операционных систем. Очевидно, что GRUB запускается, когда ОС отсутствует, поэтому у него есть собственное соглашение об именах дисков. Ниже приведены соглашения об именах дисков GRUB:
Версия GRUB | Соглашение об именовании дисков | Значение |
---|---|---|
2 | hd0, msdos1 |
Жесткий диск номер 0 и раздел номер 1, имеющий таблицу разделов MS-DOS |
2 | hd1, msdos3 |
Жесткий диск номер 1 и раздел номер 3, имеющий таблицу разделов MS-DOS. |
2 | hd2, gpt1 |
Жесткий диск номер 2 и раздел номер 1, имеющий таблицу разделов GPT. |
1 | hd0, 0 |
Жесткий диск номер 0 и номер раздела 1 |
В GRUB жесткий диск начинается с 0, а номера разделов начинаются с 1, тогда как соглашения об именах дисков и разделов в ОС начинаются с 1. Поскольку соглашения об именах дисков в ОС и GRUB различны, должно быть сопоставление пользователей, и именно поэтому был создан файл device.map
.
# cat /boot/grub2/device.map
# this device map was generated by anaconda
(hd0) /dev/sda
Файл device.map
будет использоваться командами типа grub2-install
, чтобы понять, на каком диске установлены основные файлы GRUB. Вот пример этого файла:
# strace -o delete_it.txt grub2-install /dev/sda
Installing for i386-pc platform.
Installation finished. No error reported.
# cat delete_it.txt | grep -i 'device.map'
openat(AT_FDCWD, "/boot/grub2/device.map", O_RDONLY) = 3
read(3, "# this device map was generated "..., 4096) = 64
openat(AT_FDCWD, "/boot/grub2/device.map", O_RDONLY) = 3
read(3, "# this device map was generated "..., 4096) = 64
Команда grub2-install
будет принимать входные данные в виде соглашений об именах дисков ОС, поскольку пользователи не знают о соглашениях об именах дисков GRUB. Во время выполнения grub2-install
преобразует соглашения об именах дисков SCSI в соглашения об именах дисков GRUB, читая файл device.map
.
grub.cfg
Это основной файл конфигурации GRUB. Как вы можете видеть на рисунке 3-2, это огромный файл сценария, который создается путем обращения к некоторым другим файлам сценариев, о которых мы скоро поговорим. Настоятельно рекомендуется не изменять содержимое grub.cfg
, поскольку это может привести к невозможности загрузки вашей версии Linux. Это файл, из которого часть-3 GRUB получает следующие инструкции:
-
Местоположение ядра (kernel) и initramfs
- /boot/vmlinuz-<version>
- /boot/initramfs-<version>
-
Параметры командной строки ядра
- Имя корневой файловой системы, ее расположение и т. д.
Рисунок 3-2. Файл grub.cfg
GRUB имеет свой собственный набор команд, как вы можете видеть здесь:
Команда GRUB | Цель |
---|---|
menuentry |
Название будет напечатано на экране. |
set root |
Это предоставит имена дисков и разделов, где хранятся ядро и initramfs. |
linux |
Абсолютный путь к файлу ядра Linux |
initrd |
Абсолютный путь к файлу initramfs Linux. |
Итак, последовательность загрузки GRUB 2 в системе Fedora на базе BIOS следующая:
-
Включение системы: сначала BIOS, затем POST, затем BIOS, и затем первый сектор.
-
Сначала идет начальная загрузка (часть-1 GRUB), затем часть-2 GRUB, а затем часть-3 GRUB.
-
Часть-3 GRUB прочитает ранее показанный
grub.cfg
из/boot/grub2/
(в случае Ubuntu это будет/boot/grub/
) и распечатает экран приветствия, как показано на рисунке 3. 3.Рисунок 3-3. Экран приветствия
-
В тот момент, когда пользователь выбирает
menuentry
в Ubuntu, он запускает командыset root
,linux
иinitrd
и начинает загрузку ядра и initramfs в память. -
В дистрибутивах Linux, подобных Fedora, вы найдете другой подход. Будет файл
grub.cfg
, ноmenuentry
,set root
,linux
иinitrd
будут недоступны вgrub.cfg
. В восходящем проекте GRUB появилась новая разработка под названием BLS. Мы рассмотрим ее позже в этой главе.
i386-pc
В этом каталоге находятся все модули файловой системы (драйверы), поддерживаемые GRUB (см. рисунок 3-4). Все файлы *.mod
являются модулями. Используя эти модули, GRUB может загружать в память файлы ядра и initramfs. Например, /boot
этой системы имеет файловую систему ext4, поэтому, очевидно, при просмотре и загрузке файлов vmlinuz
и initramfs
из /boot
GRUB нуждается в модуле ext4
, который он получает из файла ext4.mod
. Аналогично, если /boot
находится в файловой системе XFS или UFS, то файлы xfs.mod
и ufs.mod
присутствуют в /boot/grub2/i386-pc
. В то же время вы найдете такие модули, как http.mod
и pxe.mod
. Это означает, что часть-3 GRUB 2 может загружать файлы ядра и initramfs с устройств http
и pxe
. В общем, файлы *.mod
добавляют функции, а не только устройства. Эти функции могут включать поддержку устройств, поддержку файловой системы или поддержку протоколов.
Рисунок 3-4. Файлы *.mod
из /boot/grub2/i386-pc
Раньше /boot
под LVM был невозможен, и причина была проста. GRUB должен был понимать устройства LVM. Чтобы понять и собрать устройство LVM, GRUB понадобится модуль LVM, а также двоичные файлы LVM, такие как vgscan
, vgchange
, pvs
, lvscan
и т. д. Это увеличивает размер GRUB как пакета; следовательно, поставщики корпоративных систем Linux всегда избегали использования /boot
на устройствах LVM. Но с появлением UEFI GRUB начал поддерживать /boot
на устройствах LVM.
Как вы можете видеть на рисунке 3-5, наряду с файлами *.mod
вы найдете еще несколько файлов в папке /boot/grub2/i386-pc/
.
Рисунок 3-5. Файлы в дополнение к *.mod
Файл core.img
является частью-3 GRUB 2. Таким образом, последовательность загрузки Linux выглядит следующим образом:
-> Power on -> BIOS -> POST -> BIOS ->
-> part-1 of GRUB2 -> Part-2 of GRUB2 -> core.img -> grub.cfg ->
-> if /boot is on an xfs filesystem -> /boot/grub2/i386-pc/xfs.mod ->
-> load vmlinuz & initramfs in main memory.
Как только ядро окажется в памяти, работа GRUB 2 будет завершена. Оставшуюся часть загрузки будет выполнять ядро, о чем мы поговорим в главе 4.
/etc/default/grub
Еще один важный файл — это, конечно же, /etc/default/grub
. См. рисунок 3-6.
Рисунок 3-6. Содержимое каталога /etc/default
Этот файл используется GRUB для принятия пользователем косметических изменений и изменений ядра в командной строке.
$ cat /etc/default/grub
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/root_vg-swap rd.lvm.lv=root_vg/root
rd.lvm.lv=root_vg/swap console=ttyS0,115200 console=tty0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
Как вы можете видеть, в этом файле мы можем изменить время ожидания по умолчанию для экрана приветствия GRUB, шрифт, подменю и параметры командной строки ядра по умолчанию, такие как имя корневого устройства, имя устройства подкачки и т. д.
/etc/grub.d/
Вот тут-то и происходит самое интересное в GRUB 2.
В GRUB 2 есть команда grub2-mkconfig
. Название команды предполагает, что она создаст файл конфигурации GRUB grub.cfg
, на который будет ссылаться часть-3 GRUB для отображения экрана приветствия. Файл grub2-mkconfig
сначала возьмет входные параметры командной строки оформления и ядра из /etc/default/grub
и запустит файлы сценариев, перечисленные на рисунке 3-7, из каталога /etc/grub.d/
.
Рисунок 3-7. Содержимое каталога /etc/grub.d/
Как видите, файлам присвоены номера. Это означает, что они будут запускаться по порядку.
Файлы сценариев 00_header
, 01_users
, 08_fallback_counting
, 10_reset_boot_success
и 12_menu_auto_hide
выполняют служебную работу. Например, файл сценария 00_header
отвечает за добавление заголовка в файл grub.cfg
. Если взять Fedora Linux, то после запуска файла grub2-mkconfig
в grub.cfg
будет добавлен следующий заголовок:
### BEGIN /etc/grub.d/00_header ###
set pager=1
if [ -f ${config_directory}/grubenv ]; then
load_env -f ${config_directory}/grubenv
elif [ -s $prefix/grubenv ]; then
load_env
fi
if [ "${next_entry}" ] ; then
set default="${next_entry}"
set next_entry=
save_env next_entry
set boot_once=true
else
set default="${saved_entry}"
fi
if [ x"${feature_menuentry_id}" = xy ]; then
menuentry_id_option="--id"
else
menuentry_id_option=""
fi
export menuentry_id_option
if [ "${prev_saved_entry}" ]; then
set saved_entry="${prev_saved_entry}"
save_env saved_entry
set prev_saved_entry=
save_env prev_saved_entry
set boot_once=true
fi
function savedefault {
if [ -z "${boot_once}" ]; then
saved_entry="${chosen}"
save_env saved_entry
fi
}
function load_video {
if [ x$feature_all_video_module = xy ]; then
insmod all_video
else
insmod efi_gop
insmod efi_uga
insmod ieee1275_fb
insmod vbe
insmod vga
insmod video_bochs
insmod video_cirrus
fi
}
terminal_output console
if [ x$feature_timeout_style = xy ] ; then
set timeout_style=menu
set timeout=5
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=5
fi
### END /etc/grub.d/00_header ###
Файл сценария 08_fallback_counting
добавит в grub.cfg
следующее содержимое:
### BEGIN /etc/grub.d/08_fallback_counting ###
insmod increment
# Check if boot_counter exists and boot_success=0 to activate this behaviour.
if [ -n "${boot_counter}" -a "${boot_success}" = "0" ]; then
# if countdown has ended, choose to boot rollback deployment,
# i.e. default=1 on OSTree-based systems.
if [ "${boot_counter}" = "0" -o "${boot_counter}" = "-1" ]; then
set default=1
set boot_counter=-1
# otherwise decrement boot_counter
else
decrement boot_counter
fi
save_env boot_counter
fi
### END /etc/grub.d/08_fallback_counting ###
Как вы можете видеть, в файл добавляется код, который будет отслеживать значение тайм-аута по умолчанию для экрана приветствия GRUB, точно так же, как остальные файлы (10_reset_boot_success
и 12_menu_auto_hide
) будут выполнять служебную работу для GRUB. Давайте посмотрим на файлы сценариев, которые делают GRUB 2 одним из лучших загрузчиков для мультизагрузки.
10_linux
Этот файл содержит почти 500 строк файла сценария bash. Всякий раз, когда пользователь выполняет команду grub2-mkconfig
, он в порядке очереди запускает и этот скрипт. Файл 10_linux
ищет, какие еще дистрибутивы Linux вы установили в своей системе. Он буквально пройдёт раздел за разделом и найдет все остальные версии Linux, установленные в вашей системе. Если есть какие-либо другие, то он создаст для каждого из них строку меню в grub.cfg
. Наряду с этим в каждый пункт меню он добавит соответствующие записи ядра и initramfs. Разве это не удивительно?
Предположим, вы сначала установили Ubuntu, а затем Fedora; теперь вам не нужно вручную добавлять записи Ubuntu в grub.cfg
Fedora. Вам нужно просто запустить grub2-mkconfig
. Команда запустит для нас 10_linux
и в конечном итоге обнаружит, что установлена Ubuntu, и добавит для нее соответствующую запись.
20_linux_xen
После 10_linux
этот файл сценария выяснит, установлено ли в вашей системе ядро XEN. Если да, то соответствующая запись будет добавлена в grub.cfg
. Большинство дистрибьюторов Linux поставляют XEN как отдельный пакет ядра. XEN в основном используется гипервизорами.
20_ppc_terminfo
Если ваша система имеет архитектуру PPC или PowerPC от IBM, то этот файл сценария найдет для нее соответствующее ядро и добавит соответствующую запись в grub.cfg
.
30_os_prober
Если на вашем жестком диске установлена какая-либо операционная система, отличная от Linux, этот файл сценария найдет эту ОС и создаст для нее соответствующую запись. Другими словами, если в вашей системе установлена Windows, она автоматически обнаружит это и сделает соответствующую запись в grub.cfg
. Именно по этой причине после установки нашей третьей ОС (Fedora 31) в системе UEFI мы получили список операционных систем, ничего не делая. Вы можете увидеть экран приветствия, представленный в Fedora 31, на рисунке 3-8.
Рисунок 3-8. Экран приветствия
После установки Fedora Anaconda запустила в фоновом режиме grub2-mkconfig
, который в конечном итоге запустил 30_os_prober
, нашел установку Windows и сделал для нее соответствующую запись в grub.cfg
.
30_uefi-firmware
Этот сценарий будет успешно работать только в том случае, если у вас есть система UEFI. Задача этого файла сценария — добавить соответствующие записи прошивки UEFI в grub.cfg
. Как вы можете видеть на рисунке 3-8, запись System setup
была добавлена с помощью файла сценария 30_uefi-firmware
.
### BEGIN /etc/grub.d/30_uefi-firmware ###
menuentry 'System setup' $menuentry_id_option 'uefi-firmware' {
fwsetup
}
### END /etc/grub.d/30_uefi-firmware ###
Если пользователь выберет опцию «System setup», то система снова загрузится с использованием прошивки UEFI. Вы можете увидеть интерфейс прошивки UEFI на рисунке 3-9.
Рисунок 3-9. Прошивка UEFI
40_custom и 41_custom
Они передаются пользователю на случай, если пользователь захочет добавить некоторые пользовательские записи в grub.cfg
. Например, если grub2-mkconfig
не может добавить ни одну из установленных ОС в качестве записей, пользователи могут добавить пользовательскую запись в эти два пользовательских файла. Вы можете создавать свои собственные файлы, но вам необходимо убедиться, что каждому из них присвоен номер и есть разрешение на выполнение.
GRUB 2 в системе на базе UEFI
Опять же, есть три места, где GRUB 2 хранит свои файлы. На рисунке 3-10 показаны каталоги и файлы в них.
Рисунок 3-10. Расположение GRUB 2 в системе на базе UEFI
Файл grub.cfg
, показанный ранее в /boot/grub2/
, был перемещен внутрь ESP (/boot/efi/EFI/fedora/
). Также, как видите, каталога i386-pc
нет. Это связано с широкой поддержкой устройств и файловых систем, предоставляемой EFI. Внутри ESP вы найдете несколько файлов *.efi
, включая наши двоичные файлы shim.efi
и grubx64.efi
. Файл /etc/default/grub
, отвечающий за косметические изменения GRUB и параметры командной строки ядра, по-прежнему находится в том же месте. Файл device.map
недоступен, поскольку команда grub2-install
не имеет значения в системе UEFI. Мы поговорим об этой команде позже в этой главе.
Спецификация загрузчика (BLS)
BLS — это новое развитие в проектах GRUB, которое ещё не было принято многими основными дистрибутивами. В частности, эта схема была принята операционными системами на базе Fedora, такими как RHEL, Fedora, Centos, Oracle Linux и т. д., но не была принята дистрибутивами на основе Debian, такими как Ubuntu, Mint и т. д.
В системах на базе BIOS любая ОС, контролирующая первые 512 байт, контролирует и все последовательности загрузки операционных систем, поэтому каждая ОС пытается завладеть первыми 512 байтами. Такая ситуация возникает потому, что BIOS всегда попадает в первые 512 байт жесткого диска и вызывает часть-1 загрузчика (bootstrap). Переходы от части-1 к части-2 и от части-2 к части-3 происходят позже, а затем в конце часть-3 считывает файл конфигурации, специфичный для загрузчика (bcdedit
в случае Windows, grub.cfg
в случае Linux). Если в этом файле конфигурации есть записи для других установленных ОС, они получат возможность загрузиться. Короче говоря: тот, кто контролирует первые 512 байт, контролирует всю последовательность загрузки. Но с ESP каждая ОС получает равные шансы на загрузку, поскольку UEFI проверяет каталоги ESP и выводит список всех доступных записей ОС. Разработчики начали задаваться вопросом, можно ли получить что-то подобное в системе на базе BIOS, и придумали BLS.
В BLS было введено новое место (пятое) для хранения файлов, связанных с загрузчиком, а именно /boot/loader/
. Итак, теперь у нас есть пять мест, где GRUB будет хранить свои файлы.
/boot/grub2/
/etc/default/grub
/etc/grub.d
/boot/efi/EFI/<OS_vendor>/
(только в случае UEFI)/boot/loader/
(здесь будут храниться файлы BLS)
Идея в том, что после установки нового ядра само ядро со своими постскриптами (что-то вроде пакета kernel-core
в случае с Fedora) создаст запись для нового ядра в каталоге /boot/loader/
. Например, у нас установлен этот пакет ядра:
# rpm -q kernel
Kernel-5.3.7-301.fc31.x86_64
Это тот же пакет, который содержит файлы /boot/vmlinuz
и /boot/initramfs
. После установки этого ядра оно подготавливает следующий файл:
# cat /boot/loader/entries/36543031048348f9965e3e12e48bd2b1-5.3.7-301.fc31.x86_64.conf
title Fedora (5.3.7-301.fc31.x86_64) 31 (Thirty One)
version 5.3.7-301.fc31.x86_64
linux /vmlinuz-5.3.7-301.fc31.x86_64
initrd /initramfs-5.3.7-301.fc31.x86_64.img
options $kernelopts
grub_users $grub_users
grub_arg --unrestricted
grub_class kernel
Как видите, в файле четыре ключевых записи.
title
— заголовок, который будет напечатан в части-3 GRUBlinux
— расположение и имя файла ядраinitrd
— расположение и имя файла initramfsoptions
— переменная$kernelopts
, объявленная в файле/boot/grub2/grubenv
.
# cat /boot/grub2/grubenv
# GRUB Environment Block
saved_entry=2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64
menu_auto_hide=1
boot_success=0
kernelopts=root=/dev/mapper/fedora_localhost--live-root ro
resume=/dev/mapper/fedora_localhost--live-swap
rd.lvm.lv=fedora_localhost-live/root
rd.lvm.lv=fedora_localhost-live/swap rhgb quiet
boot_indeterminate=0
По сути, kernelopts
предоставляет параметры командной строки ядра, такие как имя корневой файловой системы (/dev/mapper/fedora_localhost--live-root
) и режим, в котором она должна быть смонтирована (ro
— read only (только чтение)).
Итак, последовательность загрузки становится такой:
BIOS -> POST -> BIOS
Часть-1 GRUB -> часть-2 GRUB -> часть-3 GRUB
Часть-3 GRUB -> читает
grub.cfg
Часть-3 GRUB -> читает
/boot/loader/entries/*
Печатает все заголовки файлов, которые присутствуют в
/boot/loader/entries
.
Например, предположим, что установлена новая ОС или установлено новое ядро. Они должны создать свой собственный файл записи и поместить его в каталог /boot/loader/entries/
первого основного раздела. Таким образом, каждый раз, когда часть-3 GRUB первой основной ОС считывает запись, другая ОС будет иметь возможность загрузиться. Файл записи можно создать с помощью команды kernel-install
Fedora.
# kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.
x86_64/vmlinuz
Команда создаст соответствующую запись для kernel-5.3.7-301.fc31.x86_64
в /boot/loader/entries/
, как показано здесь:
# ls /boot/loader/entries/ -l
total 8
-rw-r--r--. 1 root root 329 Dec 9 10:18 2058a9f13f9e489dba29c477a8ae2493-0-rescue.conf
-rw-r--r--. 1 root root 249 Oct 22 01:04 2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64.conf
Номер, связанный с файлом *.conf
, уникален. У BLS есть свои преимущества и недостатки.
Вот преимущества:
-
Каждая ОС получит равные шансы на загрузку.
-
Работает независимо от прошивки BIOS и UEFI.
-
В случае с BIOS последняя установка Linux удаляет часть-1 и часть-2 ранее установленной операционной системы, которая устарела, поскольку последняя установка Linux будет делать свою собственную запись через команду
kernel-install
в более ранних ОС.
И недостатки:
-
BLS еще не полностью реализован. Если вторая ОС хочет войти в первую ОС, то
/boot
первой ОС должен быть общим. На данный момент это не так. Так что я считаю это не полной реализацией. -
BLS излишне усложняет последовательность загрузки, поскольку у нас есть два файла конфигурации, на которые нужно ссылаться:
grub.conf
и<uniq_no><kernel_version>.conf
из/boot/loader/entries/
. BLS особенно усложняет жизнь в случае решения проблем «невозможно загрузиться». -
За исключением дистрибутивов на основе Fedora, никто еще не принял BLS, что кажется мудрым решением. Похоже, что Fedora больше всего привержена апстрим-разработке; следовательно, BLS был реализован в Fedora.
Распространенные проблемы с загрузчиком
Основываясь на этих знаниях, давайте попробуем решить некоторые из наиболее распространенных проблем, связанных с загрузчиком, «невозможно загрузиться».
Проблема 1, «Невозможно загрузиться» (загрузчик Bootloader)
Проблема: После включения системы вы попадаете в командную строку GRUB, как показано на рисунке 3-11.
Рисунок 3-11. Приглашение GRUB 2
Это то, что вы видите на своем экране. Вы наверняка сталкивались с этой ошибкой хотя бы раз в жизни. Давайте попробуем решить эту проблему.
-
Вы сможете решить проблему, только если будете знать, в чем она заключается. Однако сейчас мы понятия не имеем, в чем проблема, поскольку мы только запустили систему и вот что получаем.
-
Экран называется приглашением GRUB. Если это называется приглашением, значит вы можете выполнять с его помощью команды. Помните, что это командная строка GRUB, а это значит, что она может принимать только команды GRUB.
-
Судя по рисунку 3-11, какая из трех частей GRUB предоставила нам приглашение GRUB?
-
Конечно, это должна быть часть-3, поскольку часть-1 и часть-2 занимают очень мало места и не могут вместить такой функционал. Итак, мы успешно дошли до части-3 GRUB, и самое главное, не имеет значения, есть ли в этой системе UEFI или BIOS. Раз мы дошли до части-3, значит мы вышли из среды прошивки. Это решающий вывод. Сейчас мы можем сосредоточиться только на части-3.
-
Какова цель части-3 GRUB? Простая. Она читает
grub.cfg
и оттуда получает расположение ядра и initramfs. Если это система с поддержкой BLS, она получает имена ядра и initramfs из каталогов/boot/loader/entries/
. В этом примере мы предполагаем, что эта система не поддерживает BLS. Затем в части-3 загружаютсяvmlinuz
иinitramfs
в память. -
Поскольку часть-3 предоставила нам приглашение GRUB, но не удалось загрузить ОС, это означает, что либо файлы ядра и initramfs отсутствуют, либо файл
grub.cfg
не указывает правильное расположение этих файлов. -
Итак, в такой ситуации мы можем попробовать загрузить Fedora вручную. Вручную означает, что мы предоставим файлам ядра и initramfs абсолютные пути с помощью командной строки GRUB. Вот как это можно сделать.
-
linux
— это команда GRUB, с помощью которой нам нужно указать абсолютный путь к файлу ядра (vmlinuz
). Как мы знаем, файлvmlinuz
находится в каталоге/boot
, а GRUB следует своему собственному соглашению об именах дисков. Таким образом, путь к/boot
будет номером жесткого диска 0 и номером раздела 1. Конечно, вы можете не знать, на каком жестком диске или разделе был сохранен/boot
. В этом случае вы можете воспользоваться функцией автозаполнения GRUB. Вы можете дважды нажать Tab, и GRUB предложит вам доступные параметры. Давайте узнаем жесткий диск и номер раздела/boot
. См. рисунок 3-12.Рисунок 3-12. Доступные разделы на жестком диске номер 0
Первое нажатие Tab после
hd0
показало нам, что на жестком диске с номером 0 доступны два раздела. Второй раздел не читается GRUB, поэтому, конечно, второй раздел не может быть/boot
. Следовательно, мы выберем разделmsdos1
. Затем, как показано на рисунке 3-13, мы начнем искать в нем файлvmlinuz
с помощью автозаполнения.Рисунок 3-13. Файл
vmlinuz
Как вы можете видеть, внутри жесткого диска номер 0 и раздела номер 1 мы нашли два файла
vmlinuz
; один — это аварийное ядро, а другой — обычный файл ядра Fedora 31. Как показано на рисунке 3-14, мы выберем обычное ядро и предоставим ему имя корневой файловой системы. Если вы не знаете имя корневой файловой системы вашей системы, вы можете загрузить систему с помощью аварийного или live-образа и проверить записи/etc/fstab
. Мы поговорим о режиме восстановления в главе 10.Рисунок 3-14. Имя корневой файловой системы и флаг
ro
Абсолютный путь к файлу
vmlinuz
:(hd0,msdos1)/vmlinuz-5.3.7-301.fc31.x86_64
. Далее следует параметр командной строки ядраro
, который означает «только для чтения». Послеro
у нас есть параметрroot
командной строки ядра, которому мы передали имя корневой файловой системы нашей системы:/dev/mapper/fedora_localhost--live-root
. Это устройствоlvm
.grub> linux (hd0,msdos1)/vmlinuz-5.3.7-301.fc31.x86_64 ro root=/dev/mapper/fedora_localhost--live-root
После успешного выполнения команды
linux
нам нужно передать имяinitramfs
. У нас есть две доступные команды:initrd
иinitrd16
. См. рисунок 3-15.grub> initrd (hd0,msdos1)/initramfs-5.3.7-301.fc31.x86_64.img
Рисунок 3-15. Команды
linux
,initrd
иboot
в действии -
В тот момент, когда вы выполняете команду
boot
, как показано на рисунках 3-16 и 3-17, часть-3 GRUB примет эти входные данные и загрузит/boot/vmlinuz-5.3.7-301.fc31.x86_64
изsda1 (hd0,msdos1)
. Затем он загрузит/boot/initramfs-5.3.7-301.fc31.x86_64.img
и передаст управление ядру. В конечном итоге ядро смонтирует корневую файловую систему (/
) из/dev/mapper/fedora_locahost--live-root
в каталоге/
и отобразит экран входа в систему.Рисунок 3-16. Сообщения консоли во время загрузки
Рисунок 3-17. Экран входа в систему
-
В случае с Ubuntu 18 команды немного другие. В Fedora 31 мы передали адрес раздела
/boot
непосредственно командеlinux
, тогда как в Ubuntu у нас есть отдельная команда GRUB, называемаяset root
.Как вы можете видеть на рисунке 3-18, имя корневой файловой системы системы Ubuntu 18 —
/dev/sda1
. Это стандартный раздел, в отличие от устройстваlvm
в Fedora 31.Рисунок 3-18. В Ubuntu немного другой подход
Как только мы предоставим правильные данные для GRUB 2, мы попадем на экран входа в систему. Вы можете увидеть экран входа в Ubuntu на рисунке 3-19.
Рисунок 3-19. Экран входа в систему, представленный Ubuntu
-
Возвращаясь к нашей системе Fedora, поскольку она уже загружена, мы можем восстановить файл
grub.cfg
с помощью командыgrub2-mkconfig
, как показано на рисунке 3-20.Рисунок 3-20. Команда
grub2-mkconfig
Мы можем выполнить
grub-mkconfig
в случае Ubuntu. См. рисунок 3-21.Рисунок 3-21. Команда
grub-mkconfig
в UbuntuНо если это система UEFI и вы хотите повторно создать
grub.cfg
, то, как показано на рисунке 3-22, расположениемgrub.cfg
будет ESP.Рисунок 3-22.
grub2-mkconfig
в системе на базе UEFI -
После создания
grub.cfg
нам необходимо заново создать записи BLS для Fedora.# kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301. fc31.x86_64/vmlinuz
Команда создаст соответствующую запись для kernel-5.3.7-301.fc31.x86_64 в
/boot/loader/entries/
.# ls /boot/loader/entries/ -l total 8 -rw-r--r--. 1 root root 329 Dec 9 10:18 2058a9f13f9e489dba29c477a8ae2493-0-rescue.conf -rw-r--r--. 1 root root 249 Oct 22 01:04 2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64.conf
-
Если Fedora работает в системе UEFI, шаг BLS остается прежним.
-
После перезагрузки Fedora загружается без проблем, проблема «невозможно загрузиться» устранена.
Проблема 2, «Невозможно загрузиться» (загрузчик Bootloader)
Проблема: После включения система проходит этап прошивки, но после этого, как видно на Рисунке 3-23, на экране ничего не отображается.
Рисунок 3-23. Пустой экран
Решение для системы на базе BIOS
Вот шаги решения этой проблемы:
-
Раз этап прошивки BIOS пройден, значит что-то не так на уровне загрузчика.
-
Поскольку мы ничего не видим на экране, это означает, что часть-1 или часть-2 GRUB отсутствует или, по крайней мере, они повреждены (512 байт + 31 КБ). Если бы дело дошло до части-3, мы бы получили хотя бы приглашение GRUB. Итак, проблема локализована, и план действий — заменить часть-1 и часть-2 GRUB.
-
Это можно сделать с помощью команды
grub2-install
. Сначала либо загрузитесь с live-носителя того же дистрибутива Linux, либо, если возможно, загрузитесь в режиме восстановления. Live-образ и режим восстановления будут описаны в Главе 10.
Как вы можете видеть на рисунке 3-24, grub2-install
принимает имя устройства в качестве входных данных. Обратите внимание, что имя устройства не должно быть номером раздела; скорее, это должно быть имя диска. Это связано с тем, что часть-1 и часть GRUB должны быть установлены в первые 512 байт + 31 КБ диска, а не внутри раздела. Вам необходимо заменить sda
на имя вашего диска.
Рисунок 3-24. Команда grub2-install
Наряду с частью-1 и частью-2 файлов загрузчика, grub2-install
восстанавливает или переустанавливает каталог i386-pc
, который содержит все модули загрузчика GRUB 2. Мы можем перекрестно проверить это, установив модули в собственный каталог. См. рисунок 3-25.
Рисунок 3-25. Установка grub2
во временный каталог
Вы можете видеть, что все файлы GRUB 2 были восстановлены вместе с файлами модулей GRUB.
# ls temp/grub2/
fonts grubenv i386-pc
# ls -l temp/grub2/i386-pc/ | wc -l
279
После перезагрузки Fedora должна загрузиться нормально, и проблема «невозможно загрузиться» должна быть устранена. Если GRUB отправляет вас в командную строку, вам необходимо выполнить шаги, упомянутые для проблемы 1, поскольку grub2-install
восстанавливает двоичные файлы, но не восстанавливает файл grub.cfg
.
Но что, если вы столкнетесь с аналогичной проблемой в системе на базе UEFI?
Решение для системы на базе UEFI
Вот шаги:
-
Как вы уже догадались, нам нужно просто изменить имя устройства, переданное командой
grub2-install
, как показано на рисунке 3-26. Имя устройства должно быть ESP.
Рисунок 3-26. Команда grub-install
в системе на базе UEFI
Проблема 3, «Невозможно загрузиться» (загрузчик Bootloader + ядро)
Проблема: Отсутствует /boot
.
Решение для систем на базе BIOS
Вот шаги:
-
Восстановить потерянный каталог
/boot
невозможно (или, по крайней мере, это выходит за рамки этой книги). -
Загрузитесь в режиме восстановления или загрузитесь с live-образа и смонтируйте корневую файловую систему нашей «не загружающейся» системы. Режим восстановления и его работа обсуждаются в главе 10.
-
Сначала создайте новый каталог
/boot
и установите для него соответствующие разрешения.# mkdir /boot
# chmod 555 /boot
# chown root:root /boot
Если
/boot
должен быть отдельным разделом, примонтируйте в него правильный раздел.
-
Как мы знаем, в
/boot
хранятся файлы загрузчика, ядра и initramfs. Поскольку/boot
отсутствует, нам нужно создать для него каждый файл.# dnf reinstall kernel
-
Это для системы на базе Fedora. Если это система на базе Debian, вы можете использовать команду
apt-get
и переустановить ядро. -
Это приведет к установке файла
vmlinuz
, а также к восстановлению для него файла initramfs.
-
Теперь нам нужно установить GRUB.
-
# grub2-install /dev/<disk_name>
-
В нашем случае это команда
# grub2-install /dev/sda
.
-
-
Это восстановит часть-1 и часть-2 GRUB и каталог
i386-pc
из/boot/grub2
. -
Чтобы восстановить часть-3 GRUB и получить некоторые инструменты, предоставляемые GRUB, нам нужно установить два пакета в системе на базе Fedora.
# dnf reinstall grub2 grub2-tools
-
Как следует из названия, пакет
grub2
предоставляет часть-3 GRUB, аgrub2-tools
предоставляет некоторые инструменты, такие какgrub2-install
.
-
Теперь пришло время восстановить файл конфигурации GRUB.
# grub2-mkconfig -o /boot/grub2/grub.cfg
-
Наконец, исправьте BLS.
# kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz
-
Решение для систем на базе UEFI
Вот шаги:
-
/boot
и/boot/efi/
— это отдельные точки монтирования.# mkdir /boot
# chmod 555 /boot
# chown root:root /boot
# yum reinstall kernel
-
Теперь нам нужно создать раздел ESP, и, как мы знаем, это должен быть раздел VFAT. Затем назначьте ему тип раздела ESP.
# mkdir /boot/efi
-
# mount /dev/sda2 /boot/efi
В нашем случае для ESP я создал раздел sda2.
-
# grub2-install --efi-directory=/boot/efi
Это установит файл
grubx64.efi
в ESP.
-
Остальные необходимые файлы предоставляются пакетами
grub2-efi
,shim
иgrub2-tools
.# yum reinstall grub2-efi shim grub2-tools
Восстановление файлов конфигурации.
# grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
# kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz
После перезагрузки системы она загружается без проблем.
Теперь пришло время пролить свет на среду безопасной загрузки UEFI.
Функция Secure Boot UEFI
Secure Boot (безопасная загрузка) — замечательная функция UEFI. Это гарантирует, что во время загрузки не будет запускаться ненадежный двоичный файл. До сих пор мы видели следующее:
-
Цифровая подпись представляет собой уникальную строку.
Цифровая подпись любого файла будет сгенерирована на основе закрытого ключа.
Та же самая цифровая подпись может быть восстановлена из открытого ключа.
Если файл не изменен, то цифровая подпись должна совпадать.
-
Microsoft создала свою пару ключей (открытый и закрытый).
-
Microsoft поставила цифровую подпись своим файлам, связанным с загрузчиком (BCD), своим закрытым ключом.
-
Открытый ключ Microsoft присутствует внутри UEFI.
-
Во время загрузки UEFI восстановит цифровую подпись загрузчика, используя доступный открытый ключ. Если цифровые подписи не совпадают, UEFI отменит выполнение файлов
.efi
. -
Чтобы использовать эту функцию в среде Linux, был создан новый загрузчик под названием
shim
, подписанный закрытым ключом Microsoft, чтобы UEFI разрешил выполнениеshim.efi
. -
Задача
shim.efi
— вызвать настоящий файл GRUB, то естьgrubx64.efi
.
Но безопасная загрузка на этом не заканчивается. Поскольку существует вероятность того, что сам grubx64.efi
был скомпрометирован, или фактически любой код, который выполняется после загрузчика, мог быть скомпрометирован, защиты среды загрузки только до уровня загрузчика недостаточно; следовательно, в наши дни функция безопасной загрузки обеспечивает безопасность всей процедуры загрузки Linux. Вот как это работает:
-
Fedora подготовит свою собственную пару ключей и подпишет файлы GRUB закрытым ключом Fedora.
-
Открытый ключ Fedora будет храниться в файле
shim.efi
. -
По мере продолжения загрузки цифровая подпись GRUB будет восстановлена с использованием открытого ключа, находящегося внутри
shim.efi
. -
Если подпись совпадает, то
grubx64.efi
и другие файлы загрузчика смогут запускаться с помощью UEFI. -
Основная задача GRUB — загрузка ядра (
/boot/vmlinuz
). -
Этот файл
vmlinuz
также может быть скомпрометирован, поэтому во избежание этого ядро будет подписано тем же закрытым ключом, который использовался для подписи GRUB. -
Цифровая подпись
vmlinuz
будет восстановлена с использованием открытого ключа, находящегося внутриshim.efi
. -
После успешной проверки цифровой подписи ядро берет на себя управление последовательностью загрузки.
-
Но ядро использует множество модулей/драйверов, которые со временем вставляются внутрь ядра. Таким образом, эти модули, которые снова являются двоичными файлами, могут быть скомпрометированы, и поскольку они станут частью ядра/
vmlinuz
, то в конечном итоге само ядро будет скомпрометировано. -
Итак, ядро как пакет подготовит свою пару ключей. Все модули будут подписаны закрытым ключом этого ядра, а открытый ключ будет поставляться вместе с самим пакетом ядра. Закрытый ключ пакета ядра будет позже уничтожен.
-
Во время загрузки, при вставке модулей в ядро, цифровая подпись модуля будет восстановлена с использованием открытого ключа, который находится в ядре.
-
Благодаря упомянутым шагам функция безопасной загрузки гарантирует, что выполняются только двоичные файлы от доверенных сторон.
Блок-схемы, показанные на рисунке 3-27, еще больше упростят процедуру загрузки.
Рисунок 3-27. Процедура Secure Boot
Проект мультизагрузки 100 ОС
Один из моих студентов задал мне вопрос: сколько операционных систем мы можем установить на одну систему и создать для них мультизагрузку с одним загрузчиком? Я не знал ответа, но решил попытаться узнать. Я решил, что буду использовать загрузчик GRUB 2 для загрузки каждой установленной мной операционной системы. Я занимаюсь установкой и мультизагрузкой операционных систем уже почти два года. На данный момент я установил 106 операционных систем. Это наша третья система, которую я назвал Jarvis. Ниже приведены сведения об аппаратном и программном обеспечении прошивки Jarvis:
Прошивка UEFI.
Подключены два диска (sda и sdb).
Метод загрузки — UEFI.
sda отформатирован с использованием таблицы разделов GPT.
sdb отформатирован с использованием таблицы разделов MS-DOS.
Все операционные системы идентифицируются и загружаются загрузчиком GRUB 2.
Операционные системы, установленные на диске sda, были установлены путем установки метода загрузки UEFI, и на нем есть все новые операционные системы. Операционные системы, находящиеся на sdb, устанавливались путем установки устаревшего метода загрузки прошивки. sdb содержит большинство операционных систем старшего поколения или, по крайней мере, те операционные системы, которые не имеют поддержки UEFI. Вот подробности:
Раздел | Операционная система | Файловая система | Размер |
---|---|---|---|
sda-1 | ESP (EFI System Partition) | FAT32 | 20 GB |
sda-2 | MSR (Microsoft Recovery) | MSR | 16 MB |
sda-3 | Windows 10 | NTFS | 9.7 GB |
sda-4 | Swap | Swap | 2.01 GB |
sda-5 | openSUSE Linux 13.2 | EXT4 | 10 GB |
sda-6 | Mint Linux 17.2 | EXT4 | 10 GB |
sda-7 | Oracle OpenSolaris 11.2 | ZFS | 10 GB |
sda-8 | Sabayon Linux 15.06 | EXT4 | 10 GB |
sda-9 | Some random free space | N/A | 8.4 MB |
sda-10 | Kali Linux 2.0 | EXT4 | 10 GB |
sda-11 | Arch Linux 2015-8.1 | EXT4 | 10 GB |
sda-12 | Debian Linux 8.1 | EXT4 | 10 GB |
sda-13 | Semplice Linux 7.0.1 | EXT4 | 10 GB |
sda-14 | Slackware 14.1 Linux | EXT4 | 10 GB |
sda-15 | Openmandriva 2014.2 | EXT4 | 10 GB |
sda-16 | Mate Ubuntu Linux15.04 | EXT4 | 10 GB |
sda-17 | Steam OS beta | EXT4 | 10 GB |
sda-18 | Manjaro Linux 0.8.13.1 | EXT4 | 10 GB |
sda-19 | Netrunner Linux 16 | EXT4 | 10 GB |
sda-20 | Windows 8 | NTFS | 10 GB |
sda-21 | Korora Linux 22 | EXT4 | 10 GB |
sda-22 | KaOS Linux 2015.08 | EXT4 | 10 GB |
sda-23 | Lubuntu Linux 15.04 | EXT4 | 10 GB |
sda-24 | Sonar Linux 2015.2 | EXT4 | 10 GB |
sda-25 | Antergos Linux 2015.08.18 | EXT4 | 10 GB |
sda-26 | Mythbuntu Linux 14.04.2 | EXT4 | 10 GB |
sda-27 | Rosa Linux fresh R5 | EXT4 | 10 GB |
sda-28 | SparkyLinux 4.0 | EXT4 | 10 GB |
sda-29 | Vinux Linux 4.0 | EXT4 | 10 GB |
sda-30 | Xubuntu Linux 14.04.3 | EXT4 | 10 GB |
sda-31 | Ubuntu Studio 14.04.3 | EXT4 | 10 GB |
sda-32 | Suse Enterprise 12 | EXT4 | 10 GB |
sda-33 | Ubuntu Linux 14.04 | EXT4 | 10 GB |
sda-34 | Ubuntu Linux 15.04 | EXT4 | 10 GB |
sda-35 | Scientific Linux 7 | EXT4 | 10 GB |
sda-36 | CentOS Linux 7 | EXT4 | 10 GB |
sda-37 | Solus Linux Daily | EXT4 | 10 GB |
sda-38 | Ubuntu Server 14 Linux | EXT4 | 10 GB |
sda-39 | Fedora 21 Linux | EXT4 | 10 GB |
sda-40 | Fedora 22 Linux | EXT4 | 10 GB |
sda-41 | BlackArch 2015.07.31 | EXT4 | 10 GB |
sda-42 | Gentoo Linux multilib 20140826 | EXT4 | 10 GB |
sda-43 | Calculate Linux 14.16.2 | EXT4 | 10 GB |
sda-44 | Fedora 20 Linux | EXT4 | 10 GB |
sda-45 | Fedora 23 Linux | EXT4 | 10 GB |
sda-46 | Manjaro Linux 15-0.9 | EXT4 | 10 GB |
sda-47 | Ubuntu Linux 16.04 | EXT4 | 10 GB |
sda-48 | chapeau Linux 23 | EXT4 | 10 GB |
sda-49 | Arquetype Linux 22 | EXT4 | 10 GB |
sda-50 | Fx64 Linux 22 | EXT4 | 10 GB |
sda-51 | Viperr Linux 7 | EXT4 | 10 GB |
sda-52 | Hanthana Linux 21 | EXT4 | 10 GB |
sda-53 | Qubes R3.1 Linux | EXT4 | 10 GB |
sda-54 | Fedora 24 | EXT4 | 10 GB |
sda-55 | Korora-23 | EXT4 | 10 GB |
sda-56 | sabayon-16 | EXT4 | 10 GB |
sda-57 | Korora-24 | EXT4 | 10 GB |
sda-58 | Sonar 16 Linux | EXT4 | 10 GB |
sda-59 | Viper 9 Linux | EXT4 | 10 GB |
sda-60 | Arquetype Linux 23 | EXT4 | 10 GB |
sda-61 | Manjaro Linux 16 | EXT4 | 10 GB |
sda-62 | Manjaro Linux Gaming 16 | EXT4 | 10 GB |
sda-63 | Calculate Linux 15 | EXT4 | 10 GB |
Таким образом, общее количество установок ОС UEFI на диске sda равно 59, поскольку четыре раздела зарезервированы для вещей, подобных ESP и MSR. Ниже приведены сведения об установке диска sdb:
Раздел | Операционная система | Файловая система | Размер |
---|---|---|---|
sdb-1 | PCBSD 10.1.2 | ZFS | 10 GB |
sdb-2 | Magia 2 Linux | EXT4 | 10 GB |
sdb-3 | Magia 3 Linux | EXT4 | 10 GB |
sdb-4 | Extended/secondary | N/A | 970 GB примерно |
sdb-5 | Q4OS Linux 1.2.8 | EXT4 | 10 GB |
sdb-6 | Qubes R2 Linux | EXT4 | 10 GB |
sdb-7 | Pardus Linux 2013 | EXT4 | 10 GB |
sdb-8 | GoboLinux 015 | EXT4 | 10 GB |
sdb-9 | Crux Linux 3.1 | EXT4 | 10 GB |
sdb-10 | Point Linux 3.0 | EXT4 | 10 GB |
sdb-11 | Extix Linux 15.3 | EXT4 | 10 GB |
sdb-12 | Bodhi Linux 3.0 | EXT4 | 10 GB |
sdb-13 | Debian Linux 7.0 | EXT4 | 10 GB |
sdb-14 | Debian Linux 6.0 | EXT4 | 10 GB |
sdb-15 | BOSS Linux 6.1 | EXT4 | 10 GB |
sdb-16 | CrunchBang rc1 Linux | EXT4 | 10 GB |
sdb-17 | Handy Linux 2.1 | EXT4 | 10 GB |
sdb-18 | Lite Linux 2.4 | EXT4 | 10 GB |
sdb-19 | WattOS Linux R9 | EXT4 | 10 GB |
sdb-20 | PinGuy OS 14.04.3 Linux | EXT4 | 10 GB |
sdb-21 | SuperX 3.0 Linux | EXT4 | 10 GB |
sdb-22 | JuLinux 10X Rev 3.1 Linux | EXT4 | 10 GB |
sdb-23 | Black Lab Linux 2015.7 | EXT4 | 10 GB |
sdb-24 | Hamara Linux 1.0.3 | EXT4 | 10 GB |
sdb-25 | Peppermint LInux 20150518 | EXT4 | 10 GB |
sdb-26 | Ubuntu 13.10 Linux | EXT4 | 10 GB |
sdb-27 | LinuxMint 13 mate | EXT4 | 10 GB |
sdb-28 | Linux Mint 14.1 cinnamon | EXT4 | 10 GB |
sdb-29 | LinuxMint 15 xfce | EXT4 | 10 GB |
sdb-30 | LinuxMint 16 KDE | EXT4 | 10 GB |
sdb-31 | Peppermint 4 20131113 | EXT4 | 10 GB |
sdb-32 | Peppermint 5 20140623 | EXT4 | 10 GB |
sdb-33 | Fedora 12 | EXT4 | 10 GB |
sdb-34 | Trisquel 7 Linux | EXT4 | 10 GB |
sdb-35 | Oracle Linux 7.1 | EXT4 | 10 GB |
sdb-36 | Fedora 14 Linux | EXT4 | 10 GB |
sdb-37 | Fedora 15 Linux | EXT4 | 10 GB |
sdb-38 | Fedora 17 Linux | EXT4 | 10 GB |
sdb-39 | Fedora 19 Linux | EXT4 | 10 GB |
sdb-40 | RHEL 6.5 Linux | EXT4 | 10 GB |
sdb-41 | SolydX 201506 | EXT4 | 10 GB |
sdb-42 | Oracle Linux 6.7 | EXT4 | 10 GB |
sdb-43 | OpenSuse 11.3 | EXT4 | 10 GB |
sdb-44 | LMDE (Linux Mint 2 Debian edition) | EXT4 | 10 GB |
sdb-45 | Centrych Linux 12.04 | EXT4 | 10 GB |
sdb-46 | Elementary OS 2013 | EXT4 | 10 GB |
sdb-47 | Elementary OS 2015 | EXT4 | 10 GB |
sdb-48 | Sabayon 13.08 Linux | EXT4 | 10 GB |
sdb-49 | Deepin 2013 Linux | EXT4 | 10 GB |
sdb-50 | Deepin 15.1 Linux | EXT4 | 10 GB |
Общее количество операционных систем, загружающих способ BIOS на sdb-дисках, составляет 50 – 2 = 48.
Два раздела зарезервированы для подкачки и в качестве расширенного раздела.
Итак, общее количество установок в системе Jarvis равно 106, и, как вы можете видеть на рисунке 3-28, все эти ОС являются мультизагрузочными с использованием загрузчика GRUB 2. Благодаря этому проекту я понял, что этому нет конца. Комбинация GRUB 2 и UEFI может работать с большим количеством операционных систем.
Рисунок 3-28. 106 операционных систем, перечисленных в GRUB 2
Как мне удалось установить столько операционных систем? Это просто. Я запускал команду grub-mkconfig
после каждой установки новой ОС, которая находила все операционные системы со всех подключенных дисков.
# time grub-mkconfig -o multiboot_grub.cfg
Предыдущая команда используется после установки Ubuntu 18, которая была 106-й ОС в списке.
Как вы можете видеть на рисунке 3-29, когда я установил 106-ю ОС, выполнение grub-mkconfig
заняло почти час, а полученный файл конфигурации GRUB содержал 5500 строк.
Рисунок 3-29. Время, затраченное командой grub-mkconfig
Фиктивный маленький загрузчик
Мы знаем, что BIOS переходит к первым 512 байтам и вызывает загрузчик GRUB 2. Чтобы понять, как именно BIOS вызывает загрузчик, сделаем свой загрузчик. Наш загрузчик будет очень маленьким по сравнению с GRUB 2. Наш загрузчик будет просто печатать !
на экране. Но на этом примере вы сможете понять, как BIOS переходит к загрузчикам, как в GRUB 2, как показано здесь:
# cat boot.nasm
;
; Note: this example is written in Intel Assembly syntax
;
[BITS 16]
[ORG 0x7c00]
boot:
mov al, '!' ; Character for interrupt
mov ah, 0x0e ; Display character
mov bh, 0x00 ; Set video mode
mov bl, 0x07 ; Clear/Scroll screen down
int 0x10 ;- BIOS interrupt 10 which is taking inputs
; from al, ah, bh, bl
jmp $
times 510-($-$$) db 0 ; Out of 512 bytes first 510 bytes are
; filled with 0's.
; In the real world it will be filled with
; grub's boot strap.
db 0x55 ; &
db 0xaa ; | tells BIOS that this is the device which
; is active/fdisk sign/boot flag.
# nasm -f bin boot.nasm && qemu-system-x86_64 boot
Это создаст загрузочный диск (образ диска) из файла boot.nasm
и он станет входными данными для qemu
, который выполнит его. Как вы можете видеть на рисунке 3-30, вы увидите символ !
напечатанным на экране.
Рисунок 3-30. Наш маленький загрузчик
По сути, машина qemu
рассматривает boot
как диск, и всякий раз, когда машина qemu
завершает этап BIOS, BIOS передает управление в первые 512 байт загрузочного диска. Здесь вы обнаружите, что первые 510 байт забиты нулями, а в последние 2 байта записан символ !
(загрузчик), и он будет напечатан на нашем экране.
На данный момент мы получили хороший обзор GRUB 2; Теперь, продвигаясь дальше, в следующем разделе мы обсудим, что на самом деле происходит внутри GRUB 2.
GRUB 2 на низком уровне
Во время написания этой книги последним доступным исходным кодом GRUB был GRUB 2.04, который я здесь использовал. Бинарный файл загрузчика bootstrap (если система основана на BIOS) в первых 440 байтах из 512 называется boot.img
и доступен по адресу /usr/lib/grub/i386-pc/boot.img
.
# ls -lh /usr/lib/grub/i386-pc/boot.img
-rw-r--r--. 1 root root 512 Mar 28 2019 /usr/lib/grub/i386-pc/boot.img
# file /usr/lib/grub/i386-pc/boot.img
/usr/lib/grub/i386-pc/boot.img: DOS/MBR boot sector
Файл boot.img
создается из исходного кода, записанного в файле /GRUB 2.04/grub-core/boot/i386/pc/boot.S
.
Ниже приводится его фрагмент:
/* -*-Asm-*- */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1999,2000,2001,2002,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/symbol.h>
#include <grub/machine/boot.h>
/*
* defines for the code go here
*/
/* Print message string */
#define MSG(x) movw $x, %si; call LOCAL(message)
#define ERR(x) movw $x, %si; jmp LOCAL(error_message)
.macro floppy
part_start:
LOCAL(probe_values):
.byte 36, 18, 15, 9, 0
LOCAL(floppy_probe):
pushw %dx
/*
* Perform floppy probe.
*/
#ifdef __APPLE__
LOCAL(probe_values_minus_one) = LOCAL(probe_values) — 1
movw MACRO_DOLLAR(LOCAL(probe_values_minus_one)), %si
#else
movw MACRO_DOLLAR(LOCAL(probe_values)) — 1, %si
#endif
LOCAL(probe_loop):
/* reset floppy controller INT 13h AH=0 */
xorw %ax, %ax
int MACRO_DOLLAR(0x13)
Вы можете рассматривать boot.img
как первый этап загрузчика или часть-1 GRUB. Этот файл boot.img
передает управление diskboot.img
, который является частью-2 GRUB.
# ls -lh /usr/lib/grub/i386-pc/diskboot.img
-rw-r--r--. 1 root root 512 Mar 28 2019 /usr/lib/grub/i386-pc/diskboot.img
# file /usr/lib/grub/i386-pc/diskboot.img
/usr/lib/grub/i386-pc/diskboot.img: data
Файл diskboot.img
создан на основе исходного кода grub-2.04/grub-core/boot/i386/pc/diskboot.S
. Ниже приводится его фрагмент:
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1999,2000,2001,2002,2006,2007,2009,2010 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/symbol.h>
#include <grub/machine/boot.h>
/*
* defines for the code go here
*/
#define MSG(x) movw $x, %si; call LOCAL(message)
.file "diskboot.S"
.text
/* Tell GAS to generate 16-bit instructions so that this code works
in real mode. */
.code16
.globl start, _start
start:
_start:
/*
* _start is loaded at 0x8000 and is jumped to with
* CS:IP 0:0x8000 in kernel.
*/
Затем файл diskboot.img
загружает фактическую основную часть GRUB 2, которая является частью-3 GRUB. Вы также можете считать, что часть-3 GRUB — это ядро загрузчика. На этом этапе GRUB 2 сможет читать файловую систему.
# ls /boot/grub2/i386-pc/core.img -lh
-rw-r--r--. 1 root root 30K Dec 9 10:18 /boot/grub2/i386-pc/core.img
Из /GRUB 2.00/grub-core/kern/main.c
GRUB 2 устанавливает имя корневого устройства, читает grub.cfg
и в конце показывает список операционных систем для выбора.
Надеюсь, вы теперь понимаете, как работает GRUB 2. Ниже приводится краткое изложение того, что мы обсудили до сих пор:
Загрузчик — это первый код, который запускается после прошивки.
Загрузчик/GRUB копирует ядро в память.
Загрузчик загружает образ initramfs в память и передает ядру указатель на него.
Загрузчик передает управление ядру.
Глава 4
Ядро
В этой главе будет рассмотрено ядро.
Загрузка ядра в память
Это интересная глава. До сих пор мы видели, что до этого этапа GRUB 2 полностью контролировал процедуру загрузки. Теперь ему нужно передать управление ядру. В этой главе мы увидим, как и где загрузчик загружает ядро. Другими словами, как извлекается ядро. Затем мы увидим задачи, связанные с загрузкой, выполняемые ядром Linux, и, в конце, то, как ядро запускает systemd.
Примечание
Исходный код ядра, используемого в этой главе, — версии kernel-5.4.4
. Когда я писал эту книгу, это был последний доступный стабильный код; см. https://www.kernel.org/. Отличным ресурсом по этой теме является книга Inside Linux, написанная 0xAX. Я многому научился благодаря ей, и я уверен, что вы тоже научитесь. Вы можете найти книгу по адресу https://0xax.gitbooks.io/linux-inside/. (Эта книга в русском переводе: https://github.com/proninyaroslav/linux-insides-ru. Прим. пер.)
Чтобы передать управление ядру, загрузчик должен выполнить две основные задачи.
Загрузить ядро в память.
Установить некоторые поля ядра в соответствии с протоколом загрузки.
Полный протокол загрузки доступен по адресу https://www.kernel.org/doc/Documentation/x86/boot.txt. Исходный протокол загрузки был определен не кем иным, как Линусом Торвальдсом.
~ ~
| Protected-mode kernel |
100000 +-------------------------+
| I/O memory hole |
0A0000 +-------------------------+
| Reserved for BIOS | Leave as much as possible unused
~ ~
| Command line | (Can also be below the X+10000
| | mark)
X+10000 +-------------------------+
| Stack/heap | For use by the kernel real-mode
| | code.
X+08000 +-------------------------+
| Kernel setup | The kernel real-mode code.
| Kernel boot sector | The kernel legacy boot sector.
X +-------------------------+
| Boot loader | <- Boot sector entry point
| | 0000:7C00. You will see the same
| | address location at our boot.asm
| | file which we created above.
001000 +-------------------------+
| Reserved for MBR/BIOS |
000800 +-------------------------+
| Typically used by MBR |
000600 +-------------------------+
| BIOS use only |
000000 +-------------------------+
(Сейчас там лежит немного измененная версия. Прим. пер.)
~ ~
| Protected-mode kernel |
100000 +------------------------+
| I/O memory hole |
0A0000 +------------------------+
| Reserved for BIOS | Leave as much as possible unused
~ ~
| Command line | (Can also be below the X+10000 mark)
X+10000 +------------------------+
| Stack/heap | For use by the kernel real-mode code.
X+08000 +------------------------+
| Kernel setup | The kernel real-mode code.
| Kernel boot sector | The kernel legacy boot sector.
X +------------------------+
| Boot loader | <- Boot sector entry point 0000:7C00
001000 +------------------------+
| Reserved for MBR/BIOS |
000800 +------------------------+
| Typically used by MBR |
000600 +------------------------+
| BIOS use only |
000000 +------------------------+
Согласно протоколу загрузки, загрузчик обязан передать или установить некоторые поля заголовка ядра. Поля следующие: имя корневого устройства, параметры монтирования, такие как ro
или rw
, имя initramfs, размер initramfs и т. д. Эти же поля называются параметрами командной строки ядра, и мы уже знаем, что параметры командной строки ядра передаются GRUB'ом/загрузчиком ядру.
GRUB не загружает ядро (/boot/vmlinuz
) в любое произвольное место; оно всегда загружается в специальное место. Это специальное место будет зависеть от используемого дистрибутива и версии Linux, а также от архитектуры процессора системы. vmlinuz
— это архивный файл, архив состоит из трёх частей.
Vmlinuz (bZimage) =
Header + kernel setup code + vmlinux (actual compressed kernel)
(part-1) (part-2) (part-3)
После загрузки ядра в память
Здесь нам нужно представить, что GRUB 2 загрузил ядро в память в специальное место. Вот шаги начального уровня, выполняемые файлом архива ядра vmlinuz
, как только он загружается в память:
-
Как только загрузчик загружает ядро в память в определенном месте, запускается двоичный файл, созданный из файла
arch/x86/boot/header.S
. -
Возникает путаница в том, что
vmlinuz
является архивом, но загрузчик его еще не распаковал. Загрузчик просто загрузил ядро в определенное место. Тогда почему код, который находится внутри архиваvmlinuz
, способен выполняться? -
Сначала мы увидим краткий ответ, а подробный ответ будет обсуждаться в разделе «Что извлекает vmlinuz?» этой главы. Итак, краткий ответ: двоичного файла, созданного из файла
arch/x86/boot/header.S
, в архиве нет; скорее, это часть заголовка, выполняющего задачуkernel_setup
. Он находится вне архива.Vmlinuz (bZimage) = Header + kernel setup code + vmlinux (actual compressed kernel) --->Outside of archive<--- + -------->Inside archive<---- --->header.s file is here<---
-
Давайте пока предположим, что
vmlinuz
был извлечен, и продолжим нашу загрузку. До сих пор мы видели, что GRUB загружает ядро в память в специальное место и запускает двоичный файл, созданный изarch/x86/boot/header.S
. Этот двоичный файл отвечает за частьkernel_setup
. Файлkernel_setup
выполняет следующие задачи:- Выравнивает регистры сегментов
- Настраивает стек и BSS
В каждой главе блок-схема даст нам четкое представление о том, что мы узнали и, с точки зрения загрузки, чего мы достигли. На рисунке 4-1 показано начало блок-схемы, которую мы будем строить в этой главе по мере продвижения. Показаны действия, выполняемые кодом
kernel_setup
файлаheader.s
.Рисунок 4-1. Действия, выполняемые
kernel_setup
-
Затем код переходит к функции
main()
по адресуarch/x86/boot/main.c
. Файлmain.c
также является частью заголовка ядра, и этот заголовок находится вне фактического архива.
Vmlinuz (bZimage) =
Header + kernel setup code + vmlinux (actual compressed kernel)
--->Outside of archive<--- + -------->Inside archive<---------
--->main.c file is here<---
# vim arch/x86/boot/main.c
void main(void)
{
/* First, copy the boot header into the "zeropage" */
copy_boot_params();
/* Initialize the early-boot console */
console_init();
if (cmdline_find_option_bool("debug"))
puts("early console in setup code\n");
/* End of heap check */
init_heap();
/* Make sure we have all the proper CPU support */
if (validate_cpu()) {
puts("Unable to boot — please use a kernel appropriate "
"for your CPU.\n");
die();
}
/* Tell the BIOS what CPU mode we intend to run in. */
set_bios_mode();
/* Detect memory layout */
detect_memory();
/* Set keyboard repeat rate (why?) and query the lock flags */
keyboard_init();
/* Query Intel SpeedStep (IST) information */
query_ist();
/* Query APM information */
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
query_apm_bios();
#endif
/* Query EDD information */
#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
query_edd();
#endif
/* Set the video mode */
set_video();
/* Do the last things and invoke protected mode */
go_to_protected_mode();
}
Как видите, исходный код main.c
отвечает за следующее:
-
Он копирует параметры загрузки (параметры командной строки ядра) из загрузчика. Функция
copy_boot_params
будет использоваться для копирования следующих параметров загрузки, передаваемых загрузчиком:debug, earlyprintk, ro, root, ramdisk_image, ramdisk_size etc.
-
Он инициализирует консоль и проверяет, был ли передан пользователем параметр командной строки ядра, подобный
debug
. Если да, то ядро покажет на экране подробные сообщения. -
Он инициализирует кучу.
-
Если CPU не может быть проверен, он выдает сообщение об ошибке через функцию
validate_cpu()
. В таких дистрибутивах, как Fedora и Ubuntu, сообщение об ошибке меняется от'unable to boot — please use the kernel appropriate for your cpu'
до чего-то вроде'The CPU is not supported'
. Настройка также приведет к панике ядра, и загрузка будет остановлена. -
Затем он определяет расположение памяти и печатает его на экране на ранней стадии загрузки. Те же сообщения о структуре памяти можно увидеть после загрузки с помощью команды
dmesg
, как показано здесь:[0.000000] BIOS-provided physical RAM map: [0.000000] BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usable [0.000000] BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserved [0.000000] BIOS-e820: [mem 0x0000000000059000-0x000000000009cfff] usable [0.000000] BIOS-e820: [mem 0x000000000009d000-0x00000000000fffff] reserved [0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007e5f7fff] usable [0.000000] BIOS-e820: [mem 0x000000007e5f8000-0x000000007e5f8fff] ACPI NVS [0.000000] BIOS-e820: [mem 0x000000007e5f9000-0x000000007e5f9fff] reserved [0.000000] BIOS-e820: [mem 0x000000007e5fa000-0x0000000087f62fff] usable [0.000000] BIOS-e820: [mem 0x0000000087f63000-0x000000008952bfff] reserved [0.000000] BIOS-e820: [mem 0x000000008952c000-0x0000000089599fff] ACPI NVS [0.000000] BIOS-e820: [mem 0x000000008959a000-0x00000000895fefff] ACPI data [0.000000] BIOS-e820: [mem 0x00000000895ff000-0x00000000895fffff] usable [0.000000] BIOS-e820: [mem 0x0000000089600000-0x000000008f7fffff] reserved [0.000000] BIOS-e820: [mem 0x00000000f0000000-0x00000000f7ffffff] reserved [0.000000] BIOS-e820: [mem 0x00000000fe010000-0x00000000fe010fff] reserved [0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000086e7fffff] usable
-
Инициализирует клавиатуру и ее раскладку.
-
Устанавливает основной режим видео.
-
Переходит в защищенный режим через функцию
go_to_protected_mode()
. Пожалуйста, обратитесь к рисунку 4-2 для лучшего понимания.
Рисунок 4-2. Блок-схема
Защищенный режим
До этого момента мы работали в реальном режиме, который имеет 20-битные ограничения по адресу, из-за чего мы можем получить доступ к 1 МБ памяти. С помощью функции go_to_protected_mode()
ядро переключило CPU из реального режима в защищенный. Защищенный режим имеет ограничение по 32-битному адресу, поэтому процессор может получить доступ к 4 ГБ памяти. Говоря простым языком, в реальном режиме будут запускаться только те программы, которые имеют 16-битный набор инструкций, например BIOS. В защищенном режиме будут работать только 32-битные программы. Ядро выполняет некоторые задачи, связанные с оборудованием, в защищенном режиме, а затем запускает CPU в длинном режиме.
Обратите внимание, что эта книга соответствует архитектуре Intel x86, а обсуждения реального, защищенного и длинного режима основаны на 64-битной архитектуре Intel.
Длинный режим
Длинный режим не накладывает никаких ограничений на память CPU. Он может использовать всю установленную память. Перевод процессора в длинный режим будет достигнут с помощью файла head_64.S
из arch/x86/boot/compressed/head_64.S
. Он отвечает за следующее:
-
Подготовка к длинному режиму означает проверку поддержки длинного режима.
-
Вход в длинный режим.
-
Распаковка ядра.
Ниже приведены функции, которые вызываются из файла ассемблера head_64.S
:
$ cat arch/x86/boot/compressed/head_64.S | grep -i call
call 1f
call verify_cpu
call get_sev_encryption_bit
call 1f
call 1f
call .Ladjust_got
* this function call.
call paging_prepare
* this function call.
call cleanup_trampoline
call 1f
call .Ladjust_got
call 1f
* Relocate efi_config->call().
call make_boot_params
call 1f
* Relocate efi_config->call().
call efi_main
call extract_kernel /* returns kernel location in %rax */
.quad efi_call
Функция | Что делает |
---|---|
verify_cpu |
Обеспечивает наличие у процессора длинного режима. |
make_boot_params |
Обеспечивает параметры времени загрузки, передаваемые загрузчиком. |
efi_main |
Материалы, связанные с прошивкой UEFI. |
extract_kernel |
Функция определена в файле arch/x86/boot/compressed/misc.c . Это функция, которая распаковывает vmlinux из vmlinuz . |
Для лучшего понимания обратитесь к блок-схеме, показанной на рисунке 4-3.
Рисунок 4-3. Блок-схема, обновленная
Подождите: если ядро еще не распаковано, то как нам действовать на этом этапе? Вот и длинный ответ.
Кто извлекает vmlinuz?
Пока мы понимаем, что именно GRUB загружает ядро в память, но при этом заметили, что образ vmlinuz
— это архив. Итак, кто же извлекает этот образ? GRUB?
Нет, это не GRUB. Скорее, ядро извлекает само себя. Да, я сказал, что ядро извлекает ядро. vmlinuz
может быть единственным в мире файлом операционной системы, который извлекается сам. Но как можно распаковаться самому? Чтобы понять это, давайте сначала узнаем больше о vmlinuz
.
«VM» в vmlinuz
означает «виртуальная память» (virtual memory). На более ранних этапах разработки Linux концепция виртуальной памяти еще не была разработана, поэтому при ее добавлении к имени ядра Linux добавлялись символы «vm». «z» означает заархивированный файл.
$ file vmlinuz-5.0.9-301.fc30.x86_64
vmlinuz-5.0.9-301.fc30.x86_64: Linux kernel x86 boot executable bzImage, version 5.0.9-301.fc30.x86_64 (mockbuild@bkernel04.phx2.fedoraproject.org) #1 SMP Tue Apr 23 23:57:35 U, RO-rootFS, swap_dev 0x8, Normal VGA
Как видите, vmlinuz
— это bzImage
(bzImage
расшифровывается как «big zimage»). vmlinuz
— это сжатый файл двоичного файла vmlinux
реального ядра. Вы не можете распаковать этот файл с помощью gunzip/bunzip
или даже с помощью tar
. Самый простой способ извлечь vmlinuz
и получить файл vmlinux
— использовать файл сценария extract-vmlinux
, предоставляемый пакетом kernel-devel
(в случае Fedora). Файл будет находиться по адресу /usr/src/kernels/<kernel_version>/scripts/extract-vmlinux
.
# ./extract-vmlinux /boot/vmlinuz-5.3.7-301.fc31.x86_64 >> /boot/temp/vmlinux
# file /boot/temp/*
/boot/temp/vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=ec96b29d8e4079950644230c0b7868942bb70366, stripped
Существуют различные способы открытия файлов ядра vmlinux
и vmlinuz
.
$ xxd vmlinux | less
$ objdump vmlinux | less
$ objdump vmlinux -D | less
$ hexdump vmlinux | less
$ od vmlinux | less
Мы будем использовать команду od
с некоторыми переключателями, чтобы открыть файл vmlinuz
.
$ od -A d -t x1 vmlinuz-5.0.9-301.fc30.x86_64 | less
0000000 4d 5a ea 07 00 c0 07 8c c8 8e d8 8e c0 8e d0 31
0000016 e4 fb fc be 40 00 ac 20 c0 74 09 b4 0e bb 07 00
0000032 cd 10 eb f2 31 c0 cd 16 cd 19 ea f0 ff 00 f0 00
0000048 00 00 00 00 00 00 00 00 00 00 00 00 82 00 00 00
0000064 55 73 65 20 61 20 62 6f 6f 74 20 6c 6f 61 64 65
0000080 72 2e 0d 0a 0a 52 65 6d 6f 76 65 20 64 69 73 6b
0000096 20 61 6e 64 20 70 72 65 73 73 20 61 6e 79 20 6b
0000112 65 79 20 74 6f 20 72 65 62 6f 6f 74 2e 2e 2e 0d
0000128 0a 00 50 45 00 00 64 86 04 00 00 00 00 00 00 00
0000144 00 00 01 00 00 00 a0 00 06 02 0b 02 02 14 80 37
0000160 8e 00 00 00 00 00 80 86 26 02 f0 48 00 00 00 02
0000176 00 00 00 00 00 00 00 00 00 00 20 00 00 00 20 00
0000192 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000208 00 00 00 c0 b4 02 00 02 00 00 00 00 00 00 0a 00
0000224 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000256 00 00 00 00 00 00 06 00 00 00 00 00 00 00 00 00
0000272 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000288 00 00 00 00 00 00 00 00 00 00 80 39 8e 00 48 09
0000304 00 00 00 00 00 00 00 00 00 00 2e 73 65 74 75 70
0000320 00 00 e0 43 00 00 00 02 00 00 e0 43 00 00 00 02
0000336 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00
0000352 50 60 2e 72 65 6c 6f 63 00 00 20 00 00 00 e0 45
0000368 00 00 20 00 00 00 e0 45 00 00 00 00 00 00 00 00
0000384 00 00 00 00 00 00 40 00 10 42 2e 74 65 78 74 00
0000400 00 00 80 f3 8d 00 00 46 00 00 80 f3 8d 00 00 46
0000416 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00
0000432 50 60 2e 62 73 73 00 00 00 00 80 86 26 02 80 39
0000448 8e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000464 00 00 00 00 00 00 80 00 00 c8 00 00 00 00 00 00
0000480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff
0000496 ff 22 01 00 38 df 08 00 00 00 ff ff 00 00 55 aa
0000512 eb 66 48 64 72 53 0d 02 00 00 00 00 00 10 c0 37
0000528 00 01 00 80 00 00 10 00 00 00 00 00 00 00 00 00
0000544 00 00 00 00 50 5a 00 00 00 00 00 00 ff ff ff 7f
0000560 00 00 00 01 01 15 3f 00 ff 07 00 00 00 00 00 00
0000576 00 00 00 00 00 00 00 00 b1 03 00 00 11 f3 89 00
0000592 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00
0000608 00 c0 b4 02 90 01 00 00 8c d8 8e c0 fc 8c d2 39
0000624 c2 89 e2 74 16 ba 50 58 f6 06 11 02 80 74 04 8b
# od -A d -t x1 /boot/vmlinuz-5.3.7-301.fc31.x86_64 | grep -i '1f 8b 08 00'
0018864 8f 1f 8b 08 00 00 00 00 00 02 03 ec fd 79 7c 54
Итак, с адреса 0018864
запускается фактическое ядро (vmlinux
), тогда как файл vmlinuz
начинается с 0000000
. Это означает, что от 0000000
до 0018864
у нас есть заголовок файла, например header.S
, misc.c
и т. д. Это позволит извлечь фактическое ядро (vmlinux
) из vmlinuz
. Вы можете рассматривать заголовок как header двоичного файла vmlinux
, и когда этот header доступен, он становится vmlinuz. В следующих разделах мы увидим, как процедура ядра извлекает vmlinuz
.
extract_kernel
Давайте вернемся к функции extract_kernel
из arch/x86/boot/compressed/misc.c
.
asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
unsigned char *input_data,
unsigned long input_len,
unsigned char *output,
unsigned long output_len)
Как видите, функция будет принимать семь аргументов.
Аргумент | Цель |
---|---|
rmode |
Указатель на структуру boot_params , заполняемую загрузчиком. |
heap |
Указатель на файл boot_heap , который представляет начальный адрес кучи ранней загрузки. |
input_data |
Указатель на начало сжатого ядра или другими словами указатель на arch/x86/boot/compressed/vmlinux.bin.bz2 |
input_len |
Размер сжатого ядра |
output |
Начальный адрес будущего распакованного ядра. |
output_len |
Размер распакованного ядра |
run_size |
Объем места, необходимого для запуска ядра, включая разделы .bss и .brk . |
Вместе с ядром загрузчик также загрузит в память initramfs. Мы поговорим об initramfs в главе 5. Итак, перед извлечением образа ядра заголовок или процедура ядра должны позаботиться о том, чтобы извлечение vmlinuz не перезаписало и не перекрыло уже загруженный образ initramfs. Таким образом, функция extract_kernel также позаботится о вычислении адресного пространства initramfs и соответствующим образом скорректирует распаковку образа ядра. Как только мы получим правильный адрес, по которому заголовок может распаковать vmlinuz, он извлечет туда ядро.
asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
unsigned char *input_data,
unsigned long input_len,
unsigned char *output,
unsigned long output_len)
{
const unsigned long kernel_total_size = VO__end — VO__text;
unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
unsigned long needed_size;
/* Retain x86 boot parameters pointer passed from startup_32/64. */
boot_params = rmode;
/* Clear flags intended for solely in-kernel use. */
boot_params->hdr.loadflags &= ~KASLR_FLAG;
sanitize_boot_params(boot_params);
if (boot_params->screen_info.orig_video_mode == 7) {
vidmem = (char *) 0xb0000;
vidport = 0x3b4;
} else {
vidmem = (char *) 0xb8000;
vidport = 0x3d4;
}
lines = boot_params->screen_info.orig_video_lines;
cols = boot_params->screen_info.orig_video_cols;
console_init();
/*
* Save RSDP address for later use. Have this after console_init()
* so that early debugging output from the RSDP parsing code can be
* collected.
*/
boot_params->acpi_rsdp_addr = get_rsdp_addr();
debug_putstr("early console in extract_kernel\n");
free_mem_ptr = heap; /* Heap */
free_mem_end_ptr = heap + BOOT_HEAP_SIZE;
/*
* The memory hole needed for the kernel is the larger of either
* the entire decompressed kernel plus relocation table, or the
* entire decompressed kernel plus .bss and .brk sections.
*
* On X86_64, the memory is mapped with PMD pages. Round the
* size up so that the full extent of PMD pages mapped is
* included in the check against the valid memory table
* entries. This ensures the full mapped area is usable RAM
* and doesnt include any reserved areas.
*/
needed_size = max(output_len, kernel_total_size);
#ifdef CONFIG_X86_64
needed_size = ALIGN(needed_size, MIN_KERNEL_ALIGN);
#endif
/* Report initial kernel position details. */
debug_putaddr(input_data);
debug_putaddr(input_len);
debug_putaddr(output);
debug_putaddr(output_len);
debug_putaddr(kernel_total_size);
debug_putaddr(needed_size);
#ifdef CONFIG_X86_64
/* Report address of 32-bit trampoline */
debug_putaddr(trampoline_32bit);
#endif
choose_random_location((unsigned long)input_data, input_len,
(unsigned long *)&output,
needed_size,
&virt_addr);
/* Validate memory location choices. */
if ((unsigned long)output & (MIN_KERNEL_ALIGN — 1))
error("Destination physical address inappropriately aligned");
if (virt_addr & (MIN_KERNEL_ALIGN — 1))
error("Destination virtual address inappropriately aligned");
#ifdef CONFIG_X86_64
if (heap > 0x3fffffffffffUL)
error("Destination address too large");
if (virt_addr + max(output_len, kernel_total_size) > KERNEL_IMAGE_SIZE)
error("Destination virtual address is beyond the kernel mapping area");
#else
if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))
error("Destination address too large");
#endif
#ifndef CONFIG_RELOCATABLE
if ((unsigned long)output != LOAD_PHYSICAL_ADDR)
error("Destination address does not match LOAD_PHYSICAL_ADDR");
if (virt_addr != LOAD_PHYSICAL_ADDR)
error("Destination virtual address changed when not relocatable");
#endif
debug_putstr("\nDecompressing Linux... ");
__decompress(input_data, input_len, NULL, NULL, output, output_len,
NULL, error);
parse_elf(output);
handle_relocations(output, output_len, virt_addr);
debug_putstr("done.\nBooting the kernel.\n");
return output;
}
Метод распаковки будет выбран в соответствии с алгоритмом сжатия, использованным во время компиляции ядра. Методы распаковки можно увидеть в том же файле misc.c
.
#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif
#ifdef CONFIG_KERNEL_BZIP2
#include "../../../../lib/decompress_bunzip2.c"
#endif
#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif
#ifdef CONFIG_KERNEL_XZ
#include "../../../../lib/decompress_unxz.c"
#endif
#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif
Как только ядро будет распаковано в память, точка входа извлеченного ядра будет получена из функции extract_kernel
, и CPU перейдет внутрь ядра.
Внутри ядра
Ядро выполняет множество функций, но я перечислю то, что представляет наибольший интерес для изучения загрузки.
-
Ядро установит размер стека ядра равным 16 КБ, если архитектура 64-битная. Это означает, что каждый новый процесс получит свой собственный стек ядра размером 16 КБ.
-
Для параметра
page_size
будет установлено значение 4 КБ, что является размером страницы по умолчанию в 64-разрядной архитектуре Intel. -
Ядро подготовит механизм обработки прерываний и исключений, также называемый таблицей дескрипторов прерываний (interrupt descriptor table) (IDT).
-
Ядро установит механизм обработки ошибок страниц.
-
Ядро будет собирать сведения о файле initramfs, такие как имя файла, размер, адрес, адрес перемещения, старший и младший номера нового корневого устройства и т. д., из
/arch/x86/kernel/setup.c
. -
Затем оно извлекает initramfs из файла исходного кода
init/initramfs.c
. -
Наконец, оно запускает systemd с помощью функции
start_kernel
изinit/main.c
.
Вы заметите, что это первый раз, когда мы вышли за пределы каталога arch
. Это означает, что мы можем рассматривать этот код как независимый от архитектуры. После запуска ядро выполняет множество функций, и охватить все это в этой книге практически невозможно. Что касается загрузки, главное для ядра — запуск systemd из initramfs. Поскольку initramfs уже загружен в память загрузчиком, для извлечения содержимого initramfs требуются сведения о файле initramfs, которые ядро получит из /arch/x86/kernel/setup.c
.
Initramfs file name,
Initramfs file size,
Initramfs files address,
Initramfs files relocation address,
Major and minor numbers on which initramfs will be mounted.
Как только ядро получит сведения о файле initramfs, оно извлечет архив initramfs из файла init/initramfs.c
. Мы обсудим, как именно ядро извлекает initramfs в память в главе 5. Чтобы смонтировать initramfs в качестве корневого устройства, ему нужны виртуальные файловые системы, такие как proc
, sys
, dev
и т. д., поэтому ядро подготавливает их соответствующим образом.
err = register_filesystem(&proc_fs_type);
if (err)
return;
Позже ядро смонтирует извлеченные файлы initramfs как root с помощью функции do_mount_root
файла init/do_mounts.c
. Как только initramfs смонтируется в памяти, ядро запустит из него systemd. systemd будет запущен через ту же функцию start_kernel
, что и файл init/main.c
.
asmlinkage void __init start_kernel(void)
Как только корневая файловая система будет готова, ядро войдёт в неё и создаст два потока: процесс с PID 1 — это процесс systemd, и процесс с PID 2 — это поток ядра (kthread). Для лучшего понимания обратитесь к блок-схеме, показанной на рисунке 4-4.
Рисунок 4-4. Блок-схема, снова обновленная
На рисунке 4-5 показана полная последовательность загрузки, которую мы обсуждали до сих пор.
Рисунок 4-5. Последовательность загрузки на блок-схеме
Прежде чем мы продолжим и посмотрим, как ядро извлекает initramfs и запускает из него systemd, нам необходимо понять основы initramfs, например, зачем он нам нужен, какова его структура и т. д. Как только мы поймем важность и основы initramfs, мы продолжим нашу последовательность загрузки с ролью systemd в последовательности загрузки.
Глава 5
initramfs
В этой главе мы обсудим, почему нам действительно нужен initramfs и почему он важен в процедуре загрузки. Мы знаем, что initramfs загружается в память загрузчиком, но мы еще не обсуждали, как извлекается initramfs. Данная глава будет посвящена именно этому. Мы также увидим шаги по извлечению, перестроению и настройке initramfs. Позже мы увидим структуру initramfs, а также последовательность загрузки системы внутри initramfs.
Зачем нужен initramfs?
Цель процедуры загрузки — предоставить пользователю его собственные файлы, находящиеся в корневой файловой системе. Другими словами, обязанность ядра — найти, смонтировать и предоставить пользователю корневую файловую систему. Для достижения этой цели ядру необходимо запустить двоичный файл systemd, который снова находится в корневой файловой системе пользователя. Теперь это стало проблемой курицы и яйца. Чтобы запустить процесс systemd, сначала нам нужно смонтировать корневую файловую систему, а чтобы смонтировать корневую файловую систему, нам нужно запустить systemd из корневой файловой системы. Кроме того, помимо фактической корневой файловой системы у пользователей могут быть файлы в некоторых других файловых системах, таких как NFS, CIFS и т. д., и этот список других файловых систем также находится внутри корневой файловой системы (/etc/fstab
).
Итак, чтобы решить эту проблему курицы и яйца, разработчики придумали решение под названием initramfs (что означает «initial RAM filesystem»). initramfs — это временная корневая файловая система (внутри основной памяти), которая будет использоваться для монтирования фактической корневой файловой системы (с жесткого диска или из сети). Итак, вся цель initramfs — смонтировать корневую файловую систему пользователя с жесткого диска/сети. В идеале ядро способно самостоятельно смонтировать корневую файловую систему с диска без initramfs, но в наши дни корневая файловая система пользователя может находиться где угодно. Это может быть RAID, LVM или устройство множественного связывания (multipath device). Это может быть n типов файловых систем, таких как XFS, BTRFS, ext4, ext3, NFS и т. д. Это может быть даже зашифрованная файловая система, такая как LUKS. Таким образом, для ядра практически невозможно включить все эти сценарии в свой собственный двоичный файл vmlinux
. Позвольте мне представить в этом разделе несколько реальных сценариев.
Допустим, корневая файловая система находится в NFS и концепция initramfs отсутствует. Это означает, что ядро должно самостоятельно смонтировать корневую файловую систему пользователя из NFS. В таком случае ядру необходимо выполнить следующие задачи:
Поднять основной сетевой интерфейс.
Вызвать DHCP-клиент и получить IP-адрес от DHCP-сервера.
Найдти общий ресурс NFS и связанный с ним сервер NFS.
Подключить общий ресурс NFS (корневую файловую систему).
Для выполнения этих шагов ядро должно иметь следующие двоичные файлы: NetworkManager
, dhclient
, mount
и т. д.
Теперь предположим, что корневая файловая система находится на программном RAID-устройстве. Значит ядру необходимо выполнить следующие задачи:
-
Сначала найти RAID-диски с помощью
mdadm --examine --scan
. -
После того, как базовые диски, на которых размещен программный RAID, идентифицированы, необходимо собрать RAID с помощью
mdadm --assemble --scan
. -
Для этого в ядре должны быть двоичные файлы
mount
иmdadm
, а также некоторые файлы конфигурации программных RAID-устройств.
Теперь предположим, что корневая файловая система находится на логическом томе LVM. Тогда ядру придется самостоятельно выполнить следующие задачи:
-
Найти физические тома с помощью
pvs
. -
Найти группу томов с помощью
vgscan
, а затем активировать ее с помощьюvgchange
. -
Сканировать LVS с помощью
lvscan
. -
Наконец, как только
root lv
будет заполнен, смонтировать его как корневую файловую систему. -
Для этого в ядре должны быть двоичные файлы
pvscan
,pvs
,lvscan
,vgscan
,lvs
иvgchange
.
Допустим, корневая файловая система находится на зашифрованном блочном устройстве. Тогда ядру необходимо выполнить следующие задачи:
-
Получить пароль от пользователя и/или вставить аппаратный токен (например, смарт-карту или USB-ключ безопасности).
-
Создать цель расшифровки с помощью device mapper.
Для достижения всего этого ядру нужны двоичные файлы, связанные с LUKS.
В ядре невозможно реализовать все возможности корневой файловой системы; поэтому разработчики придумали концепцию initramfs, единственной целью которой является монтирование корневой файловой системы.
Ядро по-прежнему может выполнять все шаги, которые мы обсуждали. Например, если вы собираете простую систему Linux с командной строкой из LFS (www.linuxfromscratch.org/), вам не нужен initramfs для монтирования корневой файловой системы, поскольку ядро само по себе способно смонтировать корневую файловую систему. Но в тот момент, когда вы попытаетесь добавить в него графический интерфейс через BLFS, вам понадобится initramfs.
Итак, вывод такой: ядро может монтировать корневую файловую систему самостоятельно, но для этого ядро должно хранить все обсуждаемые бинарники, вспомогательные библиотеки, файлы конфигурации и т. д. в файле vmlinuz
. Это создаст много проблем.
-
Это испортит основную идею бинарного файла ядра.
-
Бинарный файл ядра будет огромного размера. Больший размер двоичного файла будет трудно поддерживать.
-
Огромным двоичным файлом сложно управлять, обновлять, совместно использовать и обрабатывать его на серверах (с точки зрения пакетов RPM).
-
Подход не будет соответствовать правилу KISS (keep it simple, stupid (делай проще, тупица)).
Инфраструктура
Чтобы понять структуру initramfs, нам нужно сначала разобраться с тремя различными файловыми системами.
ramfs
Для простоты понимания мы сравним ramfs с механизмом кэширования ядра. В Linux есть уникальная функция, называемая страничным кэшем (page cache). Всякий раз, когда вы выполняете какие-либо транзакции ввода-вывода, она кэширует эти транзакции на страницах. Кэширование страниц в памяти — это всегда хорошо. Это сохранит наши будущие транзакции ввода-вывода. И всякий раз, когда система сталкивается с проблемой нехватки памяти, ядро просто удаляет эти кэшированные страницы из памяти. ramfs похожа на нашу кеш-память. Но проблема с ramfs в том, что у нее нет резервного хранилища; следовательно, она не может выгружать страницы (своп тоже является устройством хранения). Итак, очевидно, что ядро не сможет освободить эту память, так как сохранять эти страницы некуда. Следовательно, ramfs будет продолжать расти, и вы не сможете реально ограничить ее размер. Что мы можем сделать, так это разрешить запись в ramfs только пользователям root, чтобы облегчить ситуацию.
tmpfs
tmpfs похож на ramfs, но с некоторыми дополнениями. Мы можем установить ограничение на размер tmpfs, чего нам не удалось сделать в ramfs. Кроме того, страницы tmpfs могут использовать пространство подкачки.
rootfs
rootfs — это tmpfs, который является экземпляром ramfs. Преимущество rootfs в том, что вы не можете его размонтировать. Это происходит по той же причине, по которой вы не можете завершить процесс systemd
.
initramfs использует ramfs в качестве файловой системы, и пространство, занимаемое initramfs в памяти, будет освобождено после монтирования корневой файловой системы пользователя.
# dmesg | grep Free
[ 0.813330] Freeing SMP alternatives memory: 36K
[ 3.675187] Freeing initrd memory: 32548K
[ 5.762702] Freeing unused decrypted memory: 2040K
[ 5.767001] Freeing unused kernel image memory: 2272K
[ 5.776841] Freeing unused kernel image memory: 2016K
[ 5.783116] Freeing unused kernel image memory: 1580K
Раньше вместо initramfs в Linux использовался initrd
(initial RAM disk), но сейчас initrd
устарел, и поэтому мы перечислим лишь несколько важных моментов для его сравнения с initramfs.
- initrd
-
-
Форматирование /обработка как блочного устройства означает, что
initrd
не может масштабироваться. Это означает, что как только вы внесетеinitrd
в память и рассмотрите его как блочное устройство, вы не сможете увеличить или уменьшить его размер. -
Мы потратим часть памяти в кеше, поскольку
initrd
рассматривается как блочное устройство, поскольку ядро Linux предназначено для хранения содержимого блочного устройства в кеше для уменьшения количества транзакций ввода-вывода. Короче говоря, без необходимости ядро будет кэшировать содержимоеinitrd
, которое уже находится в памяти.
-
- initramfs
-
-
В
initrd
всегда будут присутствовать издержки драйвера файловой системы и его двоичных файлов, таких какmke2fs
. Командаmke2fs
используется для создания файловых систем ext2/3/4. Это означает, что часть области ОЗУ сначала будет отформатирована с помощью файловой системы ext2/3/4 с помощьюmke2fs
, а затем в нее будет извлеченinitrd
, тогда как initramfs аналогичен tmpfs, который вы можете увеличивать или уменьшать в любое время на лету. -
Дублирование данных между блочными устройствами и кэшем отсутствует.
-
Чтобы использовать initramfs в качестве корневой файловой системы, ядру не нужны какие-либо драйверы или двоичные файлы, такие как
mke2fs
, поскольку архив initramfs будет извлечен в основную память в том виде, в каком он есть.
-
# ls -lh /boot/initramfs-5.3.7-301.fc31.x86_64.img
-rw-------. 1 root root 32M Dec 9 10:19 /boot/initramfs-5.3.7-301.fc31.x86_64.img
Мы можем использовать инструмент lsinitrd
, чтобы просмотреть содержимое initramfs, или извлечь initramfs с помощью инструмента skipcpio
.
# lsinitrd
Image: /boot/initramfs-5.3.7-301.fc31.x86_64.img: 32M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x 3 root root 0 Jul 25 2019 .
-rw-r--r-- 1 root root 2 Jul 25 2019 early_cpio
drwxr-xr-x 3 root root 0 Jul 25 2019 kernel
drwxr-xr-x 3 root root 0 Jul 25 2019 kernel/x86
drwxr-xr-x 2 root root 0 Jul 25 2019 kernel/x86/microcode
-rw-r--r-- 1 root root 100352 Jul 25 2019 kernel/x86/
microcode/GenuineIntel.bin
========================================================================
Version: dracut-049-27.git20181204.fc31.1
Arguments: -f
dracut modules:
bash
systemd
systemd-initrd
nss-softokn
i18n
network-manager
network
ifcfg
drm
plymouth
dm
kernel-modules
kernel-modules-extra
kernel-network-modules
lvm
qemu
qemu-net
resume
rootfs-block
terminfo
udev-rules
dracut-systemd
usrmount
base
fs-lib
shutdown
========================================================================
drwxr-xr-x 12 root root 0 Jul 25 2019 .
crw-r--r-- 1 root root 5, 1 Jul 25 2019 dev/console
crw-r--r-- 1 root root 1, 11 Jul 25 2019 dev/kmsg
crw-r--r-- 1 root root 1, 3 Jul 25 2019 dev/null
crw-r--r-- 1 root root 1, 8 Jul 25 2019 dev/random
crw-r--r-- 1 root root 1, 9 Jul 25 2019 dev/urandom
lrwxrwxrwx 1 root root 7 Jul 25 2019 bin -> usr/bin
drwxr-xr-x 2 root root 0 Jul 25 2019 dev
drwxr-xr-x 11 root root 0 Jul 25 2019 etc
drwxr-xr-x 2 root root 0 Jul 25 2019 etc/cmdline.d
drwxr-xr-x 2 root root 0 Jul 25 2019 etc/conf.d
-rw-r--r-- 1 root root 124 Jul 25 2019 etc/conf.d/systemd.conf
-rw-r--r-- 1 root root 0 Jul 25 2019 etc/fstab.empty
-rw-r--r-- 1 root root 240 Jul 25 2019 etc/group
-rw-r--r-- 1 root root 22 Jul 25 2019 etc/hostname
lrwxrwxrwx 1 root root 25 Jul 25 2019 etc/initrd-release -> ../usr/lib/initrd-release
-rw-r--r-- 1 root root 8581 Jul 25 2019 etc/ld.so.cache
-rw-r--r-- 1 root root 28 Jul 25 2019 etc/ld.so.conf
drwxr-xr-x 2 root root 0 Jul 25 2019 etc/ld.so.conf.d
-rw-r--r-- 1 root root 17 Jul 25 2019 etc/ld.so.conf.d/libiscsi-x86_64.conf
-rw-rw-r-- 1 root root 19 Jul 25 2019 etc/locale.conf
drwxr-xr-x 2 root root 0 Jul 25 2019 etc/lvm
-rw-r--r-- 1 root root 102256 Jul 25 2019 etc/lvm/lvm.conf
-rw-r--r-- 1 root root 2301 Jul 25 2019 etc/lvm/lvmlocal.conf
-r--r--r-- 1 root root 33 Jul 25 2019 etc/machine-id
drwxr-xr-x 2 root root 0 Jul 25 2019 etc/modprobe.d
Чтобы извлечь содержимое initramfs, используйте двоичный файл skipcpio
из /usr/lib/dracut/skipcpio/
. Файл skipcpio
предоставляется инструментом dracut
. Мы рассмотрим dracut
в главе 6.
# /usr/lib/dracut/skipcpio initramfs-5.3.7-301.fc31.x86_64.img | gunzip -c | cpio -idv
Если вы посмотрите на извлеченное содержимое initramfs, вы удивитесь, узнав, что оно выглядит так же, как корневая файловая система пользователя. Обратите внимание, что мы извлекли файлы initramfs в каталог /root/boot
.
# ls -lh /root/boot/
total 44K
lrwxrwxrwx. 1 root root 7 Mar 26 18:03 bin -> usr/bin
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 dev
drwxr-xr-x. 11 root root 4.0K Mar 26 18:03 etc
lrwxrwxrwx. 1 root root 23 Mar 26 18:03 init -> usr/lib/systemd/systemd
lrwxrwxrwx. 1 root root 7 Mar 26 18:03 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Mar 26 18:03 lib64 -> usr/lib64
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 proc
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 root
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 run
lrwxrwxrwx. 1 root root 8 Mar 26 18:03 sbin -> usr/sbin
-rwxr-xr-x. 1 root root 3.1K Mar 26 18:03 shutdown
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 sys
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 sysroot
drwxr-xr-x. 2 root root 4.0K Mar 26 18:03 tmp
drwxr-xr-x. 8 root root 4.0K Mar 26 18:03 usr
drwxr-xr-x. 3 root root 4.0K Mar 26 18:03 var
Вы найдете каталоги bin
, sbin
, usr
и т. д., var
, lib
и lib64
, которые мы привыкли видеть в корневой файловой системе нашего пользователя. Наряду с этим вы заметите каталоги виртуальной файловой системы, такие как dev
, run
, proc
, sys
и т. д. Таким образом, initramfs аналогичен корневой файловой системе пользователя. Давайте рассмотрим каждый каталог, чтобы лучше понять реализацию initramfs.
Реализация initramfs
Теперь посмотрим, что такое initramfs и как именно устроен initramfs. Из этого раздела вы поймете, что initramfs — это не что иное, как небольшая корневая файловая система.
bin
Обычные исполняемые файлы
Мы можем использовать все следующие исполняемые файлы в системе, завершившей процедуру загрузки. Поскольку все эти исполняемые файлы доступны внутри initramfs, когда система еще загружается, мы сможем использовать все эти команды во время загрузки.
cat, chown, cp, dmesg, echo, grep, gzip, less, ln, mkdir, mv, ps, rm, sed,
sleep, umount, uname, vi, loadkeys, kbd_mode, flock, tr, true, stty, mount,
sort etc.
[root@fedorab boot]# ls -la bin/
total 7208
drwxr-xr-x. 2 root root 4096 Jan 10 12:01 .
drwxr-xr-x. 8 root root 4096 Dec 19 14:30 ..
-rwxr-xr-x. 1 root root 1237376 Dec 19 14:30 bash
-rwxr-xr-x. 1 root root 50160 Dec 19 14:30 cat
-rwxr-xr-x. 1 root root 82688 Dec 19 14:30 chown
-rwxr-xr-x. 1 root root 177144 Dec 19 14:30 cp
-rwxr-xr-x. 1 root root 89344 Dec 19 14:30 dmesg
-rwxr-xr-x. 1 root root 2666 Dec 19 14:30 dracut-cmdline
-rwxr-xr-x. 1 root root 422 Dec 19 14:30 dracut-cmdline-ask
-rwxr-xr-x. 1 root root 1386 Dec 19 14:30 dracut-emergency
-rwxr-xr-x. 1 root root 2151 Dec 19 14:30 dracut-initqueue
-rwxr-xr-x. 1 root root 1056 Jan 10 12:01 dracut-mount
-rwxr-xr-x. 1 root root 517 Dec 19 14:30 dracut-pre-mount
-rwxr-xr-x. 1 root root 928 Dec 19 14:30 dracut-pre-pivot
-rwxr-xr-x. 1 root root 482 Dec 19 14:30 dracut-pre-trigger
-rwxr-xr-x. 1 root root 1417 Dec 19 14:30 dracut-pre-udev
-rwxr-xr-x. 1 root root 45112 Dec 19 14:30 echo
-rwxr-xr-x. 1 root root 76768 Dec 19 14:30 findmnt
-rwxr-xr-x. 1 root root 38472 Dec 19 14:30 flock
-rwxr-xr-x. 1 root root 173656 Dec 19 14:30 grep
-rwxr-xr-x. 1 root root 107768 Dec 19 14:30 gzip
-rwxr-xr-x. 1 root root 78112 Dec 19 14:30 journalctl
-rwxr-xr-x. 1 root root 17248 Dec 19 14:30 kbd_mode
-rwxr-xr-x. 1 root root 387504 Dec 19 14:30 kmod
-rwxr-xr-x. 1 root root 192512 Dec 19 14:30 less
-rwxr-xr-x. 1 root root 85992 Dec 19 14:30 ln
-rwxr-xr-x. 1 root root 222616 Dec 19 14:30 loadkeys
lrwxrwxrwx. 1 root root 4 Dec 19 14:30 loginctl -> true
-rwxr-xr-x. 1 root root 158056 Dec 19 14:30 ls
-rwxr-xr-x. 1 root root 99080 Dec 19 14:30 mkdir
-rwxr-xr-x. 1 root root 80264 Dec 19 14:30 mkfifo
-rwxr-xr-x. 1 root root 84560 Dec 19 14:30 mknod
-rwsr-xr-x. 1 root root 58984 Dec 19 14:30 mount
-rwxr-xr-x. 1 root root 169400 Dec 19 14:30 mv
-rwxr-xr-x. 1 root root 50416 Dec 19 14:30 plymouth
-rwxr-xr-x. 1 root root 143408 Dec 19 14:30 ps
-rwxr-xr-x. 1 root root 60376 Dec 19 14:30 readlink
-rwxr-xr-x. 1 root root 83856 Dec 19 14:30 rm
-rwxr-xr-x. 1 root root 127192 Dec 19 14:30 sed
-rwxr-xr-x. 1 root root 52272 Dec 19 14:30 setfont
-rwxr-xr-x. 1 root root 16568 Dec 19 14:30 setsid
lrwxrwxrwx. 1 root root 4 Dec 19 14:30 sh -> bash
-rwxr-xr-x. 1 root root 46608 Dec 19 14:30 sleep
-rwxr-xr-x. 1 root root 140672 Dec 19 14:30 sort
-rwxr-xr-x. 1 root root 96312 Dec 19 14:30 stat
-rwxr-xr-x. 1 root root 92576 Dec 19 14:30 stty
-rwxr-xr-x. 1 root root 240384 Dec 19 14:30 systemctl
-rwxr-xr-x. 1 root root 20792 Dec 19 14:30 systemd-cgls
-rwxr-xr-x. 1 root root 19704 Dec 19 14:30 systemd-escape
-rwxr-xr-x. 1 root root 62008 Dec 19 14:30 systemd-run
-rwxr-xr-x. 1 root root 95168 Dec 19 14:30 systemd-tmpfiles
-rwxr-xr-x. 1 root root 173752 Dec 19 14:30 teamd
-rwxr-xr-x. 1 root root 58400 Dec 19 14:30 tr
-rwxr-xr-x. 1 root root 45112 Dec 19 14:30 true
-rwxr-xr-x. 1 root root 442552 Dec 19 14:30 udevadm
-rwsr-xr-x. 1 root root 41912 Dec 19 14:30 umount
-rwxr-xr-x. 1 root root 45120 Dec 19 14:30 uname
-rwxr-xr-x. 1 root root 1353704 Dec 19 14:30 vi
Специальные исполняемые файлы
Команда | Цель |
---|---|
bash |
initramfs предоставит нам оболочку во время загрузки. |
mknod |
Мы сможем создавать устройства. |
udevadm |
Мы сможем управлять устройствами. dracut использует udev , инструмент, управляемый событиями, который запускает определенные программы, такие как lvm , mdadm и т. д., при выполнении определенных правил udev . Например, при совпадении определенных правил udev тома хранилищ и файлы устройств сетевых карт будут отображаться в каталоге /dev . |
kmod |
Инструмент для управления модулями ядра. |
Сетевые исполняемые файлы
В bin
доступен только один двоичный файл, связанный с сетью, и это teamd
(initramfs может обрабатывать объединенные сетевые устройства).
Хуки
Мы обсудим хуки в главах 7 и 9.
dracut-cmdline dracut-cmdline-ask
dracut-emergency dracut-initqueue
dracut-mount dracut-pre-pivot
dracut-pre-trigger dracut-pre-udev
Исполняемые файлы Systemd
Команда | Цель |
---|---|
systemd |
Это прародитель всех процессов, который заменил собой init . Именно он является самым первым процессом, который запускается в тот момент, когда мы входим в initramfs. |
systemctl |
Менеджер служб Systemd. |
systemd-cgls |
Отображает существующие контрольные группы (cgroups). |
systemd-escape |
Преобразует строку в формат юнита systemd, этот формат также называют экранированным (escaped). |
systemd-run |
Запускает программы как службы, но во временной области действия. |
systemd-tmpfiles |
Создает, удаляет и очищает изменяемые и временные файлы и каталоги. |
journalctl |
Инструмент для работы с журналом systemd. |
sbin
Файловая система и исполняемые файлы, связанные с хранилищем
Команда | Цель |
---|---|
blkid |
Чтение атрибутов устройства |
chroot |
Изменение корня файловой системы |
e2fsck |
Проверка файловых систем ext2/3/4 |
fsck, fsck.ext4 |
Проверка и восстановление файловой системы |
swapoff |
Остановка устройства подкачки |
dmsetup |
Инструмент для управления логическими томами LVM |
dmeventd |
Демон мониторинга событий для устройств device-mapper |
lvm |
Инструмент управления LVM, который предоставляет команды lvscan , vgscan , vgchange , pvs и т. д. |
lvm_scan |
Скрипт для поиска устройств LVM |
Сетевые исполняемые файлы
Команда | Цель |
---|---|
dhclient |
Получение IP от DHCP-сервера |
losetup |
Настрока петлевого устройства loop |
Netroot |
Поддержка root по сети |
NetworkManager |
Инструмент для управления сетевыми устройствами |
Специальные исполняемые файлы
Команда | Цель |
---|---|
depmod |
Создание файла elements.dep (символическая ссылка kmod ) |
lsmod |
Вывод списка загруженных модулей (символическая ссылка kmod ) |
modinfo |
Распечатка информации о модуле (символическая ссылка kmod ) |
modprobe |
Загрузка или вставка модулей (символическая ссылка kmod ) |
rmmod |
Удаление загруженного модуля (символическая ссылка kmod ) |
init / systemd |
Первый процесс |
kexec |
Ядро kexec, используемое Kdump |
udevadm |
Диспетчер udev |
Базовые исполняемые файлы
Наконец, вот базовые исполняемые файлы:
halt, poweroff, reboot
[root@fedorab boot]# ls -lah sbin/
total 13M
drwxr-xr-x. 2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 8 root root 4.0K Dec 19 14:30 ..
-rwxr-xr-x. 1 root root 126K Dec 19 14:30 blkid
-rwxr-xr-x. 1 root root 50K Dec 19 14:30 chroot
lrwxrwxrwx. 1 root root 11 Dec 19 14:30 depmod -> ../bin/kmod
-rwxr-xr-x. 1 root root 2.9M Dec 19 14:30 dhclient
-r-xr-xr-x. 1 root root 45K Dec 19 14:30 dmeventd
-r-xr-xr-x. 1 root root 159K Dec 19 14:30 dmsetup
-rwxr-xr-x. 2 root root 340K Dec 19 14:30 e2fsck
-rwxr-xr-x. 1 root root 58K Dec 19 14:30 fsck
-rwxr-xr-x. 2 root root 340K Dec 19 14:30 fsck.ext4
lrwxrwxrwx. 1 root root 16 Dec 19 14:30 halt -> ../bin/systemctl
lrwxrwxrwx. 1 root root 22 Dec 19 14:30 init -> ../lib/systemd/systemd
-rwxr-xr-x. 1 root root 1.2K Dec 19 14:30 initqueue
lrwxrwxrwx. 1 root root 11 Dec 19 14:30 insmod -> ../bin/kmod
-rwxr-xr-x. 1 root root 197 Dec 19 14:30 insmodpost.sh
-rwxr-xr-x. 1 root root 203K Dec 19 14:30 kexec
-rwxr-xr-x. 1 root root 496 Dec 19 14:30 loginit
-rwxr-xr-x. 1 root root 117K Dec 19 14:30 losetup
lrwxrwxrwx. 1 root root 11 Dec 19 14:30 lsmod -> ../bin/kmod
-r-xr-xr-x. 1 root root 2.4M Dec 19 14:30 lvm
-rwxr-xr-x. 1 root root 3.5K Dec 19 14:30 lvm_scan
lrwxrwxrwx. 1 root root 11 Dec 19 14:30 modinfo -> ../bin/kmod
lrwxrwxrwx. 1 root root 11 Dec 19 14:30 modprobe -> ../bin/kmod
-rwxr-xr-x. 1 root root 2.7K Dec 19 14:30 netroot
-rwxr-xr-x. 1 root root 5.3M Dec 19 14:30 NetworkManager
-rwxr-xr-x. 1 root root 16K Dec 19 14:30 nologin
-rwxr-xr-x. 1 root root 150K Dec 19 14:30 plymouthd
lrwxrwxrwx. 1 root root 16 Dec 19 14:30 poweroff -> ../bin/systemctl
-rwxr-xr-x. 1 root root 1.4K Dec 19 14:30 rdsosreport
lrwxrwxrwx. 1 root root 16 Dec 19 14:30 reboot -> ../bin/systemctl
lrwxrwxrwx. 1 root root 11 Dec 19 14:30 rmmod -> ../bin/kmod
-rwxr-xr-x. 1 root root 25K Dec 19 14:30 swapoff
-rwxr-xr-x. 1 root root 6.0K Dec 19 14:30 tracekomem
lrwxrwxrwx. 1 root root 14 Dec 19 14:30 udevadm -> ../bin/udevadm
Разве не удивительно видеть, что, не имея реальной корневой файловой системы пользователя, мы можем использовать и управлять оболочкой, сетью, модулями, устройствами и т. д.? Другими словами, вам на самом деле не нужна корневая файловая система пользователя, если только пользователь не хочет получить доступ к своим личным файлам. Просто шучу.
Теперь на ум приходит вопрос: где и как мы можем использовать все эти команды? Эти исполняемые файлы или команды будут автоматически использоваться initramfs. Или, если сказать правильно, эти исполняемые файлы или команды будут использоваться systemd initramfs для монтирования фактической корневой файловой системы пользователя, но если systemd не сможет этого сделать, он предоставит нам оболочку, и мы сможем использовать эти команды и устранять неполадки в дальнейшем. Мы обсудим это в главах 7, 8 и 9.
etc
Исполняемые файлы из каталогов bin
и sbin
будут иметь свои собственные файлы конфигурации и храниться в каталоге etc
в initramfs.
[root@fedorab boot]# tree etc/
etc/
├── cmdline.d
├── conf.d
│ └── systemd.conf
├── fstab.empty
├── group
├── hostname
├── initrd-release -> ../usr/lib/initrd-release
├── ld.so.cache
├── ld.so.conf
├── ld.so.conf.d
│ └── libiscsi-x86_64.conf
├── locale.conf
├── lvm
│ ├── lvm.conf
│ └── lvmlocal.conf
├── machine-id
├── modprobe.d
│ ├── firewalld-sysctls.conf
│ ├── kvm.conf
│ ├── lockd.conf
│ ├── mlx4.conf
│ ├── nvdimm-security.conf
│ └── truescale.conf
├── mtab -> /proc/self/mounts
├── os-release -> initrd-release
├── passwd
├── plymouth
│ └── plymouthd.conf
├── sysctl.conf
├── sysctl.d
│ └── 99-sysctl.conf -> ../sysctl.conf
├── systemd
│ ├── journald.conf
│ └── system.conf
├── system-release -> ../usr/lib/fedora-release
├── udev
│ ├── rules.d
│ │ ├── 11-dm.rules
│ │ ├── 59-persistent-storage-dm.rules
│ │ ├── 59-persistent-storage.rules
│ │ ├── 61-persistent-storage.rules
│ │ └── 64-lvm.rules
│ └── udev.conf
├── vconsole.conf
└── virc
10 directories, 35 files
Виртуальные файловые системы
Виртуальная файловая система — это тип файловой системы, в которой файлы фактически не хранятся на диске. Вместо этого вся файловая система находится в памяти. Это имеет свои преимущества и недостатки; например, вы получаете очень высокую пропускную способность, но файловая система не может хранить данные постоянно. Внутри initramfs доступны три виртуальные файловые системы: dev
, proc
и sys
. Здесь я дал краткое введение в файловые системы, но мы поговорим о них подробно в следующих главах:
[root@fedorab boot]# ls -lah dev
total 8.0K
drwxr-xr-x. 2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 12 root root 4.0K Dec 19 14:33 ..
crw-r--r--. 1 root root 5, 1 Dec 19 14:30 console
crw-r--r--. 1 root root 1, 11 Dec 19 14:30 kmsg
crw-r--r--. 1 root root 1, 3 Dec 19 14:30 null
crw-r--r--. 1 root root 1, 8 Dec 19 14:30 random
crw-r--r--. 1 root root 1, 9 Dec 19 14:30 urandom
[root@fedorab boot]# ls -lah proc/
total 8.0K
drwxr-xr-x. 2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 12 root root 4.0K Dec 19 14:33 ..
[root@fedorab boot]# ls -lah sys/
total 8.0K
drwxr-xr-x. 2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 12 root root 4.0K Dec 19 14:33 ..
dev
На данный момент существует только пять файлов устройств по умолчанию, но при загрузке системы udev
полностью заполнит этот каталог. Файлы устройств console
, kmsg
, null
, random
и urandom
будут созданы самим ядром, или, другими словами, эти файлы устройств создаются вручную с помощью команды mknod
, но остальные файлы устройств будут заполнены udev
.
proc и sys
Как только ядро возьмет на себя управление процедурой загрузки, оно создаст и заполнит эти каталоги. Файловая система proc
будет хранить всю информацию, связанную с процессами, например /proc/1/status
, тогда как sys
будет хранить информацию об устройствах и их драйверах, например /sys/fs/ext4/sda5/errors_count
.
usr, var
Как мы все знаем, в наши дни usr
представляет собой отдельную иерархию файловой системы в корневой файловой системе. Наши /bin
, /sbin
, /lib
и /lib64
— это не что иное, как символические ссылки на /usr/bin
, /usr/sbin
, /usr/lib
и /usr/lib64
.
# ls -l bin
lrwxrwxrwx. 1 root root 7 Dec 21 12:19 bin -> usr/bin
# ls -l sbin
lrwxrwxrwx. 1 root root 8 Dec 21 12:19 sbin -> usr/sbin
# ls -la usr
total 40
drwxr-xr-x. 8 root root 4096 Dec 21 12:19 .
drwxr-xr-x. 12 root root 4096 Dec 21 12:19 ..
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 bin
drwxr-xr-x. 12 root root 4096 Dec 21 12:19 lib
drwxr-xr-x. 4 root root 12288 Dec 21 12:19 lib64
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 libexec
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 sbin
drwxr-xr-x. 5 root root 4096 Dec 21 12:19 share
# ls -la var
total 12
drwxr-xr-x. 3 root root 4096 Dec 21 12:19 .
drwxr-xr-x. 12 root root 4096 Dec 21 12:19 ..
lrwxrwxrwx. 1 root root 11 Dec 21 12:19 lock -> ../run/lock
lrwxrwxrwx. 1 root root 6 Dec 21 12:19 run -> ../run
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 tmp
lib, lib64
Существует почти 200 библиотек, и почти все они предоставляются glibc
, например libc.so.6
.
Каталоги lib
и lib64
являются символическими ссылками /usr/lib
и /usr/lib64
.
# ls -l lib
lrwxrwxrwx. 1 root root 7 Dec 21 12:19 lib -> usr/lib
# ls -l lib64
lrwxrwxrwx. 1 root root 9 Dec 21 12:19 lib64 -> usr/lib64
# ls -la lib/
total 128
drwxr-xr-x. 12 root root 4096 Dec 21 12:19 .
drwxr-xr-x. 8 root root 4096 Dec 21 12:19 ..
drwxr-xr-x. 3 root root 4096 Dec 21 12:19 dracut
-rwxr-xr-x. 1 root root 34169 Dec 21 12:19 dracut-lib.sh
-rw-r--r--. 1 root root 31 Dec 21 12:19 fedora-release
drwxr-xr-x. 6 root root 4096 Dec 21 12:19 firmware
-rwxr-xr-x. 1 root root 6400 Dec 21 12:19 fs-lib.sh
-rw-r--r--. 1 root root 238 Dec 21 12:19 initrd-release
drwxr-xr-x. 6 root root 4096 Dec 21 12:19 kbd
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 modprobe.d
drwxr-xr-x. 3 root root 4096 Dec 21 12:19 modules
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 modules-load.d
-rwxr-xr-x. 1 root root 25295 Dec 21 12:19 net-lib.sh
lrwxrwxrwx. 1 root root 14 Dec 21 12:19 os-release -> initrd-release
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 sysctl.d
drwxr-xr-x. 5 root root 4096 Dec 21 12:19 systemd
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 tmpfiles.d
drwxr-xr-x. 3 root root 4096 Dec 21 12:19 udev
# ls -la lib64/libc.so.6
lrwxrwxrwx. 1 root root 12 Dec 21 12:19 lib64/libc.so.6 -> libc-2.30.so
# dnf whatprovides lib64/libc.so.6
glibc-2.30-5.fc31.x86_64 : The GNU libc libraries
Repo : @System
Matched from:
Filename : /lib64/libc.so.6
Загрузка initramfs
Основной порядок загрузки внутри initramfs легко понять:
-
Поскольку initramfs является корневой файловой системой (временной), он создаст среду, необходимую для запуска процессов. initramfs будет смонтирован как корневая файловая система (временная
/
), и из нее будут запускаться такие программы, как systemd. -
После этого корневая файловая система нового пользователя с вашего жесткого диска или сети будет смонтирована во временный каталог внутри initramfs.
-
Как только корневая файловая система пользователя смонтирована внутри initramfs, ядро запустит двоичный файл
init
, который является символической ссылкой наsystemd
, первый процесс операционной системы.# ls init -l lrwxrwxrwx. 1 root root 23 Dec 21 12:19 init -> usr/lib/systemd/systemd
-
Как только все будет в порядке, временная корневая файловая система (корневая файловая система initramfs) будет размонтирована, а systemd позаботится об остальной части последовательности загрузки. В главе 7 будет рассмотрена загрузка systemd.
Мы можем перекрестно проверить, действительно ли ядро запускает процесс init/systemd
, как только оно извлекает initramfs. Для этого мы можем изменить сценарий init
, но препятствием является то, что systemd является двоичным файлом, тогда как раньше init
был скриптом. Мы можем легко редактировать init
, поскольку это файл сценария, но мы не можем редактировать двоичный файл systemd. Однако, чтобы иметь хорошее понимание и проверить нашу последовательность загрузки, чтобы увидеть, вызывается ли systemd, как только ядро извлекает initramfs, мы будем использовать систему на основе init
. Это был бы справедливый пример, поскольку systemd заменяет систему init
. Кроме того, init
по-прежнему является символической ссылкой на systemd. Мы будем использовать систему Centos 6, которая представляет собой дистрибутив Linux на основе init
.
Сначала извлечем initramfs.
# zcat initramfs-2.6.32-573.el6.x86_64.img | cpio –idv
[root@localhost initramfs]# ls -lah
total 120K
drwxr-xr-x. 26 root root 4.0K Mar 27 12:56 .
drwxr-xr-x. 3 root root 4.0K Mar 27 12:56 ..
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 bin
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 cmdline
drwxr-xr-x. 3 root root 4.0K Mar 27 12:56 dev
-rw-r--r--. 1 root root 19 Mar 27 12:56 dracut-004-388.el6
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 emergency
drwxr-xr-x. 8 root root 4.0K Mar 27 12:56 etc
-rwxr-xr-x. 1 root root 8.8K Mar 27 12:56 init
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 initqueue
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 initqueue-finished
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 initqueue-settled
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 initqueue-timeout
drwxr-xr-x. 7 root root 4.0K Mar 27 12:56 lib
drwxr-xr-x. 3 root root 4.0K Mar 27 12:56 lib64
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 mount
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 netroot
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 pre-mount
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 pre-pivot
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 pre-trigger
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 pre-udev
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 proc
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 sbin
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 sys
drwxr-xr-x. 2 root root 4.0K Mar 27 12:56 sysroot
drwxrwxrwt. 2 root root 4.0K Mar 27 12:56 tmp
drwxr-xr-x. 8 root root 4.0K Mar 27 12:56 usr
drwxr-xr-x. 4 root root 4.0K Mar 27 12:56 var
Откройте файл инициализации и добавьте в него следующий баннер:
"We are inside the init process. Init is replaced by Systemd"
# vim init
#!/bin/sh
#
# Licensed under the GPLv2
#
# Copyright 2008-2009, Red Hat, Inc.
# Harald Hoyer <harald@redhat.com>
# Jeremy Katz <katzj@redhat.com>
echo "We are inside the init process. Init is replaced by Systemd"
wait_for_loginit()
{
if getarg rdinitdebug; then
set +x
exec 0<>/dev/console 1<>/dev/console 2<>/dev/console
# wait for loginit
i=0
while [ $i -lt 10 ]; do
.
.
.
Переупакуйте initramfs под именем test.img.
[root@localhost initramfs]# find . | cpio -o -c | gzip -9 > /boot/test.img
163584 blocks
# ls -lh /boot/
total 66M
-rw-r--r--. 1 root root 105K Jul 23 2015 config-2.6.32-573.el6.x86_64
drwxr-xr-x. 3 root root 1.0K Aug 7 2015 efi
-rw-r--r--. 1 root root 163K Jul 20 2011 elf-memtest86+-4.10
drwxr-xr-x. 2 root root 1.0K Dec 21 16:12 grub
-rw-------. 1 root root 27M Dec 21 15:55 initramfs-2.6.32-573.el6.x86_64.img
-rw-------. 1 root root 5.3M Dec 21 16:03 initrd-2.6.32-573.el6.x86_64kdump.img
drwx------. 2 root root 12K Dec 21 15:54 lost+found
-rw-r--r--. 1 root root 162K Jul 20 2011 memtest86+-4.10
-rw-r--r--. 1 root root 202K Jul 23 2015 symvers-2.6.32-573.el6.x86_64.gz
-rw-r--r--. 1 root root 2.5M Jul 23 2015 System.map-2.6.32-573.el6.x86_64
-rw-r--r--. 1 root root 27M Mar 27 13:16 test.img
-rwxr-xr-x. 1 root root 4.1M Jul 23 2015 vmlinuz-2.6.32-573.el6.x86_64
Загрузитесь с новым initramfs test.img
, и сразу после распаковки initramfs вы заметите, что наш баннер печатается.
.
.
.
cpuidle: using governor ladder
cpuidle: using governor menu
EFI Variables Facility v0.08 2004-May-17
usbcore: registered new interface driver hiddev
usbcore: registered new interface driver usbhid
usbhid: v2.6:USB HID core driver
GRE over IPv4 demultiplexor driver
TCP cubic registered
Initializing XFRM netlink socket
NET: Registered protocol family 17
registered taskstats version 1
rtc_cmos 00:01: setting system clock to 2020-03-27 07:53:44 UTC (1585295624)
Initalizing network drop monitor service
Freeing unused kernel memory: 1296k freed
Write protecting the kernel read-only data: 10240k
Freeing unused kernel memory: 732k freed
Freeing unused kernel memory: 1576k freed
We are inside the init process. Init is replaced by Systemd
dracut: dracut-004-388.el6
dracut: rd_NO_LUKS: removing cryptoluks activation
device-mapper: uevent: version 1.0.3
device-mapper: ioctl: 4.29.0-ioctl (2014-10-28) initialised:
dm-devel@redhat.com
udev: starting version 147
dracut: Starting plymouth daemon
.
.
Как ядро извлекает initramfs из памяти?
Давайте потратим минуту и попытаемся вспомнить все, что мы узнали до сих пор.
Первым запускается загрузчик.
Загрузчик копирует ядро и initramfs в память.
Ядро извлекается само.
Загрузчик передает расположение initramfs ядру.
Ядро извлекает initramfs в память.
Ядро запускает systemd из извлеченного initramfs.
Извлечение происходит в файле ядра init/initramfs.c
. За извлечение отвечает функция populate_rootfs
.
Функция populate_rootfs
:
.
.
static int __init populate_rootfs(void)
{
/* Load the built in initramfs */
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
if (err)
panic("%s", err); /* Failed to decompress INTERNAL initramfs */
if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE))
goto done;
if (IS_ENABLED(CONFIG_BLK_DEV_RAM))
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
else
printk(KERN_INFO "Unpacking initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start, initrd_end — initrd_start);
if (err) {
clean_rootfs();
populate_initrd_image(err);
}
done:
/*
* If the initrd region is overlapped with crashkernel reserved region,
* free only memory that is not part of crashkernel region.
*/
if (!do_retain_initrd && initrd_start && !kexec_free_initrd())
free_initrd_mem(initrd_start, initrd_end);
initrd_start = 0;
initrd_end = 0;
flush_delayed_fput();
return 0;
}
.
.
Функция unpack_to_rootfs
:
.
.
static char * __init unpack_to_rootfs(char *buf, unsigned long len)
{
long written;
decompress_fn decompress;
const char *compress_name;
static __initdata char msg_buf[64];
header_buf = kmalloc(110, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
if (!header_buf || !symlink_buf || !name_buf)
panic("can't allocate buffers");
state = Start;
this_header = 0;
message = NULL;
while (!message && len) {
loff_t saved_offset = this_header;
if (*buf == '0' && !(this_header & 3)) {
state = Start;
written = write_buffer(buf, len);
buf += written;
len -= written;
continue;
}
if (!*buf) {
buf++;
len--;
this_header++;
continue;
}
this_header = 0;
decompress = decompress_method(buf, len, &compress_name);
pr_debug("Detected %s compressed data\n", compress_name);
if (decompress) {
int res = decompress(buf, len, NULL, flush_buffer, NULL,
&my_inptr, error);
if (res)
error("decompressor failed");
} else if (compress_name) {
if (!message) {
snprintf(msg_buf, sizeof msg_buf,
"compression method %s not configured",
compress_name);
message = msg_buf;
}
} else
error("invalid magic at start of compressed archive");
if (state != Reset)
error("junk at the end of compressed archive");
this_header = saved_offset + my_inptr;
buf += my_inptr;
len -= my_inptr;
}
dir_utime();
kfree(name_buf);
kfree(symlink_buf);
kfree(header_buf);
return message;
}
.
.
Внутри функции populate_rootfs
есть функция unpack_to_rootfs
. Это рабочая функция, которая распаковывает initramfs и возвращает 0 в случае неудачи и 1 в случае успеха. Также обратите внимание на интересные параметры функции.
-
__initramfs_start:
это точное местоположение/адрес загруженного initramfs (initramfs будет загружен загрузчиком, поэтому очевидно, что местоположение адреса также предоставляется загрузчиком черезboot_protocol
). -
__initramfs_size:
это размер образа initramfs.
Как ядро монтирует initramfs как root?
Образ initramfs — это просто (возможно, сжатый) архивный файл cpio
. Ядро извлекает его, создавая в памяти файловую систему tmpfs/ramfs в качестве корневой файловой системы. Итак, на самом деле не существует фиксированного местоположения; ядро просто выделяет память для извлеченных файлов по мере их выполнения. Мы уже видели, что GRUB 2/загрузчик помещает ядро в определенное место, которое будет зависеть от архитектуры, но извлечение образа initramfs не происходит в каком-либо конкретном месте.
Теперь, прежде чем продолжить последовательность загрузки, нам нужно разобраться с инструментом dracut, который генерирует initramfs. Этот инструмент поможет нам лучше понять initramfs и systemd.
Глава 6
dracut
Если сказать просто, то dracut — это инструмент, который создает файловую систему initramfs в системах на базе Fedora. Системы на базе Debian и Ubuntu используют аналогичный инструмент под названием update-initramfs. Если вы хотите сгенерировать, восстановить или настроить существующие initramfs, вам следует знать, как использовать инструмент dracut. В этой главе объясняется, как работает dracut, а также как создавать и настраивать initramfs. Кроме того, вы узнаете о некоторых наиболее распространенных проблемах «невозможно загрузиться», связанных с initramfs.
Приступая к работе
Каждое ядро имеет свой собственный файл initramfs, но вы можете задаться вопросом, почему вам никогда не приходилось использовать команду dracut для создания initramfs при установке нового ядра. Вы просто посмотрели в папку /boot
и нашли там соответствующий initramfs. Что ж, когда вы устанавливаете новое ядро, команда post-scripts
пакета ядра rpm
вызывает dracut и создает для вас initramfs. Давайте посмотрим, как это работает в системе на базе Fedora:
# rpm -q --scripts kernel-core-5.3.7-301.fc31.x86_64
postinstall scriptlet (using /bin/sh):
if [ `uname -i` == "x86_64" -o `uname -i` == "i386" ] &&
[ -f /etc/sysconfig/kernel ]; then
/bin/sed -r -i -e 's/^DEFAULTKERNEL=kernel-smp$/DEFAULTKERNEL=kernel/' /etc/sysconfig/kernel || exit $?
fi
preuninstall scriptlet (using /bin/sh):
/bin/kernel-install remove 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz || exit $?
posttrans scriptlet (using /bin/sh):
/bin/kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz || exit $?
Как видите, команда post-scripts
пакета ядра вызывает скрипт kernel-install
. Сценарий установки ядра выполняет все сценарии, доступные в /usr/lib/kernel/install.d
.
# vim /bin/kernel-install
if ! [[ $MACHINE_ID ]]; then
ENTRY_DIR_ABS=$(mktemp -d /tmp/kernel-install.XXXXX) || exit 1
trap "rm -rf '$ENTRY_DIR_ABS'" EXIT INT QUIT PIPE
elif [[ -d /efi/loader/entries ]] || [[ -d /efi/$MACHINE_ID ]]; then
ENTRY_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION"
elif [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then
ENTRY_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"
elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]]; then
ENTRY_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION"
elif mountpoint -q /efi; then
ENTRY_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION"
elif mountpoint -q /boot/efi; then
ENTRY_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION"
else
ENTRY_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"
fi
export KERNEL_INSTALL_MACHINE_ID=$MACHINE_ID
ret=0
readarray -t PLUGINS <<<"$(
dropindirs_sort ".install" \
"/etc/kernel/install.d" \
"/usr/lib/kernel/install.d"
)"
Здесь вы можете увидеть сценарии, выполняемые kernel-install
:
# ls /usr/lib/kernel/install.d/ -lh
total 36K
-rwxr-xr-x. 1 root root 744 Oct 10 18:26 00-entry-directory.install
-rwxr-xr-x. 1 root root 1.9K Oct 19 07:46 20-grubby.install
-rwxr-xr-x. 1 root root 6.6K Oct 10 13:05 20-grub.install
-rwxr-xr-x. 1 root root 829 Oct 10 18:26 50-depmod.install
-rwxr-xr-x. 1 root root 1.7K Jul 25 2019 50-dracut.install
-rwxr-xr-x. 1 root root 3.4K Jul 25 2019 51-dracut-rescue.install
-rwxr-xr-x. 1 root root 3.4K Oct 10 18:26 90-loaderentry.install
-rwxr-xr-x. 1 root root 1.1K Oct 10 13:05 99-grub-mkconfig.install
Как видите, при этом выполняется скрипт 50-dracut.install
. Именно этот скрипт выполняет команду dracut
и создает initramfs для конкретного ядра.
for ((i=0; i < "${#BOOT_OPTIONS[@]}"; i++)); do
if [[ ${BOOT_OPTIONS[$i]} == root\=PARTUUID\=* ]]; then
noimageifnotneeded="yes"
break
fi
done
dracut -f ${noimageifnotneeded:+--noimageifnotneeded} "$BOOT_DIR_ABS/$INITRD" "$KERNEL_VERSION"
ret=$?
;;
remove)
rm -f -- "$BOOT_DIR_ABS/$INITRD"
ret=$?
;;
esac
exit $ret
Аналогично, существует скрипт 51-dracut-rescue.install
, который создаст initramfs для ядра rescue.
if [[ ! -f "$BOOT_DIR_ABS/$INITRD" ]]; then
dracut -f --no-hostonly -a "rescue" "$BOOT_DIR_ABS/$INITRD" "$KERNEL_VERSION"
((ret+=$?))
fi
if [[ "${BOOT_DIR_ABS}" != "/boot" ]]; then
{
echo "title $PRETTY_NAME — Rescue Image"
echo "version $KERNEL_VERSION"
echo "machine-id $MACHINE_ID"
echo "options ${BOOT_OPTIONS[@]} rd.auto=1"
echo "linux $BOOT_DIR/linux"
echo "initrd $BOOT_DIR/initrd"
} > $LOADER_ENTRY
else
cp -aT "${KERNEL_IMAGE%/*}/bls.conf" $LOADER_ENTRY
sed -i 's/'$KERNEL_VERSION'/0-rescue-'${MACHINE_ID}'/' $LOADER_ENTRY
fi
Следовательно, каждое ядро будет иметь свой собственный файл initramfs.
# ls -lh /boot | grep -e vmlinuz -e initramfs
-rw-------. 1 root root 80M Dec 2 18:32 initramfs-0-rescue-280526b3bc5e4c49ac83c8e5fbdfdb2e.img
-rw-------. 1 root root 28M Dec 23 06:37 initramfs-5.3.16-300.fc31.x86_64.img
-rw-------. 1 root root 30M Dec 2 18:33 initramfs-5.3.7-301.fc31.x86_64.img
-rwxr-xr-x. 1 root root 8.9M Dec 2 18:32 vmlinuz-0-rescue-280526b3bc5e4c49ac83c8e5fbdfdb2e
-rwxr-xr-x. 1 root root 8.9M Dec 13 23:51 vmlinuz-5.3.16-300.fc31.x86_64
-rwxr-xr-x. 1 root root 8.9M Oct 22 01:04 vmlinuz-5.3.7-301.fc31.x86_64
Обратите внимание на размеры файлов ядра (vmlinuz
) и связанного с ним initramfs. Файл initramfs намного больше ядра.
Создание образа initramfs
Сначала проверьте, какое ядро установлено в вашей системе с помощью этой команды:
# rpm -qa | grep -i kernel-5
kernel-5.3.16-300.fc31.x86_64
kernel-5.3.7-301.fc31.x86_64
Выберите версию ядра, для которой вы хотите создать новый образ initramfs, и передайте его в dracut.
# dracut /boot/new.img 5.3.7-301.fc31.x86_64 -v
dracut: Executing: /usr/bin/dracut /boot/new.img 5.3.7-301.fc31.x86_64 -v
dracut: dracut module 'busybox' will not be installed, because command 'busybox' could not be found!
dracut: dracut module 'stratis' will not be installed, because command 'stratisd-init' could not be found!
dracut: dracut module 'biosdevname' will not be installed, because command 'biosdevname' could not be found!
dracut: dracut module 'busybox' will not be installed, because command 'busybox' could not be found!
dracut: dracut module 'stratis' will not be installed, because command 'stratisd-init' could not be found!
dracut: *** Including module: bash ***
dracut: *** Including module: systemd ***
dracut: *** Including module: systemd-initrd ***
dracut: *** Including module: nss-softokn ***
dracut: *** Including module: i18n ***
dracut: *** Including module: network-manager ***
dracut: *** Including module: network ***
dracut: *** Including module: ifcfg ***
dracut: *** Including module: drm ***
dracut: *** Including module: plymouth ***
.
.
В предыдущем коде dracut создаст файл initramfs с именем new.img
в каталоге /boot
для 64-битного ядра Fedora kernel-5.3.7-301.fc31.x86_64
.
# ls -lh new.img
-rw-------. 1 root root 28M Dec 23 08:16 new.img
Если версия ядра не указана, то dracut создаст initramfs для ядра, с которым была загружена система. Версия ядра, переданная в dracut, должна соответствовать каталогу ядра, расположенному в папке /lib/modules/
.
# ls /lib/modules/ -l
total 4
drwxr-xr-x. 6 root root 4096 Dec 9 10:18 5.3.7-301.fc31.x86_64
# ls /lib/modules/5.3.7-301.fc31.x86_64/ -l
total 18084
-rw-r--r--. 1 root root 249 Oct 22 01:04 bls.conf
lrwxrwxrwx. 1 root root 38 Oct 22 01:04 build -> /usr/src/kernels/5.3.7-301.fc31.x86_64
-rw-r--r--. 1 root root 213315 Oct 22 01:03 config
drwxr-xr-x. 5 root root 4096 Oct 24 04:44 extra
drwxr-xr-x. 13 root root 4096 Oct 24 04:43 kernel
-rw-r--r--. 1 root root 1127438 Dec 9 10:18 modules.alias
-rw-r--r--. 1 root root 1101059 Dec 9 10:18 modules.alias.bin
-rw-r--r--. 1 root root 1688 Oct 22 01:04 modules.block
-rw-r--r--. 1 root root 8324 Oct 22 01:04 modules.builtin
-rw-r--r--. 1 root root 10669 Dec 9 10:18 modules.builtin.bin
-rw-r--r--. 1 root root 60853 Oct 22 01:04 modules.builtin.modinfo
-rw-r--r--. 1 root root 415475 Dec 9 10:18 modules.dep
-rw-r--r--. 1 root root 574502 Dec 9 10:18 modules.dep.bin
-rw-r--r--. 1 root root 381 Dec 9 10:18 modules.devname
-rw-r--r--. 1 root root 153 Oct 22 01:04 modules.drm
-rw-r--r--. 1 root root 59 Oct 22 01:04 modules.modesetting
-rw-r--r--. 1 root root 2697 Oct 22 01:04 modules.networking
-rw-r--r--. 1 root root 139947 Oct 22 01:04 modules.order
-rw-r--r--. 1 root root 700 Dec 9 10:18 modules.softdep
-rw-r--r--. 1 root root 468520 Dec 9 10:18 modules.symbols
-rw-r--r--. 1 root root 572778 Dec 9 10:18 modules.symbols.bin
lrwxrwxrwx. 1 root root 5 Oct 22 01:04 source -> build
-rw-------. 1 root root 4426726 Oct 22 01:03 System.map
drwxr-xr-x. 2 root root 4096 Oct 22 01:02 updates
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 vdso
-rwxr-xr-x. 1 root root 9323208 Oct 22 01:04 vmlinuz
Как мы знаем, initramfs — это временная корневая файловая система, и ее основная цель — предоставить среду, которая поможет смонтировать корневую файловую систему пользователя. Корневая файловая система пользователя может быть локальной для системы или сетевым устройством, и для использования этого устройства ядро должно иметь драйверы (модули) для этого оборудования и во время загрузки получать эти модули из initramfs.
Например, предположим, что корневая файловая система пользователя представляет собой локально подключенный жесткий диск, а жесткий диск — устройство SCSI. Таким образом, в архив initramfs должны быть добавлены драйверы SCSI.
# lsinitrd | grep -i scsi | awk '{ print $9 }'
etc/ld.so.conf.d/libiscsi-x86_64.conf
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/firmware/iscsi_ibft.ko.xz
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/iscsi_boot_sysfs.ko.xz
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/libiscsi.ko.xz
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/qla4xxx
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/qla4xxx/qla4xxx.ko.xz
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/scsi_transport_iscsi.ko.xz
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/scsi_transport_srp.ko.xz
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/virtio_scsi.ko.xz
usr/lib/udev/scsi_id
Помимо устройства SCSI пользователи могли настроить устройство RAID. Если да, то ядру необходимы драйверы устройств RAID для идентификации и сборки устройства RAID. Аналогично, некоторые жесткие диски пользователей можно подключить через карту HBA. В таких ситуациях ядру необходимы модули, подобные qlaXxxx
.
# lsinitrd | grep -i qla
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/qla4xxx
usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/qla4xxx/qla4xxx.ko.xz
Обратите внимание, что в настоящее время '/lib'
является символической ссылкой на '/usr/lib/'
.
В случае некоторых пользователей жесткий диск может подключаться по оптоволоконному каналу через Ethernet. Тогда ядру нужны модули FCOE. В виртуализированной среде жесткий диск может быть виртуальным диском, доступным гипервизору. В этом случае для подключения корневой файловой системы пользователя необходим модуль virtIO
. Таким образом, список оборудования и соответствующих ему модулей можно продолжить.
Очевидно, что ядро не может хранить все эти необходимые файлы модулей (.ko
) в собственном двоичном файле (vmlinuz
). Следовательно, одной из основных задач initramfs является хранение всех модулей, необходимых для монтирования корневой файловой системы пользователя. Это также одна из причин, почему размер файла initramfs намного больше размера файла ядра. Но помните, initramfs не является источником модулей. Модули всегда предоставляются ядром и архивируются в initramfs с помощью dracut. Ядро (vmlinuz
) является источником всех модулей, но, как вы можете догадаться, размер ядра будет огромным, если ядро будет хранить все модули в своем двоичном файле vmlinuz
. Следовательно, наряду с пакетом ядра был представлен новый пакет с именем kernel-modules
, и этот пакет предоставляет все модули, находящиеся в папке /lib/modules/<kernel-version-arch>
; dracut извлекает только те модули (файлы .ko
), которые необходимы для монтирования корневой файловой системы пользователя.
# rpm -qa | grep -i kernel
Kernel-headers-5.3.6-300.fc31.x86_64
kernel-modules-extra-5.3.7-301.fc31.x86_64
kernel-modules-5.3.7-301.fc31.x86_64
kernel-core-5.3.16-300.fc31.x86_64
kernel-core-5.3.7-301.fc31.x86_64
kernel-5.3.16-300.fc31.x86_64
abrt-addon-kerneloops-2.12.2-1.fc31.x86_64
kernel-5.3.7-301.fc31.x86_64
libreport-plugin-kerneloops-2.10.1-2.fc31.x86_64
Kernel-modules-5.3.16-300.fc31.x86_64
# rpm -ql kernel-modules-5.3.7-301.fc31.x86_64 | wc -l
1698
# rpm -ql kernel-modules-5.3.7-301.fc31.x86_64
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/atmtcp.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/eni.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/firestream.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/he.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/nicstar.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/solos-pci.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/atm/suni.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/auxdisplay/cfag12864b.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/auxdisplay/cfag12864bfb.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/auxdisplay/charlcd.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/auxdisplay/hd44780.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/auxdisplay/ks0108.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bcma/bcma.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth/ath3k.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth/bcm203x.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth/bfusb.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth/bluecard_cs.ko.xz
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth/bpa10x.ko.xz
.
.
Как видите, пакет kernel-modules
, входящий в состав kernel-5.3.7-301
, содержит почти 1698 модулей. Кроме того, пакет kernel-module
будет зависимостью пакета kernel
; следовательно, при каждой установке kernel
пакет kernel-modules
будет извлекаться и устанавливаться операционной системой на базе Fedora.
Dracut и модули
Теперь мы рассмотрим модули dracut.
Как dracut выбирает модули?
Чтобы понять, как dracut извлекает модули в initramfs, сначала нам нужно разобраться с командой depmod
. depmod
анализирует все модули ядра в папке /lib/modules/<kernel-version-arch>
и составляет список всех модулей вместе с их зависимыми модулями. Он хранит этот список в файле modules.dep
. (Обратите внимание, что в системах на базе Fedora рекомендуется ссылаться на расположение модуля как /usr/lib/modules/<kernel_version>/*
.) Вот пример:
# vim /lib/modules/5.3.7-301.fc31.x86_64/modules.dep
.
.
kernel/arch/x86/kernel/cpu/mce/mce-inject.ko.xz:
kernel/arch/x86/crypto/des3_ede-x86_64.ko.xz: kernel/crypto/des_generic.ko.xz
kernel/arch/x86/crypto/camellia-x86_64.ko.xz:
kernel/arch/x86/crypto/blowfish-x86_64.ko.xz: kernel/crypto/blowfish_common.ko.xz
kernel/arch/x86/crypto/twofish-x86_64.ko.xz: kernel/crypto/twofish_common.ko.xz
.
.
В этом коде вы можете видеть, что модулю с именем des3_ede
для правильной работы необходим модуль des_generic
. В другом примере вы можете видеть, что модули blowfish
имеют в качестве зависимости модуль blowfish_comman
. Итак, dracut читает файл modules.dep
и начинает извлекать модули ядра в образ initramfs из /lib/modules/5.3.7-301.fc31.x86_64/kernel/
.
# ls /lib/modules/5.3.7-301.fc31.x86_64/kernel/ -l
total 44
drwxr-xr-x. 3 root root 4096 Oct 24 04:43 arch
drwxr-xr-x. 4 root root 4096 Oct 24 04:43 crypto
drwxr-xr-x. 80 root root 4096 Oct 24 04:43 drivers
drwxr-xr-x. 43 root root 4096 Oct 24 04:43 fs
drwxr-xr-x. 4 root root 4096 Oct 24 04:43 kernel
drwxr-xr-x. 8 root root 4096 Oct 24 04:43 lib
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 mm
drwxr-xr-x. 51 root root 4096 Oct 24 04:43 net
drwxr-xr-x. 3 root root 4096 Oct 24 04:43 security
drwxr-xr-x. 13 root root 4096 Oct 24 04:43 sound
drwxr-xr-x. 3 root root 4096 Oct 24 04:43 virt
Ядро предоставляет тысячи модулей, но каждый модуль не обязательно добавлять в initramfs. Следовательно, при сборе модулей dracut извлекает очень специфические модули.
# find /lib/modules/5.3.7-301.fc31.x86_64/ -name '*.ko.xz' | wc -l
3539
Если бы dracut вытащил каждый модуль, размер initramfs был бы большим. Кроме того, зачем выдергивать каждый модуль, если в нем нет необходимости? Таким образом, dracut извлекает только те модули, которые необходимы для монтирования корневой файловой системы пользователя в этой системе.
# lsinitrd | grep -i '.ko.xz' | wc -l
221
Как видите, в initramfs всего 221 модуль, тогда как в ядре почти 3539 модулей.
Если мы включим 3539 модулей в initramfs, это сделает initramfs огромным, что в конечном итоге замедлит производительность загрузки, поскольку время загрузки и распаковки архива initramfs будет большим. Также нам нужно понимать, что основная задача initramfs — смонтировать корневую файловую систему пользователя. Поэтому имеет смысл включать только те модули, которые необходимы для монтирования корневой файловой системы. Например, модули, связанные с Bluetooth, не обязательно добавлять в initramfs, поскольку корневая файловая система никогда не будет получена с устройства, подключенного по Bluetooth. Таким образом, вы не найдете никаких модулей, связанных с Bluetooth, в initramfs, хотя есть несколько модулей Bluetooth, предоставляемых ядром (kernel-modules
).
# find /lib/modules/5.3.7-301.fc31.x86_64/ -name 'bluetooth'
/lib/modules/5.3.7-301.fc31.x86_64/kernel/net/bluetooth
/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth
# lsinitrd | grep -i blue
<no_output>
По умолчанию dracut добавит в initramfs только модули, специфичные для хоста. Это делается путем проверки текущего состояния системы и модулей, которые в данный момент используются системой. Привязка к конкретному хосту — это подход по умолчанию в каждом ведущем дистрибутиве Linux. Системы, подобные Fedora и Ubuntu, также создают общий образ initramfs, называемый rescue initramfs image (спасательный образ initramfs). Спасательный initramfs включает в себя все возможные модули для устройств, на которых пользователи могут создать корневую файловую систему. Идея состоит в том, что общий initramfs должен быть применим ко всем системам. Таким образом, размер спасательных initramfs всегда будет больше, чем initramfs, специфичных для хоста. dracut имеет кучу логики, позволяющей решить, какие модули необходимы для монтирования корневой файловой системы. Это то, что говорит справочная страница dracut, но помните, что в Linux на базе Fedora параметр --hostonly
используется по умолчанию.
--hostonly
или -H
. При использовании этой опции полученный образ будет содержать только те модули dracut, модули ядра и файловые системы, которые необходимы для загрузки данной конкретной машины. Это имеет тот недостаток, что вы не можете установить диск на другой контроллер или компьютер и что вы не можете переключиться на другую корневую файловую систему, не создав заново образ initramfs. Опция --hostonly
предназначена только для экспертов, и вам придется сохранить неработающие фрагменты. По крайней мере, сохраните копию образа общего назначения (и соответствующего ядра) в качестве запасного варианта для восстановления вашей системы».В главе 5 мы видели, что существует ряд двоичных файлов, модулей и файлов конфигурации, которые были выбраны dracut и добавлены в initramfs, но как dracut выбирает файлы из большой корневой файловой системы пользователя?
Файлы выбираются путем запуска сценариев в папке /usr/lib/dracut/modules.d
. Это место, где хранятся все скрипты dracut. dracut запускает эти сценарии при создании initramfs, как показано здесь:
# ls /usr/lib/dracut/modules.d/ -l
total 288
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 00bash
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 00systemd
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 00warpclock
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 01fips
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 01systemd-initrd
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 02systemd-networkd
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 03modsign
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 03rescue
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 04watchdog
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 05busybox
drwxr-xr-x. 2 root root 4096 Oct 24 04:42 05nss-softokn
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 05rdma
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 10i18n
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 30convertfs
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 35network-legacy
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 35network-manager
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 40network
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 45ifcfg
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 45url-lib
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 50drm
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 50plymouth
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 80lvmmerge
drwxr-xr-x. 2 root root 4096 Oct 24 04:42 90bcache
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90btrfs
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90crypt
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90dm
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90dmraid
drwxr-xr-x. 2 root root 4096 Oct 24 04:44 90dmsquash-live
drwxr-xr-x. 2 root root 4096 Oct 24 04:44 90dmsquash-live-ntfs
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90kernel-modules
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90kernel-modules-extra
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90kernel-network-modules
drwxr-xr-x. 2 root root 4096 Oct 24 04:44 90livenet
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90lvm
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90mdraid
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90multipath
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90qemu
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90qemu-net
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 90stratis
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 91crypt-gpg
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 91crypt-loop
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95cifs
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95debug
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95fcoe
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95fcoe-uefi
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95fstab-sys
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95iscsi
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95lunmask
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95nbd
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95nfs
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95resume
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95rootfs-block
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95ssh-client
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95terminfo
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95udev-rules
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 95virtfs
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 97biosdevname
drwxr-xr-x. 2 root root 4096 Jan 6 12:42 98dracut-systemd
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 98ecryptfs
drwxr-xr-x. 2 root root 4096 Oct 24 04:44 98ostree
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 98pollcdrom
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 98selinux
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 98syslog
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 98usrmount
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99base
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99earlykdump
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99fs-lib
drwxr-xr-x. 2 root root 4096 Oct 24 04:44 99img-lib
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99kdumpbase
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99shutdown
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99squash
drwxr-xr-x. 2 root root 4096 Oct 24 04:43 99uefi-lib
Тот же результат можно просмотреть, используя
# dracut --list-modules
Всякий раз, когда мы пытаемся создать файловую систему initramfs, dracut начинает выполнять файлы сценариев module-setup.sh
в каждом каталоге в /usr/lib/dracut/modules.d/
.
# find /usr/lib/dracut/modules.d/ -name 'module-setup.sh'
/usr/lib/dracut/modules.d/95iscsi/module-setup.sh
/usr/lib/dracut/modules.d/98ecryptfs/module-setup.sh
/usr/lib/dracut/modules.d/30convertfs/module-setup.sh
/usr/lib/dracut/modules.d/90crypt/module-setup.sh
/usr/lib/dracut/modules.d/10i18n/module-setup.sh
/usr/lib/dracut/modules.d/99earlykdump/module-setup.sh
/usr/lib/dracut/modules.d/95nbd/module-setup.sh
.
.
.
/usr/lib/dracut/modules.d/04watchdog/module-setup.sh
/usr/lib/dracut/modules.d/90lvm/module-setup.sh
/usr/lib/dracut/modules.d/35network-legacy/module-setup.sh
/usr/lib/dracut/modules.d/01systemd-initrd/module-setup.sh
/usr/lib/dracut/modules.d/99squash/module-setup.sh
/usr/lib/dracut/modules.d/05busybox/module-setup.sh
/usr/lib/dracut/modules.d/50drm/module-setup.sh
Этот скрипт module-setup.sh
выберет модуль, двоичные файлы и файлы конфигурации, специфичные для этого хоста. Например, первый скрипт module-setup.sh, который будет запускаться из каталога 00bash
, будет включать двоичный файл bash
в initramfs.
# vim /usr/lib/dracut/modules.d/00bash/module-setup.sh
#!/usr/bin/bash
# called by dracut
check() {
require_binaries /bin/bash
}
# called by dracut
depends() {
return 0
}
# called by dracut
install() {
# If another shell is already installed, do not use bash
[[ -x $initdir/bin/sh ]] && return
# Prefer bash as /bin/sh if it is available.
inst /bin/bash && ln -sf bash "${initdir}/bin/sh"
}
Как видите, файл сценария добавляет двоичный файл /bin/bash
в initramfs. Давайте рассмотрим другой пример, на этот раз из plymouth
.
# vim /usr/lib/dracut/modules.d/50plymouth/module-setup.sh
#!/usr/bin/bash
pkglib_dir() {
local _dirs="/usr/lib/plymouth /usr/libexec/plymouth/"
if type -P dpkg-architecture &>/dev/null; then
_dirs+=" /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/plymouth"
fi
for _dir in $_dirs; do
if [ -x $_dir/plymouth-populate-initrd ]; then
echo $_dir
return
fi
done
}
# called by dracut
check() {
[[ "$mount_needs" ]] && return 1
[ -z $(pkglib_dir) ] && return 1
require_binaries plymouthd plymouth plymouth-set-default-theme
}
# called by dracut
depends() {
echo drm
}
# called by dracut
install() {
PKGLIBDIR=$(pkglib_dir)
if grep -q nash ${PKGLIBDIR}/plymouth-populate-initrd \
|| [ ! -x ${PKGLIBDIR}/plymouth-populate-initrd ]; then
. "$moddir"/plymouth-populate-initrd.sh
else
PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$dracutfunctions" \
${PKGLIBDIR}/plymouth-populate-initrd -t "$initdir"
fi
inst_hook emergency 50 "$moddir"/plymouth-emergency.sh
inst_multiple readlink
if ! dracut_module_included "systemd"; then
inst_hook pre-trigger 10 "$moddir"/plymouth-pretrigger.sh
inst_hook pre-pivot 90 "$moddir"/plymouth-newroot.sh
fi
}
Простой запрос require_binaries
покажет все двоичные файлы, которые dracut добавит в общий initramfs.
# grep -ir "require_binaries" /usr/lib/dracut/modules.d/
/usr/lib/dracut/modules.d/90mdraid/module-setup.sh: require_binaries mdadm expr || return 1
/usr/lib/dracut/modules.d/80lvmmerge/module-setup.sh: require_binaries lvm dd swapoff || return 1
/usr/lib/dracut/modules.d/95cifs/module-setup.sh: require_binaries mount.cifs || return 1
/usr/lib/dracut/modules.d/91crypt-gpg/module-setup.sh: require_binaries gpg || return 1
/usr/lib/dracut/modules.d/91crypt-gpg/module-setup.sh: require_binaries gpg-agent &&
/usr/lib/dracut/modules.d/91crypt-gpg/module-setup.sh: require_binaries gpg-connect-agent &&
/usr/lib/dracut/modules.d/91crypt-gpg/module-setup.sh: require_binaries /usr/libexec/scdaemon &&
/usr/lib/dracut/modules.d/45url-lib/module-setup.sh: require_binaries curl || return 1
/usr/lib/dracut/modules.d/90stratis/module-setup.sh: require_binaries stratisd-init thin_check thin_repair mkfs.xfs xfs_admin xfs_growfs ||
return 1
/usr/lib/dracut/modules.d/90multipath/module-setup.sh: require_binaries multipath || return 1
/usr/lib/dracut/modules.d/95iscsi/module-setup.sh: require_binaries iscsi-iname iscsiadm iscsid || return 1
/usr/lib/dracut/modules.d/95ssh-client/module-setup.sh: require_binaries ssh scp || return 1
/usr/lib/dracut/modules.d/35network-manager/module-setup.sh: require_binaries sed grep || return 1
/usr/lib/dracut/modules.d/90dmsquash-live-ntfs/module-setup.sh: require_binaries ntfs-3g || return 1
/usr/lib/dracut/modules.d/91crypt-loop/module-setup.sh: require_binaries losetup || return 1
/usr/lib/dracut/modules.d/05busybox/module-setup.sh: require_binaries busybox || return 1
/usr/lib/dracut/modules.d/99img-lib/module-setup.sh: require_binaries tar gzip dd bash || return 1
/usr/lib/dracut/modules.d/90dm/module-setup.sh: require_binaries dmsetup || return 1
/usr/lib/dracut/modules.d/03modsign/module-setup.sh: require_binaries keyctl || return 1
/usr/lib/dracut/modules.d/97biosdevname/module-setup.sh: require_binaries biosdevname || return 1
/usr/lib/dracut/modules.d/95nfs/module-setup.sh: require_binaries rpc.statd mount.nfs mount.nfs4 umount || return 1
/usr/lib/dracut/modules.d/90dmraid/module-setup.sh: require_binaries dmraid || return 1
/usr/lib/dracut/modules.d/95fcoe/module-setup.sh: require_binaries dcbtool fipvlan lldpad ip readlink fcoemon fcoeadm || return 1
/usr/lib/dracut/modules.d/00warpclock/module-setup.sh: require_binaries /sbin/hwclock || return 1
/usr/lib/dracut/modules.d/35network-legacy/module-setup.sh: require_binaries ip dhclient sed awk grep || return 1
/usr/lib/dracut/modules.d/00bash/module-setup.sh: require_binaries /bin/bash
/usr/lib/dracut/modules.d/95nbd/module-setup.sh: require_binaries nbd-client || return 1
/usr/lib/dracut/modules.d/90btrfs/module-setup.sh: require_binaries btrfs || return 1
/usr/lib/dracut/modules.d/00systemd/module-setup.sh: if require_binaries $systemdutildir/systemd; then
/usr/lib/dracut/modules.d/10i18n/module-setup.sh: require_binaries setfont loadkeys kbd_mode || return 1
/usr/lib/dracut/modules.d/90lvm/module-setup.sh: require_binaries lvm || return 1
/usr/lib/dracut/modules.d/50plymouth/module-setup.sh: require_binaries plymouthd plymouth plymouth-set-default-theme
/usr/lib/dracut/modules.d/95fcoe-uefi/module-setup.sh: require_binaries dcbtool fipvlan lldpad ip readlink || return 1
Опять же, dracut не включает в себя все модули из /usr/lib/dracut/modules.d
. Он включает в себя только модули, специфичные для хоста. В следующем разделе вы узнаете, как добавлять или исключать определенные модули из initramfs.
Настройка initramfs
У dracut также есть свои модули. Модули ядра и модули dracut отличаются. Dracut собирает двоичные файлы для конкретного хоста, связанные библиотеки, файлы конфигурации и модули аппаратных устройств и группирует их под названием dracut modules. Модули ядра состоят из файлов .ko
аппаратного устройства. Список модулей dracut можно просмотреть либо из /usr/lib/dracut/modules.d/
, либо с помощью команды dracut --list-modules
.
# dracut --list-modules | xargs -n6
bash systemd warpclock fips systemd-initrd systemd-networkd
modsign rescue watchdog busybox nss-softokn rdma
i18n convertfs network-legacy network-manager network ifcfg
url-lib drm plymouth lvmmerge bcache btrfs
crypt dm dmraid dmsquash-live dmsquash-live-ntfs kernel-modules
kernel-modules-extra kernel-network-modules livenet lvm mdraid multipath
qemu qemu-net stratis crypt-gpg crypt-loop cifs
debug fcoe fcoe-uefi fstab-sys iscsi lunmask
nbd nfs resume rootfs-block ssh-client terminfo
udev-rules virtfs biosdevname dracut-systemd ecryptfs ostree
pollcdrom selinux syslog usrmount base earlykdump
fs-lib img-lib kdumpbase shutdown squash uefi-lib
Если вы хотите добавить или исключить определенные модули dracut (а не модуль аппаратного устройства) из initramfs, то dracut.conf
играет здесь жизненно важную роль. Обратите внимание, что dracut.conf
— это файл конфигурации dracut, а не initramfs; следовательно, он не будет доступен внутри initramfs.
# lsinitrd | grep -i 'dracut.conf'
<no output>
Dracut будет ссылаться на файл dracut.conf
при создании initramfs. По умолчанию это будет пустой файл.
# cat /etc/dracut.conf
# PUT YOUR CONFIG IN separate files
# in /etc/dracut.conf.d named "<name>.conf"
# SEE man dracut.conf(5) for options
В dracut.conf
предусмотрены различные параметры, которые вы можете использовать для добавления или исключения модуля.
Предположим, вы хотите исключить файлы, связанные с plymouth
(двоичные файлы, файлы конфигурации, модули и т.д.) из initramfs; тогда вы можете либо добавить omit_dracutmodules+=plymouth
в dracut.conf
, либо использовать переключатель omit (-o)
двоичного файла dracut
. Вот пример:
# lsinitrd | grep -i plymouth | wc -l
118
В загруженном ядре имеется почти 118 файлов, связанных с Plymouth. Давайте сейчас попробуем опустить файлы, связанные с plymouth
.
# dracut -o plymouth /root/new.img
# lsinitrd /root/new.img | grep -i plymouth | wc -l
4
Как вы можете ясно видеть, все модули dracut, связанные с plymouth
, были удалены из нашего недавно созданного initramfs. Таким образом, двоичные файлы, файлы конфигурации, библиотеки и модули аппаратных устройств, связанные с plymouth
(если они доступны), не будут записываться dracut в initramfs. Того же результата можно добиться, добавив omit_dracutmodules+=plymouth
в dracut.conf
.
# cat /etc/dracut.conf | grep -v '#'
omit_dracutmodules+=plymouth
# dracut /root/new.img --force
# lsinitrd /root/new.img | grep -i plymouth
-rw-r--r-- 1 root root 454 Jul 25 2019 usr/lib/systemd/system/systemd-ask-password-plymouth.path
-rw-r--r-- 1 root root 435 Jul 25 2019 usr/lib/systemd/system/systemd-ask-password-plymouth.service
drwxr-xr-x 2 root root 0 Jul 25 2019 usr/lib/systemd/system/systemd-ask-password-plymouth.service.wants
lrwxrwxrwx 1 root root 33 Jul 25 2019 usr/lib/systemd/system/systemd-ask-password-plymouth.service.wants/systemd-vconsole-setup.
service -> ../systemd-vconsole-setup.service
На странице руководства написано следующее:
Иногда вы не хотите, чтобы модуль dracut был включен по причинам скорости, размера или функциональности. Для этого либо укажите переменную
omit_dracutmodules
в файле конфигурации dracut.conf
или /etc/dracut.conf.d/myconf.conf
(см. dracut.conf(5)
), либо используйте опцию -o
или --omit
в командной строке: # dracut -o "multipath lvm" no-multipath-lvm.img
Подобно тому, как мы пропустили модуль dracut, мы можем добавить любой модуль, доступный в /usr/lib/dracut/modules.d
. Мы можем использовать ключ --add dracut
или использовать add_dracutmodules+= в dracut.conf
. Например, вы можете видеть, что в наш initramfs new.img
не добавлены модули/файлы/бинарники NFS, поскольку моя тестовая система не загружается из NFS и не использует в ней какую-либо точку монтирования NFS. Очевидно, что dracut пропустит модуль nfs
из /usr/lib/dracut/modules.d
. Итак, давайте добавим его в наш initramfs.
# lsinitrd | grep -i nfs
<no_output>
# cat /etc/dracut.conf
# PUT YOUR CONFIG IN separate files
# in /etc/dracut.conf.d named ".conf"
# SEE man dracut.conf(5) for options
# omit_dracutmodules+=plymouth
add_dracutmodules+=nfs
# dracut /root/new.img --force
# lsinitrd /root/new.img | grep -i nfs | wc -l
33
Мы также можем добиться этого, используя команду dracut
с ключом --add
.
# lsinitrd /root/new.img | grep -i nfs
# dracut --add nfs /root/new.img --force
# lsinitrd /root/new.img | grep -i nfs
Arguments: --add 'nfs' --force
nfs
-rw-r--r-- 1 root root 15 Jul 25 2019 etc/modprobe.d/nfs.conf
drwxr-xr-x 2 root root 0 Jul 25 2019 usr/lib64/libnfsidmap
-rwxr-xr-x 1 root root 50416 Jul 25 2019 usr/lib64/libnfsidmap/nsswitch.so
-rwxr-xr-x 1 root root 54584 Jul 25 2019 usr/lib64/libnfsidmap.so.1.0.0
lrwxrwxrwx 1 root root 20 Jul 25 2019 usr/lib64/libnfsidmap.so.1 -> libnfsidmap.so.1.0.0
-rwxr-xr-x 1 root root 42744 Jul 25 2019 usr/lib64/libnfsidmap/sss.so
-rwxr-xr-x 1 root root 46088 Jul 25 2019 usr/lib64/libnfsidmap/static.so
-rwxr-xr-x 1 root root 62600 Jul 25 2019 usr/lib64/libnfsidmap/umich_ldap.so
-rwxr-xr-x 1 root root 849 Oct 8 2018 usr/lib/dracut/hooks/cleanup/99-nfsroot-cleanup.sh
-rwxr-xr-x 1 root root 3337 Oct 8 2018 usr/lib/dracut/hooks/cmdline/90-parse-nfsroot.sh
-rwxr-xr-x 1 root root 874 Oct 8 2018 usr/lib/dracut/hooks/pre-udev/99-nfs-start-rpc.sh
drwxr-xr-x 5 root root 0 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs
drwxr-xr-x 2 root root 0 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/blocklayout
-rw-r--r-- 1 root root 16488 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/blocklayout/blocklayoutdriver.ko.xz
drwxr-xr-x 2 root root 0 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs_common
-rw-r--r-- 1 root root 2584 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs_common/grace.ko.xz
-rw-r--r-- 1 root root 3160 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs_common/nfs_acl.ko.xz
drwxr-xr-x 2 root root 0 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/filelayout
-rw-r--r-- 1 root root 11220 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/filelayout/nfs_layout_nfsv41_files.ko.xz
drwxr-xr-x 2 root root 0 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/flexfilelayout
-rw-r--r-- 1 root root 20872 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/flexfilelayout/nfs_layout_flexfiles.ko.xz
-rw-r--r-- 1 root root 109684 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/nfs.ko.xz
-rw-r--r-- 1 root root 18028 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/nfsv3.ko.xz
-rw-r--r-- 1 root root 182756 Jul 25 2019 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/fs/nfs/nfsv4.ko.xz
-rwxr-xr-x 1 root root 4648 Oct 8 2018 usr/lib/nfs-lib.sh
-rwsr-xr-x 1 root root 187680 Jul 25 2019 usr/sbin/mount.nfs
lrwxrwxrwx 1 root root 9 Jul 25 2019 usr/sbin/mount.nfs4 -> mount.nfs
-rwxr-xr-x 1 root root 719 Oct 8 2018 usr/sbin/nfsroot
drwxr-xr-x 4 root root 0 Jul 25 2019 var/lib/nfs
drwxr-xr-x 2 root root 0 Jul 25 2019 var/lib/nfs/rpc_pipefs
drwxr-xr-x 3 root root 0 Jul 25 2019 var/lib/nfs/statd
drwxr-xr-x 2 root root 0 Jul 25 2019 var/lib/nfs/statd/sm
Подобно тому, как мы добавили дополнительный модуль dracut nfs
в нашу initramfs, точно так же мы можем иметь только модуль nfs в нашей initramfs с помощью добавления параметра dracutmodules+=nfs
в файл dracut.conf
. Это означает, что результирующий initramfs будет содержать только модуль nfs
. Остальные модули из /usr/lib/dracut/modules.d/
будут удалены.
# cat /etc/dracut.conf
# omit_dracutmodules+=plymouth
# add_dracutmodules+=nfs
dracutmodules+=nfs
# dracut /root/new.img --force
# lsinitrd /root/new.img
Image: /root/new.img: 20M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x 3 root root 0 Jul 25 2019 .
-rw-r—r-- 1 root root 2 Jul 25 2019 early_cpio
drwxr-xr-x 3 root root 0 Jul 25 2019 kernel
drwxr-xr-x 3 root root 0 Jul 25 2019 kernel/x86
drwxr-xr-x 2 root root 0 Jul 25 2019 kernel/x86/microcode
-rw-r—r-- 1 root root 100352 Jul 25 2019 kernel/x86/microcode/
GenuineIntel.bin
========================================================================
Version:
Arguments: --force
dracut modules:
nss-softokn
network-manager
network
kernel-network-modules
nfs
=======================================================================
Как видите, был добавлен только модуль nfs
вместе с его зависимостями, такими как модуль dracut network
. Также обратите внимание на разницу в размерах между обеими версиями initramfs.
# ls -lh initramfs-5.3.16-300.fc31.x86_64.img
-rw-------. 1 root root 28M Dec 23 06:37 initramfs-5.3.16-300.fc31.x86_64.img
# ls -lh /root/new.img
-rw-------. 1 root root 20M Dec 24 11:05 /root/new.img
Того же самого можно добиться, используя ключ -m
или --modules
в dracut.
# dracut -m nfs /root/new.img --force
Если вы хотите добавить только модуль аппаратного устройства, обратите внимание, что модуль аппаратного устройства означает файлы *.ko
, предоставляемые пакетом kernel-modules
в /lib/modules/<kernel-version>/drivers/<module-name>
. Тогда ключ dracut --add
или add_dracutmodules+=
не поможет, поскольку эти два переключателя добавляют модули dracut, а не файл модуля ядра (.ko
). Итак, чтобы добавить модуль ядра, нам нужно использовать либо ключ dracut --add-drivers
, либо driver+=
, либо add_drivers+=
в dracut.conf
. Вот пример:
# lsinitrd /root/new.img | grep -i ath3k
Модуль ath3k
, связанный с Bluetooth, отсутствует в нашем initramfs, но это один из модулей, предоставляемых ядром.
# ls -lh /lib/modules/5.3.16-300.fc31.x86_64/kernel/drivers/bluetooth/ath3k.ko.xz
Добавим его, как показано здесь:
# dracut --add-drivers ath3k /root/new.img --force
Теперь он добавлен, как показано здесь:
# lsinitrd /root/new.img | grep -i ath3k
Arguments: --add-drivers 'ath3k' --force
-rw-r--r-- 1 root root 246804 Jul 25 03:54 usr/lib/firmware/ath3k-1.fw
-rw-r--r-- 1 root root 5652 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/bluetooth/ath3k.ko.xz
Как видите, в initramfs добавлен модуль ath3k.ko
.
Модуль dracut или модуль ядра?
Давайте рассмотрим, когда добавлять модуль dracut, а когда — модуль ядра. Вот сценарий: корневая файловая система вашего хоста находится на обычном устройстве SCSI. Итак, очевидно, что ваш initramfs не имеет ни модуля ядра multipath.ko
, ни файла конфигурации, подобного multipath.conf
.
-
Внезапно вы решаете перенести свою корневую файловую систему с обычного локального диска на SAN (я бы никогда не рекомендовал вносить такие изменения в производственную систему), а сеть SAN подключена через устройство множественного связывания (multipath device).
-
Чтобы получить все окружение устройства множественного связывания, сюда нужно добавить модуль dracut multipath, чтобы все окружение устройства множественного связывания было загружено в initramfs.
-
Через несколько дней вы добавляете новую сетевую карту в ту же систему, и поставщик сетевой карты предоставил для нее драйверы. Драйвер — это не что иное, как файл
.ko
(kernel object). Чтобы добавить этот модуль в ваш initramfs, вам нужно добавить опцию модуля ядра. При этом будет добавлен драйвер только сетевой карты, а не всей среды.
Но что, если вы хотите добавить в initramfs какой-то конкретный файл, который не является ни модулем ядра, ни модулем dracut? Dracut предоставляет переменные install_items+=
и --include
файла dracut.conf
, с помощью которых мы можем добавлять определенные файлы. Файлы могут быть любыми: от обычного текста до двоичного файла и т. д.
# lsinitrd /root/new.img | grep -i date
<no_output>
Двоичный файл date
по умолчанию отсутствует в initramfs. Но чтобы добавить двоичный файл, мы можем использовать переключатель install_itsems+
.
# cat /etc/dracut.conf
# PUT YOUR CONFIG IN separate files
# in /etc/dracut.conf.d named "<name>.conf"
# SEE man dracut.conf(5) for options
# omit_dracutmodules+=plymouth
# add_dracutmodules+=nfs
# dracutmodules+=nfs
install_items+=date
# dracut /root/new.img --force
# lsinitrd /root/new.img | grep -i date
-rwxr-xr-x 1 root root 122456 Jul 25 02:36 usr/bin/date
Как видите, двоичный файл date
добавлен, но самое главное, он добавляет не только двоичный файл, но и библиотеку, необходимую для запуска команды date
. То же самое можно добиться с помощью переключателя --install
команды dracut
. Но существует ограничение; эта команда не может добавлять пользовательские двоичные файлы. Для этого нам нужно использовать ключ --include
в dracut. С помощью --include
вы можете включить в initramfs обычные файлы, каталоги или даже двоичные файлы. В случае с двоичным файлом, если вашему двоичному файлу требуется вспомогательная библиотека, вам необходимо указать имя этой библиотеки с ее абсолютным путем.
Проблема 4, «Невозможно загрузиться» (initramfs)
Проблема: Производственная система Linux была перезагружена через четыре месяца для регулярного обслуживания и перестала загружаться. На экране продолжает выдаваться сообщение об ошибке:
.
dracut-initqueue[444]: warning: dracut-initqueue timeout — starting timeout scripts
dracut-initqueue[444]: warning: dracut-initqueue timeout — starting timeout scripts
dracut-initqueue[444]: warning: dracut-initqueue timeout — starting timeout scripts
dracut-initqueue[444]: warning: dracut-initqueue timeout — starting timeout scripts
.
Решение: Вот шаги для решения проблемы:
-
Сообщение об ошибке начинается с сообщения о невозможности доступа к устройству подкачки, а затем время ожидания процесса истекает.
[TIME] Timed out waiting for device /dev/mapper/fedora_localhost--live-swap
Это важная информация, поскольку она говорит о том, что с файловой системой этой системы что-то не так.
-
Устройство подкачки создано на жестком диске, на нем создана файловая система подкачки. Теперь само устройство подкачки отсутствует. Таким образом, либо сам базовый диск недоступен, либо файловая система подкачки повреждена. Имея это понимание, мы теперь можем сосредоточиться только на стороне хранения. Изолировать проблему важно, поскольку проблема «невозможно загрузиться» имеет тысячи ситуаций, которые могут привести к остановке загрузки системы.
-
Либо мы загрузимся в режиме восстановления, либо можем использовать живой образ того же дистрибутива и версии. Это система Fedora 31, и, как показано на рисунке 6-1, я воспользуюсь опцией восстановления из GRUB.
Рисунок 6-1. Заставка GRUB
-
После загрузки в режиме восстановления мы смонтируем корневую файловую систему пользователя и выполним в ней
chroot
. Почему же режим восстановления может загружаться, если обычное ядро не может загружаться в той же системе? Это правильный вопрос, и ответ будет рассмотрен в главе 10. -
Поскольку мы можем смонтировать корневую файловую систему в аварийном ядре, но не можем смонтировать ее в обычном ядре, это означает, что что-то не так с образом initramfs. Возможно, отсутствует какой-то модуль, необходимый для работы с HDD. Давайте проверим эту теорию.
-
Это виртуализированная система, то есть в ней есть виртуальный диск. Это можно увидеть из каталога
/dev
.# ls /dev/vd* vda vda1 vda2
-
Для работы с виртуализированными дисками нам необходимо наличие модуля
virtio_blk
в initramfs.# lsinitrd /boot/new.img | grep -i virt Arguments: --omit-drivers virtio_blk -rw-r--r-- 1 root root 14132 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/char/virtio_console.ko.xz -rw-r--r-- 1 root root 25028 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/net/virtio_net.ko.xz -rw-r--r-- 1 root root 7780 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/virtio_scsi.ko.xz -rw-r--r-- 1 root root 499 Feb 26 2018 usr/lib/sysctl.d/60-libvirtd.conf
Как вы можете видеть, модуль
virtio_blk
отсутствует. -
Поскольку
virtio_blk
отсутствует, очевидно, что ядро не может обнаружить и получить доступ к дискуvda
, на котором у пользователя есть корневая файловая система, а также файловая система подкачки. -
Чтобы решить эту проблему, нам нужно добавить отсутствующий модуль
virtio_blk
в initramfs.# dracut --add-drivers=virtio_blk /boot/new.img --force
# lsinitrd | grep -i virtio_blk -rw-r--r-- 1 root root 8356 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/block/virtio_blk.ko.xz
-
Мы загрузимся, используя наш initramfs
new.img
. Как загрузить систему вручную с помощью командной строки GRUB уже обсуждалось в разделе Проблема 1, «Невозможно загрузиться». -
После добавления отсутствующего модуля
virtio_blk
проблема «невозможно загрузиться» была исправлена. Вы можете увидеть успешно загрузившуюся систему на рисунке 6-2.
Рисунок 6-2. Экран входа в Fedora
Проблема 5, «Невозможно загрузиться» (initramfs)
Проблема: На рисунке 6-3 показано, что отображается на экране.
Рисунок 6-3. Сообщения консоли
Решение: Вот шаги для решения проблемы:
-
Теперь это легко понять и решить.
-
Сообщение об ошибке не требует пояснений; сам файл initramfs отсутствует.
-
Либо сам initramfs отсутствует, либо просто в файле
/boot/loader/entries/*
неверная запись. В нашем случае сам initramfs отсутствует. -
Итак, нам нужно загрузиться в режиме восстановления и смонтировать корневую файловую систему пользователя.
-
Либо переустановите пакет rpm ядра, чтобы часть пакета
postscripts
восстановила отсутствующие initramfs, а также соответствующим образом обновила записи BLS. -
Или можно перегенерировать initramfs с помощью команды
dracut
.
Параметры командной строки ядра
Как мы уже видели, GRUB принимает параметры командной строки ядра и передает их ядру. Ядро имеет сотни параметров командной строки, и практически невозможно охватить каждый параметр. Поэтому мы остановимся только на тех параметрах, которые необходимы при загрузке операционной системы. Если вас интересуют все параметры командной строки ядра, посетите следующую страницу: https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html.
Список параметров на этой странице относится к ядрам серии 4, но большая часть объяснений параметров применима и к ядрам серии 5. Лучший вариант — всегда просматривать документацию ядра по адресу /usr/share/doc/
.
root
-
Это один из основных параметров командной строки ядра. Конечная цель загрузки — смонтировать корневую файловую систему пользователя. Параметр командной строки ядра
root
предоставляет имя корневой файловой системы пользователя, которую ядро должно смонтировать. -
Systemd, запускаемый из initramfs, от имени ядра монтирует корневую файловую систему пользователя.
-
Если корневая файловая система пользователя недоступна или ядро не может ее смонтировать, это будет считаться панической ситуацией для ядра.
init
-
Ядро запускает systemd из initramfs, и это становится первым процессом. Он также называется PID-1 и является родительским для каждого процесса.
-
Но если вы разработчик и хотите запустить свой собственный двоичный файл вместо systemd, вы можете использовать параметр командной строки ядра
init
. Вот пример:init=/sbin/yogesh
Как вы можете видеть на рисунке 6-4, вместо systemd будет запущен двоичный файл
yogesh
.Рисунок 6-4. Параметры командной строки ядра
Но
yogesh
недоступен в реальной корневой файловой системе; следовательно, как показано на рисунке 6-5, он не сможет загрузиться.Рисунок 6-5. Аварийная оболочка
-
Система бросила нас в аварийную оболочку. Обратитесь к главе 8 для подробного обсуждения отладки оболочек.
-
Причина сброса нас в аварийную оболочку и причина проблемы «невозможно загрузиться» указаны в
/run/initramfs/rdsosreport.txt
. На рисунке 6-6 показан фрагмент файлаrdsosreport.txt
.Рисунок 6-6. Файл
rdsosreport.txt
-
Здесь интересно отметить, что наш двоичный файл
/sbin/yogesh
будет вызываться вchroot
-окружении фактической корневой файловой системы. Мы еще не обсуждалиchroot
; подробное обсуждение вы можете найти в главе 10.
ro
-
Это вспомогательный параметр для параметра командной строки ядра
root
. Параметрro
означает файловую систему «только для чтения» (read-only). Корневая файловая система пользователя будет смонтирована внутри initramfs и будет смонтирована в режиме только для чтения, если будет передан параметр командной строки ядраro
.ro
является выбором по умолчанию во всех основных дистрибутивах Linux.
rhgb и quite
-
Почти каждый дистрибутив Linux показывает анимацию во время загрузки, чтобы сделать процедуру загрузки более увлекательной, но важные сообщения консоли, необходимые для анализа последовательности загрузки, будут скрыты за анимацией. Чтобы остановить анимацию и увидеть на экране подробные сообщения консоли, удалите параметры
rhgb
иquite
. -
Когда переданы
rhgb
иquite
, как вы можете видеть на рисунке 6-7, будет показана анимацияplymouth
.Рисунок 6-7. Экран
plymouth
-
Когда
rhgb
иquite
удалены, как вы можете видеть на рисунке 6-8, сообщения консоли будут доступны пользователю.Рисунок 6-8. Сообщения консоли
-
Вы также можете нажать Escape на экране анимации (
plymouth
) и увидеть сообщения консоли, но для этого вам нужно физически присутствовать перед производственной системой, что маловероятно.
selinux
-
Иногда, чтобы решить проблемы «невозможно загрузиться», вам нужно полностью избавиться от SELinux. На это время вы можете передать параметр командной строки ядра
selinux=0
. Это полностью отключит SELinux.
Это были некоторые параметры командной строки ядра, которые напрямую влияют на последовательность загрузки. Как и в случае с параметрами командной строки ядра, GRUB также может принимать параметры командной строки dracut, которые будут приниматься initramfs или, точнее, systemd initramfs.
Параметры командной строки dracut
С точки зрения непрофессионала, вы можете рассматривать параметры командной строки, начинающиеся с rd.
, как параметры командной строки dracut, которые будут поняты initramfs.
rd.auto (rd.auto=1)
-
Согласно странице руководства, это позволяет автоматически собирать специальные устройства, такие как cryptoLUKS, dmraid, mdraid или lvm. По умолчанию выключено.
-
Мы можем рассмотреть сценарий, аналогичный предыдущему, когда в вашей системе не был настроен
mdraid
(s/w raid
), но позже вы реализовали его и хотите, чтобы это устройство активировалось во время загрузки. Другими словами, состояние хранилища машины изменяется во время создания initramfs. Теперь, не создавая новый initramfs, вы хотите, чтобы новая конфигурация (LVM или LUKS) была активирована во время загрузки.
rd.hostonly=0
-
Согласно странице руководства, этот параметр удаляет все скомпилированные в конфигурации хост-системы, на которых был построен образ initramfs. Это помогает при загрузке, если изменилась структура диска, особенно в сочетании с
rd.auto
или другими параметрами, определяющими структуру. -
Предположим, ваш поставщик видеокарты (например, Nvidia) предоставил вам специальные драйверы/модули, которые присутствуют в вашем initramfs, но эти модули начали создавать проблемы. Поскольку графический драйвер будет загружен на ранней стадии загрузки, вам следует избегать использования этого модуля; вместо этого вы хотите использовать универсальный драйвер (
vesa
). В этом сценарии вы можете использоватьrd.hostonly=0
. С помощью этого параметра initramfs загрузит универсальный драйвер и не будет использовать драйвер Nvidia, специфичный для хоста.
rd.fstab=0
-
Согласно странице руководства, используйте этот параметр, если вы не хотите использовать специальные параметры монтирования для корневой файловой системы, расположенной в
/etc/fstab
реального корня.
rd.skipfsck
-
Согласно странице руководства, этот параметр пропускает
fsck
дляrootfs
и/usr
. Если вы монтируете/usr
в режиме только для чтения иinit
системы выполняетfsck
перед перемонтированием, вы можете использовать эту опцию, чтобы избежать дублирования. -
Большинство администраторов Linux имеют неправильное представление о
fsck
и о том, как он сочетается с параметром командной строки ядраro
. Большинство из нас думает, что ядро сначала монтирует фактическую корневую файловую систему в режимеro
, а затем выполняет для нееfsck
, чтобы операцияfsck
не повредила данные корневой файловой системы. Как толькоfsck
завершится успешно, он перемонтирует корневую файловую систему в режиме чтения-записи, обратившись к/etc/fstab
. -
Но у этого понимания есть основной недостаток:
fsck
не может быть выполнен в смонтированной файловой системе независимо от режимаro
илиrw
.
Корневая файловая система пользователя следующей системы Fedora находится на устройстве sda5, и в настоящее время она смонтирована в режиме только для чтения, поэтому fsck
завершится ошибкой, поскольку файловая система смонтирована:
# fsck.ext4 /dev/sda5
e2fsck 1.45.3 (14-Jul-2019)
/dev/sda5 is mounted.
e2fsck: Cannot continue, aborting.
Таким образом, доказано, что целью монтирования корневой файловой системы пользователя в режиме ro
не является выполнение fsck
. Тогда в чем причина передачи параметра командной строки ro
ядру? Давайте обсудим это через последовательность загрузки.
-
Ядро извлекает initramfs и передает параметры командной строки, такие как
root
иro
, в systemd, который запускается из initramfs. -
systemd находит фактическую корневую файловую систему.
-
Как только корневая файловая система (устройство) определена, systemd выполняет для нее
fsck
. -
Если
fsck
прошел успешно, systemd монтирует корневую файловую систему какro
(согласно переданному параметру командной строки ядра) внутри самого initramfs. Она будет смонтирована только для чтения в каталоге/sysroot
initramfs. -
Как вы можете видеть на рисунке 6-9, ядро извлекло файл initramfs и запустило из него systemd (я удалил параметры
rhgb
иquite
).
Рисунок 6-9. Сообщения консоли
Затем Systemd просканировал подключенные устройства хранения на наличие корневой файловой системы и нашел ее. Прежде чем монтировать корневую файловую систему пользователя, сначала выполняется fsck
, а затем монтируется внутри initramfs в каталоге sysroot
. Корневая файловая система пользователя будет смонтирована в режиме только для чтения.
-
Причину монтирования в режиме только для чтения легко понять. Предположим, что система не загружается, но ей удалось смонтировать корневую файловую систему пользователя в
sysroot
и предоставить нам оболочку для решения проблемы «невозможно загрузиться». Пользователи могут случайно повредить или даже удалить корневую файловую систему пользователя, смонтированную подsysroot
. Поэтому, чтобы предотвратить такие несчастные случаи с корневой файловой системой пользователя, предпочтительно монтировать ее в режиме только для чтения.# switch_root:/# ls -ld /sysroot/ dr-xr-xr-x 19 root 0 4096 Sep 10 2017 /sysroot/
-
Как использовать оболочки отладки и как их предоставляет initramfs, будет обсуждаться в главе 8.
-
На рисунке 6-10 показано, как systemd продолжает свою последовательность загрузки и покидает среду initramfs.
Рисунок 6-10. Сообщения консоли
-
Как вы можете видеть на рисунке 6-10, переключатель root покидает текущую среду initramfs и изменяет root с временной корневой файловой системы initramfs на каталог
/sysroot
, в котором смонтирована корневая файловая система пользователя. (Процесс переключения root будет обсуждаться в главе 9.) -
Сразу после входа в корневую файловую систему пользователя systemd корневой файловой системы пользователя читает
/etc/fstab
и предпринимает соответствующие действия в точках монтирования. Например, в этой системе Fedora есть запись корневой файловой системы пользователя, а также запись/boot
(загрузка осуществляется в отдельном разделе):# cat /etc/fstab /dev/mapper/fedora_localhost--live-root / ext4 defaults 1 1 UUID=eea3d947-0618-4d8c-b083-87daf15b2679 /boot ext4 defaults 1 2 /dev/mapper/fedora_localhost--live-swap none swap defaults 0 0
-
Как вы можете видеть на рисунке 6-11, на этом этапе systemd выполнит
fsck
только на загрузочном устройстве перед его монтированием. Обратите внимание, чтоfsck
не выполняется в корневой файловой системе пользователя, поскольку он уже был выполнен внутри среды initramfs. Кроме того, корневая файловая система пользователя в настоящее время смонтирована, и мы все знаем, что нет смысла выполнятьfsck
на устройстве подкачки.Рисунок 6-11. Сообщения
fsck
в консоли -
Если бы были какие-либо другие дополнительные точки монтирования, такие как
/usr
, на этом устройстве также была бы выполненаfsck
. -
fsck
зависит от пятого параметра/etc/fstab
. Если он равен 1, тоfsck
будет выполняться во время загрузки. Этот параметрfstab
неприменим к корневой файловой системе пользователя, посколькуfsck
будет обязательно выполняться в корневой файловой системе пользователя внутри initramfs, то есть перед чтением файла/etc/fstab
. -
rd.skipfsck
применим только к root и к корневой файловой системе пользователя. Он неприменим ни к какой другой файловой системе, например/boot
.
rd.driver.blacklist, rd.driver.pre и rd.driver.post
Это из справочной страницы rd.driver.blacklist
:
rd.driver.blacklist=<drivername>[,<drivername>,...]
rd.driver.blacklist
— один из наиболее важных параметров командной строки dracut. Как следует из названия, он заносит в черный список указанные модули. Давайте попробуем занести в черный список драйверы, связанные с virtio
, которые очень важны для виртуальных гостевых систем.
# lsmod | grep -i virt
virtio_balloon 24576 0
virtio_net 57344 0
virtio_console 40960 2
virtio_blk 20480 3
net_failover 20480 1 virtio_net
Он также доступен в initramfs.
# lsinitrd | grep -i virtio
-rw-r--r-- 1 root root 8356 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/block/virtio_blk.ko.xz
-rw-r--r-- 1 root root 14132 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/char/virtio_console.ko.xz
-rw-r--r-- 1 root root 25028 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/net/virtio_net.ko.xz
-rw-r--r-- 1 root root 7780 Jul 25 03:54 usr/lib/modules/5.3.7-301.fc31.x86_64/kernel/drivers/scsi/virtio_scsi.ko.xz
Помните: чтобы занести модуль в черный список, как показано на рисунке 6-12, вам необходимо убедиться, что все остальные зависимые модули также должны быть занесены в черный список; в противном случае зависимые модули извлекут модуль из черного списка. Например, в этом случае модули virtio_balloon
, virtio_net
, virtio_console
, virtio_blk
и virtio_pci
зависят друг от друга. Это означает, что если мы внесем в черный список только virtio_blk
, другие зависимые модули все равно будут загружать модуль virtio_blk
.
Рисунок 6-12. Параметр командной строки ядра
Драйверы, связанные с virtio
, важны. Это тот же самый драйвер, посредством которого виртуальные диски и сети гипервизоров подвергаются воздействию гостевой операционной системы. Поскольку мы занесли их в черный список, гостевая ОС перестанет загружаться. Сообщения консоли «невозможно загрузиться» можно увидеть на рисунке 6-13.
Рисунок 6-13. Сообщения консоли
Итак, занесение модулей virtio
в черный список прошло успешно, но в этом подходе есть две проблемы:
-
rd.driver.blacklist
будет блокировать только те модули, которые загружаются из initramfs. -
Нам нужно каждый раз вручную предоставлять список модулей в
rd.driver.blacklist
.
Если модуля нет в initramfs, то вы не сможете реально заблокировать его загрузку. Например, модуль bluetooth
не загружается из initramfs, но ядро загружает его после среды initramfs.
# lsmod | grep -i bluetooth
bluetooth 626688 37 btrtl,btintel,btbcm,bnep,btusb,rfcomm
ecdh_generic 16384 1 bluetooth
rfkill 28672 5 bluetooth
# lsinitrd | grep -i bluetooth
<no_output>
Чтобы заблокировать ядру загрузку модуля bluetooth
, нам нужно указать команде modprobe
заблокировать загрузку модуля. modprobe
— это двоичный файл, который загружает или удаляет модули от имени ядра.
Создайте новый файл blacklist.conf
. (Вы можете выбрать любое имя, но оно должно иметь суффикс .conf
) и занесите модуль в черный список.
# cat /etc/modprobe.d/blacklist.conf
blacklist bluetooth
Но после перезагрузки вы обнаружите, что bluetooth
снова загружается ядром.
# lsmod | grep -i bluetooth
bluetooth 626688 37 btrtl,btintel,btbcm,bnep,btusb,rfcomm
ecdh_generic 16384 1 bluetooth
rfkill 28672 5 bluetooth
Это связано с тем, что от модуля bluetooth
зависят несколько других модулей, таких как btrtl
, btintel
, btbcm
, bnep
, btusb
, rfcomm
и rfkill
. Следовательно, modprobe
загрузил bluetooth
как зависимость от других модулей. В таких ситуациях нам нужно обмануть команду modprobe
, добавив строку install bluetooth /bin/true
в файл blacklist.conf
, как показано здесь:
# cat /etc/modprobe.d/blacklist.conf
install bluetooth /bin/true
После перезагрузки вы обнаружите, что модуль bluetooth
заблокирован.
# lsmod | grep -i bluetooth
<no_output>
Вы также можете использовать /bin/false
вместо /bin/true
.
После объяснения параметра rd.driver.blacklist
параметры командной строки rd.driver.pre
и rd.driver.post
dracut становятся проще для понимания, а справочные страницы не требуют пояснений, как показано здесь:
rd.driver.pre=<drivername>[,<drivername>,...]
<drivername>
. Этот параметр можно указывать несколько раз.rd.driver.post=<drivername>[,<drivername>,...]
<drivername>
после загрузки всех модулей автоматической загрузки. Этот параметр можно указывать несколько раз.rd.debug
Это взято со страницы руководства:
rd.debug
устанавливает -x
для оболочки dracut. Если systemd активен в initramfs, весь вывод записывается в журнал systemd, который вы можете проверить с помощью "journalctl -ab". Если systemd не активен, журналы записываются в dmesg
и /run/initramfs/init.log
. Если установлено "quiet", вывод также записывается в консоль.rd.debug
включит ведение журнала отладки systemd, который будет регистрировать огромные сообщения на консоли, а также в журналах systemd. Подробные сообщения, предоставляемые rd.debug
, будут полезны при выявлении проблем, связанных с невозможностью загрузки, связанных с systemd.
rd.memdebug=[0-4]
Это взято из справочной страницы:
0 — no output
1 — partial /proc/meminfo
2 — /proc/meminfo
3 — /proc/meminfo + /proc/slabinfo
4 — /proc/meminfo + /proc/slabinfo + tracekomem
Это выведет на экран всю информацию, связанную с подсистемой памяти, такую как содержимое файлов
meminfo
иslabinfo
.
Параметры командной строки dracut lvm, raid и множественного связывания
Это взято со страниц руководства:
rd.lvm=0
rd.lvm.vg=<volume group name>
rd.lvm.vg
можно указать несколько раз в командной строке ядра.rd.lvm.lv=<logical volume name>
rd.lvm.lv
можно указать несколько раз в командной строке ядра.rd.lvm.conf=0
/etc/lvm/lvm.conf
, который может существовать в initramfs-
Из этих параметров вы должны, по крайней мере, обратить внимание на параметр
rd.lvm.lv
, передаваемый GRUB. Цельюrd.lvm.lv
является активация данного устройства LVM на ранней стадии загрузки. По умолчанию основные дистрибьюторы Linux активируют только root и заменяют (если настроено) устройства LV. Активация только корневой файловой системы во время загрузки ускоряет процедуру загрузки. После переключения корня с initramfs на фактическую корневую файловую систему systemd может активировать оставшиеся группы томов согласно списку в/etc/fstab
. -
Аналогичным образом, dracut предоставляет параметры командной строки, связанные с множественным связыванием и RAID, которые также не требуют пояснений.
MD RAID
rd.md=0
rd.md.imsm=0
imsm/isw
, вместо этого использовать DM RAIDrd.md.ddf=0
ddf
, вместо этого использовать DM RAIDrd.md.conf=0
mdadm.conf
, включенный в initramfsrd.md.waitclean=1
rd.md.uuid=<md raid uuid>
DM RAID
rd.dm=0
rd.dm.uuid=<dm raid uuid>
MULTIPATH
rd.multipath=0
dracut предоставляет n параметров командной строки для сетей, NFS, CIFS, iSCSI, FCoE и т. д. Это также означает, что существуют различные параметры, на которые вы можете настроить свою корневую файловую систему, но практически невозможно охватить все без исключения параметры командной строки dracut. Также я не сторонник загрузки системы со всеми этими сложными структурами. Я верю в то, что корневая файловая система пользователя всегда должна находиться на локальном диске, чтобы процедура загрузки была простой, главным образом потому, что более простая последовательность загрузки позволяет быстрее исправить ситуацию в случае возникновения проблемы «невозможно загрузиться».
rd.break и rd.shell
rd.shell
предоставит нам оболочку в конце последовательности загрузки, а с помощью rd.break
мы можем прервать последовательность загрузки. Но чтобы понять эти параметры, нам нужно хорошо разбираться в systemd. Следовательно, прежде чем обсуждать rd.break
и хуки dracut, мы сначала обсудим systemd в следующей главе. Ниже приведены параметры, принимаемые rd.break
:
Параметры | Цель |
---|---|
cmdline |
Собирает параметры командной строки ядра. |
pre-udev |
Запускается перед запуском обработчика udev . |
pre-trigger |
В этом хуке вы можете установить переменные среды udev с помощью 'udevadm' control --property=KEY=value или управлять дальнейшим выполнением udev . |
pre-mount |
Запускается перед монтированием корневой файловой системы пользователя в /sysroot . |
mount |
Запускается после монтирования корневой файловой системы в /sysroot . |
pre-pivot |
Выполняется непосредственно перед переключением на фактическую корневую файловую систему. |
Глава 7
systemd (часть I)
Вот что мы знаем о последовательности загрузки на данный момент:
-
Загрузчик загружает ядро и initramfs в память.
-
Ядро будет загружено в определенное место (место, зависящее от архитектуры), тогда как initramfs будет загружен в любое доступное место.
-
Ядро извлекает само себя с помощью заголовка файла
vmlinuz
. -
Ядро извлекает initramfs в основную память (
init/initramfs.c
) и монтирует его как временную корневую файловую систему (/
) в основную память. -
Ядро запускает (
init/main.c
) systemd в качестве первого процесса с PID-1 из временной корневой файловой системы. -
systemd находит корневую файловую систему пользователя и выполняет
chroot
-доступ к ней.
В этой главе рассматривается, как systemd, получивший управление от initramfs, монтирует корневую файловую систему пользователя, а также приводится детальная последовательность загрузки системы внутри initramfs. Но перед этим нам нужно понять systemd как процесс.
Приведу цитату из справочной страницы systemd:
Структура
systemd был впервые представлен в Fedora 15. Мы все знаем, что systemd — это замена сценариев инициализации (в буквальном смысле, /sbin/init
теперь является символической ссылкой на /usr/lib/systemd/systemd
), и он удивительно сокращает время загрузки. Однако на самом деле systemd — это нечто большее, чем просто замена init
. Вот что делает systemd:
-
Ведет журналы с помощью
journalctl
. -
Широко использует cgroups версии 1 и 2.
-
Сокращает время загрузки.
-
Управляет юнитами.
service
— это всего лишь один тип юнита, который обрабатывает systemd. Ниже приведены юниты, которые systemd предоставляет и управляет ими:
Юнит | Цель |
---|---|
systemd.service |
Для управления сервисами |
systemd.socket |
Для создания сокетов и управления ими |
systemd.device |
Для создания и использования устройств на основе входных данных udev |
systemd.mount |
Для монтирования файловой системы |
systemd.automount |
Для автоматического монтирования файловой системы |
systemd.swap |
Для создания устройств подкачки и управления ими |
systemd.target |
Группа сервисов вместо уровней запуска |
systemd.path |
Информация о пути, отслеживаемом systemd, для активации на основе пути |
systemd.timer |
Для активации по времени |
systemd.slic |
Управление ресурсами, такими как CPU, память, ввод-вывод для сервисных юнитов |
Файлы юнитов будут храниться и загружаться из этих трех мест:
Путь | Описание |
---|---|
/etc/systemd/system |
Локальная конфигурация |
/run/systemd/system |
Юниты времени выполнения |
/usr/lib/systemd/system |
Юниты установленных пакетов |
/etc/systemd/system
— это местоположение администратора, тогда как /usr/lib/systemd/system
— это местоположение поставщика приложений. Это означает, что местоположение администратора будет иметь приоритет над местоположением поставщика приложения, если один и тот же файл юнита присутствует в обоих местах. Обратите внимание, что в этой главе все команды выполняются из каталога, в который был распакован initramfs.
# tree etc/systemd/
etc/systemd/
├── journald.conf
└── system.conf
0 directories, 2 files
# ls usr/lib/systemd/system | column
basic.target plymouth-switch-root.service
cryptsetup.target poweroff.target
ctrl-alt-del.target poweroff.target.wants
default.target reboot.target
dracut-cmdline-ask.service reboot.target.wants
dracut-cmdline.service remote-fs-pre.target
dracut-emergency.service remote-fs.target
dracut-initqueue.service rescue.service
dracut-mount.service rescue.target
dracut-pre-mount.service rescue.target.wants
dracut-pre-pivot.service rpcbind.target
dracut-pre-trigger.service shutdown.target
dracut-pre-udev.service sigpwr.target
emergency.service slices.target
emergency.target sockets.target
emergency.target.wants sockets.target.wants
final.target swap.target
halt.target sysinit.target
halt.target.wants sysinit.target.wants
initrd-cleanup.service sys-kernel-config.mount
initrd-fs.target syslog.socket
initrd-parse-etc.service systemd-ask-password-console.path
initrd-root-device.target systemd-ask-password-console.service
initrd-root-fs.target systemd-ask-password-console.service.wants
initrd-switch-root.service systemd-ask-password-plymouth.path
initrd-switch-root.target systemd-ask-password-plymouth.service
initrd-switch-root.target.wants systemd-ask-password-plymouth.service.wants
initrd.target systemd-fsck@.service
initrd.target.wants systemd-halt.service
initrd-udevadm-cleanup-db.service systemd-journald-audit.socket
kexec.target systemd-journald-dev-log.socket
kexec.target.wants systemd-journald.service
kmod-static-nodes.service systemd-journald.socket
local-fs-pre.target systemd-kexec.service
local-fs.target systemd-modules-load.service
multi-user.target systemd-poweroff.service
multi-user.target.wants systemd-random-seed.service
network-online.target systemd-reboot.service
network-pre.target systemd-sysctl.service
network.target systemd-tmpfiles-setup-dev.service
nss-lookup.target systemd-tmpfiles-setup.service
nss-user-lookup.target systemd-udevd-control.socket
paths.target systemd-udevd-kernel.socket
plymouth-halt.service systemd-udevd.service
plymouth-kexec.service systemd-udev-settle.service
plymouth-poweroff.service systemd-udev-trigger.service
plymouth-quit.service systemd-vconsole-setup.service
plymouth-quit-wait.service timers.target
plymouth-reboot.service umount.target
plymouth-start.service
Третье местоположение, /run/systemd/system
, является временным и будет использоваться внутри системы systemd для управления юнитами. Например, оно будет широко использоваться при создании сокетов. Фактически, /run
— это отдельная файловая система, представленная в systemd для хранения данных времени выполнения. На данный момент в initramfs каталог /run
пуст, что очевидно, поскольку initramfs не используется.
# ls run/
<no_output>
Кроме того, ожидается, что в initramfs будет меньше файлов юнитов, чем в корневой файловой системе пользователя. dracut соберет только те файлы юнитов systemd, которые необходимы для монтирования корневой файловой системы пользователя. Например, нет смысла добавлять в initramfs файлы юнитов systemd, связанные с httpd
или mysql
. Давайте попробуем разобраться в одном из файлов юнита service
systemd, как показано здесь:
# cat /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.target
Wants=sshd-keygen.target
[Service]
Type=notify
EnvironmentFile=-/etc/crypto-policies/back-ends/opensshserver.config
EnvironmentFile=-/etc/sysconfig/sshd-permitrootlogin
EnvironmentFile=-/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS $CRYPTO_POLICY $PERMITROOTLOGIN
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=multi-user.target
Этот файл юнита службы sshd
не будет частью initramfs, поскольку вам не нужна служба ssh
для монтирования корневой файловой системы пользователя. Файл юнита service
разделен на три части: [Unit]
, [Service]
и [Install]
:
-
[Unit]:
After=network.target sshd-keygen.target
Служба
sshd
запустится только в том случае, еслиnetwork.target
иsshd-keygen
(т.е. перечисленные юниты) были успешно запущены. Если какой-либо из них потерпит неудачу, то службаsshd
также потерпит неудачу.Wants=sshd-keygen.target
Это менее серьезная версия
Requires
. Если какой-либо из юнитов, упомянутых в разделеWants
, не запустится, то служба sshd (или другая зависимая служба) все равно запустится, тогда как в случае сRequires
службаsshd
запустится только если юниты, упомянутые в разделеRequires
, были успешно запущены.Before
является противоположностьюAfter
.Wants
,After
,Before
иRequires
работают независимо друг от друга. Общепринятой практикой является совместное использованиеWants
иAfter
.Conflicts=
Эту директиву можно использовать для вывода списка юнитов, конфликтующих с текущим юнитом. Запуск этого юнита может остановить перечисленные конфликтующие юниты.
OnFailure=
Юниты
OnFailure
запустятся, когда какой-либо конкретный юнит достигнет состояния сбоя. -
[Service]:
ExecStart=/usr/sbin/sshd
Запуск юнита
sshd
просто запускает двоичный файл, упомянутый вExecStart
. -
[Install]:
Раздел
Install
юнит-файла не используется systemd. Скорее, он используется командойsystemctl enable
илиdisable
. Он будет использоватьсяsystemctl
для создания или удаления символических ссылок.
Как systemd сокращает время загрузки?
Леннарт Пёттеринг, создатель systemd, в своем блоге http://0pointer.de/blog/projects/systemd.html приводит классический пример того, как systemd сокращает время загрузки. Этот блог — один из лучших ресурсов, если вы действительно хотите глубоко погрузиться в мир systemd.
Существует четыре демона: syslog
, dbus
, avahi
и bluetooth
.
syslog
необходим каждому демону для регистрации сообщений. Итак, syslog
является обязательным требованием для любого другого демона. Для работы avahi
необходим syslog
и dbus
. bluetooth
нуждается в dbus
и syslog
, но не требует запуска avahi
. При использовании модели сценариев SysV Init
происходит следующее:
Первым запускается
syslog
.Когда он будет полностью готов, будет запущен сервис
dbus
.После
dbus
запуститсяavahi
.Наконец, будет запущена служба
bluetooth
. См. рисунок 7-1.
Рисунок 7-1. Модель инициализации
bluetooth
и avahi
не зависят друг от друга, но bluetooth
должен ждать, пока запустится avahi
. Дистрибутивы, подобные Ubuntu, используют upstart
вместо init
, что в некоторой степени ускоряет загрузку. В upstart
службы, которые не зависят друг от друга, будут запускаться параллельно, то есть avahi
и bluetooth
будут запускаться вместе. Для справки см. рисунок 7-2.
Рисунок 7-2. Модель upstart
В systemd
все службы запускаются одновременно с помощью sockets
. Вот пример:
-
systemd создаст сокет для
syslog
(который был заменен наjournald
). -
Сокет
/dev/log
— это символическая ссылка на файл/run/systemd/journal/dev-log
.# file /dev/log /dev/log: symbolic link to /run/systemd/journal/dev-log
# file /run/systemd/journal/dev-log /run/systemd/journal/dev-log: socket
Как упоминалось ранее, файловая система
run
будет использоваться systemd для создания файла сокета. -
Для
dbus
сокет создается в/run/dbus/system_bus_socket
. Для запускаdbus
необходимо, чтобы был запущен журналjournald
, но поскольку система все еще загружается, а журналjournald/syslog
еще не полностью запущен,dbus
записывает свои сообщения в сокет журналаjournald
/dev/log
, и всякий раз, когда службаjournald
полностью готова, он будет получать сообщения из сокета. -
То же самое касается службы
bluetooth
; для запуска необходимо, чтобы службаdbus
была запущена. Таким образом, systemd создаст сокет/run/dbus/system_bus_socket
перед запуском службыdbus
. Службаbluetooth
не будет ждать запускаdbus
. Для лучшего понимания вы можете обратиться к рисунку 7-3.Рисунок 7-3. Модель systemd
-
Если в сокете, созданном
systemd
, заканчивается буфер, службаbluetooth
будет заблокирована до тех пор, пока сокет не станет доступен. Такой подход к сокетам значительно сократит время загрузки.
Этот подход на основе сокетов изначально был опробован в macOS. В то время это называлось launchd
. Леннарт Пёттеринг черпал из этого вдохновение.
systemd-analyze
systemd предоставляет инструмент systemd-analyze
для проверки времени загрузки системы.
# systemd-analyze
Startup finished in 1.576s (kernel) + 1.653s (initrd) + 11.574s (userspace) = 14.805s
graphical.target reached after 11.561s in userspace
Как видите, моей системе Fedora потребовалось 1,5 секунды для инициализации ядра; затем она провела 1,6 секунды внутри initramfs и почти 11 секунд потребовалось для запуска служб или инициализацию пользовательского пространства. Общее время составило почти 15 секунд. Общее время рассчитывается непосредственно от загрузчика до графического интерфейса.
Несколько важных примечаний:
-
Общее время не включает время, затраченное средами рабочего стола, такими как GNOME, KDE, Cinnamon и т. д. Это имеет смысл, поскольку среды рабочего стола не обрабатываются systemd, поэтому инструмент systemd не может рассчитать затраченное время по средам рабочего стола.
-
Кроме того, существует вероятность того, что из-за подхода сокетов systemd службы все равно запускались даже по истечении общего времени (14,805 секунд).
Итак, чтобы получить больше информации и очистить данные, systemd-analyse
предоставляет инструмент blame
.
# systemd-analyze blame
31.202s dnf-makecache.service
10.517s pmlogger.service
9.264s NetworkManager-wait-online.service
4.977s plymouth-switch-root.service
2.994s plymouth-quit-wait.service
1.674s systemd-udev-settle.service
1.606s lightdm.service
1.297s pmlogger_check.service
938ms docker.service
894ms dracut-initqueue.service
599ms pmcd.service
590ms lvm2-monitor.service
568ms abrtd.service
482ms firewalld.service
461ms systemd-logind.service
430ms lvm2-pvscan@259:3.service
352ms initrd-switch-root.service
307ms bolt.service
290ms systemd-machined.service
288ms registries.service
282ms udisks2.service
269ms libvirtd.service
255ms sssd.service
209ms systemd-udevd.service
183ms systemd-journal-flush.service
180ms docker-storage-setup.service
169ms systemd-journald.service
156ms polkit.service
.
.
Вывод blame
может быть легко понят неправильно; т. е. две службы могут инициализироваться одновременно, и, таким образом, время, затрачиваемое на инициализацию обеих служб, намного меньше, чем сумма обоих отдельных времен вместе взятых. Для получения более точных данных вы можете использовать инструмент построения графиков systemd-analyse
, который сгенерирует график и предоставит множество дополнительных сведений о времени загрузки. Вы можете увидеть сгенерированное изображение графика на рисунке 7-4.
# systemd-analyze plot > plot.svg
# eog plot.svg
Рисунок 7-4. Сгенерированное изображение графика
Ниже приведены некоторые другие инструменты, предоставляемые systemd-analyze, которые можно использовать для определения времени загрузки.
systemd-analyze <tool> | Описание |
---|---|
time |
Печатает время, проведенное в ядре. |
blame |
Печатает список запущенных юнитов, упорядоченный по времени init . |
critical-chain [UNIT...] |
Печатает дерево критической по времени цепочки юнитов. |
plot |
Выводит график в SVG, показывающий инициализацию служб. |
dot [UNIT...] |
Выводит график зависимостей в формате dot(1) . |
log-level [LEVEL] |
Получает/устанавливает порог регистрации для менеджера. |
log-target [TARGET] |
Получает/устанавливает цель ведения журнала для менеджера. |
dump |
Сериализация выходного состояния диспетчера служб. |
cat-config |
Показывает файл конфигурации и дополнительные юниты. |
unit-files |
Перечисляет файлы и символические ссылки для юнитов. |
units-paths |
Перечисляет каталоги загрузки для юнитов. |
exit-status [STATUS...] |
Перечисляет определения статуса завершения. |
syscall-filter [NAME...] |
Печатает список системных вызовов в фильтре seccomp. |
condition... |
Оценивает условия и утверждения. |
verify FILE... |
Проверяет файлы юнитов на корректность. |
service-watchdogs [BOOL] |
Получает/устанавливает состояние наблюдения за службой. |
calendar SPEC... |
Проверяет повторяющиеся события календарного времени. |
timestamp... |
Проверяет метку времени. |
timespan SPAN... |
Проверяет временной интервал. |
security [UNIT...] |
Анализирует безопасность юнита. |
Проблема 6, «Невозможно загрузиться» (systemd)
Проблема: Система успешно загружается, но служба nagios
не запускается во время загрузки.
Вот шаги для решения этой проблемы:
-
Сначала нам нужно изолировать проблему. Удалите параметры командной строки ядра
rhgb quiet
, когда на экране появится GRUB. -
Подробные журналы показывают, что система может загружаться, но служба
nagios
не запускается во время загрузки. Как видите, службаNetworkManager
systemd, отвечающая за сеть, успешно запустилась. Это означает, что это не проблема с сетевым соединением.13:23:52 systemd: Starting Network Manager... 13:23:52 systemd: Started Kernel Samepage Merging (KSM) Tuning Daemon. 13:23:52 systemd: Started Install ABRT coredump hook. 13:23:52 abrtd: Init complete, entering main loop 13:23:52 systemd: Started Load CPU microcode update. 13:23:52 systemd: Started Authorization Manager. 13:23:53 NetworkManager[1356]: <info> [1534389833.1078] NetworkManager is starting... (for the first time) 13:23:53 NetworkManager[1356]: <info> [1534389833.1079] Read config: /etc/NetworkManager/NetworkManager.conf (lib: 00-server.conf, 10-slaves-order.conf) 13:23:53 NetworkManager[1356]: <info> [1534389833.1924] manager[0x558b0496a0c0]: monitoring kernel firmware directory '/lib/firmware'. 13:23:53 NetworkManager[1356]: <info> [1534389833.2051] dns-mgr[0x558b04971150]: init: dns=default, rc-manager=file 13:23:53 systemd: Started Network Manager.
-
Служба
nagios
пытается запуститься сразу после службыNetworkManager
. Это означает, чтоnagios
должен был указатьAfter=network.target
в своем юнит-файле. Но службаnagios
не запускается.13:24:03 nagios: Nagios 4.2.4 starting... (PID=5006) 13:24:03 nagios: Local time is Thu 13:24:03 AEST 2018 13:24:03 nagios: LOG VERSION: 2.0 13:24:03 nagios: qh: Socket '/usr/local/nagios/var/rw/nagios.qh' successfully initialized 13:24:03 nagios: qh: core query handler registered 13:24:03 nagios: nerd: Channel hostchecks registered successfully 13:24:03 nagios: nerd: Channel servicechecks registered successfully 13:24:03 nagios: nerd: Channel opathchecks registered successfully 13:24:03 nagios: nerd: Fully initialized and ready to rock! Nagios Can't ping devices (not 100% packet loss at the end of each line) 13:24:04 nagios: HOST ALERT: X ;DOWN;SOFT;1;CRITICAL — X: Host unreachable @ X. rta nan, lost 100%
Решение: Странно то, что в сообщении об ошибке nagios
говорится, что его не удалось запустить, поскольку он не может подключиться к сети, но, согласно NetworkManager
, он успешно запущен, и система уже подключена к сети.
Проблема явно вызвана подходом systemd к «ускорению процедуры загрузки». Чтобы разместить систему в сети, systemd должен проделать большую работу: инициализировать сетевые карты, активировать ссылку, поместить IP на сетевую карту, проверить, доступны ли уже дублирующиеся IP-адреса, начать общение в сети и т. д. Очевидно, что чтобы закончить все это, systemd потребуется некоторое время. В моей тестовой системе полная загрузка сети заняла почти 20 секунд. Конечно, systemd не может приостановить загрузку на все это время. Если systemd будет ждать, пока сеть полностью загрузится, то один из основных аспектов нововведения systemd по ускорению процесса загрузки будет потерян.
systemd с помощью NetworkManager
сделает все возможное, чтобы убедиться, что мы находимся в сети, но он не будет ждать создания указанной пользователем сети и не будет ждать, пока будут выполнены все правила топологии.
В некоторых ситуациях, подобных этой проблеме «невозможно загрузиться», возможно, что NetworkManager
приказал systemd инициализировать nagios
, который зависел от network.target
, но сеть еще не полностью загружена, поэтому nagios
не может связаться со своими серверами.
-
Чтобы решить такие проблемы, systemd предлагает включить
NetworkManager-wait-online.service
. Эта служба заставитNetworkManager
ждать, пока сеть полностью не загрузится. Как только сеть будет полностью загружена,NetworkManager
подаст сигнал systemd о запуске служб, зависящих отnetwork.target
.# cat /usr/lib/systemd/system/NetworkManager-wait-online.service [Unit] Description=Network Manager Wait Online Documentation=man:nm-online(1) Requires=NetworkManager.service After=NetworkManager.service Before=network-online.target [Service] Type=oneshot ExecStart=/usr/bin/nm-online -s -q --timeout=30 RemainAfterExit=yes [Install] WantedBy=network-online.target
Это просто вызывает двоичный файл
nm-online
и передает ему ключ -s. Служба будет приостанавливать работуNetworkManager
максимум на 30 секунд.Вот что говорится на странице руководства об nm-online:
- «Дождитесь завершения запуска
NetworkManager
, а не просто ожидайте подключения к сети. Запуск считается завершенным, как толькоNetworkManager
активирует (или попытается активировать) все автоматически активируемые соединения, доступные с учетом текущего состояния сети. (Обычно это полезно только во время загрузки; после завершения загрузки командаnm-online -s
сразу завершит работу, независимо от текущего состояния сети.)» - «Дождитесь завершения запуска
-
После включения
NetworkManager-wait-online-service
проблема была решена, а время загрузки немного сократилось. Как вы можете видеть на рисунке 7-5, большую часть времени загрузки, как и ожидалось, заняла службаNetworkManager-wait-online-service
.
Рисунок 7-5. График после включения службы NetworkManager-wait-online-service
systemd предоставляет еще один инструмент — bootchart
, который по сути представляет собой демон, с помощью которого вы можете провести анализ производительности процесса загрузки Linux. Он соберет данные во время загрузки и построит из них график. Вы можете рассматривать загрузочную диаграмму как расширенную версию графика systemd-analyze
. Чтобы использовать этот инструмент, как показано на рисунке 7-6, вам необходимо передать полный путь к двоичному файлу systemd-bootchart
в параметр командной строки ядра init
.
Рисунок 7-6. Параметры командной строки ядра
После успешного завершения загрузки, как показано на рисунке 7-7, инструмент создаст детализированный график загрузки в виде изображения в каталоге /run/log/bootchart*
. После создания изображения systemd-bootchart
передаст управление systemd, и systemd продолжит процесс загрузки.
Рисунок 7-7. График загрузки
Поскольку теперь мы понимаем основы systemd, мы можем продолжить нашу приостановленную загрузку. На данный момент мы достигли стадии, когда ядро извлекло initramfs из оперативной памяти и запустило из него бинарный файл systemd. После запуска процесса systemd он будет следовать обычной последовательности загрузки.
Поток systemd внутри initramfs
systemd будет запущен из initramfs и будет следовать последовательности загрузки, показанной на рисунке 7-8. Харальд Хойер (который создал dracut initramfs и является ведущим разработчиком systemd) создал эту блок-схему, которая также доступна на страницах руководства systemd.
Рисунок 7-8. Блок-схема загрузки
Эта блок-схема взята из man-страницы dracut. Конечная цель systemd в процедуре загрузки — смонтировать корневую файловую систему пользователя внутри initramfs (sysroot
) и затем переключиться на нее. Как только systemd перенесет switch_root
в новую корневую файловую систему (пользователя), он покинет среду initramfs и продолжит процедуру загрузки, запустив службы пользовательского пространства, такие как httpd
, mysql
и т. д. Он также отрисует рабочий стол/графический интерфейс, если пользователь загружает систему в графическом режиме. Целью этой книги является описание последовательности загрузки до тех пор, пока systemd не смонтирует корневую файловую систему пользователя и затем переключится на нее. Есть несколько причин не описывать последовательность загрузки после switch_root
. Здесь я упомяну причины, которые очень важны:
-
Конечная цель загрузки — смонтировать корневую файловую систему пользователя и предоставить ее пользователю, что подробно рассматривается в этой книге.
-
Действия, выполняемые systemd после initramfs, легко понять, поскольку systemd выполняет аналогичные действия, но в новой среде корневой файловой системы.
-
Производственные системы обычно не работают в графическом режиме.
-
В Linux есть несколько рабочих столов, таких как GNOME, KDE, Cinnamon, Unity и т. д. У каждого пользователя есть свой любимый рабочий стол, и практически невозможно документировать каждый шаг, выполняемый каждым рабочим столом во время загрузки.
Итак, учитывая это понимание, в этой главе мы рассмотрим последовательность загрузки до basic.target
. См. рисунок 7-9.
Рисунок 7-9. Последовательность загрузки до basic.target
systemd-journal.socket
Каждый процесс должен регистрировать свои сообщения. Фактически процесс, служба или демон запустится только в том случае, если сможет регистрировать свои сообщения в механизме журналирования ОС. В настоящее время механизм журналирования ОС ведется в journald
. Итак, очевидно, что сначала необходимо запустить службу journald
, но, как мы знаем, systemd не будет ждать, пока службы полностью запустятся. Чтобы ускорить процедуру, используется подход сокетов. Следовательно, systemd должен сначала запустить сокеты journald
. Служба journald
создает следующие четыре сокета и прослушивает сообщения:
systemd-journald.socket
systemd-journald-dev-log.socket
systemd-journald-audit.socket
syslog.socket
Эти сокеты будут использоваться демонами, приложениями и каждым процессом для регистрации своих сообщений.
# vim usr/lib/systemd/system/systemd-journald.socket
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Journal Socket
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Before=sockets.target
# Mount and swap units need this. If this socket unit is removed by an
# isolate request the mount and swap units would be removed too,
# hence let's exclude this from isolate requests.
IgnoreOnIsolate=yes
[Socket]
ListenStream=/run/systemd/journal/stdout
ListenDatagram=/run/systemd/journal/socket
SocketMode=0666
PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
Service=systemd-journald.service
# cat usr/lib/systemd/system/systemd-journald-dev-log.socket | grep -v '#'
[Unit]
Description=Journal Socket (/dev/log)
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Before=sockets.target
IgnoreOnIsolate=yes
[Socket]
Service=systemd-journald.service
ListenDatagram=/run/systemd/journal/dev-log
Symlinks=/dev/log
SocketMode=0666
PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
SendBuffer=8M
Мы уже обсуждали, как работают сокеты, особенно сокет /dev/log
. Следующим шагом в последовательности загрузки является dracut-cmdline.service
.
dracut-cmdline.service
После инициализации сокетов journald
systemd собирает параметры командной строки ядра, такие как переменные root
, rflags
и fstype
, через /usr/lib/systemd/system/dracut-cmdline.service
. Это также называется хуком cmdline для initramfs, о которой мы упоминали в конце главы 6. Этот хук можно вызвать, передав значение cmdline
в rd.break
(параметр командной строки dracut). Мы рассмотрим этот этап процесса загрузки, используя хук cmdline
. Нам нужно передать параметр командной строки dracut rd.break=cmdline
ядру во время загрузки.
Внутри initramfs systemd вызывает этот хук из /usr/lib/systemd/system/dracut-cmdline.service
.
# cat usr/lib/systemd/system/dracut-cmdline.service
# This file is part of dracut.
#
# See dracut.bootup(7) for details
[Unit]
Description=dracut cmdline hook
Documentation=man:dracut-cmdline.service(8)
DefaultDependencies=no
Before=dracut-pre-udev.service
After=systemd-journald.socket
Wants=systemd-journald.socket
ConditionPathExists=/usr/lib/initrd-release
ConditionPathExistsGlob=|/etc/cmdline.d/*.conf
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/cmdline
ConditionKernelCommandLine=|rd.break=cmdline
ConditionKernelCommandLine=|resume
ConditionKernelCommandLine=|noresume
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-cmdline
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
Как видите, systemd вызвал скрипт dracut-cmdline
. Скрипт доступен в самом initramfs, который собирает параметры командной строки ядра.
# vim bin/dracut-cmdline
# Get the "root=" parameter from the kernel command line, but differentiate
# between the case where it was set to the empty string and the case where it
# wasn't specified at all.
if ! root="$(getarg root=)"; then
root_unset='UNSET'
fi
rflags="$(getarg rootflags=)"
getargbool 0 ro && rflags="${rflags},ro"
getargbool 0 rw && rflags="${rflags},rw"
rflags="${rflags#,}"
fstype="$(getarg rootfstype=)"
if [ -z "$fstype" ]; then
fstype="auto"
fi
export root
export rflags
export fstype
make_trace_mem "hook cmdline" '1+:mem' '1+:iomem' '3+:slab' '4+:komem'
# run scriptlets to parse the command line
getarg 'rd.break=cmdline' -d 'rdbreak=cmdline' && emergency_shell -n cmdline "Break before cmdline"
source_hook cmdline
[ -f /lib/dracut/parse-resume.sh ] && . /lib/dracut/parse-resume.sh
case "${root}${root_unset}" in
block:LABEL=*|LABEL=*)
root="${root#block:}"
root="$(echo $root | sed 's,/,\\x2f,g')"
root="block:/dev/disk/by-label/${root#LABEL=}"
rootok=1 ;;
block:UUID=*|UUID=*)
root="${root#block:}"
root="block:/dev/disk/by-uuid/${root#UUID=}"
rootok=1 ;;
block:PARTUUID=*|PARTUUID=*)
root="${root#block:}"
root="block:/dev/disk/by-partuuid/${root#PARTUUID=}"
rootok=1 ;;
block:PARTLABEL=*|PARTLABEL=*)
root="${root#block:}"
root="block:/dev/disk/by-partlabel/${root#PARTLABEL=}"
rootok=1 ;;
/dev/*)
root="block:${root}"
rootok=1 ;;
UNSET|gpt-auto)
# systemd's gpt-auto-generator handles this case.
rootok=1 ;;
esac
[ -z "${root}${root_unset}" ] && die "Empty root= argument"
[ -z "$rootok" ] && die "Don't know how to handle 'root=$root'"
export root rflags fstype netroot NEWROOT
export -p > /dracut-state.sh
exit 0
По сути, в этом хуке будут экспортироваться три параметра (параметры командной строки ядра):
root = имя корневой файловой системы пользователя.
rflags = флаги корневой файловой системы пользователя (
ro
илиrw
)fstype = Auto (автоматическое монтирование или нет)
Давайте посмотрим, как эти параметры обнаруживаются с помощью initramfs (или с помощью хука cmdline initramfs). Для получения этих трех параметров командной строки ядра будет использоваться именованная функция getarg
.
root="$(getarg root=)
rflags="$(getarg rootflags=)
fstype="$(getarg rootfstype=)"
.
.
export root
export rflags
export fstype
Функция getarg
определена в файле /usr/lib/dracut-lib.sh
initramfs.
# vim usr/lib/dracut-lib.sh
getarg() {
debug_off
local _deprecated _newoption
while [ $# -gt 0 ]; do
case $1 in
-d) _deprecated=1; shift;;
-y) if _dogetarg $2 >/dev/null; then
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Kernel command line option '$2' is deprecated, use '$_newoption' instead." || warn "Option '$2' is deprecated."
fi
echo 1
debug_on
return 0
fi
_deprecated=0
shift 2;;
-n) if _dogetarg $2 >/dev/null; then
echo 0;
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Kernel command line option '$2' is deprecated, use '$_newoption=0' instead." || warn "Option '$2' is deprecated."
fi
debug_on
return 1
fi
_deprecated=0
shift 2;;
*) if [ -z "$_newoption" ]; then
_newoption="$1"
fi
if _dogetarg $1; then
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Kernel command line option '$1' is deprecated, use '$_newoption' instead." || warn "Option '$1' is deprecated."
fi
debug_on
return 0;
fi
_deprecated=0
shift;;
esac
done
debug_on
return 1
}
Функция getarg
вызывает функцию _dogetarg
из того же файла.
_dogetarg() {
local _o _val _doecho
unset _val
unset _o
unset _doecho
CMDLINE=$(getcmdline)
for _o in $CMDLINE; do
if [ "${_o%%=*}" = "${1%%=*}" ]; then
if [ -n "${1#*=}" -a "${1#*=*}" != "${1}" ]; then
# if $1 has a "=<value>", we want the exact match
if [ "$_o" = "$1" ]; then
_val="1";
unset _doecho
fi
continue
fi
if [ "${_o#*=}" = "$_o" ]; then
# if cmdline argument has no "=<value>", we assume "=1"
_val="1";
unset _doecho
continue
fi
_val="${_o#*=}"
_doecho=1
fi
done
if [ -n "$_val" ]; then
[ "x$_doecho" != "x" ] && echo "$_val";
return 0;
fi
return 1;
}
Затем функция _dogetarg()
вызывает функцию с именем getcmdline
, которая собирает фактические параметры командной строки ядра из /proc/cmdline
.
getcmdline() {
local _line
local _i
local CMDLINE_ETC_D
local CMDLINE_ETC
local CMDLINE_PROC
unset _line
if [ -e /etc/cmdline ]; then
while read -r _line || [ -n "$_line" ]; do
CMDLINE_ETC="$CMDLINE_ETC $_line";
done </etc/cmdline;
fi
for _i in /etc/cmdline.d/*.conf; do
[ -e "$_i" ] || continue
while read -r _line || [ -n "$_line" ]; do
CMDLINE_ETC_D="$CMDLINE_ETC_D $_line";
done <"$_i";
done
if [ -e /proc/cmdline ]; then
while read -r _line || [ -n "$_line" ]; do
CMDLINE_PROC="$CMDLINE_PROC $_line"
done </proc/cmdline;
fi
CMDLINE="$CMDLINE_ETC_D $CMDLINE_ETC $CMDLINE_PROC"
printf "%s" "$CMDLINE"
}
Вот последовательность загрузки на данный момент:
-
Загрузчик получает от пользователя параметры командной строки ядра и сохраняет их в своем собственном файле конфигурации (
grub.cfg
). -
Он передает эти параметры командной строки ядру, заполняя заголовок ядра.
-
Ядро извлекает себя и копирует параметры командной строки ядра, находящиеся в заголовке ядра.
-
Ядро извлекает initramfs в память и использует его как временную корневую файловую систему.
-
В той же процедуре ядро подготавливает виртуальные файловые системы, такие как
proc
,sys
,dev
,devpts
,shm
и т. д. -
Ядро сохраняет параметры командной строки в файле
/proc/cmdline
. -
systemd собирает параметры командной строки ядра, читая файл
/proc/cmdline
, и сохраняет их в переменныхroot
,rootfs
иfstype
.
Мы можем проверить эту процедуру, используя хук cmdline
.
Возвращаясь к сценарию /bin/dracut-cmdline
, давайте посмотрим:
export root
export rflags
export fstype
make_trace_mem "hook cmdline" '1+:mem' '1+:iomem' '3+:slab' '4+:komem'
# run scriptlets to parse the command line
getarg 'rd.break=cmdline' -d 'rdbreak=cmdline' && emergency_shell -n cmdline "Break before cmdline"
source_hook cmdline
[ -f /lib/dracut/parse-resume.sh ] && . /lib/dracut/parse-resume.sh
Условие гласит, что если пользователь передал параметр rd.break=cmdline
в разделе ядра GRUB, то необходимо выполнить функцию emergency_shell
. На рисунке 7-10 показано это состояние.
Рисунок 7-10. Условие
Если пользователь передал rd.break=cmdline
, сценарий вызывает функцию с именем emergency_shell
. Как следует из названия, он предоставляет оболочку отладки, и если оболочка отладки успешно запущена, она вызывает другую функцию с именем source_hook
и передает ей параметр cmdline
. Кто бы ни написал этот код, чтобы предоставить пользователям оболочку отладки, он гениальный программист!
На этом этапе мы не будем обсуждать функцию аварийной оболочки, так как сначала нам нужно больше понять systemd. Поэтому мы обсудим это более подробно в главе 8.
На рисунке 7-11 показана блок-схема работы юнитов dracut-cmdline.service
.
Рисунок 7-11. Блок-схема dracut-cmdline.service
Идя дальше, имя корневой файловой системы пользователя может быть просто /dev/sda5
, но на то же самое устройство sda5 можно ссылаться через uuid
, partuuid
или label
. В конце концов, все остальные ссылки на sda5 должны добираться до /dev/sda5
; следовательно, ядро подготавливает файлы символических ссылок для всех этих разных имен устройств в /dev/disk/
. См. рисунок 7-12.
Рисунок 7-12. Содержимое каталога /dev/disk
Тот же сценарий /bin/dracut-cmdline
преобразует имя корневой файловой системы sda5 в /dev/disk/by-uuid/6588b8f1-7f37-4162-968c-8f99eacdf32e
.
case "${root}${root_unset}" in
block:LABEL=*|LABEL=*)
root="${root#block:}"
root="$(echo $root | sed 's,/,\\x2f,g')"
root="block:/dev/disk/by-label/${root#LABEL=}"
rootok=1 ;;
block:UUID=*|UUID=*)
root="${root#block:}"
root="block:/dev/disk/by-uuid/${root#UUID=}"
rootok=1 ;;
block:PARTUUID=*|PARTUUID=*)
root="${root#block:}"
root="block:/dev/disk/by-partuuid/${root#PARTUUID=}"
rootok=1 ;;
block:PARTLABEL=*|PARTLABEL=*)
root="${root#block:}"
root="block:/dev/disk/by-partlabel/${root#PARTLABEL=}"
rootok=1 ;;
/dev/*)
root="block:${root}"
rootok=1 ;;
UNSET|gpt-auto)
# systemd's gpt-auto-generator handles this case.
rootok=1 ;;
esac
[ -z "${root}${root_unset}" ] && die "Empty root= argument"
[ -z "$rootok" ] && die "Don't know how to handle 'root=$root'"
export root rflags fstype netroot NEWROOT
export -p > /dracut-state.sh
exit 0
Давайте посмотрим на хук cmdline
в действии. Как показано на рисунке 7-13, передайте rd.break=cmdline
в строке ядра GRUB.
Рисунок 7-13. Параметр командной строки ядра
Ядро извлечет initramfs, запустится процесс systemd, systemd инициализирует сокеты journald
, и, как вы можете видеть на рисунке 7-14, systemd перебросит нас в оболочку cmdline, поскольку мы сказали systemd прервать (перехватить, hook) последовательность загрузки перед выполнением хука dracut-cmdline
.
Рисунок 7-14. Хук командной строки
В настоящее время мы находимся внутри initramfs и приостановили (подключили dracut) последовательность загрузки systemd после systemd-journal.socket
. Поскольку dracut-cmdline.service
еще не запущен, systemd еще не собрал параметры командной строки ядра, такие как root
, rsflags
и fstype
, из /proc/cmdline
. Для лучшего понимания посмотрите рисунок 7-15. Кроме того, символические ссылки в /dev/disk
dracut еще не создал.
Рисунок 7-15. Хук командной строки
Поскольку systemd еще не собрал имя корневой файловой системы пользователя, нет никаких сомнений в том, что вы не найдете корневую файловую систему пользователя, смонтированную внутри initramfs. sysroot
— это каталог внутри initramfs, куда systemd монтирует корневую файловую систему пользователя. См. рисунок 7-16.
Рисунок 7-16. Каталог sysroot
Но если мы не передадим какой-либо аргумент в rd.break
или просто выйдем из текущей оболочки cmdline, мы будем переброшены в оболочку switch_root
. Оболочка switch_root
— это заключительный этап последовательности загрузки systemd внутри initramfs. На рисунке 7-17 вы можете видеть, что мы передаем rd.break
без каких-либо аргументов.
Рисунок 7-17. Параметр командной строки ядра rd.break
Как вы можете видеть на рисунке 7-18, в оболочке switch_root
после выполнения dracut-cmdline.service
вы обнаружите, что параметры командной строки ядра были собраны systemd. Кроме того, корневая файловая система пользователя смонтирована внутри initramfs под sysroot
.
Рисунок 7-18. Хук switch_root
Если мы выйдем из этого этапа, switch_root
(pivot_root
) будет выполнен systemd, и он покинет среду initramfs. Позже systemd выполнит оставшуюся процедуру загрузки, и, как показано на рисунке 7-19, в конечном итоге мы получим рабочий стол.
Рисунок 7-19. Экран входа в Fedora
Возвращаясь к нашей последовательности загрузки, на данный момент мы достигли стадии pre-udev
. Увидеть это вы можете обратившись к рисунку 7-20.
Рисунок 7-20. Описанная выше последовательность загрузки
dracut-pre-udev.service
Далее systemd будет работать с подключенными устройствами. Для этого systemd должен запустить демон udev
, но перед запуском службы udev
он проверяет, хотят ли пользователи остановить процедуру загрузки до того, как udev
запустится. Если пользователь передал командную строку dracut параметр rd.break=pre-udev
, systemd остановит загрузку непосредственно перед запуском демона udev
.
# cat usr/lib/systemd/system/dracut-pre-udev.service | grep -v '#'
[Unit]
Description=dracut pre-udev hook
Documentation=man:dracut-pre-udev.service(8)
DefaultDependencies=no
Before=systemd-udevd.service dracut-pre-trigger.service
After=dracut-cmdline.service
Wants=dracut-cmdline.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-udev
ConditionKernelCommandLine=|rd.break=pre-udev
ConditionKernelCommandLine=|rd.driver.blacklist
ConditionKernelCommandLine=|rd.driver.pre
ConditionKernelCommandLine=|rd.driver.post
ConditionPathExistsGlob=|/etc/cmdline.d/*.conf
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-udev
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Это приведет нас к оболочке pre-udev
. Обратите внимание на переменные After
, Before
и Wants
. Выполнение dracut-pre-udev.service
просто запускает двоичный файл /bin/dracut-pre-udev
из initramfs. На рисунке 7-21 мы передали rd.break=pre-udev
в качестве параметра командной строки ядра.
Рисунок 7-21. Передача параметра командной строки ядра pre-udev
Чтобы понять хук pre-udev
, вы можете просто перечислить содержимое /dev
, и на рисунке 7-22 вы заметите, что нет файла устройства с именем sda. sda — это наш жесткий диск, на котором находится корневая файловая система.
Рисунок 7-22. Хук pre-udev
Причина отсутствия файлов устройства sda заключается в том, что демон udev
еще не запущен. Демон будет запущен из юнит-файла /usr/lib/systemd/system/systemd-udevd.service
, который запустится после хука pre-udev
.
# cat usr/lib/systemd/system/systemd-udevd.service | grep -v '#'
[Unit]
Description=udev Kernel Device Manager
Documentation=man:systemd-udevd.service(8) man:udev(7)
DefaultDependencies=no
After=systemd-sysusers.service systemd-hwdb-update.service
Before=sysinit.target
ConditionPathIsReadWrite=/sys
[Service]
Type=notify
OOMScoreAdjust=-1000
Sockets=systemd-udevd-control.socket systemd-udevd-kernel.socket
Restart=always
RestartSec=0
ExecStart=/usr/lib/systemd/systemd-udevd
KillMode=mixed
WatchdogSec=3min
TasksMax=infinity
PrivateMounts=yes
ProtectHostname=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallFilter=@system-service @module @raw-io
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
LockPersonality=yes
IPAddressDeny=any
Давайте попробуем понять, как работает udev
и как он создает файлы устройств в каталоге /dev
.
Именно ядро определяет подключенное к системе оборудование; точнее, драйверы, скомпилированные внутри ядра, или модули, вставленные позже, обнаруживают оборудование и регистрируют свои объекты в sysfs
(точка монтирования /sys
). Благодаря точке монтирования /sys
эти данные становятся доступными для пользовательского пространства и таких инструментов, как udev
. Итак, именно ядро определяет оборудование с помощью драйверов и создает файл устройства в /dev
, который представляет собой файловую систему devfs
. После этого ядро отправляет uevent
в udevd
, и udevd
меняет имя, владельца или группу файла устройства или устанавливает соответствующие разрешения в соответствии с правилами, определенными здесь:
/etc/udev/rules.d,
/lib/udev/rules.d, and
/run/udev/rules.d
# ls etc/udev/rules.d/
59-persistent-storage.rules 61-persistent-storage.rules
# ls lib/udev/rules.d/
50-udev-default.rules 70-uaccess.rules 75-net-description.rules 85-nm-unmanaged.rules
60-block.rules 71-seat.rules 80-drivers.rules 90-vconsole.rules
60-persistent-storage.rules 73-seat-late.rules 80-net-setup-link.rules 99-systemd.rules
initramfs содержит мало файлов правил udev
по сравнению с доступными правилами udev
, присутствующими в корневой файловой системе пользователя. По сути, он содержит только те правила, которые необходимы для управления устройствами корневой файловой системы пользователя. Как только udevd
получит управление, он вызовет соответствующие юниты systemd на основе lib/udev/rules.d/99-systemd.rules
.
# cat lib/udev/rules.d/99-systemd.rules
SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/net/devices/$name"
SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k"
SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_WANTS}+="bluetooth.target", ENV{SYSTEMD_USER_WANTS}+="bluetooth.target"
ENV{ID_SMARTCARD_READER}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="smartcard.target", ENV{SYSTEMD_USER_WANTS}+="smartcard.target"
SUBSYSTEM=="sound", KERNEL=="card*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sound.target", ENV{SYSTEMD_USER_WANTS}+="sound.target"
SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target"
SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target", ENV{SYSTEMD_USER_WANTS}+="printer.target"
SUBSYSTEM=="udc", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-gadget.target"
Правило помечается тегом systemd
. Это означает, что всякий раз, когда обнаруживается устройство bluetooth
, udevd
вызывает bluetooth.target
из systemd. bluetooth.target
выполнит двоичный файл /usr/libexec/bluetooth/bluetoothd
, который позаботится об остальной части обработки устройства bluetooth
. Итак, полная последовательность обработки udevd
устройства bluetooth
следующая:
-
Если у пользователя есть устройство bluetooth, подключенное к системе во время загрузки, именно ядро или драйверы, скомпилированные в ядре, или модули, вставленные позже, будут обнаруживать устройство Bluetooth и зарегистрируют его объект в
/sys
. -
Позже ядро создаст файл устройства в точке монтирования
/dev
. После создания файла устройства ядро отправитuevent
вudevd
. -
udevd
будет ссылаться наlib/udev/rules.d/99-systemd.rules
из initramfs и вызывать systemd. Согласно тегу, systemd должен обрабатывать остальную часть. -
systemd выполнит файл
bluetooth.target
, который выполнит двоичный файлbluetoothd
, и оборудование bluetooth будет готово к использованию.
Конечно, блютуз — это не то железо, которое необходимо в момент загрузки. Я взял этот пример для простоты понимания.
Итак, мы дошли до systemd-udev.service
. systemd продолжит последовательность загрузки и выполнит dracut-pre-trigger.service
. Вы можете увидеть последовательность загрузки на рисунке 7-23.
Рисунок 7-23. Описанная выше последовательность загрузки
dracut-pre-trigger.service
Последовательность загрузки initramfs systemd будет нарушена (перехвачена, hooked), если пользователь передал параметр командной строки dracut rd.break=pre-trigger
. На рисунке 7-24 вы можете видеть, что мы передали pre-trigger
в качестве аргумента параметра командной строки ядра rd.break
.
Рисунок 7-24. Параметр командной строки ядра rd.break=pre-trigger
Это приведет нас к запуску оболочки pre-trigger
, что происходит сразу после запуска службы udevd
. Сначала давайте посмотрим, как это происходит в оболочке pre-trigger
.
# cat usr/lib/systemd/system/dracut-pre-trigger.service | grep -v '#'
[Unit]
Description=dracut pre-trigger hook
Documentation=man:dracut-pre-trigger.service(8)
DefaultDependencies=no
Before=systemd-udev-trigger.service dracut-initqueue.service
After=dracut-pre-udev.service systemd-udevd.service systemd-tmpfiles-setup-dev.service
Wants=dracut-pre-udev.service systemd-udevd.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-trigger
ConditionKernelCommandLine=|rd.break=pre-trigger
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-trigger
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Обратите внимание на разделы After
, Before
и Wants
юнит-файла сервиса. Этот служебный файл выполнит /bin/dracut-pre-trigger
из initramfs, если этот каталог ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-trigger
существует и если пользователь передал rd.break=pre-trigger
в качестве командной строки.
[root@fedorab boot]# cat bin/dracut-pre-trigger
#!/usr/bin/sh
export DRACUT_SYSTEMD=1
if [ -f /dracut-state.sh ]; then
. /dracut-state.sh 2>/dev/null
fi
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
source_conf /etc/conf.d
make_trace_mem "hook pre-trigger" '1:shortmem' '2+:mem' '3+:slab' '4+:komem'
source_hook pre-trigger
getarg 'rd.break=pre-trigger' 'rdbreak=pre-trigger' && emergency_shell -n
pre-trigger "Break pre-trigger"
udevadm control --reload >/dev/null 2>&1 || :
export -p > /dracut-state.sh
exit 0
Как видите, он проверяет переданные параметры командной строки dracut (rd.break=pre-trigger
) с помощью функции getarg
. Ранее в этой главе мы видели, как работает getarg
. Если пользователь передал rd.break=pre-trigger
, то она вызовет функцию emergency_shell
с переданным ей параметром pre-trigger
. Функция emergency_shell
записана в файле dracut-lib.sh
. Эта функция предоставит нам предварительную оболочку. В главе 8 описывается процедура предоставления аварийной оболочки.
Как следует из названия pre-trigger
и как вы можете видеть на рисунке 7-25, мы остановили последовательность загрузки непосредственно перед срабатыванием udev
. Следовательно, диск sda еще недоступен в dev
.
Рисунок 7-25. Хук pre-trigger
Это связано с тем, что триггер udevadm
еще не выполнен. Служба dracut-pre-trigger.service
выполняет только udevadm control --reload
, который перезагружает правила udev
. Как показано на рисунке 7-26, служба systemd-udev.service
запущена, но служба systemd-udev-trigger
еще не запущена.
Рисунок 7-26. Хук pre-trigger
systemd-udev-trigger.service
Рисунок 7-27 показывает этап загрузки, которого мы достигли.
Рисунок 7-27. Последовательность загрузки на данный момент
Как мы видели, с pre-udev
/dev не был заполнен, поскольку сам systemd-udevd.service
не был запущен. С pre-trigger
то же самое: /dev
не заполнен, но служба udevd
запущена. Служба udevd
создаст среду для запуска различных инструментов udev
, таких как udevadm
. Используя среду, предоставляемую демоном udevd
, как вы можете видеть на рисунке 7-28, внутри pre-trigger
мы сможем выполнить udevadm
, который мы не могли использовать в оболочке pre-udev
.
Рисунок 7-28. Хук pre-trigger
Как вы можете видеть внутри переключателя pre-trigger
, устройство sda еще не создано. Но поскольку у нас есть готовая среда udevadm
, мы можем обнаружить устройства через нее. Как показано на рисунке 7-29, сначала мы смонтируем файловую систему конфигурации ядра.
pre-trigger:/ # udevadm trigger --type=subsystems --action=add
Затем запустим udevadm
для добавления устройств.
pre-trigger:/ # udevadm trigger --type=devices --action=add
Рисунок 7-29. Хук pre-trigger
Как вы можете видеть на рисунке 7-29, устройства sda созданы. Те же команды будут запущены systemd через systemd-udev-trigger.service
, который обнаружит и создаст файлы устройства хранения в /dev
.
# cat usr/lib/systemd/system/systemd-udev-trigger.service | grep -v '#'
[Unit]
Description=udev Coldplug all Devices
Documentation=man:udev(7) man:systemd-udevd.service(8)
DefaultDependencies=no
Wants=systemd-udevd.service
After=systemd-udevd-kernel.socket systemd-udevd-control.socket
Before=sysinit.target
ConditionPathIsReadWrite=/sys
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/udevadm trigger –type=subsystems –action=add
ExecStart=/usr/bin/udevadm trigger –type=devices –action=add
Но, как вы можете видеть на рисунке 7-30, та же команда udevadm
не будет успешной в хуке pre-udev
, поскольку среда udev отсутствует.
Рисунок 7-30. udevadm
в хуке pre-udev
В этом важность dracut-pre-trigger.service
или хука pre-trigger
.
Блок-схема, представленная на рисунке 7-31, поможет вам понять шаги, предпринятые systemd внутри initramfs. Блок-схема станет еще более понятной после прочтения главы 8. Я настоятельно рекомендую вернуться к этой главе после прочтения главы 8.
Рисунок 7-31. Блок-схема
local-fs.target
Как вы можете видеть на рисунке 7-32, мы достигли этапа загрузки local-fs-target
.
Рисунок 7-32. Описанная выше последовательность загрузки
Итак, systemd дошёл до local-fs.target
. До сих пор systemd запускал службы одну за другой только потому, что устройства хранения не были готовы. Поскольку триггер udevadm
прошел успешно и устройства хранения были заполнены, пришло время подготовить точки монтирования, что будет достигнуто с помощью local-fs.target
. Прежде чем войти в local-fs.target
, обязательно запустите local-fs.pre.target
.
# cat usr/lib/systemd/system/local-fs-pre.target
[Unit]
Description=Local File Systems (Pre)
Documentation=man:systemd.special(7)
RefuseManualStart=yes
# cat usr/lib/systemd/system/local-fs.target
[Unit]
Description=Local File Systems
Documentation=man:systemd.special(7)
DefaultDependencies=no
Conflicts=shutdown.target
After=local-fs-pre.target
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
Навигация по systemd-fstab-generator
будет осуществляться через страницу local-fs.target
.
man page — systemd.special
systemd-fstab-generator(3)
автоматически добавляет зависимости типа Before=
ко всем юнитам монтирования, которые ссылаются на локальные точки монтирования для этого целевого модуля. Кроме того, он добавляет к этому целевому юниту зависимости типа Wants=
для тех монтирований, перечисленных в /etc/fstab
, для которых установлена опция автоматического монтирования.Двоичный файл systemd-fstab-generator
будет вызываться из файла initramfs.
# file usr/lib/systemd/system-generators/systemd-fstab-generator
usr/lib/systemd/system-generators/systemd-fstab-generator: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e16e9d4188e2cab491f551b5f703a5caa645764b, for GNU/Linux 3.2.0, stripped
Фактически, systemd запускает все генераторы на ранней стадии загрузки.
# ls -l usr/lib/systemd/system-generators
total 92
-rwxr-xr-x. 1 root root 3750 Dec 21 12:19 dracut-rootfs-generator
-rwxr-xr-x. 1 root root 45640 Dec 21 12:19 systemd-fstab-generator
-rwxr-xr-x. 1 root root 37032 Dec 21 12:19 systemd-gpt-auto-generator
systemd-fstab-generator
— один из них. Основная задача systemd-fstab-generator
— прочитать командную строку ядра и создать файлы юнитов монтирования systemd в каталоге /tmp
или /run/systemd/generator/
(продолжайте читать, и все это будет иметь смысл). Как видите, это двоичный файл, а это значит, что нам нужно проверить исходный код C systemd, чтобы понять, что он делает. systemd-fstab-generator
либо не принимает никаких входных данных, либо принимает три входных сигнала.
# /usr/lib/systemd/system-generators/systemd-fstab-generator /dev/sda5
This program takes zero or three arguments.
Конечно, тремя входными данными являются имя корневой файловой системы, тип файловой системы и флаг корневой файловой системы. На момент написания этой книги последней версией systemd была версия 244, поэтому мы использовали ее для объяснения. Ранее показанное сообщение об ошибке поступает из src/shared/generator.h
.
# vim systemd-244/src/shared/generator.h
/* Similar to DEFINE_MAIN_FUNCTION, but initializes logging and assigns positional arguments. */
#define DEFINE_MAIN_GENERATOR_FUNCTION(impl) \
_DEFINE_MAIN_FUNCTION( \
({ \
log_setup_generator(); \
if (argc > 1 && argc != 4) \
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), \
"This program takes zero or three arguments."); \
}), \
impl(argc > 1 ? argv[1] : "/tmp", \
argc > 1 ? argv[2] : "/tmp", \
Бинарный файл systemd-fstab-generator
создан из src/fstab-generator/fstab-generator.c
.
# vim systemd-244/src/fstab-generator/fstab-generator.c
static int run(const char *dest, const char *dest_early, const char *dest_late) {
int r, r2 = 0, r3 = 0;
assert_se(arg_dest = dest);
assert_se(arg_dest_late = dest_late);
r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
(void) determine_root();
/* Always honour root= and usr= in the kernel command line if we are in an initrd */
if (in_initrd()) {
r = add_sysroot_mount();
r2 = add_sysroot_usr_mount();
r3 = add_volatile_root();
} else
r = add_volatile_var();
/* Honour /etc/fstab only when that's enabled */
if (arg_fstab_enabled) {
/* Parse the local /etc/fstab, possibly from the initrd */
r2 = parse_fstab(false);
/* If running in the initrd also parse the /etc/fstab from the host */
if (in_initrd())
r3 = parse_fstab(true);
else
r3 = generator_enable_remount_fs_service(arg_dest);
}
return r < 0 ? r : r2 < 0 ? r2 : r3;
}
DEFINE_MAIN_GENERATOR_FUNCTION(run);
Как видите, сначала он анализирует параметры командной строки с помощью функции proc_cmdline_parse
.
root = root filesystem name
rootfstype = root filesystem type
rootflags = ro, rw or auto etc.
systemd-fstab-generator
запускается дважды: когда он находится внутри initramfs и когда он находится вне initramfs. Как только systemd выйдет из initramfs (после монтирования корневой файловой системы пользователя в sysroot
), systemd-fstab-generator
соберет параметры командной строки для файловой системы usr (если это отдельный раздел и если его запись доступна в /etc/fstab
).
'usr' filesystem name
'usr' filesystem type
'usr' filesystem flags
Для простоты понимания мы рассмотрим следующее:
Внутри initramfs: Перед монтированием корневой файловой системы пользователя в /sysroot
Вне initramfs: После монтирования корневой файловой системы пользователя в /sysroot
Таким образом, двоичный файл systemd-fstab-generator
будет собирать параметры командной строки, связанные с корневой файловой системой пользователя, когда systemd работает внутри initramfs, и он будет собирать параметры командной строки, связанные с файловой системой usr, когда systemd работает вне initramfs. systemd запущен внутри или за пределами initramfs, будет проверяться с помощью функции in_initrd
. Функция записана в файле src/basic/util.c
. Интересно проверить, как он проверяет, находится ли он внутри или вне среды initramfs.
# vim systemd-244/src/basic/util.c
bool in_initrd(void) {
struct statfs s;
int r;
if (saved_in_initrd >= 0)
return saved_in_initrd;
/* We make two checks here:
*
* 1. the flag file /etc/initrd-release must exist
* 2. the root file system must be a memory file system
*
* The second check is extra paranoia, since misdetecting an
* initrd can have bad consequences due the initrd
* emptying when transititioning to the main systemd.
*/
r = getenv_bool_secure("SYSTEMD_IN_INITRD");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
if (r >= 0)
saved_in_initrd = r > 0;
else
saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
statfs("/", &s) >= 0 &&
is_temporary_fs(&s);
return saved_in_initrd;
}
Он проверяет, доступен ли файл /etc/initrd-release
. Если этого файла нет, это означает, что мы находимся за пределами initramfs. Затем эта функция вызывает функцию statfs
, которая предоставит подробную информацию о файловой системе, как показано здесь:
struct statfs {
__fsword_t f_type; /* Type of filesystem (see below) */
__fsword_t f_bsize; /* Optimal transfer block size */
fsblkcnt_t f_blocks; /* Total data blocks in filesystem */
fsblkcnt_t f_bfree; /* Free blocks in filesystem */
fsblkcnt_t f_bavail; /* Free blocks available to unprivileged user */
fsfilcnt_t f_files; /* Total file nodes in filesystem */
fsfilcnt_t f_ffree; /* Free file nodes in filesystem */
fsid_t f_fsid; /* Filesystem ID */
__fsword_t f_namelen; /* Maximum length of filenames */
__fsword_t f_frsize; /* Fragment size (since Linux 2.6) */
__fsword_t f_flags; /* Mount flags of filesystem (since Linux 2.6.36) */
__fsword_t f_spare[xxx]; /* Padding bytes reserved for future use */
};
Затем он вызывает функцию is_temporary_fs()
, которая написана внутри /src/basic/stat-util.c
.
bool is_temporary_fs(const struct statfs *s) {
return is_fs_type(s, TMPFS_MAGIC) ||
is_fs_type(s, RAMFS_MAGIC);
}
Как видите, он проверяет, присвоен ли корневой файловой системе магический номер ramfs. Если да, то мы внутри initramfs. В нашем случае мы находимся внутри среды initramfs, поэтому эта функция вернет true
и продолжит работу с src/fstab-generator/fstab-generator.c
, чтобы создать только юнит-файлы -.mount
(sysroot.mount
) корневой файловой системы. Если бы мы находились за пределами initramfs (после монтирования sysroot
с корневой файловой системой пользователя), он бы создал юнит-файл -.mount
для файловой системы usr. Короче говоря, сначала он проверяет, находимся ли мы внутри initramfs. Если да, то он создает юнит-файл монтирования для корневой файловой системы, а если мы снаружи, то он создает его для файловой системы usr (если это отдельная файловая система). Чтобы увидеть это в действии, мы перейдем на этап switch_root
(хук), чтобы мы могли запустить двоичный файл systemd-fstab-generator
вручную.
-
Сначала я удалил содержимое каталога
/tmp
. Это связано с тем, что генераторfstab
создает файлы юнитов монтирования внутри/tmp
. -
Запустим двоичный файл
systemd-fstab-generator
, и, как вы можете видеть на рисунке 7-33, он создаст пару файлов в/tmp
.Рисунок 7-33.
Systemd-fstab-generator
-
Создан юнит-файл
sysroot.mount
. Как следует из названия, он был создан для монтирования корневой файловой системы пользователя. Файл юнита был создан путем чтения/proc/cmdline
. Пожалуйста, обратитесь к рисунку 7-34, чтобы увидеть содержимое файлаsysroot.mount
.Рисунок 7-34. Файл
sysroot.mount
Корневая файловая система будет смонтирована из sda5 (с использованием UUID) в каталог
sysroot
. -
Проверьте раздел
require
юнит-файлаsysroot.mount
. В нем говорится, что сначала необходимо выполнитьsystemd-fsck-root.service
, прежде чем монтировать корневую файловую систему. На рисунке 7-35 показан файлsystemd-fsck-root.service
.Рисунок 7-35. Содержимое файла
systemd-fsck-root.service
Таким образом, во время загрузки, если вы находитесь внутри initramfs, systemd-fstab-generator
сгенерирует юнит-файлы монтирования для корневой файловой системы пользователя, а также будет сгенерирован соответствующий служебный файл fsck
.
В конце последовательности загрузки initramfs systemd обратится к этим файлам из каталога /tmp
, сначала выполнит fsck
на корневом устройстве и смонтирует корневую файловую систему в sysroot
(внутри initramfs); в конечном итоге будет выполнено switch_root
.
Теперь вы должны понимать, что хотя имя исполняемого файла — systemd-fstab-generator
, он на самом деле не создает файл /etc/fstab
. Скорее, его задача состоит в том, чтобы создать юниты монтирования systemd для root
(когда внутри initramfs) и usr
(когда вне initramfs) в /tmp
или внутри каталогов run/systemd/generator/
. Эта система имеет только корневую точку монтирования, поэтому юнит-файлы systemd созданы только для корневой файловой системы. Внутри initramfs он вызывает add_sysroot_mount
для монтирования корневой файловой системы пользователя. После монтирования корневая файловая система systemd вызывает функцию add_sysroot_usr_mount
. Эти функции вызывают именованную функцию add_mount
, которая, в свою очередь, создает юнит-файлы монтирования systemd. Ниже приведен фрагмент функции add_mount
из src/fstab-generator/fstab-generator.c
:
# vim systemd-244/src/fstab-generator/fstab-generator.c
r = unit_name_from_path(where, ".mount", &name);
if (r < 0)
return log_error_errno(r, "Failed to generate unit name: %m");
r = generator_open_unit_file(dest, fstab_path(), name, &f);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"SourcePath=%s\n"
"Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n",
source);
/* All mounts under /sysroot need to happen later, at initrd-fs.target time. IOW, it's not
* technically part of the basic initrd filesystem itself, and so shouldn't inherit the default
* Before=local-fs.target dependency. */
if (in_initrd() && path_startswith(where, "/sysroot"))
fprintf(f, "DefaultDependencies=no\n");
Текущая система имеет только корневой раздел. Чтобы помочь вам понять это еще лучше, я подготовил тестовую систему, в которой root
, boot
, usr
, var
и opt
являются отдельными файловыми системами:
UUID = f7ed74b5-9085-4f42-a1c4-a569f790fdad / ext4 defaults 1 1
UUID = 06609f65-5818-4aee-a9c5-710b76b36c68 /boot ext4 defaults 1 2
UUID = 68fa7990-edf9-4a03-9011-21903a676322 /opt ext4 defaults 1 2
UUID = 6fa78ab3-6c05-4a2f-9907-31be6d2a1071 /usr ext4 defaults 1 2
UUID = 9c721a59-b62d-4d60-9988-adc8ed9e8770 /var ext4 defaults 1 2
Мы перейдем в оболочку pre-pivot
initramfs (которую мы еще не обсуждали). На рисунке 7-36 показано, что мы передали ядру параметр командной строки rd.break=pre-pivot
.
Рисунок 7-36. Параметр командной строки ядра
Как вы можете видеть на рисунке 7-37, в хуке pre-pivot
файловая система root
будет смонтирована вместе с файловой системой usr
, поскольку хук pre-pivot
останавливает последовательность загрузки после монтирования корневой файловой системы пользователя в sysroot
. Но opt
, var
и boot
не будут смонтированы.
Рисунок 7-37. Хук pre-pivot
Даже если вы запустите systemd-fstab-generator
, вы обнаружите, что будут созданы только файлы юнитов usr
и монтирования root
. Вы можете увидеть выходные данные systemd-fstab-generator
на рисунке 7-38.
Рисунок 7-38. systemd-fstab-generator
в хуке pre-pivot
Это доказывает, что в среде initramfs будут смонтированы только root
и usr
. Остальные точки монтирования будут смонтированы после initramfs или после переключения на root. Поскольку файловая система var
еще не смонтирована, логи journalctl
будут сохраняться из файловой системы /run
, а, как мы знаем, это временная файловая система. Это ясно говорит о том, что внутри среды initramfs вы не можете получить доступ к постоянным логам journald
, которые находятся в /var/log
. Пожалуйста, обратитесь к рисункам 7-39, 7-40 и 7-41, чтобы лучше понять это.
Рисунок 7-39. Команда journalctl
в хуке pre-pivot
Рисунок 7-40. Логи, предоставленные journalctl
из /run
Рисунок 7-41. Поведение journalctl
в хуке pre-pivot
Вы заметили одну вещь? Служба dracut-cmdline
считывает параметры командной строки ядра, а параметры командной строки, связанные с usr
, недоступны в /proc/cmdline
. Но как systemd удается смонтировать файловую систему usr
? Кроме того, во время генерации initramfs dracut не копирует в него файл /etc/fstab
.
# lsinitrd | grep -i fstab
-rw-r--r-- 1 root root 0 Jul 25 03:54 etc/fstab.empty
-rwxr-xr-x 1 root root 45640 Jul 25 03:54 usr/lib/systemd/system-generators/systemd-fstab-generator
# lsinitrd -f etc/fstab.empty
<no_output>
Тогда как systemd удается смонтировать файловую систему usr
внутри initramfs, если в ней нет записи?
Когда systemd-fstab-generator
запускается во время local-fs.target
, он монтирует файлы юнитов только для root; затем он продолжает последовательность загрузки и монтирует корневую файловую систему в sysroot
. После монтирования корневой файловой системы она считывает запись usr
из /etc/sysroot/etc/fstab
, создает юнит-файл usr.mount
и в конце монтирует его. Давайте перепроверим это:
Добавим хук
pre-pivot
.Удалим
/etc/fstab
из смонтированного/sysroot
.Запустим
systemd-fstab-generator
.См. рисунок 7-42.
Поскольку имя корневой файловой системы будет получено с помощью dracut-cmdline
из proc/cmdline
, systemd-fstab-generator
создаст sysroot.mount
. Но поскольку файл fstab
отсутствует внутри sysroot
, он будет рассматривать usr
как отдельный недоступный раздел и пропустит создание юнит-файла usr.mount
, даже если usr
является отдельной точкой монтирования.
Рисунок 7-42. Поведение systemd-fstab-generator
Что делать, если вы хотите иметь отдельные точки монтирования, подобные opt
и var
, внутри /sysroot
или в среде initramfs? На странице руководства systemd есть ответ на этот вопрос, показанный здесь:
initrd-fs.target в systemd.special(7)
.systemd-fstab-generator(3)
автоматически добавляет зависимости типа Before=
в sysroot-usr.mount
и все точки монтирования, найденные в /etc/fstab
, у которых есть x-initrd.mount
и для которых не установлены параметры монтирования noauto
.Итак, нам нужно использовать опцию x-initrd.mount [systemd.mount]
в /etc/fstab
. Например, здесь я включил точку монтирования var
внутри initramfs через ту же среду pre-pivot
:
pre-pivot:/# vi /sysroot/etc/fstab
UUID=f7ed74b5-9085-4f42-a1c4-a569f790fdad / ext4 defaults 1 1
UUID=06609f65-5818-4aee-a9c5-710b76b36c68 /boot ext4 defaults 1 2
UUID=68fa7990-edf9-4a03-9011-21903a676322 /opt ext4 defaults 1 2
UUID=6fa78ab3-6c05-4a2f-9907-31be6d2a1071 /usr ext4 defaults 1 2
UUID=9c721a59-b62d-4d60-9988-adc8ed9e8770 /var ext4 defaults,x-initrd.mount 1 2
Как вы можете видеть на рисунке 7-43, файл юнита монтирования var
создан, но fsck
доступен только для корневой файловой системы. Пожалуйста, обратитесь к блок-схеме на рисунке 7-44, которая поможет вам лучше понять это.
Рисунок 7-43. Работа systemd-fstab-generator
Рисунок 7-44. Блок-схема
swap.target
Как вы можете видеть на рисунке 7-45, мы достигли стадии загрузки swap.target
.
Рисунок 7-45. Последовательность загрузки на данный момент
Это будет выполняться параллельно с local-fs.target
. local-fs.target
создает точки монтирования для root
и usr
, тогда как swap.target
создает юнит-файлы монтирования для устройства подкачки. Как только файл монтирования корневой файловой системы готов, sysroot
монтируется в соответствии с ним. systemd-fstab-generator
прочитает fstab
и, если запись об устройстве подкачки присутствует, создаст юнит-файл swap.mount
. Это означает, что файл swap.mount
будет создан только после переключения в корневую файловую систему пользователя (switch_root
в sysroot
). На этом этапе файл swap.mount
не будет создан.
dracut-initqueue.service
Эта служба создает фактические устройства root
, swap
и usr
. Давайте разберемся в этом на примере.
С помощью хука pre-udev
мы увидели, что sda-подобные устройства недоступны. Ни одна из команд udevadm
не будет работать, поскольку сама служба udevd
еще не запущена. См. рисунок 7-46.
Рисунок 7-46. Работа хука pre-udev
При использовании хука pre-trigger
устройство sda не создается, но служба udevd
запускается; следовательно, как вы можете видеть на рисунках 7-47 и 7-48, вы можете использовать инструмент, подобный udevadm
, который создаст устройство sda
в /dev
, но не будет создавать на нем устройства lvm
или подобные raid
. Такие устройства также называются устройствами dm
(device mapper). Таким образом, служба pre-trigger
не сможет создавать файлы устройств для корня, если она находится на lvm
, и поэтому устройства типа /dev/fedora_localhost-live/
не будут созданы.
Рисунок 7-47. Хук pre-trigger
Рисунок 7-48. Устройства sda были созданы под хуком pre-trigger
Служба dracut-initqueue.service
еще не запущена. Давайте сначала посмотрим, что именно говорит юнит-файл.
# cat usr/lib/systemd/system/dracut-initqueue.service | grep -v '#'
[Unit]
Description=dracut initqueue hook
Documentation=man:dracut-initqueue.service(8)
DefaultDependencies=no
Before=remote-fs-pre.target
Wants=remote-fs-pre.target
After=systemd-udev-trigger.service
Wants=systemd-udev-trigger.service
ConditionPathExists=/usr/lib/initrd-release
ConditionPathExists=|/lib/dracut/need-initqueue
ConditionKernelCommandLine=|rd.break=initqueue
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-initqueue
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Как вы можете видеть, эта служба просто запускает сценарий /bin/dracut-initqueue
, и если мы откроем этот сценарий, то обнаружим, что он фактически выполняет команду udevadm settle
со значением 0 для timeout
.
# vim bin/dracut-initqueue
while :; do
check_finished && break
udevadm settle --exit-if-exists=$hookdir/initqueue/work
check_finished && break
if [ -f $hookdir/initqueue/work ]; then
rm -f -- "$hookdir/initqueue/work"
fi
for job in $hookdir/initqueue/*.sh; do
[ -e "$job" ] || break
job=$job . $job
check_finished && break 2
done
udevadm settle --timeout=0 >/dev/null 2>&1 || continue
for job in $hookdir/initqueue/settled/*.sh; do
[ -e "$job" ] || break
job=$job . $job
check_finished && break 2
done
udevadm settle --timeout=0 >/dev/null 2>&1 || continue
# no more udev jobs and queues empty.
sleep 0.5
В конечном итоге это запустит команду lvm_scan
из lib/dracut/hooks/initqueue/timeout/
. Обратите внимание на параметры командной строки ядра root
и rd.break
, которые передаются на рисунке 7-49.
Рисунок 7-49. Параметры командной строки ядра
Как вы можете видеть на рисунке 7-50, команда lvm_scan
записана в одном из файлов.
Рисунок 7-50. Хук initqueue
Итак, здесь у нас есть два варианта: либо мы можем просто выполнить /bin/dracut-initqueue
, либо, как показано на рисунке 7-51, мы можем выполнить команду lvm_scan
либо из хука pre-trigger
, либо из хука initqueue
.
Рисунок 7-51. Команда lvm_scan
в хуке initqueue
Поскольку мы обсудили часть initramfs, связанную с LVM, сейчас самое время рассмотреть одну из наиболее распространенных и важных проблем «невозможно загрузиться».
Проблема 7, «Невозможно загрузиться» (systemd + Root LVM)
Проблема: Мы изменили стандартное имя корневого устройства с /dev/mapper/fedora_localhost--live-root
на /dev/mapper/root_vg-root
. Мы сделали соответствующую запись в /etc/fstab
, но после перезагрузки система не может загрузиться. На рисунке 7-52 показано, что видно на экране.
Рисунок 7-52. Сообщения консоли
Поскольку теперь мы лучше понимаем dracut-initqueue
, мы видим, что сообщения об ошибках явно означают, что systemd не может собрать корневое устройство lvm
.
-
Давайте сначала изолируем проблему, вспомнив выполненные шаги. Исходное имя корневого уровня выглядит следующим образом:
# cat /etc/fstab /dev/mapper/fedora_localhost--live-root / ext4 defaults 1 1 UUID=eea3d947-0618-4d8c-b083-87daf15b2679 /boot ext4 defaults 1 2 /dev/mapper/fedora_localhost--live-swap none ext4 defaults 0 0
-
Имя корневой группы томов было изменено.
# vgrename fedora_localhost-live root_vg The volume group Fedora_localhost-live was successfully renamed to root_vg.
-
Запись
/etc/fstab
корневогоlvm
была соответствующим образом изменена./dev/mapper/root_vg-root / ext4 defaults 1 1 UUID=eea3d947-0618-4d8c-b083-87daf15b2679 /boot ext4 defaults 1 2 /dev/mapper/root_vg-swap none swap defaults 0 0
Но после перезагрузки systemd начинает выдавать сообщения об ошибках dracut-initqueue timeout
.
Кажется, что шаги были выполнены правильно, но нам нужно продолжить расследование, чтобы понять, почему dracut-initqueue
не может собирать LVM.
Если мы подождем некоторое время на экране ошибки, как показано на рисунке 7-53, systemd автоматически переключит нас на аварийную оболочку (emergency shell). В главе 8 мы рассмотрим подробно, как systemd помещает нас в аварийную оболочку.
Рисунок 7-53. Аварийная оболочка
Как показано на рисунке 7-54, мы просканируем доступные в данный момент устройства LV и смонтируем root vg
, чтобы проверить его содержимое.
Рисунок 7-54. Активация устройств LV
Как видите, root_vg
(переименованный vg
) доступен, и мы тоже можем его активировать. Это явно означает, что метаданные LVM не повреждены и что у устройства LVM нет проблем с целостностью. Как показано на рисунке 7-55, мы смонтируем root_vg
во временный каталог и перекрестно проверим его записи fstab
из самой аварийной оболочки.
Рисунок 7-55. Монтирование корневой файловой системы
vg
не поврежден, записи fstab
верны, и мы можем смонтировать корневой vg
. Чего же в таком случае не хватает?
Недостающая часть заключается в том, что параметры командной строки ядра не были настроены в GRUB. См. рисунок 7-56.
Рисунок 7-56. Параметры командной строки ядра
Для загрузки нам нужно убрать экран-заставку GRUB и изменить параметры командной строки ядра, как показано на рисунке 7-57.
Рисунок 7-57. Параметры командной строки старого ядра
См. рисунок 7-58 для ознакомления с новыми параметрами командной строки ядра.
Рисунок 7-58. Параметры командной строки нового ядра
После загрузки системы измените содержимое /etc/default/grub
с этого:
# cat /etc/default/grub
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/fedora_localhost--live-swap rd.lvm.lv=fedora_localhost-live/root rd.lvm.lv=fedora_localhost-live/swap
console=ttyS0,115200 console=tty0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
на это:
# cat /etc/default/grub
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/root_vg-swap rd.lvm.lv=root_vg/root rd.lvm.lv=root_vg/swap console=ttyS0,115200 console=tty0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
Нет необходимости изменять файл /etc/default/grub
, поскольку Fedora использует записи BLS из /boot/loader/entries
.
Измените /boot/grub2/grubenv
с этого:
# cat /boot/grub2/grubenv
saved_entry=2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64
menu_auto_hide=1
boot_success=0
kernelopts=root=/dev/mapper/fedora_localhost--live-root ro resume=/dev/mapper/fedora_localhost--live-swap rd.lvm.lv=fedora_localhost-live/root rd.lvm.lv=fedora_localhost-live/swap console=ttyS0,115200 console=tty0
boot_indeterminate=9
на это:
# cat /boot/grub2/grubenv
saved_entry=2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64
menu_auto_hide=1
boot_success=0
kernelopts=root=/dev/root_vg/root ro resume=/dev/mapper/root_vg-swap rd.lvm.lv=root_vg/root rd.lvm.lv=root_vg/swap console=ttyS0,115200
console=tty0
boot_indeterminate=9
Это устраняет проблему «невозможно загрузиться».
plymouth
Теперь пришло время поговорить об одном интересном сервисе под названием plymouth
. Раньше Linux отображал загрузочные сообщения прямо на консоли, что было скучно для пользователей настольных компьютеров. Таким образом, был введен Plymouth, как показано здесь:
# cat usr/lib/systemd/system/plymouth-start.service
[Unit]
Description=Show Plymouth Boot Screen
DefaultDependencies=no
Wants=systemd-ask-password-plymouth.path systemd-vconsole-setup.service
After=systemd-vconsole-setup.service systemd-udev-trigger.service systemd-udevd.service
Before=systemd-ask-password-plymouth.service
ConditionKernelCommandLine=!plymouth.enable=0
ConditionVirtualization=!container
[Service]
ExecStart=/usr/sbin/plymouthd --mode=boot --pid-file=/var/run/plymouth/pid --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
Type=forking
KillMode=none
SendSIGKILL=no
Как вы можете видеть, из юнит-файла /usr/lib/systemd/system/plymouth-start.service
plymouth
запускается сразу после systemd-udev-trigger.service
и перед dracut-initqueue.service
, как показано на рисунке 7-59.
Рисунок 7-59. Последовательность загрузки
Как показано на рисунке 7-60, plymouth
будет активен на протяжении всей процедуры загрузки.
Рисунок 7-60. plymouth
plymouth
— это инструмент, который показывает анимацию во время загрузки. Например, в Fedora не отображаются сообщения консоли, показанные на рисунке 7-61.
Рисунок 7-61. Когда plymouth
недоступен
Плимут покажет вам анимацию, показанную на рисунке 7-62.
Рисунок 7-62. Экран plymouth
Установка plymouth
Если вы хотите установить различные темы plymouth
, вы можете сделать следующее:
-
Загрузите
plymouth-theme
с сайта gnome-look.org или используйте следующее:# dnf install plymouth-theme*
-
Извлеките загруженную тему в следующую папку:
/usr/share/plymouth/themes/
.# ls -l /usr/share/plymouth/themes/ total 52 drwxr-xr-x. 2 root root 4096 Apr 26 2019 bgrt drwxr-xr-x 3 root root 4096 Mar 30 09:15 breeze drwxr-xr-x 2 root root 4096 Mar 30 09:15 breeze-text drwxr-xr-x. 2 root root 4096 Mar 30 09:15 charge drwxr-xr-x. 2 root root 4096 Apr 26 2019 details drwxr-xr-x 2 root root 4096 Mar 30 09:15 fade-in drwxr-xr-x 2 root root 4096 Mar 30 09:15 hot-dog drwxr-xr-x 2 root root 4096 Mar 30 09:15 script drwxr-xr-x 2 root root 4096 Mar 30 09:15 solar drwxr-xr-x 2 root root 4096 Mar 30 09:15 spinfinity drwxr-xr-x. 2 root root 4096 Apr 26 2019 spinner drwxr-xr-x. 2 root root 4096 Apr 26 2019 text drwxr-xr-x. 2 root root 4096 Apr 26 2019 tribar
-
Вам необходимо пересобрать initramfs, поскольку
plymouth
запускается из среды initramfs. Например, его файл конфигурации необходимо обновить для новой темыplymouth
.# cat /etc/plymouth/plymouthd.conf # Administrator customizations go in this file # [Daemon] # Theme=fade-in [Daemon] Theme=hot-dog
После перезагрузки, как показано на рисунке 7-63, вы увидите новую тему plymouth
под названием hot-dog
.
Рисунок 7-63. Тема plymouth
hot-dog
Управление plymouth
Поскольку plymouth
запускается на ранней стадии, dracut предоставляет некоторые параметры командной строки для управления поведением plymouth
.
plymouth.enable=0
полное отключение plymouth bootsplash.
rd.plymouth=0
отключение plymouth bootsplash только для initramfs.
Изображение хот-дога, показанное ранее, называется заставкой (splash screen). Чтобы увидеть установленную/выбранную заставку, вы можете использовать следующее:
# plymouth --show-splash
Другой основной мотив plymouth
— сохранить все сообщения во время загрузки в простом текстовом файле, который пользователи могут просмотреть после загрузки. Журналы будут храниться в /var/log/boot.log
, но помните, что этот файл поддерживается компанией plymouth
. Это означает, что вы увидите сообщения о загрузке только после запуска plymouth
. Но в то же время нам нужно иметь в виду, что plymouth
запускается на ранней стадии initramfs (сразу после запуска udevd
).
# less /varlog/boot.log
------------ Sat Jul 06 01:43:12 IST 2019 ------------
OK Started Show Plymouth Boot Screen.
OK Reached target Paths.
OK Started Forward Password R...s to Plymouth Directory Watch.
OK Found device /dev/mapper/fedora_localhost--live-root.
OK Reached target Initrd Root Device.
OK Found device /dev/mapper/fedora_localhost--live-swap.
Starting Resume from hiber...fedora_localhost--live-swap...
OK Started Resume from hibern...r/fedora_localhost--live-swap.
OK Reached target Local File Systems (Pre) .
OK Reached target Local File Systems.
Starting Create Volatile Files and Directories...
OK Started Create Volatile Files and Directories.
OK Reached target System Initialization.
OK Reached target Basic System.
OK Started dracut initqueue hook.
OK Reached target Remote File Systems (Pre) .
OK Reached target Remote File Systems.
Starting File System Check...fedora_localhost--live-root...
OK Started File System Check ...r/fedora_localhost--live-root.
Mounting /sysroot...
OK Mounted /sysroot.
OK Reached target Initrd Root File System.
Starting Reload Configuration from the Real Root...
OK Started Reload Configuration from the Real Root.
OK Reached target Initrd File Systems.
OK Reached target Initrd Default Target.
Starting dracut pre-pivot and cleanup hook...
OK Started dracut pre-pivot and cleanup hook.
Starting Cleaning Up and Shutting Down Daemons...
OK Stopped target Timers.
OK Stopped dracut pre-pivot and cleanup hook.
OK Stopped target Initrd Default Target.
OK Stopped target Remote File Systems.
OK Stopped target Remote File Systems (Pre) .
OK Stopped dracut initqueue hook.
Starting Plymouth switch root service...
OK Stopped target Initrd Root Device.
OK Stopped target Basic System.
OK Stopped target System Initialization.
.
.
Структура
plymouth
принимает входные данные из initramfs/systemd, чтобы понять, какой этап процедуры загрузки был завершен (в процентах от процедуры загрузки), и соответственно показывает анимацию или индикатор выполнения на экране. За работу plymouth
отвечают два двоичных файла.
/bin/plymouth (интерфейс к plymouthd)
/usr/sbin/plymouthd (основной двоичный файл, который показывает заставку и записывает сообщения загрузки в файл boot.log)
Внутри initramfs доступны различные службы plymouth
, на которые опирается systemd.
# ls -l usr/lib/systemd/system/ -l | grep -i plymouth
-rw-r--r--. 1 root root 384 Dec 21 12:19 plymouth-halt.service
-rw-r--r--. 1 root root 398 Dec 21 12:19 plymouth-kexec.service
-rw-r--r--. 1 root root 393 Dec 21 12:19 plymouth-poweroff.service
-rw-r--r--. 1 root root 198 Dec 21 12:19 plymouth-quit.service
-rw-r--r--. 1 root root 204 Dec 21 12:19 plymouth-quit-wait.service
-rw-r--r--. 1 root root 386 Dec 21 12:19 plymouth-reboot.service
-rw-r--r--. 1 root root 547 Dec 21 12:19 plymouth-start.service
-rw-r--r--. 1 root root 295 Dec 21 12:19 plymouth-switch-root.service
-rw-r--r--. 1 root root 454 Dec 21 12:19 systemd-ask-password-plymouth.path
-rw-r--r--. 1 root root 435 Dec 21 12:19 systemd-ask-password-plymouth.service
drwxr-xr-x. 2 root root 4096 Dec 21 12:19 systemd-ask-password-plymouth.service.wants
systemd при работе в initramfs время от времени вызывает эти службы на этапе загрузки. Как видите, каждая служба вызывает двоичный файл plymouthd
и передает переключатели в соответствии с текущим этапом загрузки. Например, plymouth-start.service
просто запускает двоичный файл plymouthd
в режиме boot
. Есть только два режима; один для boot
, а другой для shutdown
.
# cat usr/lib/systemd/system/plymouth* | grep -i execstart
ExecStart=/usr/sbin/plymouthd --mode=shutdown --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=/usr/sbin/plymouthd --mode=shutdown --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=/usr/sbin/plymouthd --mode=shutdown --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=-/usr/bin/plymouth quit <<---
ExecStart=-/usr/bin/plymouth --wait
ExecStart=/usr/sbin/plymouthd --mode=reboot --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=/usr/sbin/plymouthd --mode=boot --pid-file=/var/run/plymouth/pid --attach-to-session
ExecStartPost=-/usr/bin/plymouth show-splash
ExecStart=-/usr/bin/plymouth update-root-fs --new-root-dir=/sysroot <<---
Другой пример, который мы можем рассмотреть, заключается в том, что во время switch_root
systemd просто вызывает plymouth-switch-root.service
, который, в свою очередь, запускает двоичный файл plymouthd
с обновленной корневой файловой системой как sysroot
. Другими словами, вы можете сказать вместе с switch_root
, что plymouth
меняет свой корневой каталог с initramfs на фактическую корневую файловую систему. Двигаясь дальше, вы можете увидеть, что systemd запускает службу plymouth
точно так же, как systemd отправляет сообщение quit
в plymouthd
в конце последовательности загрузки. При этом вы наверняка заметили, что systemd вызывает plymouth
и в момент перезагрузки или завершения работы. На самом деле это не имеет большого значения, поскольку он просто вызывает один и тот же plymouthd
в соответствующем режиме.
sysinit.target
Итак, мы дошли до этапа sysinit.target
. На рисунке 7-64 показана последовательность загрузки, которую мы рассмотрели до сих пор.
Рисунок 7-64. Описанная выше последовательность загрузки
Поскольку это юнит target
, его задача — удерживать или запускать кучу других юнитов (сервисов, сокетов и т. д.). Список юнитов будет доступен в каталоге wants
. Как видите, доступные файлы юнитов представляют собой не что иное, как символические ссылки на исходные файлы юнитов сервисов.
# ls -l usr/lib/systemd/system/sysinit.target.wants/
total 0
kmod-static-nodes.service -> ../kmod-static-nodes.service
plymouth-start.service -> ../plymouth-start.service
systemd-ask-password-console.path -> ../systemd-ask-password-console.path
systemd-journald.service -> ../systemd-journald.service
systemd-modules-load.service -> ../systemd-modules-load.service
systemd-sysctl.service -> ../systemd-sysctl.service
systemd-tmpfiles-setup-dev.service -> ../systemd-tmpfiles-setup-dev.service
systemd-tmpfiles-setup.service -> ../systemd-tmpfiles-setup.service
systemd-udevd.service -> ../systemd-udevd.service
systemd-udev-trigger.service -> ../systemd-udev-trigger.service
Большинство служб уже запущены до того, как мы достигнем sysinit.target
. Например, systemd-udevd.service
и systemd-udev-trigger.service
(после службы предварительного запуска) уже запущены, и мы уже видели, что systemd-udevd.service
выполнит двоичный файл /usr/lib/systemd/systemd-udevd
, тогда как служба systemd-udev-trigger
выполнит двоичный файл udevadm
. Тогда почему мы снова запускаем эти службы с помощью sysinit.target
? Нет, мы не запускаем. sysinit.target
запустит только те службы, которые еще не запущены, и будет игнорировать любые действия с уже запущенными службами. Давайте посмотрим назначение каждого из этих файлов сервисных юнитов.
Юнит-файл kmod-static-nodes
systemd выполняет двоичный файл kmod
с переключателем static-nodes
. В главе 5 мы уже видели, что lsmod
, insmod
, modinfo
, modprobe
, depmod
и т. д. являются символическими ссылками на двоичный файл kmod
.
# lsinitrd | grep -i kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/depmod -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/insmod -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/lsmod -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/modinfo -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/modprobe -> ../bin/kmod
lrwxrwxrwx 1 root root 11 Jul 25 03:54 usr/sbin/rmmod -> ../bin/kmod
# cat usr/lib/systemd/system/kmod-static-nodes.service | grep -v '#'
[Unit]
Description=Create list of static device nodes for the current kernel
DefaultDependencies=no
Before=sysinit.target systemd-tmpfiles-setup-dev.service
ConditionCapability=CAP_SYS_MODULE
ConditionFileNotEmpty=/lib/modules/%v/modules.devname
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/kmod static-nodes --format=tmpfiles --output=/run/tmpfiles.d/static-nodes.conf
С помощью переключателя static-nodes
systemd просто собирает все статические узлы (устройства), присутствующие в системе. Зачем нам статические узлы в эпоху динамической обработки узлов (udev
)? Есть некоторые модули, такие как fuse
или ALSA
, которым нужны файлы устройств, присутствующие в /dev
, иначе они могут их создать. Но это может быть опасно, поскольку файлы устройств создаются kernel
или udev
. Таким образом, чтобы модули не создавали файлы устройств, systemd создаст статические узлы, такие как /dev/fuse
или /dev/snd/seq
, через kmod-static-nodes.service
. Ниже приведены статические узлы, созданные kmod-static-nodes.service
в системе Fedora:
# kmod static-nodes
Module: fuse
Device node: /dev/fuse
Type: character device
Major: 10
Minor: 229
Module: btrfs
Device node: /dev/btrfs-control
Type: character device
Major: 10
Minor: 234
Module: loop
Device node: /dev/loop-control
Type: character device
Major: 10
Minor: 237
Module: tun
Device node: /dev/net/tun
Type: character device
Major: 10
Minor: 200
Module: ppp_generic
Device node: /dev/ppp
Type: character device
Major: 108
Minor: 0
Module: uinput
Device node: /dev/uinput
Type: character device
Major: 10
Minor: 223
Module: uhid
Device node: /dev/uhid
Type: character device
Major: 10
Minor: 239
Module: vfio
Device node: /dev/vfio/vfio
Type: character device
Major: 10
Minor: 196
Module: hci_vhci
Device node: /dev/vhci
Type: character device
Major: 10
Minor: 137
Module: vhost_net
Device node: /dev/vhost-net
Type: character device
Major: 10
Minor: 238
Module: vhost_vsock
Device node: /dev/vhost-vsock
Type: character device
Major: 10
Minor: 241
Module: snd_timer
Device node: /dev/snd/timer
Type: character device
Major: 116
Minor: 33
Module: snd_seq
Device node: /dev/snd/seq
Type: character device
Major: 116
Minor: 1
Module: cuse
Device node: /dev/cuse
Type: character device
Major: 10
Minor: 203
Далее у нас есть сервис plymouth
, который уже запущен; затем у нас есть systemd-ask-password-console.path
, который представляет собой юнит-файл .path
.
# cat usr/lib/systemd/system/systemd-ask-password-console.path | grep -v '#'
[Unit]
Description=Dispatch Password Requests to Console Directory Watch
Documentation=man:systemd-ask-password-console.service(8)
DefaultDependencies=no
Conflicts=shutdown.target emergency.service
After=plymouth-start.service
Before=paths.target shutdown.target cryptsetup.target
ConditionPathExists=!/run/plymouth/pid
[Path]
DirectoryNotEmpty=/run/systemd/ask-password
MakeDirectory=yes
Юнит-файл .path
предназначен для активации на основе пути, но поскольку мы не зашифровали наш корневой диск с помощью LUKS, у нас нет фактического служебного файла, который будет принимать пароль от пользователя. Если бы мы настроили LUKS, у нас был бы файл сервисного юнита /usr/lib/systemd/system/systemd-ask-password-plymouth.service
, как показано здесь:
# cat usr/lib/systemd/system/systemd-ask-password-plymouth.service
[Unit]
Description=Forward Password Requests to Plymouth
Documentation=http://www.freedesktop.org/wiki/Software/systemd/
PasswordAgents
DefaultDependencies=no
Conflicts=shutdown.target
After=plymouth-start.service
Before=shutdown.target
ConditionKernelCommandLine=!plymouth.enable=0
ConditionVirtualization=!container
ConditionPathExists=/run/plymouth/pid
[Service]
ExecStart=/usr/bin/systemd-tty-ask-password-agent --watch --plymouth
Как видите, это выполнение двоичного файла systemd-tty-ask-password-agent
, который запрашивает пароль с помощью plymouth
вместо TTY. Далее идет файл служебного юнита systemd-journald.service
, который запустит для нас демон journald
. До этого времени все сообщения протоколируются с помощью сокета journald
, который systemd запускает как первую службу в последовательности загрузки. Размер сокета journald
составляет 8 МБ. Если в сокете заканчивается буфер, службы будут заблокированы до тех пор, пока сокет не станет доступным. 8 МБ буферного пространства более чем достаточно для производственных систем.
# vim usr/lib/systemd/system/sysinit.target.wants/systemd-journald.service
[Unit]
Description=Journal Service
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Requires=systemd-journald.socket
After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
Before=sysinit.target
[Service]
OOMScoreAdjust=-250
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_
SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_
FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
DeviceAllow=char-* rw
ExecStart=/usr/lib/systemd/systemd-journald
FileDescriptorStoreMax=4224
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes
Restart=always
RestartSec=0
RestrictAddressFamilies=AF_UNIX AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
StandardOutput=null
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
Type=notify
WatchdogSec=3min
LimitNOFILE=524288
Далее, если вы хотите, чтобы systemd статически загрузил какой-то конкретный модуль, вам может помочь наш следующий сервис — systemd-modules-load.service
.
# cat usr/lib/systemd/system/systemd-modules-load.service | grep -v '#'
[Unit]
Description=Load Kernel Modules
Documentation=man:systemd-modules-load.service(8) man:modules-load.d(5)
DefaultDependencies=no
Conflicts=shutdown.target
Before=sysinit.target shutdown.target
ConditionCapability=CAP_SYS_MODULE
ConditionDirectoryNotEmpty=|/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d
ConditionDirectoryNotEmpty=|/etc/modules-load.d
ConditionDirectoryNotEmpty=|/run/modules-load.d
ConditionKernelCommandLine=|modules-load
ConditionKernelCommandLine=|rd.modules-load
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/systemd/systemd-modules-load
TimeoutSec=90s
Служба выполняет /usr/lib/systemd/systemd-modules-load
. Бинарный файл понимает два параметра командной строки.
module_load
: это параметр командной строки ядра.rd.module_load
: это параметр командной строки dracut.
Если вы передадите параметр командной строки dracut, то systemd-modules-load
статистически загрузит модуль в память, но для этого модуль должен присутствовать в initramfs. Если его нет в initramfs, то сначала его надо подтянуть в initramfs. При создании initramfs dracut читает файлы <module-name>.conf
отсюда:
/etc/modules-load.d/*.conf
/run/modules-load.d/*.conf
/usr/lib/modules-load.d/*.conf
Вам необходимо создать файл *.conf
и указать в нем имя модуля, который вы хотите добавить в initramfs.
Например, здесь мы создали новый образ initramfs, в котором нет модуля vfio
:
# dracut new.img
# lsinitrd | grep -i vfio
<no_output>
Чтобы статистически загрузить модуль внутри initramfs, мы создали файл vfio.conf
:
# cat /usr/lib/modules-load.d/vfio.conf
vfio
Здесь мы пересобрали initramfs:
# dracut new.img -f
# lsinitrd new.img | grep -i vfio
Jul 25 03:54 usr/lib/modules/5.3.16-300.fc31.x86_64/kernel/drivers/vfio
Jul 25 03:54 usr/lib/modules/5.3.16-300.fc31.x86_64/kernel/drivers/vfio/vfio.ko.xz
Jul 25 03:54 usr/lib/modules-load.d/vfio.conf
Как видите, модуль подтянут внутрь initramfs и загрузится в память, как только запустится сервис systemd-modules-load.service
.
Статистическая загрузка модулей — не очень хорошая идея. В наши дни модули загружаются в память динамически, когда это необходимо или по требованию, тогда как статические модули всегда загружаются в память, независимо от необходимости или требования.
Не путайте с каталогом /etc/modprobe.d
. Его использование заключается в передаче параметров модулям. Вот пример:
# cat /etc/modprobe.d/lockd.conf
options lockd nlm_timeout=10
nlm_timeour=10
— опция, передаваемая модулю lockd
. Помните, что файл .conf
внутри /etc/modprobe.d
должен иметь имя модуля. Через тот же файл конфигурации вы можете установить псевдоним для имени модуля. Вот пример:
"alias my-mod really_long_modulename"
Далее systemd установит параметры ядра sysctl
с помощью systemd-sysctl.service
.
# cat usr/lib/systemd/system/systemd-sysctl.service | grep -v '#'
[Unit]
Description=Apply Kernel Variables
Documentation=man:systemd-sysctl.service(8) man:sysctl.d(5)
DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-modules-load.service
Before=sysinit.target shutdown.target
ConditionPathIsReadWrite=/proc/sys/net/
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/systemd/systemd-sysctl
TimeoutSec=90s
systemd-sysctl.service
запустит двоичный файл /usr/lib/systemd/systemd-sysctl
, который установит параметры настройки ядра путем чтения файлов *.conf
из трех разных мест.
/etc/sysctl.d/*.conf
/run/sysctl.d/*.conf
/usr/lib/sysctl.d/*.conf
Вот пример:
# sysctl -a | grep -i swappiness
vm.swappiness = 60
Значение параметра ядра swappiness
по умолчанию установлено на 60. Если вы хотите изменить его на 10 и оно должно оставаться постоянным при перезагрузках, добавьте его в /etc/sysctl.d/99-sysctl.conf
.
# cat /etc/sysctl.d/99-sysctl.conf
vm.swappiness = 10
Вы можете перезагрузить и установить параметры sysctl
, используя это:
# sysctl -p
vm.swappiness = 10
Чтобы внести эти изменения в initramfs, вам необходимо заново создать initramfs. Во время загрузки systemd-sysctl.service
прочитает значение swappiness
из файла 99-sysctl.conf
и установит его в среде initramfs.
systemd создает множество временных файлов для беспрепятственного выполнения. После настройки параметров sysctl
он запускает следующую службу, называемую systemd-tmpfiles-setup-dev.service
, которая выполнит двоичный файл /usr/bin/systemd-tmpfiles --prefix=/dev --create --boot
. Это создаст временные файлы, связанные с файловой системой dev
, в соответствии со следующими правилами:
/etc/tmpfiles.d/*.conf
/run/tmpfiles.d/*.conf
/usr/lib/tmpfiles.d/*.conf
После sysinit.target
systemd проверит, созданы или нет необходимые сокеты, с помощью sockets.target
.
# ls usr/lib/systemd/system/sockets.target.wants/ -l
total 0
32 Jan 3 18:05 systemd-journald-audit.socket -> ../systemd-journald-audit.socket
34 Jan 3 18:05 systemd-journald-dev-log.socket -> ../systemd-journald-dev-log.socket
26 Jan 3 18:05 systemd-journald.socket -> ../systemd-journald.socket
31 Jan 3 18:05 systemd-udevd-control.socket -> ../systemd-udevd-control.socket
30 Jan 3 18:05 systemd-udevd-kernel.socket -> ../systemd-udevd-kernel.socket
Итак, наш процесс загрузки завершил последовательность действий до sysinit.target
. См. блок-схему, показанную на рисунке 7-65.
Рисунок 7-65. Описанная выше последовательность загрузки
Проблема 8, «Невозможно загрузиться» (sysctl.conf)
Проблема: После перезагрузки ядро «паникует», и система не может загрузиться. Вот что видно в консоли:
[ 4.596220] Mem-Info:
[ 4.597455] active_anon:566 inactive_anon:1 isolated_anon:0
[ 4.597455] active_file:0 inactive_file:0 isolated_file:0
[ 4.597455] unevictable:19700 dirty:0 writeback:0 unstable:0
[ 4.597455] slab_reclaimable:2978 slab_unreclaimable:3180
[ 4.597455] mapped:2270 shmem:22 pagetables:42 bounce:0
[ 4.597455] free:23562 free_pcp:1982 free_cma:0
[ 4.611930] Node 0 active_anon:2264kB inactive_anon:4kB active_file:0kB inactive_file:0kB unevictable:78800kB isolated(anon):0kB isolated(file):0kB mapped:9080kB dirty:0kB writeback:0kB shmem:88kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 0kB writeback_tmp:0kB unstable:0kB all_unreclaimable? yes
[ 4.621748] Node 0 DMA free:15900kB min:216kB low:268kB high:320kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15992kB managed:15908kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB
[ 4.632561] lowmem_reserve[]: 0 1938 4764 4764 4764
[ 4.634609] Node 0 DMA32 free:38516kB min:27404kB low:34252kB high:41100kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:2080628kB managed:2015092kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:2304kB local_pcp:0kB free_cma:0kB
[ 4.645636] lowmem_reserve[]: 0 0 2826 2826 2826
[ 4.647886] Node 0 Normal free:39832kB min:39956kB low:49944kB high:59932kB active_anon:2264kB inactive_anon:4kB active_file:0kB inactive_file:0kB unevictable:78800kB writepending:0kB present:3022848kB managed:2901924kB mlocked:0kB kernel_stack:1776kB pagetables:168kB bounce:0kB free_pcp:5624kB local_pcp:1444kB free_cma:0kB
[ 4.659458] lowmem_reserve[]: 0 0 0 0 0
[ 4.661319] Node 0 DMA: 1*4kB (U) 1*8kB (U) 1*16kB (U) 0*32kB 2*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (M) 3*4096kB (M) = 15900kB
[ 4.666730] Node 0 DMA32: 1*4kB (M) 0*8kB 1*16kB (M) 1*32kB (M) 1*64kB (M) 0*128kB 0*256kB 1*512kB (M) 3*1024kB (M) 1*2048kB (M) 8*4096kB (M) = 38516kB
[ 4.673247] Node 0 Normal: 69*4kB (UME) 16*8kB (M) 10*16kB (UME) 7*32kB (ME) 5*64kB (E) 1*128kB (E) 1*256kB (U) 9*512kB (ME) 9*1024kB (UME) 2*2048kB (ME) 5*4096kB (M) = 39892kB
[ 4.680399] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=1048576kB
[ 4.683930] Node 0 hugepages_total=2303 hugepages_free=2303 hugepages_surp=0 hugepages_size=2048kB
[ 4.687749] 19722 total pagecache pages
[ 4.689841] 0 pages in swap cache
[ 4.691580] Swap cache stats: add 0, delete 0, find 0/0
[ 4.694275] Free swap = 0kB
[ 4.696039] Total swap = 0kB
[ 4.697617] 1279867 pages RAM
[ 4.699229] 0 pages HighMem/MovableOnly
[ 4.700862] 46636 pages reserved
[ 4.703868] 0 pages cma reserved
[ 4.705589] 0 pages hwpoisoned
[ 4.707435] Tasks state (memory values in pages):
[ 4.709532] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[ 4.713849] [ 341] 0 341 5118 1178 77824 0 -1000 (md-udevd)
[ 4.717805] Out of memory and no killable processes...
[ 4.719861] Kernel panic — not syncing: System is deadlocked on memory
[ 4.721926] CPU: 3 PID: 1 Comm: systemd Not tainted 5.3.7-301.fc31.x86_64 #1
[ 4.724343] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.12.0-2.fc30 04/01/2014
[ 4.727959] Call Trace:
[ 4.729204] dump_stack+0x5c/0x80
[ 4.730707] panic+0x101/0x2d7
[ 4.747357] out_of_memory.cold+0x2f/0x88
[ 4.749172] __alloc_pages_slowpath+0xb09/0xe00
[ 4.750890] __alloc_pages_nodemask+0x2ee/0x340
[ 4.752452] alloc_slab_page+0x19f/0x320
[ 4.753982] new_slab+0x44f/0x4d0
[ 4.755317] ? alloc_slab_page+0x194/0x320
[ 4.757016] ___slab_alloc+0x507/0x6a0
[ 4.758768] ? copy_verifier_state+0x1f7/0x270
[ 4.760591] ? ___slab_alloc+0x507/0x6a0
[ 4.763266] __slab_alloc+0x1c/0x30
[ 4.764846] kmem_cache_alloc_trace+0x1ee/0x220
[ 4.766418] ? copy_verifier_state+0x1f7/0x270
[ 4.768120] copy_verifier_state+0x1f7/0x270
[ 4.769604] ? kmem_cache_alloc_trace+0x162/0x220
[ 4.771098] ? push_stack+0x35/0xe0
[ 4.772367] push_stack+0x66/0xe0
[ 4.774010] check_cond_jmp_op+0x1fe/0xe60
[ 4.775644] ? _cond_resched+0x15/0x30
[ 4.777524] ? _cond_resched+0x15/0x30
[ 4.779315] ? kmem_cache_alloc_trace+0x162/0x220
[ 4.780916] ? copy_verifier_state+0x1f7/0x270
[ 4.782357] ? copy_verifier_state+0x16f/0x270
[ 4.783785] do_check+0x1c06/0x24e0
[ 4.785218] bpf_check+0x1aec/0x24d4
[ 4.786613] ? _cond_resched+0x15/0x30
[ 4.788073] ? kmem_cache_alloc_trace+0x162/0x220
[ 4.789672] ? selinux_bpf_prog_alloc+0x1f/0x60
[ 4.791564] bpf_prog_load+0x3a3/0x670
[ 4.794915] ? seq_vprintf+0x30/0x50
[ 4.797085] ? seq_printf+0x53/0x70
[ 4.799013] __do_sys_bpf+0x7e5/0x17d0
[ 4.800909] ? __fput+0x168/0x250
[ 4.802352] do_syscall_64+0x5f/0x1a0
[ 4.803826] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 4.805587] RIP: 0033:0x7f471557915d
[ 4.807638] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d fb 5c 0c 00 f7 d8 64 89 01 48
[ 4.814732] RSP: 002b:00007fffd36da028 EFLAGS: 00000246 ORIG_RAX: 0000000000000141
[ 4.818390] RAX: ffffffffffffffda RBX: 000055fb6ad3add0 RCX: 00007f471557915d
[ 4.820448] RDX: 0000000000000070 RSI: 00007fffd36da030 RDI: 0000000000000005
[ 4.822536] RBP: 0000000000000002 R08: 0070756f7267632f R09: 000001130000000f
[ 4.826605] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
[ 4.829312] R13: 0000000000000006 R14: 000055fb6ad3add0 R15: 00007fffd36da1e0
[ 4.831792] Kernel Offset: 0x26000000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 4.835316] ---[ end Kernel panic — not syncing: System is deadlocked on memory ]---
Итак, это проблема «паники ядра». Сначала нам необходимо изолировать проблему, поскольку паника ядра может возникнуть в тысячах ситуаций. Если вы посмотрите на выделенные сообщения о панике ядра, станет ясно, что был вызван «OOM-killer», поскольку в системе не хватает памяти. Ядро пыталось освободить память из кеша и даже пыталось использовать пространство подкачки, но в конце концов сдалось, и ядро запаниковало.
Итак, мы изолировали проблему. Нам нужно сконцентрироваться на том, кто съедает память. Механизм нехватки памяти ОС (OOM) будет задействован, когда в системе наблюдается огромная нехватка памяти.
Существует три ситуации, когда OOM-killer может быть вызван во время загрузки:
В системе очень мало установленной физической памяти.
Установлены неправильные параметры настройки ядра.
В некоторых модулях есть утечка памяти.
Эта система имеет 4,9 ГБ физической памяти, что немного, но этого более чем достаточно, чтобы ядро Linux завершило последовательность загрузки.
В некоторых модулях могут быть утечки памяти, но выявить их будет непростой задачей. Итак, сначала мы проверим, были ли установлены неправильно какие-либо параметры настройки ядра, связанные с памятью.
-
Для этого мы поместимся в initramfs. На рисунке 7-66 мы передали
rd.break
в качестве параметра командной строки ядра.Рисунок 7-66. Параметр командной строки ядра
-
Перемонтируем
sysroot
в режиме чтения-записи и проверим параметрыsysctl
.switch_root:/# cat /proc/sys/vm/nr_hugepages 2400
-
Проблема заключается в неправильно зарезервированном количестве Hugepages. Мы отключим настройку, как показано на рисунке 7-67.
Рисунок 7-67. Отключение настройки Hugepages
После перезагрузки система сможет успешно загрузиться. Давайте попробуем понять, что пошло не так. Эта система имеет 4,9 ГБ памяти, и раньше Hugepages не резервировались.
# cat /proc/meminfo | grep -e MemTotal -e HugePages_Total
MemTotal: 4932916 kB
HugePages_Total: 0
# cat /proc/sys/vm/nr_hugepages
0
Размер обычной страницы составляет 4 КБ, тогда как размер Hugepages составляет 2 МБ, что в 512 раз больше, чем обычная страница. Hugepages имеет свои преимущества, но в то же время имеет и свои недостатки.
Hugepages нельзя заменить.
Ядро не использует Hugepages.
Только приложения, поддерживающие Hugepages, могут использовать Hugepages.
Кто-то неправильно установил 2400 Hugepages и пересобрал initramfs.
# echo "vm.nr_hugepages = 2400" >> /etc/sysctl.conf
# sysctl -p
vm.nr_hugepages = 2400
# dracut /boot/new.img
# reboot
Итак, 2400 Hugepages = 4,9 ГБ — это вся установленная основная память, а поскольку вся память зарезервирована в Hugepages, ядро не может ее использовать. Итак, во время загрузки, когда systemd достиг стадии sysinit.target
и выполнил systemd-sysctl.service
, сервис прочитал файл sysctl.conf
из initramfs и зарезервировал 4,9 ГБ огромных страниц, которые ядро не может использовать. Следовательно, самому ядру не хватило памяти, и система запаниковала.
basic.target
Итак, мы достигли basic.target
. Как мы знаем, target предназначены для синхронизации или группировки юнитов. basic.target
— это точка синхронизации для служб поздней загрузки.
# cat usr/lib/systemd/system/basic.target | grep -v '#'
[Unit]
Description=Basic System
Documentation=man:systemd.special(7)
Requires=sysinit.target
Wants=sockets.target timers.target paths.target slices.target
After=sysinit.target sockets.target paths.target slices.target tmp.mount
RequiresMountsFor=/var /var/tmp
Wants=tmp.mount
Таким образом, basic.target
будет успешно запущен, когда все предыдущие службы юнит-файлов requires
, wants
и after
будут успешно запущены. Фактически, почти все службы имеют After=basic.target
в своих юнит-файлах.
dracut-pre-mount.service
systemd выполнит службу dracut-pre-mount.service
непосредственно перед монтированием корневой файловой системы пользователя внутри initramfs. Поскольку это служба dracut, она будет выполняться только в том случае, если пользователь передал параметр командной строки rd.break=pre-mount
dracut. На рисунке 7-68 показано, что мы передали rd.break=pre-mount
в качестве параметра командной строки ядра.
Рисунок 7-68. Параметр командной строки ядра
Как вы можете видеть на рисунке 7-69, мы перешли в аварийную оболочку, а корневая файловая система пользователя не смонтирована в sysroot
. Да, я сказал, что это привело нас к аварийной оболочке, но вы будете удивлены, увидев, что аварийная оболочка — это не что иное, как простая оболочка bash, предоставляемая systemd, но в то время, когда загрузка еще не завершена. Чтобы лучше понять аварийную оболочку, мы на некоторое время приостановим последовательность загрузки и обсудим отладочные оболочки initramfs в главе 8. Мы возобновим последовательность загрузки приостановленного systemd в главе 9.
Рисунок 7-69. Хук pre-mount
Глава 8
Оболочки отладки
На данный момент мы знаем, что в initramfs встроен bash, и мы время от времени использовали его через хуки rd.break
. Цель этой главы — понять, как systemd предоставляет нам оболочку внутри initramfs. Какие шаги необходимо предпринять и как можно использовать их более эффективно? Но перед этим давайте подведем итог тому, что мы узнали об отладочных и аварийных оболочках initramfs.
Оболочка
rd.break
drop to a shell at the end
rd.break
переносит нас в initramfs, и через него мы можем исследовать среду initramfs. Эту среду initramfs также называют аварийным режимом (emergency mode). В обычных сценариях мы переходим в аварийный режим, когда initramfs не может смонтировать корневую файловую систему пользователя. Помните, что передача rd.break
без каких-либо параметров приведет нас к initramfs после монтирования корневой файловой системы пользователя в /sysroot
, но до выполнения на ней команды switch_root
. Подробные логи всегда можно найти в файле /run/initramfs/rdsosreport.txt
. На рисунке 8-1 показаны журналы из rdsosreport.txt
.
Рисунок 8-1. Журналы выполнения rdsosreport.txt
В сообщениях журнала вы можете ясно видеть, что нас выбросило в оболочку непосредственно перед выполнением pivot_root
. pivot_root
и switch_root
будут обсуждаться в главе 9, тогда как chroot
будет обсуждаться в главе 10. Как только вы выйдете из аварийной оболочки, systemd продолжит последовательность приостановленной загрузки и в конечном итоге предоставит экран входа в систему.
Затем мы обсудили, как мы можем использовать аварийные оболочки для решения некоторых проблем, связанных с невозможностью загрузки. Например, initramfs так же хорош, как корневая файловая система пользователя. Таким образом, у него есть двоичные файлы, связанные с lvm
, raid
и файловой системой, которые мы можем использовать для поиска, сборки, диагностики и исправления корневой файловой системы отсутствующего пользователя. Затем мы обсудили, как можно смонтировать его в /sysroot
и изучить его содержимое, чтобы, например, исправить неверные записи в grub.cfg
.
Аналогично, rd.break
предоставляет нам различные варианты прерывания последовательности загрузки на разных этапах.
udev
.udev
с помощью элемента управления udevadm
или установить --property=KEY=value
как параметры или управлять дальнейшим выполнением udev
с помощью udevadm
./sysroot
./sysroot
.Теперь давайте посмотрим, как именно systemd удается предоставить нам оболочки на этих различных этапах.
Как systemd отправляет нас в аварийную оболочку?
Рассмотрим пример хука pre-mount
. systemd из initramfs получает параметр командной строки rd.break=pre-mount
из dracut-cmdline.service
и запускает службу systemd dracut-pre-mount.service
из местоположения initramfs /usr/lib/systemd/system
. Служба будет запущена до запуска initrd-root-fs.target
, sysroot.mount
и systemd-fsck-root.service
.
# cat usr/lib/systemd/system/dracut-pre-mount.service | grep -v #'
[Unit]
Description=dracut pre-mount hook
Documentation=man:dracut-pre-mount.service(8)
DefaultDependencies=no
Before=initrd-root-fs.target sysroot.mount systemd-fsck-root.service
After=dracut-initqueue.service cryptsetup.target
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-mount
ConditionKernelCommandLine=|rd.break=pre-mount
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-mount
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Как видите, он просто выполняет сценарий /bin/dracut-pre-mount
из initramfs.
# vim bin/dracut-pre-mount
#!/usr/bin/sh
export DRACUT_SYSTEMD=1
if [ -f /dracut-state.sh ]; then
. /dracut-state.sh 2>/dev/null
fi
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
source_conf /etc/conf.d
make_trace_mem "hook pre-mount" '1:shortmem' '2+:mem' '3+:slab' '4+:komem'
# pre pivot scripts are sourced just before we doing cleanup and switch over
# to the new root.
getarg 'rd.break=pre-mount' 'rdbreak=pre-mount' && emergency_shell -n pre-mount "Break pre-mount"
source_hook pre-mount
export -p > /dracut-state.sh
exit 0
Внутри сценария /bin/dracut-pre-mount
наиболее важной строкой является следующая:
getarg rd.break=pre-mount' rdbreak=pre-mount && emergency_shell -n pre-mount "Break pre-mount"
Мы уже обсуждали функцию getarg
, которая используется для проверки того, какой параметр был передан в rd.break=
. Если был передан rd.break=pre-mount
, то будет вызвана только функция emergency-shell()
. Функция определена в /usr/lib/dracut-lib.sh
и она передает в нее предварительное монтирование в качестве строкового параметра. -n
означает следующее:
[ -n STRING ] or [ STRING ]:
True if the length of STRING is nonzeroФункция emergency_shell
принимает значение переменной _rdshell_name
как pre-mount
.
if [ "$1" = "-n" ]; then
_rdshell_name=$2
Здесь -n
считается первым аргументом ($1
), а pre-mount
— вторым аргументом ($2
). Таким образом, значение _rdshell_name
становится pre-mount
.
#vim /usr/lib/dracut-lib.sh
emergency_shell()
{
local _ctty
set +e
local _rdshell_name="dracut" action="Boot" hook="emergency"
local _emergency_action
if [ "$1" = "-n" ]; then
_rdshell_name=$2
shift 2
elif [ "$1" = "--shutdown" ]; then
_rdshell_name=$2; action="Shutdown"; hook="shutdown-emergency"
if type plymouth >/dev/null 2>&1; then
plymouth --hide-splash
elif [ -x /oldroot/bin/plymouth ]; then
/oldroot/bin/plymouth --hide-splash
fi
shift 2
fi
echo ; echo
warn "$*"
echo
_emergency_action=$(getarg rd.emergency)
[ -z "$_emergency_action" ] \
&& [ -e /run/initramfs/.die ] \
&& _emergency_action=halt
if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
_emergency_shell $_rdshell_name
else
source_hook "$hook"
warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
[ -z "$_emergency_action" ] && _emergency_action=halt
fi
case "$_emergency_action" in
reboot)
reboot || exit 1;;
poweroff)
poweroff || exit 1;;
halt)
halt || exit 1;;
esac
}
Затем, в конце, она вызывает другую функцию _emergency_shell
из того же файла (обратите внимание на подчеркивание перед именем функции). Как видите, _rdshell_name
— это аргумент функции _emergency_shell
.
_emergency_shell $_rdshell_name
Внутри функции _emergency_shell()
мы видим, что _name
получает аргумент pre-mount
.
local _name="$1"
# vim usr/lib/dracut-lib.sh
_emergency_shell()
{
local _name="$1"
if [ -n "$DRACUT_SYSTEMD" ]; then
> /.console_lock
echo "PS1=\"$_name:\\\${PWD}# \"" >/etc/profile
systemctl start dracut-emergency.service
rm -f -- /etc/profile
rm -f -- /.console_lock
else
debug_off
source_hook "$hook"
echo
/sbin/rdsosreport
echo 'You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
echo 'after mounting them and attach it to a bug report.'
if ! RD_DEBUG= getargbool 0 rd.debug -d -y rdinitdebug -d -y rdnetdebug; then
echo
echo 'To get more debug information in the report,'
echo 'reboot with "rd.debug" added to the kernel command line.'
fi
echo
echo 'Dropping to debug shell.'
echo
export PS1="$_name:\${PWD}# "
[ -e /.profile ] || >/.profile
_ctty="$(RD_DEBUG= getarg rd.ctty=)" && _ctty="/dev/${_ctty##*/}"
if [ -z "$_ctty" ]; then
_ctty=console
while [ -f /sys/class/tty/$_ctty/active ]; do
_ctty=$(cat /sys/class/tty/$_ctty/active)
_ctty=${_ctty##* } # last one in the list
done
_ctty=/dev/$_ctty
fi
[ -c "$_ctty" ] || _ctty=/dev/tty1
case "$(/usr/bin/setsid --help 2>&1)" in *--ctty*) CTTY="--ctty";; esac
setsid $CTTY /bin/sh -i -l 0<>$_ctty 1<>$_ctty 2<>$_ctty
fi
Та же строка pre-mount
была передана в PS1
. Давайте сначала посмотрим, что такое PS1
.
PS1
называется псевдопеременной. Это то, как будет показано приглашение bash, когда пользователь успешно войдет в систему. Вот пример:
[root@fedora home]#
| | | |
[username]@[host][CWD][# поскольку это пользователь root]
Идеальные записи, принимаемые bash: PS1='\u:\w\$'
.
#
; иначе $
.Итак, в нашем случае, когда мы получим аварийную оболочку, PS1
будет напечатан оболочкой следующим образом:
'pre-mount#'
Далее в исходном коде вы можете видеть, что новое значение переменной PS1
также добавляется в /etc/profile
. Причина в том, что bash читает этот файл каждый раз перед тем, как представить оболочку пользователю. В конце концов, мы просто запускаем службу dracut-emergency
.
systemctl start dracut-emergency.service
Ниже приведен файл dracut-emergency.service
из /usr/lib/systemd/system/
initramfs:
# cat usr/lib/systemd/system/dracut-emergency.service | grep -v #'
[Unit]
Description=Dracut Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target emergency.target
[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=-/bin/dracut-emergency
ExecStopPost=-/bin/rm -f -- /.console_lock
Type=oneshot
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity
KillSignal=SIGHUP
Служба просто выполняет /bin/dracut-emergency
. Этот сценарий сначала останавливает службу plymouth.
type plymouth >/dev/null 2>&1 && plymouth quit
При этом значение переменной hook
сохраняется как emergency
и вызывается функция source_hook
с аргументом emergency
.
export _rdshell_name="dracut" action="Boot" hook="emergency" source_hook "$hook"
# vim bin/dracut-emergency
#!/usr/bin/sh
export DRACUT_SYSTEMD=1
if [ -f /dracut-state.sh ]; then
. /dracut-state.sh 2>/dev/null
fi
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
source_conf /etc/conf.d
type plymouth >/dev/null 2>&1 && plymouth quit
export _rdshell_name="dracut" action="Boot" hook="emergency"
_emergency_action=$(getarg rd.emergency)
if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
FSTXT="/run/dracut/fsck/fsck_help_$fstype.txt"
source_hook "$hook"
echo
rdsosreport
echo
echo
echo Entering emergency mode. Exit the shell to continue.'
echo Type "journalctl" to view system logs.'
echo You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
echo after mounting them and attach it to a bug report.'
echo
echo
[ -f "$FSTXT" ] && cat "$FSTXT"
[ -f /etc/profile ] && . /etc/profile
[ -z "$PS1" ] && export PS1="$_name:\${PWD}# "
exec sh -i -l
else
export hook="shutdown-emergency"
warn "$action has failed. To debug this issue add \"rd.shell rd.debug\"
the kernel command line."
source_hook "$hook"
[ -z "$_emergency_action" ] && _emergency_action=halt
fi
/bin/rm -f -- /.console_lock
case "$_emergency_action" in
reboot)
reboot || exit 1;;
poweroff)
poweroff || exit 1;;
halt)
halt || exit 1;;
esac
exit 0
Функция source_hook
снова определена в /usr/lib/dracut-lib.sh
.
source_hook() {
local _dir
_dir=$1; shift
source_all "/lib/dracut/hooks/$_dir" "$@"
}
Переменная _dir
зафиксировала имя хука, которым является emergency
. Все хуки представляют собой не что иное, как набор скриптов, хранящихся и выполняемых из каталога /lib/dracut/hooks/
initramfs.
# tree usr/lib/dracut/hooks/
usr/lib/dracut/hooks/
├── cleanup
├── cmdline
│ ├── 30-parse-lvm.sh
│ ├── 91-dhcp-root.sh
│ └── 99-nm-config.sh
├── emergency
│ └── 50-plymouth-emergency.sh
├── initqueue
│ ├── finished
│ ├── online
│ ├── settled
│ │ └── 99-nm-run.sh
│ └── timeout
│ └── 99-rootfallback.sh
├── mount
├── netroot
├── pre-mount
├── pre-pivot
│ └── 85-write-ifcfg.sh
├── pre-shutdown
├── pre-trigger
├── pre-udev
│ └── 50-ifname-genrules.sh
├── shutdown
│ └── 25-dm-shutdown.sh
└── shutdown-emergency
Для хука emergency
выполняется команда /usr/lib/dracut/hooks/emergency/50-plymouth-emergency.sh
, которая останавливает службу plymouth
.
#!/usr/bin/sh
plymouth --hide-splash 2>/dev/null || :
Как только хук emergency
будет выполнен и plymouth
будет остановлен, он вернется в /bin/dracut-emergency
и напечатает следующий баннер:
echo Entering emergency mode. Exit the shell to continue.'
echo Type "journalctl" to view system logs.'
echo You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
echo after mounting them and attach it to a bug report.'
Таким образом, не важно, какое значение параметра rd.break=hook_name
передал пользователь. systemd выполнит хук emergency
, и как только баннер будет напечатан, он получит каталог /etc/profile
, в котором мы добавили PS1=_rdshell_name/PS1=hook_name
, а затем мы сможем просто запустить оболочку bash.
exec sh -i –l
Когда оболочка запустится, она прочитает /etc/profile
и найдет переменную PS1=hook_name
. В этом случае hook_name
будет pre-mount
. Вот почему было напечатано pre-mount
в качестве приглашения bash. Чтобы лучше это понять, обратитесь к блок-схеме, показанной на рисунке 8-2.
Рисунок 8-2. Блок-схема
Если пользователь передает в rd.break
какой-либо другой параметр, например, initqueue
, то он будет передан в переменные PS1
, _rdshell_name
и hook
. Позже bash будет вызван через службу emergency. Bash прочитает значение PS1
из файла /etc/profile
и отобразит имя initqueue
в командной строке.
Вывод состоит в том, что одна и та же оболочка bash будет предоставлена пользователю под разными именами приглашений (cmdline
, pre-mount
, switch_root
, pre-udev
, emergency
и т. д.) — на разных этапах загрузки initramfs.
cmdline:/# pre-udev:/#
pre-trigger:/# initqueue:/#
pre-mount:/# pre-pivot:/#
switch_root:/#
Аналогично этому systemd будет выполнять и rescue.target
.
rescue.service и emergency.service
В мире systemd службу rescue также называют однопользовательским режимом (single-user mode). Таким образом, если пользователь запросил загрузку в однопользовательском режиме, то systemd фактически помещает пользователя в аварийную оболочку на этапе rescue.service
. На рисунке 8-3 показана последовательность загрузки, рассмотренная до сих пор.
Рисунок 8-3. Блок-схема последовательности загрузки
Вы можете передать либо rescue.target
, либо runlevel1.target
или emergency.service
в systemd.unit
для загрузки в однопользовательском режиме. Как показано на рисунке 8-4, на этот раз мы будем использовать Ubuntu для изучения этапов загрузки.
Рисунок 8-4. Параметр командной строки ядра
Это приведет нас к аварийной оболочке. Однопользовательский режим, служба rescue и служба emergency запускают двоичный файл dracut-emergency
. Это тот же двоичный файл, который мы запустили в хуке dracut emergency
.
# cat usr/lib/systemd/system/emergency.service | grep -v ' #'
[Unit]
Description=Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target
Before=shutdown.target
[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=/bin/dracut-emergency
ExecStopPost=-/usr/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity
KillSignal=SIGHUP
# cat usr/lib/systemd/system/rescue.service | grep -v ' #'
[Unit]
Description=Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target
Before=shutdown.target
[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=/bin/dracut-emergency
ExecStopPost=-/usr/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity
KillSignal=SIGHUP
И, как мы все знаем, сценарий dracut-emergency
запускает оболочку bash.
# vim bin/dracut-emergency
#!/usr/bin/sh
export DRACUT_SYSTEMD=1
if [ -f /dracut-state.sh ]; then
. /dracut-state.sh 2>/dev/null
fi
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
source_conf /etc/conf.d
type plymouth >/dev/null 2>&1 && plymouth quit
export _rdshell_name="dracut" action="Boot" hook="emergency"
_emergency_action=$(getarg rd.emergency)
if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
FSTXT="/run/dracut/fsck/fsck_help_$fstype.txt"
source_hook "$hook"
echo
rdsosreport
echo
echo
echo 'Entering emergency mode. Exit the shell to continue.'
echo 'Type "journalctl" to view system logs.'
echo 'You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
echo 'after mounting them and attach it to a bug report.'
echo
echo
[ -f "$FSTXT" ] && cat "$FSTXT"
[ -f /etc/profile ] && . /etc/profile
[ -z "$PS1" ] && export PS1="$_name:\${PWD}# "
exec sh -i -l
else
export hook="shutdown-emergency"
warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
source_hook "$hook"
[ -z "$_emergency_action" ] && _emergency_action=halt
fi
/bin/rm -f -- /.console_lock
case "$_emergency_action" in
reboot)
reboot || exit 1;;
poweroff)
poweroff || exit 1;;
halt)
halt || exit 1;;
esac
exit 0
Как вы можете видеть на рисунке 8-5, sysroot
еще не смонтирован, поскольку мы еще не достигли стадии монтирования и загрузки.
Рисунок 8-5. Оболочка emergency
Надеюсь, теперь вы понимаете, как systemd представляет пользователям аварийную оболочку на различных этапах загрузки. В следующей главе мы возобновим последовательность загрузки нашего приостановленного systemd.
Глава 9
systemd (часть II)
На данный момент мы достигли службы dracut.pre-mount.service
, где корневая файловая система пользователя еще не смонтирована внутри initramfs. На следующем этапе загрузки systemd корневая файловая система будет смонтирована в sysroot
.
sysroot.mount
systemd принимает параметр командной строки mount
dracut, который приведет нас к аварийной оболочке mount
. Как вы можете видеть на рисунке 9-1, мы передали параметр командной строки ядра rd.break=mount
.
Рисунок 9-1. Параметр командной строки ядра
Как вы можете видеть на рисунке 9-2, sysroot
смонтирован с корневой файловой системой пользователя в режиме только для чтения.
Рисунок 9-2. Хук mount
Хук dracut.mount
(/usr/lib/systemd/system/dracut-mount.service
) запустит сценарий /bin/dracut-mount
из initramfs, который выполнит часть монтирования.
# vim usr/lib/systemd/system/dracut-mount.service
Как видите, это выполнение сценария dracut-mount
из initramfs, а также экспорт переменной NEWROOT
со значением sysroot
.
Environment=NEWROOT=/sysroot
ExecStart=-/bin/dracut-mount
[Unit]
Description=dracut mount hook
Documentation=man:dracut-mount.service(8)
After=initrd-root-fs.target initrd-parse-etc.service
After=dracut-initqueue.service dracut-pre-mount.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/mount
ConditionKernelCommandLine=|rd.break=mount
DefaultDependencies=no
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-mount
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
# vim bin/dracut-mount
#!/usr/bin/sh
export DRACUT_SYSTEMD=1
if [ -f /dracut-state.sh ]; then
. /dracut-state.sh 2>/dev/null
fi
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
source_conf /etc/conf.d
make_trace_mem "hook mount" '1:shortmem' '2+:mem' '3+:slab'
getarg 'rd.break=mount' -d 'rdbreak=mount' && emergency_shell -n mount "Break mount"
# mount scripts actually try to mount the root filesystem, and may
# be sourced any number of times. As soon as one suceeds, no more are sourced.
i=0
while :; do
if ismounted "$NEWROOT"; then
usable_root "$NEWROOT" && break;
umount "$NEWROOT"
fi
for f in $hookdir/mount/*.sh; do
[ -f "$f" ] && . "$f"
if ismounted "$NEWROOT"; then
usable_root "$NEWROOT" && break;
warn "$NEWROOT has no proper rootfs layout, ignoring and removing offending mount hook"
umount "$NEWROOT"
rm -f -- "$f"
fi
done
i=$(($i+1))
[ $i -gt 20 ] && emergency_shell "Can't mount root filesystem"
done
export -p > /dracut-state.sh
exit 0
В главе 8 мы видели, как именно он перенаправляет нас на аварийную оболочку и связанные с ней функции. Поскольку мы остановили последовательность загрузки после монтирования корневой файловой системы пользователя внутри initramfs, как вы можете видеть на рисунке 9-3, systemd-fstab-generator
уже был выполнен, и файлы юнита mount
уже созданы.
Рисунок 9-3. Поведение systemd-fstab-generator
Помните, что имя корневой файловой системы пользователя, добавленное в sysroot.mount
, было взято из файла /proc/cmdline
. В sysroot.mount
четко указано, что и где нужно монтировать.
initrd.target
Как мы уже неоднократно говорили, конечная цель последовательности загрузки — предоставить пользователю корневую файловую систему, и при этом основные этапы, которые выполняет systemd, заключаются в следующем:
-
Найти корневую файловую систему пользователя.
-
Смонтировать корневую файловую систему пользователя (мы дошли до этого этапа загрузки).
-
Найти другие необходимые файловые системы и смонтировать их (
usr
,var
,nfs
,cifs
и т. д.). -
Переключиться в корневую файловую систему смонтированного пользователя.
-
Запустить демоны пользовательского пространства.
-
Запустить
multi-user.target
илиgraphical.target
(это выходит за рамки этой книги).
Как видите, на данный момент мы подошли к шагу 2, который монтирует корневую файловую систему пользователя внутри initramfs. Мы все знаем, что в systemd есть .targets
, а target
— это не что иное, как набор юнит-файлов. .target
может быть успешно запущен только после успешного запуска всех его юнит-файлов.
В мире systemd существует множество целей, таких как basic.target
, multi-user.target
, graphical.target
, default.target
и sysinit.target
, и это лишь некоторые из них. Конечная цель initramfs — достичь initrd.target
. Как только initrd.target
будет успешно запущен, systemd включит в него switch_root
. Итак, сначала давайте посмотрим на initrd.target
и на то, где он находится с точки зрения последовательности загрузки. Пожалуйста, обратитесь к блок-схеме, показанной на рисунке 9-4.
Рисунок 9-4. Последовательность загрузки
Когда вы находитесь за пределами initramfs (то есть после switch_root
), default.target
systemd будет либо multi-user.target
, либо graphical.target
, тогда как внутри initramfs (то есть до switch_root
) после basic.target
, default.target
systemd будет initrd.target
. Итак, после успешного завершения sysinit.target
и basic.target
основной задачей systemd является достижение initrd.target
. Чтобы добраться туда, systemd будет использовать этап sysroot.mount
для чтения юнит-файлов монтирования, созданных systemd-fstab-generator
. Служба dracut-mount.service
смонтирует корневую файловую систему пользователя в /sysroot
, а затем systemd выполнит службу initrd-parse-etc.service
. Он проанализирует файл /sysroot/etc/fstab
и создаст юнит-файлы монтирования для usr
или любых других точек монтирования, для которых установлена опция x-initrd.mount
. Вот как работает initrd-parse-etc.service
:
# cat usr/lib/systemd/system/initrd-parse-etc.service | grep -v '#'
[Unit]
Description=Reload Configuration from the Real Root
DefaultDependencies=no
Requires=initrd-root-fs.target
After=initrd-root-fs.target
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
ConditionPathExists=/etc/initrd-release
[Service]
Type=oneshot
ExecStartPre=-/usr/bin/systemctl daemon-reload
ExecStart=-/usr/bin/systemctl --no-block start initrd-fs.target
ExecStart=/usr/bin/systemctl --no-block start initrd-cleanup.service
По сути, служба выполняет systemctl
с переключателем перезагрузки демона. Это перезагрузит конфигурацию менеджера systemd. Это перезапустит все генераторы, перезагрузит все файлы юнитов и заново создаст все дерево зависимостей. Пока демон перезагружается, все сокеты, которые systemd прослушивает от имени пользовательской конфигурации, останутся доступными. Генераторы systemd, которые будут запущены повторно, следующие:
# ls usr/lib/systemd/system-generators/ -l
total 92
-rwxr-xr-x. 1 root root 3750 Jan 10 19:18 dracut-rootfs-generator
-rwxr-xr-x. 1 root root 45640 Dec 21 12:19 systemd-fstab-generator
-rwxr-xr-x. 1 root root 37032 Dec 21 12:19 systemd-gpt-auto-generator
Как вы можете видеть, он выполнит systemd-fstab-generator
, который прочитает записи /sysroot/etc/fstab
и создаст юнит-файлы монтирования для usr
и для устройств, у которых установлена опция x-initrd.mount
. Короче говоря, systemd-fstab-generator
выполнился дважды.
Итак, когда вы переходите в оболочку монтирования (rd.break=mount
), вы фактически прерываете последовательность загрузки после целевого initrd.target
. Эта цель просто запускает следующие службы:
# ls usr/lib/systemd/system/initrd.target.wants/
dracut-cmdline-ask.service dracut-mount.service dracut-pre-trigger.service
dracut-cmdline.service dracut-pre-mount.service dracut-pre-udev.service
dracut-initqueue.service dracut-pre-pivot.service
Пожалуйста, обратитесь к рисунку 9-5 для лучшего понимания этого.
Рисунок 9-5. Общее выполнение initrd.target
switch_root/pivot_root
Теперь мы достигли финального этапа загрузки systemd — switch_root
. systemd переключает корневую файловую систему с initramfs (/
) на корневую файловую систему пользователя (/sysroot
). systemd достигает этого, выполняя следующие шаги:
-
Монтирование новой корневой файловой системы (
/sysroot
) -
Превращение его в корневую файловую систему (
/
) -
Удаление всех доступов к старой (initramfs) корневой файловой системе
-
Отключение файловой системы initramfs и освобождение файловой системы ramfs
В этой главе будут обсуждаться три основных момента.
switch_root
: Мы объясним это старымinit
способом.pivot_root
: Мы объясним это черезsystemd
.chroot
: Мы объясним это в главе 10.
Переключение на новую корневую файловую систему в системе на основе init
Система на основе инициализации использует switch_root
для переключения на новую корневую файловую систему (sysroot
). Назначение switch_root
хорошо объяснено на его man-странице, как показано здесь:
# man switch_root
NAME
switch_root — switch to another filesystem as the root of the mount tree
SYNOPSIS
switch_root [-hV]
switch_root newroot init [arg...]
DESCRIPTION
switch_root moves already mounted /proc, /dev, /sys and /run to newroot and makes newroot the new root filesystem and starts init process.
WARNING: switch_root removes recursively all files and directories on the current root filesystem.
OPTIONS
-h, --help
Display help text and exit.
-V, --version
Display version information and exit.
RETURN VALUE
switch_root returns 0 on success and 1 on failure.
NOTES
switch_root will fail to function if newroot is not the root of a mount. If you want to switch root into a directory that does not meet this requirement then you can first use a bind-mounting trick to turn any directory into a mount point:
mount --bind $DIR $DIR
Таким образом, он переключается на новую корневую файловую систему (sysroot
) и вместе с корнем перемещает виртуальные файловые системы старой корневой файловой системы (proc
, dev
, sys
и т. д.) в новый корень. Лучшая особенность switch_root
заключается в том, что после монтирования новой корневой файловой системы он самостоятельно запускает процесс инициализации. Переключение на новую корневую файловую систему происходит в исходном коде dracut. На момент написания этой книги последней версией dracut была 049. Функция switch_root
определена в файле dracut-049/modules.d/99base/init.sh
.
unset PS4
CAPSH=$(command -v capsh)
SWITCH_ROOT=$(command -v switch_root)
PATH=$OLDPATH
export PATH
if [ -f /etc/capsdrop ]; then
. /etc/capsdrop
info "Calling $INIT with capabilities $CAPS_INIT_DROP dropped."
unset RD_DEBUG
exec $CAPSH --drop="$CAPS_INIT_DROP" -- \
-c "exec switch_root \"$NEWROOT\" \"$INIT\" $initargs" || \
{
warn "Command:"
warn capsh --drop=$CAPS_INIT_DROP -- -c exec switch_root "$NEWROOT" "$INIT" $initargs
warn "failed."
emergency_shell
}
else
unset RD_DEBUG
exec $SWITCH_ROOT "$NEWROOT" "$INIT" $initargs || {
warn "Something went very badly wrong in the initramfs. Please "
warn "file a bug against dracut."
emergency_shell
}
fi
В предыдущем коде вы можете видеть, что exec switch_root
был вызван точно так же, как это было описано на странице руководства switch_root
. Определенные значения переменных NEWROOT
и INIT
следующие:
NEWROOT = "/sysroot"
INIT = 'init' or 'sbin/init'
К вашему сведению: в наши дни файл инициализации представляет собой символическую ссылку на systemd
.
# ls -l sbin/init
lrwxrwxrwx. 1 root root 22 Dec 21 12:19 sbin/init -> ../lib/systemd/systemd
Для успешного выполнения switch_root
для виртуальных файловых систем их необходимо сначала смонтировать. Это будет достигнуто с помощью dracut-049/modules.d/99base/init.sh
. Вот шаги, которые необходимо выполнить:
-
Смонтировать файловую систему
proc
. -
Смонтировать файловую систему
sys
. -
Подключить каталог
/dev
с помощьюdevtmpfs
. -
Создать файлы устройств
stdin
,stdout
,stderr
,pts
иshm
вручную. -
Создать точку монтирования
/run
с tmpfs. (Точка монтирования/run
недоступна в системах на основеinit
.)
# vim dracut-049/modules.d/99base/init.sh
NEWROOT="/sysroot"
[ -d $NEWROOT ] || mkdir -p -m 0755 $NEWROOT
OLDPATH=$PATH
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
# mount some important things
[ ! -d /proc/self ] && \
mount -t proc -o nosuid,noexec,nodev proc /proc >/dev/null
if [ "$?" != "0" ]; then
echo "Cannot mount proc on /proc! Compile the kernel with CONFIG_PROC_FS!"
exit 1
fi
[ ! -d /sys/kernel ] && \
mount -t sysfs -o nosuid,noexec,nodev sysfs /sys >/dev/null
if [ "$?" != "0" ]; then
echo "Cannot mount sysfs on /sys! Compile the kernel with CONFIG_SYSFS!"
exit 1
fi
RD_DEBUG=""
. /lib/dracut-lib.sh
setdebug
if ! ismounted /dev; then
mount -t devtmpfs -o mode=0755,noexec,nosuid,strictatime devtmpfs /dev >/dev/null
fi
if ! ismounted /dev; then
echo "Cannot mount devtmpfs on /dev! Compile the kernel with CONFIG_DEVTMPFS!"
exit 1
fi
# prepare the /dev directory
[ ! -h /dev/fd ] && ln -s /proc/self/fd /dev/fd >/dev/null 2>&1
[ ! -h /dev/stdin ] && ln -s /proc/self/fd/0 /dev/stdin >/dev/null 2>&1
[ ! -h /dev/stdout ] && ln -s /proc/self/fd/1 /dev/stdout >/dev/null 2>&1
[ ! -h /dev/stderr ] && ln -s /proc/self/fd/2 /dev/stderr >/dev/null 2>&1
if ! ismounted /dev/pts; then
mkdir -m 0755 /dev/pts
mount -t devpts -o gid=5,mode=620,noexec,nosuid devpts /dev/pts >/dev/null
fi
if ! ismounted /dev/shm; then
mkdir -m 0755 /dev/shm
mount -t tmpfs -o mode=1777,noexec,nosuid,nodev,strictatime tmpfs /dev/shm >/dev/null
fi
if ! ismounted /run; then
mkdir -m 0755 /newrun
if ! str_starts "$(readlink -f /bin/sh)" "/run/"; then
mount -t tmpfs -o mode=0755,noexec,nosuid,nodev,strictatime tmpfs /newrun >/dev/null
else
# the initramfs binaries are located in /run, so don't mount it with noexec
mount -t tmpfs -o mode=0755,nosuid,nodev,strictatime tmpfs /newrun >/dev/null
fi
cp -a /run/* /newrun >/dev/null 2>&1
mount --move /newrun /run
rm -fr -- /newrun
fi
Переключение на новую корневую файловую систему в системе на базе systemd
Шаги почти аналогичны тем, что мы обсуждали для системы на основе init
. Единственное отличие для systemd
— это двоичный файл, созданный из кода C. Итак, очевидно, что переключение корня будет происходить в исходном коде C systemd, как показано здесь:
src/shared/switch-root.c:
Во-первых, рассмотрим следующее:
new_root = sysroot
old_root = /
Это переместит виртуальные файловые системы, которые уже размещены в корневой файловой системе initramfs; затем функция path_equal
проверяет, доступен ли путь new_root
.
if (path_equal(new_root, "/"))
return 0;
Позже он выполняет системный вызов pivot_root
(init
использует switch_root
) и меняет корень с /
(корневая файловая система initramfs) на sysroot
(корневая файловая система пользователя).
pivot_root(new_root,solved_old_root_after) >= 0)
Прежде чем идти дальше, нам нужно понять, что такое pivot_root
и что он делает.
# man pivot_root
NAME
pivot_root — change the root filesystem
SYNOPSIS
pivot_root new_root put_old
DESCRIPTION
pivot_root moves the root file system of the current process to the directory put_old and makes new_root the new root file system.
Since pivot_root(8) simply calls pivot_root(2), we refer to the man page of the latter for further details:
Note that, depending on the implementation of pivot_root, root and cwd of the caller may or may not change. The following is a sequence for invoking pivot_root that works in either case, assuming that pivot_root and chroot are in the current PATH:
cd new_root
pivot_root . put_old
exec chroot . command
Note that chroot must be available under the old root and under the new root, because pivot_root may or may not have implicitly changed the root directory of the shell.
Note that exec chroot changes the running executable, which is necessary if the old root directory should be unmounted afterwards. Also note that standard input, output, and error may still point to a device on the old root file system, keeping it busy. They can easily be changed when invoking chroot (see below; note the absence of leading slashes to make it work whether pivot_root has changed the shell's root or not).
pivot_root
изменяет корневую файловую систему (корневую файловую систему initramfs) текущего процесса (systemd) на новую корневую файловую систему (sysroot
), а также изменяет запущенный исполняемый файл (systemd из initramfs) на новый (systemd из корневой файловой системы пользователя).
После pivot_root
отсоединяет старое корневое устройство initramfs (src/shared/switch-root.c
).
# vim src/shared/switch-root.c
/* We first try a pivot_root() so that we can umount the old root dir. In many cases (i.e. where rootfs is /),
* that's not possible however, and hence we simply overmount root */
if (pivot_root(new_root, resolved_old_root_after) >= 0) {
/* Immediately get rid of the old root, if detach_oldroot is set.
* Since we are running off it we need to do this lazily. */
if (unmount_old_root) {
r = umount_recursive(old_root_after, MNT_DETACH);
if (r < 0)
log_warning_errno(r, "Failed to unmount old root directory tree, ignoring: %m");
}
} else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0)
return log_error_errno(errno, "Failed to move %s to /: %m", new_root);
После успешного выполнения pivot_root
это текущее состояние:
-
sysroot
стал root (/
). -
Текущий рабочий каталог стал корневым (
/
). -
chroot
будет выполнен так, что bash изменит свой корневой каталог со старого корня (initramfs) на новую (пользовательскую) корневую файловую систему.chroot
будет обсуждаться в следующей главе.
Наконец, удалим устройство old_root
(rm -rf
).
if (chroot(".") < 0)
return log_error_errno(errno, "Failed to change root: %m");
if (chdir("/") < 0)
return log_error_errno(errno, "Failed to change directory: %m");
if (old_root_fd >= 0) {
struct stat rb;
if (fstat(old_root_fd, &rb) < 0)
log_warning_errno(errno, "Failed to stat old root directory, leaving: %m");
else
(void) rm_rf_children(TAKE_FD(old_root_fd), 0, &rb); /* takes possession of the dir fd, even on failure */
}
Для лучшего понимания я настоятельно рекомендую прочитать весь исходный код src/shared/switch-root.c
, показанный здесь:
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <unistd.h>
#include "base-filesystem.h"
#include "fd-util.h"
#include "fs-util.h"
#include "log.h"
#include "missing_syscall.h"
#include "mkdir.h"
#include "mount-util.h"
#include "mountpoint-util.h"
#include "path-util.h"
#include "rm-rf.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "switch-root.h"
#include "user-util.h"
#include "util.h"
int switch_root(const char *new_root,
const char *old_root_after, /* path below the new root, where to place the old root after the transition */
bool unmount_old_root,
unsigned long mount_flags) { /* MS_MOVE or MS_BIND */
_cleanup_free_ char *resolved_old_root_after = NULL;
_cleanup_close_ int old_root_fd = -1;
bool old_root_remove;
const char *i;
int r;
assert(new_root);
assert(old_root_after);
if (path_equal(new_root, "/"))
return 0;
/* Check if we shall remove the contents of the old root */
old_root_remove = in_initrd();
if (old_root_remove) {
old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
if (old_root_fd < 0)
return log_error_errno(errno, "Failed to open root directory: %m");
}
/* Determine where we shall place the old root after the transition */
r = chase_symlinks(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, old_root_after);
if (r == 0) /* Doesn't exist yet. Let's create it */
(void) mkdir_p_label(resolved_old_root_after, 0755);
/* Work-around for kernel design: the kernel refuses MS_MOVE if any file systems are mounted MS_SHARED. Hence
* remount them MS_PRIVATE here as a work-around.
*
* https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
return log_error_errno(errno, "Failed to set \"/\" mount propagation to private: %m");
FOREACH_STRING(i, "/sys", "/dev", "/run", "/proc") {
_cleanup_free_ char *chased = NULL;
r = chase_symlinks(i, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, i);
if (r > 0) {
/* Already exists. Let's see if it is a mount point already. */
r = path_is_mount_point(chased, NULL, 0);
if (r < 0)
return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased);
if (r > 0) /* If it is already mounted, then do nothing */
continue;
} else
/* Doesn't exist yet? */
(void) mkdir_p_label(chased, 0755);
if (mount(i, chased, NULL, mount_flags, NULL) < 0)
return log_error_errno(errno, "Failed to mount %s to %s: %m", i, chased);
}
/* Do not fail if base_filesystem_create() fails. Not all switch roots are like base_filesystem_create() wants
* them to look like. They might even boot, if they are RO and don't have the FS layout. Just ignore the error
* and switch_root() nevertheless. */
(void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID);
if (chdir(new_root) < 0)
return log_error_errno(errno, "Failed to change directory to %s: %m", new_root);
/* We first try a pivot_root() so that we can umount the old root dir. In many cases (i.e. where rootfs is /),
* that's not possible however, and hence we simply overmount root */
if (pivot_root(new_root, resolved_old_root_after) >= 0) {
/* Immediately get rid of the old root, if detach_oldroot is set.
* Since we are running off it we need to do this lazily. */
if (unmount_old_root) {
r = umount_recursive(old_root_after, MNT_DETACH);
if (r < 0)
log_warning_errno(r, "Failed to unmount old root directory tree, ignoring: %m");
}
} else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0)
return log_error_errno(errno, "Failed to move %s to /: %m", new_root);
if (chroot(".") < 0)
return log_error_errno(errno, "Failed to change root: %m");
if (chdir("/") < 0)
return log_error_errno(errno, "Failed to change directory: %m");
if (old_root_fd >= 0) {
struct stat rb;
if (fstat(old_root_fd, &rb) < 0)
log_warning_errno(errno, "Failed to stat old root directory, leaving: %m");
else
(void) rm_rf_children(TAKE_FD(old_root_fd), 0, &rb); /* takes possession of the dir fd, even on failure */
}
return 0;
}
Здесь мы успешно переключились на корневую файловую систему пользователя и вышли из среды initramfs. Теперь systemd из корневой файловой системы пользователя с PID 1 начнет работать и позаботится об остальной части процедуры загрузки, а именно:
-
systemd запустит службы пользовательского пространства, такие как
httpd
,mysql
,postfix
,network services
и т. д. -
В конечном итоге целью будет достижение
default.target
. Как мы обсуждали ранее, передswitch_root
целью, вызываемойdefault.target
systemd, будетinitrd.target
, а послеswitch_root
это будет либоmulti-user.target
, либоgraphical.target
.
Но что произойдет с существующим процессом systemd
, который запустился из initramfs (корневой файловой системы)? Его убивают после switch_root
или pivot_root
? Новый процесс systemd
начинается с корневой файловой системы пользователя?
Ответ прост.
-
systemd initramfs создает канал.
-
systemd форкается.
-
Исходный PID 1 внедряется в
/systemd
и выполняет/sysroot/usr/lib/systemd/systemd
. -
Форкнутый systemd сериализует свое состояние по каналу до PID 1 и завершает работу.
-
PID 1 десериализует данные из канала и продолжает использовать новую конфигурацию в
/
(ранее/sysroot
).
Надеюсь, вам понравилось путешествие systemd внутри initramfs. Как мы упоминали ранее, остальная часть последовательности загрузки systemd, которая будет происходить вне initramfs, будет более или менее похожа на то, что мы обсуждали до сих пор.
Способ запуска GUI выходит за рамки этой книги. В следующей главе мы обсудим live-образы ISO и режим восстановления.
Глава 10
Режим rescue и live-образы
В этой последней главе мы рассмотрим режим восстановления (восстановления) и live-образы. Во время обсуждения режима восстановления мы рассмотрим восстановление initramfs, а также некоторые проблемы, связанные с невозможностью загрузки. Обсуждение live-образов охватывает Squashfs, rootfs.img
и последовательность загрузки live-образов.
Режим восстановления
Есть два способа загрузки в режиме восстановления.
-
Через встроенное меню GRUB. См. рисунок 10-1.
Рисунок 10-1. Запись режима восстановления из GRUB
-
Через live-образ ISO. См. рисунок 10-2.
Рисунок 10-2. Вход в режим восстановления из live-образа
Как следует из названия, этот режим предназначен для восстановления систем, которые застряли в состоянии «невозможно загрузиться». Представьте себе ситуацию, когда система не может смонтировать корневую файловую систему, и вы получаете это бесконечное общее сообщение:
'dracut-initqueue: warning dracut-initqueue timeout — starting timeout scripts'
Предположим, у вас установлено только одно ядро, как показано здесь:
.
.
[ OK ] Started Show Plymouth Boot Screen.
[ OK ] Started Forward Password R...s to Plymouth Directory Watch.
[ OK ] Reached target Paths.
[ OK ] Reached target Basic System.
[ 145.832487] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
[ 146.541525] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
[ 147.130873] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
[ 147.703069] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
[ 148.267123] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
[ 148.852865] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
[ 149.430171] dracut-initqueue[437]: Warning: dracut-initqueue timeout — starting timeout scripts
.
.
Поскольку в этой системе только одно ядро (которое не может загружаться), как бы вы решили проблему «невозможно загрузиться» без среды? Режим восстановления был создан исключительно для этой цели. Давайте сначала выберем режим восстановления по умолчанию, который предустановлен в Linux и может быть выбран в меню GRUB. См. рисунок 10-3.
Рисунок 10-3. Экран GRUB
Режим восстановления загрузится нормально, и, как вы можете видеть на рисунке 10-4, если все в порядке, пользователю будет представлена корневая файловая система.
Рисунок 10-4. Корневая файловая система, смонтированная в режиме восстановления
Но на ум приходит вопрос: если нормальное ядро не может загрузиться, то как эта же система может загрузиться в режиме восстановления?
Это связано с тем, что при установке Fedora или любого другого дистрибутива Linux установщик Linux, называемый Anaconda, устанавливает два ядра внутри /boot
.
# ls -lh /boot/
total 164M
-rw-r--r--. 1 root root 209K Oct 22 01:03 config-5.3.7-301.fc31.x86_64
drwx------. 4 root root 4.0K Oct 24 04:44 efi
-rw-r--r--. 1 root root 181K Aug 2 2019 elf-memtest86+-5.01
drwxr-xr-x. 2 root root 4.0K Oct 24 04:42 extlinux
drwx------. 5 root root 4.0K Mar 28 13:37 grub2
-rw-------. 1 root root 80M Dec 9 10:18 initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img
-rw-------. 1 root root 32M Dec 9 10:19 initramfs-5.3.7-301.fc31.x86_64.img
drwxr-xr-x. 3 root root 4.0K Dec 9 10:18 loader
drwx------. 2 root root 16K Dec 9 10:12 lost+found
-rw-r--r--. 1 root root 179K Aug 2 2019 memtest86+-5.01
-rw-------. 1 root root 30M Jan 6 09:37 new.img
-rw-------. 1 root root 4.3M Oct 22 01:03 System.map-5.3.7-301.fc31.x86_64
-rwxr-xr-x. 1 root root 8.9M Dec 9 10:18 vmlinuz-0-rescue-2058a9f13f9e489dba29c477a8ae2493
-rwxr-xr-x. 1 root root 8.9M Oct 22 01:04 vmlinuz-5.3.7-301.fc31.x86_64
Как видите, vmlinuz-5.3.7-301.fc31.x86_64
— это обычное ядро, тогда как vmlinuz-0-rescue-19a08a3e86c24b459999fbac68e42c05
— это ядро восстановления, которое представляет собой отдельное ядро со своим собственным файлом initramfs, называемым initramfs-0-rescue-19a08a3e86c24b459999fbac68e42c05.img
.
Допустим, вы установили новый пакет (.rpm
или .deb
), предоставленный nvidia, в котором есть новые графические драйверы. Поскольку графические драйверы необходимо добавлять в initramfs, пакет nvidia пересобрал исходный initramfs ядра (initramfs-5.3.7-301.fc31.x86_64.img
). Итак, исходное ядро имеет недавно добавленный графический драйвер, но в аварийный initramfs этот драйвер не добавлен. Когда пользователь пытается загрузиться, система не загружается с исходным ядром (vmlinuz-5.3.7-301.fc31.x86_64
), поскольку установленный графический драйвер несовместим с подключенной видеокартой, но в то же время система будет успешно загружена в режиме восстановления, поскольку несовместимые драйверы отсутствуют в initramfs режима восстановления. Ядро режима восстановления будет иметь те же параметры командной строки, что и обычное ядро, и поэтому установленное ядро восстановления знает имя корневой файловой системы пользователя.
На рисунке 10-5 показаны параметры командной строки обычного ядра.
Рисунок 10-5. Параметры командной строки обычного ядра
На рисунке 10-6 показаны параметры командной строки аварийного ядра.
Рисунок 10-6. Параметры командной строки аварийного ядра
Режим восстановления initramfs
Размер initramfs режима восстановления (initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img
) намного больше, чем initramfs исходного ядра (initramfs-5.3.7-301.fc31.x86_64.img
).
# ls -lh /boot/
total 164M
-rw-r--r--. 1 root root 209K Oct 22 01:03 config-5.3.7-301.fc31.x86_64
drwx------. 4 root root 4.0K Oct 24 04:44 efi
-rw-r--r--. 1 root root 181K Aug 2 2019 elf-memtest86+-5.01
drwxr-xr-x. 2 root root 4.0K Oct 24 04:42 extlinux
drwx------. 5 root root 4.0K Mar 28 13:37 grub2
-rw-------. 1 root root 80M Dec 9 10:18 initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img
-rw-------. 1 root root 32M Dec 9 10:19 initramfs-5.3.7-301.fc31.x86_64.img
drwxr-xr-x. 3 root root 4.0K Dec 9 10:18 loader
drwx------. 2 root root 16K Dec 9 10:12 lost+found
-rw-r--r--. 1 root root 179K Aug 2 2019 memtest86+-5.01
-rw-------. 1 root root 30M Jan 6 09:37 new.img
-rw-------. 1 root root 4.3M Oct 22 01:03 System.map-5.3.7-301.fc31.x86_64
-rwxr-xr-x. 1 root root 8.9M Dec 9 10:18 vmlinuz-0-rescue-2058a9f13f9e489dba29c477a8ae2493
-rwxr-xr-x. 1 root root 8.9M Oct 22 01:04 vmlinuz-5.3.7-301.fc31.x86_64
Почему так? Это потому, что initramfs режима восстановления не зависит от хоста, как обычный initramfs ядра. Rescue initramfs — это общий initramfs, подготовленный с учетом всех возможных устройств, на которых пользователь может создать корневую файловую систему. Давайте сравним оба дерева initramfs.
# tree
.
├── normal_kernel
│ └── initramfs-5.3.7-301.fc31.x86_64.img
└── rescue_kernel
└── initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img
2 directories, 2 files
Мы извлечем их в соответствующие каталоги.
# /usr/lib/dracut/skipcpio
initramfs-5.3.7-301.fc31.x86_64.img | gunzip -c | cpio -idv
# /usr/lib/dracut/skipcpio
initramfs-0-rescue-2058a9f13f9e489dba29c477a8ae2493.img | gunzip -c | cpio -idv
Список файлов мы составим из извлеченного дерева initramfs.
# tree normal_kernel/ > normal.txt
# tree rescue_kernel/ > rescue.txt
Ниже приведены различия между обеими системами initramfs. Rescue система initramfs содержит почти 2189 дополнительных файлов по сравнению с обычным initramfs. Кроме того, в файл восстановления initramfs добавлено почти 719 дополнительных модулей.
# diff -yt rescue.txt normal.txt | grep '<' | wc -l
2186
# diff -yt rescue.txt normal.txt | grep '<' | grep -i '.ko' | wc -l
719
.
.
│ │ ├── lspci <
│ │ ├── mdadm <
│ │ ├── mdmon <
│ │ ├── mdraid-cleanup <
│ │ ├── mdraid_start <
│ │ ├── mount.cifs <
│ │ ├── mount.nfs <
│ │ ├── mount.nfs4 -> mount.nfs <
│ │ ├── mpathpersist <
│ │ ├── multipath <
│ │ ├── multipathd <
│ │ ├── nfsroot <
│ │ ├── partx <
│ │ ├── pdata_tools <
│ │ ├── ping -> ../bin/ping <
│ │ ├── ping6 -> ../bin/ping <
│ │ ├── rpcbind -> ../bin/rpcbind <
│ │ ├── rpc.idmapd <
│ │ ├── rpcinfo -> ../bin/rpcinfo <
│ │ ├── rpc.statd <
│ │ ├── setpci <
│ │ ├── showmount <
│ │ ├── thin_check -> pdata_tools <
│ │ ├── thin_dump -> pdata_tools <
│ │ ├── thin_repair -> pdata_tools <
│ │ ├── thin_restore -> pdata_tools <
│ │ ├── xfs_db <
│ │ ├── xfs_metadump <
│ │ └── xfs_repair <
├── lib <
│ ├── iscsi <
│ ├── lldpad <
│ ├── nfs <
│ │ ├── rpc_pipefs <
│ │ └── statd <
│ │ └── sm <
Initramfs режима восстановления будет содержать почти все модули и поддерживаемые файлы для устройства, на котором пользователь может создать корневую файловую систему, тогда как обычный initramfs будет зависеть от хоста. В нем будут только те модули и поддерживаемые файлы того устройства, на котором пользователь сделал корневую файловую систему. Если вы хотите самостоятельно выполнить восстановление initramfs, вы можете установить пакет dracut-config-generic
в системах на базе Fedora. Пакет предоставляет только один файл и имеет конфигурацию для отключения генерации initramfs для конкретного хоста.
# rpm -ql dracut-config-generic
/usr/lib/dracut/dracut.conf.d/02-generic-image.conf
# cat /usr/lib/dracut/dracut.conf.d/02-generic-image.conf
hostonly="no"
Как вы можете видеть, файл запрещает dracut создавать initramfs для конкретного хоста.
Проблема 9, «Невозможно загрузиться» (chroot)
Проблема: Как обычное, так и аварийное ядра не загружаются. На рисунке 10-7 показаны обычные сообщения о панике ядра.
Рисунок 10-7. Сообщения о панике ядра
Выдаваемые сообщения о панике ядра жалуются, что ядро не может смонтировать корневую файловую систему. Ранее мы видели, что всякий раз, когда ядро не может смонтировать корневую файловую систему пользователя, оно выдает сообщение о тайм-ауте dracut-initqueue
.
'dracut-initqueue: warning dracut-initqueue timeout — starting timeout scripts'
Однако на этот раз сообщения паники иные. Итак, похоже, что проблема не связана с корневой файловой системой пользователя. Еще одна подсказка: здесь упоминается файловая система VFS; VFS означает «виртуальная файловая система», поэтому это указывает на то, что панические сообщения не могут смонтировать корневую файловую систему из initramfs. Основываясь на этих подсказках, я думаю, мы изолировали проблему и нам следует сосредоточиться на initramfs обоих ядер.
Как вы можете видеть на рисунке 10-8, сообщения о панике ядра в режиме восстановления также похожи.
Рисунок 10-8. Сообщения о панике ядра в режиме восстановления
Решение: Вот шаги для решения проблемы:
-
Поскольку установленное аварийное ядро также вызывает панику, нам нужно использовать live-образ Fedora или любого дистрибутива Linux для загрузки. Как показано на рисунках 10-9 и 10-10, мы используем live-образ Fedora.
Рисунок 10-9. Экран приветствия live-образа
Рисунок 10-10 Загрузка с помощью live-образа
-
Система загрузилась в режиме восстановления. Последовательность загрузки live-образа будет обсуждаться в разделе «Live-образы» этой главы. Давайте сначала станем пользователем
sudo
.$ sudo su We trust you have received the usual lecture from your local system administrator. It usually boils down to these three things: #1) Respect the privacy of others. #2) Think before you type. #3) With great power comes great responsibility. [root@localhost-live liveuser] #
-
Корневой каталог, который мы видим здесь, взят из live-образа. Поскольку ядро live-образа не знает имени корневой файловой системы пользователя, оно не может смонтировать ее как аварийное ядро.
[root@localhost-live liveuser]# ls / bin boot dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
-
Выясним, что не так с initramfs нормального и аварийного ядра. Для этого нам нужно сначала смонтировать корневую файловую систему пользователя.
# vgscan -v Found volume group "fedora_localhost-live" using metadata type lvm2
# lvscan -v ACTIVE '/dev/fedora_localhost-live/swap' [2.20 GiB] inherit ACTIVE '/dev/fedora_localhost-live/root' [18.79 GiB] inherit
# pvscan -v PV /dev/sda2 VG fedora_localhost-live lvm2 [<21.00 GiB / 0 free] Total: 1 [<21.00 GiB] / in use: 1 [<21.00 GiB] / in no VG: 0 [0 ]
Как видите, эта система имеет корневую файловую систему пользователя, основанную на LVM. Физический том находится на устройстве sda. Далее мы смонтируем корневую файловую систему пользователя во временный каталог.
# mkdir temp_root
# mount /dev/fedora_localhost-live/root temp_root/
# ls temp_root/ bin dev home lib64 media opt root sbin sys tmp usr boot etc lib lost+found mnt proc run srv @System.solv user_root_fs.txt var
-
Давайте проверим статус файла initramfs.
# ls temp_root/boot/ -l total 0
Загрузочный каталог корневой файловой системы пользователя пуст. Это связано с тем, что в этой системе загрузка представляет собой отдельный раздел.
# mount /dev/sda1 temp_root/boot/
# ls temp_root/boot/ Config-5.3.7-301.fc31.x86_64 efi elf-memtest86+-5.01 extlinux grub2 loader lost+found Memtest86+-5.01 System.map-5.3.7-301.fc31.x86_64 vmlinuz-0-rescue-19a08a3e86c24b459999fbac68e42c05 vmlinuz-5.3.7-301.fc31.x86_64
Удивительно, но, как вы можете видеть, в корневой файловой системе пользователя нет доступных файлов initramfs, и это причина паники обоих ядер.
Итак, проблема выявлена, и нам необходимо перегенерировать initramfs. Чтобы создать новый initramfs, нам нужно использовать команду
dracut
, но есть некоторые проблемы.-
Какой бы двоичный файл или команду мы ни выполнили, этот двоичный файл будет из корневой файловой системы live-образа. Например, команда
dracut
будет запускаться из/usr/bin/dracut
, тогда как двоичный файл корневой файловой системы пользователя находится вtemp_root/usr/bin/dracut
. -
Для запуска любого двоичного файла необходимы вспомогательные библиотеки, такие как
libc.so
, которые снова будут использоваться из корневой файловой системы live-образа. Это означает, что вся среда, которую мы сейчас используем, взята из live-образа, и это может создать серьезные проблемы. Например, мы можем установить любой пакет, но он будет установлен в корневую файловую систему live-образа, а не в корневую файловую систему пользователя.
Короче говоря, нам нужно изменить наш текущий корень (
/
) с корневой файловой системы live-образа на корневую файловую систему пользователя (temp_root
).chroot
— это команда, которую нам нужно использовать для этого. -
-
Само название предполагает, что она изменит корень bash с текущего корня на новый.
chroot
будет успешным, только если виртуальные файловые системы уже смонтированы в новом root.root@localhost-live liveuser]# ls / bin boot dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
Наш текущий корень — это корневая файловая система live-образа. Перед
chroot
мы смонтируем виртуальные файловые системыproc
,dev
,devpts
,sys
иrun
.# mount -v --bind /dev/ temp_root/dev mount: /dev bound on /home/liveuser/temp_root/dev.
# mount -vt devpts devpts temp_root/dev/pts -o gid=5,mode=620 mount: devpts mounted on /home/liveuser/temp_root/dev/pts.
# mount -vt proc proc temp_root/proc mount: proc mounted on /home/liveuser/temp_root/proc.
# mount -vt sysfs sysfs temp_root/sys mount: sysfs mounted on /home/liveuser/temp_root/sys.
# mount -vt tmpfs tmpfs temp_root/run mount: tmpfs mounted on /home/liveuser/temp_root/run.
-
Мы готовы выполнить
chroot
в корневую файловую систему пользователя.# chroot temp_root/
# ls bin dev home lib64 media opt root sbin sys tmp usr boot etc lib lost+found mnt proc run srv @System.solv user_root_fs.txt var
Итак,
temp_root
теперь стала корневой файловой системой bash. Если вы выйдете из этой оболочки, bash изменит свой корневой каталог с корневой файловой системы пользователя на корневую файловую систему live-образа. Итак, пока мы находимся в этом экземпляре оболочки, наш корневой каталог —temp_root
. Теперь, какую бы команду или двоичный файл мы ни выполнили, они будут выполняться внутри среды корневой файловой системы пользователя. Следовательно, сейчас совершенно безопасно выполнять процессы в этой среде. -
Чтобы решить эту проблему «невозможно загрузиться», нам нужно заново создать initramfs.
root@localhost-live /]# ls /lib/modules 5.3.7-301.fc31.x86_64
[root@localhost-live /]# cd /boot/
[root@localhost-live boot]# rpm -qa | grep -i 'kernel-5' kernel-5.3.7-301.fc31.x86_64
[root@localhost-live boot]# dracut initramfs-5.3.7-301.fc31.x86_64.img 5.3.7-301.fc31.x86_64
-
Если вы хотите восстановить initramfs аварийного ядра, вам необходимо установить пакет
dracut-config-generic
. -
После перезагрузки система сможет загрузиться, и проблема «невозможно загрузиться» будет устранена.
Режим восстановления корпоративных дистрибутивов Linux
В некоторых дистрибутивах Linux, таких как CentOS, подход к использованию образа восстановления немного отличается. Корпоративная версия Linux попытается найти корневую файловую систему пользователя самостоятельно. Давайте посмотрим на это в действии. На рисунках 10-11 и 10-12 показана процедура выбора режима восстановления CentOS.
Рисунок 10-11. Экран приветствия CentOS
Рисунок 10-12. Выбор режима восстановления
Он загрузится и, как вы можете видеть на рисунке 10-13, на экране отобразятся некоторые сообщения.
Рисунок 10-13. Информационное сообщение
Если мы выберем вариант 1, «Continue», то режим восстановления выполнит поиск на диске и самостоятельно найдет корневую файловую систему. Как только корневая файловая система пользователя будет определена, он смонтирует ее в каталог /mnt/sysimage
. См. рисунок 10-14.
Рисунок 10-14. Корневая файловая система смонтирована в /mnt/sysimage
Как видите, корневая файловая система пользователя смонтирована в /mnt/sysimage
; нам просто нужно внедрить в него chroot. Но прелесть в том, что нам не нужно заранее монтировать виртуальные файловые системы. Это связано с тем, что, как вы можете видеть на рисунке 10-15, двоичный файл chroot
, используемый в CentOS, был настроен и самостоятельно монтирует виртуальные файловые системы.
Рисунок 10-15. chroot
Если бы мы выбрали вариант 2, «Read-Only Mount», то сценарии восстановления смонтировали бы корневую файловую систему пользователя в режиме «только для чтения», но в /mnt/sysimage
. Если бы мы выбрали третий вариант «Skip», система восстановления не пыталась бы найти и смонтировать корневую файловую систему пользователя самостоятельно; она просто предоставила бы нам оболочку.
Но как системе восстановления удается узнать корневую файловую систему, если в аварийном ядре CentOS ISO нет имени корневой файловой системы пользователя?
Здесь нет никакого трюка, который могла бы использовать Anaconda, чтобы узнать имя корневой файловой системы пользователя. Anaconda смонтирует каждый диск, подключенный к системе, и проверит, присутствует ли на нем /etc/fstab
или нет. Если /etc/fstab
найден, он получит из него имя корневой файловой системы пользователя. Если к вашей системе подключено огромное количество дисков, существует высокая вероятность того, что Anaconda может потребоваться много времени для монтирования корневой файловой системы пользователя. В таком случае лучше вручную смонтировать корневую файловую систему пользователя. Исходный код для поиска корневой файловой системы пользователя присутствует в исходном архиве Anaconda, как показано здесь:
# vim pyanaconda/storage/root.py
def _find_existing_installations(devicetree):
"""Find existing GNU/Linux installations on devices from the device tree.
:param devicetree: a device tree to find existing installations in
:return: roots of all found installations
"""
if not os.path.exists(conf.target.physical_root):
blivet_util.makedirs(conf.target.physical_root)
sysroot = conf.target.physical_root
roots = []
direct_devices = (dev for dev in devicetree.devices if dev.direct)
for device in direct_devices:
if not device.format.linux_native or not device.format.mountable or \
not device.controllable or not device.format.exists:
continue
try:
device.setup()
except Exception: # pylint: disable=broad-except
log_exception_info(log.warning, "setup of %s failed", [device.name])
continue
options = device.format.options + ",ro"
try:
device.format.mount(options=options, mountpoint=sysroot)
except Exception: # pylint: disable=broad-except
log_exception_info(log.warning, "mount of %s as %s failed",
[device.name, device.format.type])
blivet_util.umount(mountpoint=sysroot)
continue
if not os.access(sysroot + "/etc/fstab", os.R_OK):
blivet_util.umount(mountpoint=sysroot)
device.teardown()
continue
try:
(architecture, product, version) = get_release_string(chroot=sysroot)
except ValueError:
name = _("Linux on %s") % device.name
else:
# I'd like to make this finer grained, but it'd be very difficult
# to translate.
if not product or not version or not architecture:
name = _("Unknown Linux")
elif "linux" in product.lower():
name = _("%(product)s %(version)s for %(arch)s") % \
{"product": product, "version": version, "arch": architecture}
else:
name = _("%(product)s Linux %(version)s for %(arch)s") % \
{"product": product, "version": version, "arch": architecture}
(mounts, swaps) = _parse_fstab(devicetree, chroot=sysroot)
blivet_util.umount(mountpoint=sysroot)
if not mounts and not swaps:
# empty /etc/fstab. weird, but I've seen it happen.
continue
roots.append(Root(mounts=mounts, swaps=swaps, name=name))
Live-образы
Live-образы — одна из лучших особенностей систем Linux. Эта книга не была бы полной, если бы мы ограничились обычной загрузкой с жесткого диска. Давайте посмотрим, как загружается live-образ Linux. Сначала давайте смонтируем ISO-образ и посмотрим, что он содержит.
# mkdir live_image
# mount /dev/cdrom live_image/
mount: /home/yogesh/live_image: WARNING: device write-protected, mounted read-only.
# tree live_image/
live_image/
├── EFI
│ └── BOOT
│ ├── BOOT.conf
│ ├── BOOTIA32.EFI
│ ├── BOOTX64.EFI
│ ├── fonts
│ │ └── unicode.pf2
│ ├── grub.cfg
│ ├── grubia32.efi
│ ├── grubx64.efi
│ ├── mmia32.efi
│ └── mmx64.efi
├── images
│ ├── efiboot.img
│ ├── macboot.img
│ └── pxeboot
│ ├── initrd.img
│ └── vmlinuz
├── isolinux
│ ├── boot.cat
│ ├── boot.msg
│ ├── grub.conf
│ ├── initrd.img
│ ├── isolinux.bin
│ ├── isolinux.cfg
│ ├── ldlinux.c32
│ ├── libcom32.c32
│ ├── libutil.c32
│ ├── memtest
│ ├── splash.png
│ ├── vesamenu.c32
│ └── vmlinuz
└── LiveOS
└── squashfs.img
Live-образ разделен на четыре каталога: EFI
, images
, isolinux
и LiveOS
:
-
EFI:
Мы уже обсуждали этот каталог, когда говорили о загрузчике. Прошивка UEFI перейдет в этот каталог и запустит файл
grubx64.efi
. Файлgrubx64.efi
прочитает файлgrub.cfg
и извлечет файлыinitrd.img
иvmlinuz
из каталогаisolinux
. -
images:
Этот каталог будет использоваться в основном, если мы загружаемся через PXE. Загрузка по сети выходит за рамки этой книги.
-
isolinux:
Если UEFI загружается способом BIOS, он прочитает файл
grub.conf
отсюда. Этот каталог в основном предназначен для хранения файловinitrd
иvmlinuz
. Другими словами, это каталог/boot
для обычной корневой файловой системы. -
liveOS:
Вот где происходит волшебство. В этом каталоге есть файл с именем
sqashfs.img
. Как только вы его смонтируете, вы найдете в немrootfs.img
.
# mkdir live_image_extract_1
# mount live_image/LiveOS/squashfs.img live_image_extract_1/
# ls live_image_extract_1/
LiveOS
# ls live_image_extract_1/LiveOS/
rootfs.img
# mkdir live_image_extract_2
# mount live_image_extract_1/LiveOS/rootfs.img live_image_extract_2/
# ls live_image_extract_2/
bin boot dev etc home lib lib64 lost+found media
mnt opt proc root run sbin srv sys tmp usr var
SquashFS
Squashfs — это небольшая сжатая файловая система, доступная только для чтения. Эта файловая система обычно используется для встроенных систем, где ценен каждый байт памяти. Squashfs дает нам больше гибкости и производительности по сравнению с архивами tarball. Squashfs хранит в себе действующую корневую файловую систему Fedora (rootfs.img
), и она будет смонтирована только для чтения.
# mount | grep -i rootfs
/home/yogesh/live_image_extract_1/LiveOS/rootfs.img on /home/yogesh/live_image_extract_2 type ext4 (ro,relatime,seclabel)
Вы можете использовать команду mksquashfs
, предоставляемую squshfs-tool
, чтобы создать образ/архив Squashfs.
rootfs.img
rootfs.img
— это файловая система ext4 с типичной корневой файловой системой в ней. Некоторые дистрибутивы создают гостевого пользователя или пользователя с именем live
для live-образа, но в Fedora все делает пользователь root.
# file live_image_extract_1/LiveOS/rootfs.img
live_image_extract_1/LiveOS/rootfs.img: Linux rev 1.0 ext4 filesystem data, UUID=849bdfdc-c8a9-4fed-a727-de52e24d981f, volume name "Anaconda" (extents) (64bit) (large files) (huge files)
Последовательность загрузки live-образа
Вот последовательность действий:
-
Прошивка вызовет загрузчик (
grubx64.efi
). Он прочитает файлgrub.cfg
и скопирует файлыvmlinuz
иinitrd
из каталогаisolinux
. -
Ядро распакует себя в определенное место и извлечет initramfs в любое доступное место.
-
systemd, запущенный из initramfs, извлечет файл
rootfs.img
на целевое устройство device-mapper по адресу/dev/mapper/live-rw
, смонтирует его в корневой файловой системе (/
) и включит в негоswitch_root
. -
Как только корневая файловая система станет доступна, вы можете считать ее обычной операционной системой, установленной на CD, DVD или в файле
.iso
.
Кроме того, очевидно, что initramfs с live-образом будет намного больше по размеру по сравнению с initramfs, специфичными для хоста.