Feb 8, 2025
2. Глава. Сборка ядра Linux 6.x из исходного кода – Часть 1
Сборка ядра Linux из исходного кода — это интересный способ начать ваше путешествие в разработке ядра! Уверяю вас, путь будет долгим и трудным, но в этом и заключается его прелесть, верно? Тема сборки ядра настолько обширна, что заслуживает разделения на две главы, эту и следующую.
Вспомните, что мы уже усвоили в расширенной первой главе, опубликованной онлайн (http://www.packtpub.com/sites/default/files/downloads/9781803232225_Online_Chapter.pdf): в основном, как настроить рабочее пространство для программирования ядра Linux. Вы также познакомились с источниками документации для пользователей и разработчиков ядра, а также с несколькими полезными проектами, которые идут рука об руку с разработкой ядра/драйверов. Сейчас я предполагаю, что вы завершили главу, опубликованную онлайн, и соответственно настроили рабочую среду; если нет, пожалуйста, сделайте это перед тем, как продолжить.
Основная цель этой главы и следующей — подробно описать, как именно вы можете собрать современное ядро Linux с нуля, используя исходный код. В этой главе вы сначала узнаете о необходимых основах: номенклатуре версий ядра, рабочем процессе разработки и различных типах исходных деревьев. Затем мы перейдем к практике: вы научитесь скачивать стабильное “ванильное” ядро Linux на гостевую виртуальную машину (VM) с Linux. Под “ванильным ядром” мы подразумеваем обычный, стандартный исходный код ядра, выпущенный сообществом разработчиков ядра Linux на его репозитории, https://www.kernel.org. После этого вы немного узнаете о структуре исходного кода ядра — получая, по сути, обзор ядра с высоты 10 000 футов. Затем следует рецепт сборки ядра.
Перед тем как продолжить, важный момент: любая современная Linux-система, будь то суперкомпьютер или крошечное встраиваемое устройство, имеет три обязательных компонента:
- Загрузчик
- Ядро операционной системы (OS)
- Корневую файловую систему
Кроме того, у нее есть два необязательных компонента:
- Если семейство процессоров ARM или PPC (32- или 64-бит), файл изображения Device Tree Blob (DTB)
- Файл изображения initramfs (или initrd)
В этих двух главах мы будем заниматься только сборкой ядра ОС (Linux) из исходного кода. Мы не углубляемся в детали корневой файловой системы. В следующей главе мы узнаем, как минимально настроить загрузчик GNU GRUB, специфичный для x86.
Полный процесс сборки ядра — по крайней мере, для x86[_64] — требует в общей сложности шесть или семь шагов. Кроме необходимых подготовительных шагов, здесь мы рассмотрим первые три, а остальные — в следующей главе.
В этой главе мы рассмотрим следующие темы:
- Подготовка к сборке ядра
- Шаги для сборки ядра из исходного кода
- Шаг 1 – Получение дерева исходного кода Linux
- Шаг 2 – Распаковка дерева исходного кода
- Шаг 3 – Настройка ядра Linux
- Настройка меню ядра, Kconfig и добавление нашего собственного пункта меню
Вы можете задаться вопросом: что насчет сборки ядра Linux для другой архитектуры процессора (например, ARM 32 или 64 бит)? Мы занимаемся этим точно так же в следующей главе!
Технические требования
Я предполагаю, что вы прошли онлайн-главу “Настройка рабочего пространства ядра” и должным образом подготовили гостевую ВМ x86_64 с Ubuntu 22.04 LTS (или эквивалент) и установили все необходимые пакеты. Если нет, я настоятельно рекомендую сделать это сначала.
Чтобы получить максимум из этой книги, я также настоятельно рекомендую клонировать репозиторий GitHub этой книги (https://github.com/PacktPublishing/Linux-Kernel-Programming_2E) для кода и работать с ним в практической форме.
Подготовка к сборке ядра
Важно с самого начала понять несколько вещей, которые помогут вам по мере того, как мы продвигаемся в нашем путешествии по сборке и работе с ядром Linux. Во-первых, ядро Linux и его родственные проекты полностью децентрализованы — это виртуальное, онлайн сообщество с открытым исходным кодом! Ближайшим к “офису” для Linux является следующее: управление ядром Linux (а также несколькими десятками связанных проектов) находится в компетентных руках Linux Foundation (https://linuxfoundation.org/); далее, оно управляет организацией Linux Kernel Organization, некоммерческой организацией, которая бесплатно распространяет ядро Linux среди общественности (https://www.kernel.org/nonprofit.html).
Знали ли вы? Условия лицензии GNU GPLv2 — под которой выпущено ядро Linux и будет продолжать распространяться в обозримом будущем — никак не мешают оригинальным разработчикам взимать плату за свою работу! Просто Линус Торвальдс, а теперь и Фонд Linux, делают программное обеспечение ядра Linux доступным для всех бесплатно. Это не мешает коммерческим организациям добавлять ценность к ядру (и их продуктам, которые с ним связаны) и взимать за это плату, будь то через первоначальный единовременный платеж или сегодняшнюю типичную модель подписки SaaS/IaaS. Таким образом, открытый исходный код определенно жизнеспособен для бизнеса, что уже доказано и продолжает доказываться ежедневно. Клиенты, чей бизнес лежит в другой области, просто ищут ценность за свои деньги; бизнесы, построенные вокруг Linux, могут предоставить это, обеспечивая клиентов экспертной поддержкой, детализированными SLA и обновлениями.
Некоторые из ключевых моментов, которые мы обсудим в этом разделе, включают следующее:
- Номенклатура выпуска ядра или версии
- Типичный рабочий процесс разработки ядра
- Существование различных типов деревьев исходного кода ядра в репозитории
С этой информацией вы будете лучше подготовлены к процедуре сборки ядра. Хорошо, давайте рассмотрим каждый из вышеуказанных пунктов.
Понимание номенклатуры выпуска ядра Linux
Чтобы увидеть номер версии ядра, просто выполните команду uname -r в вашей оболочке. Как именно интерпретировать вывод команды uname -r? На нашей гостевой ВМ с дистрибутивом Ubuntu 22.04 LTS версии x86_64 мы запускаем uname, передавая опцию -r, чтобы отобразить только текущий выпуск или версию ядра:
$ uname -r
5.19.0-40-generic
Конечно, к моменту, когда вы это читаете, ядро Ubuntu 22.04 LTS, скорее всего, будет обновлено до более позднего выпуска; это совершенно нормально. Ядро 5.19.0-40-generic было тем, что я встретил с Ubuntu 22.04.2 LTS на момент написания этой главы.
Современная номенклатура номера выпуска ядра Linux выглядит следующим образом: major#.minor#[.patchlevel][-EXTRAVERSION]
Это также часто записывается или описывается как w.x[.y][-z]
.
Квадратные скобки вокруг компонентов patchlevel и EXTRAVERSION (или y и -z) указывают, что они являются необязательными. Следующая таблица суммирует значение компонентов номера выпуска:
| Компонент номера выпуска | Значение | Пример |
| ———————— | —————————————————————————————————————————————————————————– | ————————————————– |
| Major # (или w) | Основной или главный номер; сейчас мы находимся на серии ядер 6.x, таким образом основной номер - 6. | 2, 3, 4, 5, 6 |
| Minor # (или x) | Минорный номер, иерархически ниже основного номера. | от 0 и выше |
| [patchlevel] (или y) | Иерархически ниже минорного номера – также называется ABI или ревизией – применяется иногда к стабильному ядру, когда требуются значительные исправления ошибок/безопасности. | от 0 и выше |
| [-EXTRAVERSION] (или -z) | Также называется localversion; обычно используется дистрибутивами ядер и производителями для отслеживания своих внутренних изменений. | Варьируется; Ubuntu использует w.x.y-<z>-generic
|
Таблица 2.1: Номенклатура выпуска ядра Linux
Таким образом, мы можем теперь интерпретировать номер выпуска ядра дистрибутива Ubuntu 22.04 LTS, 5.19.0-40-generic:
- Major # (или w): 5
- Minor # (или x): 19
- [patchlevel] (или y): 0
- [-EXTRAVERSION] (или -z): -40-generic
Обратите внимание, что ядра дистрибутивов могут не строго следовать этим конвенциям; это зависит от них. Регулярные или “ванильные” ядра, которые выпускаются на сайте https://www.kernel.org/, действительно следуют этим конвенциям (по крайней мере, пока Линус Торвальдс не решит их изменить).
Исторически, в ядрах до версии 2.6 (то есть, теперь это древние вещи), минорный номер имел особое значение; если он был четным, это указывало на стабильный релиз ядра, а если нечетным — на нестабильный или бета-релиз. Сейчас это уже не так.
В рамках интересного упражнения по настройке ядра мы позже изменим компонент localversion (также известный как -EXTRAVERSION) ядра, которое мы соберем.
Релизы “пальцы и пальцы ног”
Далее, важно понять простой факт: с современными ядрами Linux, когда изменяется основной и/или минорный номер, это не означает, что произошло что-то грандиозное или ключевое в плане нового дизайна, архитектуры или функционала; нет, это просто, по словам Линуса, органическая эволюция.
Текущая используемая номенклатура версии ядра является слабо временной, а не основанной на функциях. Таким образом, новый основной номер появляется время от времени. Как часто именно? Линус любит называть это моделью “пальцы и пальцы ног”; когда у него заканчиваются пальцы рук и ног для подсчета минорного номера (компонента x от w.x.y), он обновляет основной номер с w на w+1. Следовательно, после итерации над 20 минорными номерами – от 0 до 19 – мы получаем новый основной номер.
На практике это происходит с ядра 3.0; таким образом, у нас есть следующее:
- от 3.0 до 3.19 (20 минорных релизов)
- от 4.0 до 4.19 (20 минорных релизов)
- от 5.0 до 5.19 (20 минорных релизов)
- от 6.0 до … (это все еще продолжается; вы понимаете идею!)
Посмотрите на Рисунок 2.1, чтобы увидеть это. Каждый релиз от минорного к следующему минорному занимает примерно от 6 до 10 недель.
Рабочий процесс разработки ядра – понимание основ
Здесь мы предоставляем краткий обзор типичного рабочего процесса разработки ядра. Любой, кто, как и вы, заинтересован в разработке ядра, должен хотя бы минимально понимать этот процесс.
Подробное описание можно найти в официальной документации ядра здесь:https://www.kernel.org/doc/html/latest/process/2.Process.html#how-thedevelopment-process-works.
Общее заблуждение, особенно в ранние годы, заключалось в том, что ядро Linux разрабатывается в хаотичной манере. Это совсем не так! Процесс разработки ядра эволюционировал в хорошо отлаженную систему с тщательно документированным процессом и ожиданиями того, что должен знать вкладчик ядра, чтобы эффективно его использовать. Я отсылаю вас к предыдущей ссылке за полными деталями.
Чтобы взглянуть на типичный цикл разработки, давайте предположим, что мы клонировали последнее основное дерево Git ядра Linux на нашу систему.
Детали, касающиеся использования мощного инструмента управления исходным кодом Git (SCM), выходят за рамки этой книги. Пожалуйста, смотрите раздел “Дополнительное чтение” за полезными ссылками на обучение использованию Git. Очевидно, я настоятельно рекомендую хотя бы базовое ознакомление с использованием Git.
Как упоминалось ранее, на момент написания этой статьи, ядро 6.1 является версией с длительной поддержкой (LTS) с самой дальней проектируемой датой окончания поддержки на данный момент (декабрь 2026 года), поэтому мы будем использовать его в последующих материалах.
Итак, как же оно появилось? Очевидно, оно эволюционировало из предыдущих кандидатов на выпуск (rc) ядер и предыдущего стабильного релиза ядра, который предшествовал ему, в данном случае это были бы ядра v6.1-rc’n’ и стабильное ядро v6.0 до него. Давайте рассмотрим эту эволюцию двумя способами: через командную строку и графически через страницу ядра на GitHub.
Просмотр журнала Git ядра через командную строку
Мы используем команду git log следующим образом, чтобы получить удобочитаемый журнал тегов в дереве Git ядра, отсортированный по дате. Здесь, поскольку нас в первую очередь интересует выпуск ядра 6.1 LTS, мы специально сократили следующий вывод, чтобы подчеркнуть эту часть:
Команда git log (которую мы используем в следующем блоке кода, и фактически любые другие подкоманды git) будет работать только на дереве Git. Мы используем следующую команду исключительно для демонстрации эволюции ядра. Чуть позже мы покажем, как вы можете клонировать дерево исходного кода Git ядра.
$ git log --date-order --tags --simplify-by-decoration \
--pretty=format:'%ai %h %d'
2023-04-23 12:02:52 -0700 457391b03803 (tag: v6.3)
2023-04-16 15:23:53 -0700 6a8f57ae2eb0 (tag: v6.3-rc7)
2023-04-09 11:15:57 -0700 09a9639e56c0 (tag: v6.3-rc6)
2023-04-02 14:29:29 -0700 7e364e56293b (tag: v6.3-rc5)
[ … ]
2023-03-05 14:52:03 -0800 fe15c26ee26e (tag: v6.3-rc1)
2023-02-19 14:24:22 -0800 c9c3395d5e3d (tag: v6.2)
2023-02-12 14:10:17 -0800 ceaa837f96ad (tag: v6.2-rc8)
[ … ]
2022-12-25 13:41:39 -0800 1b929c02afd3 (tag: v6.2-rc1)
2022-12-11 14:15:18 -0800 830b3c68c1fb (tag: v6.1)
2022-12-04 14:48:12 -0800 76dcd734eca2 (tag: v6.1-rc8)
2022-11-27 13:31:48 -0800 b7b275e60bcd (tag: v6.1-rc7)
2022-11-20 16:02:16 -0800 eb7081409f94 (tag: v6.1-rc6)
2022-11-13 13:12:55 -0800 094226ad94f4 (tag: v6.1-rc5)
2022-11-06 15:07:11 -0800 f0c4d9fc9cc9 (tag: v6.1-rc4)
2022-10-30 15:19:28 -0700 30a0b95b1335 (tag: v6.1-rc3)
2022-10-23 15:27:33 -0700 247f34f7b803 (tag: v6.1-rc2)
2022-10-16 15:36:24 -0700 9abf2313adc1 (tag: v6.1-rc1)
2022-10-02 14:09:07 -0700 4fe89d07dcc2 (tag: v6.0)
2022-09-25 14:01:02 -0700 f76349cf4145 (tag: v6.0-rc7)
[ … ]
2022-08-14 15:50:18 -0700 568035b01cfb (tag: v6.0-rc1)
2022-07-31 14:03:01 -0700 3d7cb6b04c3f (tag: v5.19)
2022-07-24 13:26:27 -0700 e0dccc3b76fb (tag: v5.19-rc8)
[ … ]
В предыдущем блоке вывода вы можете сначала увидеть, что на момент, когда я выполнил команду git log (конец апреля 2023 года), ядро 6.3 было только что выпущено! Также вы можете видеть, что семь rc ядер предшествовали этому релизу, нумеруемые как 6.3-rc1, 6.3-rc2, …, 6.3-rc7.
Продолжая исследование, мы находим то, что нас интересует – вы можете четко увидеть, что первоначальный релиз стабильного ядра 6.1 (LTS) был 11 декабря 2022 года, а его предшественник, дерево 6.0, был выпущен 2 октября 2022 года. Вы также можете проверить эти даты, обратившись к другим полезным ресурсам ядра, таким как https://kernelnewbies.org/LinuxVersions.
Для серии разработок, которая в конечном итоге привела к ядру 6.1, эта последняя дата (2 октября 2022 года) отмечает начало так называемого периода слияния для следующего стабильного ядра, который длится примерно две недели. В этот период разработчикам разрешается отправлять новый код в дерево ядра. На самом деле, реальная работа могла начаться гораздо раньше; плоды этой работы теперь сливаются в основную ветку в это время, обычно под управлением менеджеров подсистем.
Мы попытались изобразить временную шкалу этой работы на Рисунке 2.1; вы можете видеть, как более ранние ядра (начиная с 3.0) имели 20 минорных релизов. Больше деталей показано для нашего целевого ядра: 6.1 LTS.’ ![[figure21.png]]
Рисунок 2.1: Приблизительная временная шкала современных ядер Linux, подчеркивающая то, с которым мы будем в основном работать (6.1 LTS)
Через две недели после начала периода слияния (2 октября 2022 года) для ядра 6.1, 16 октября 2022 года, период слияния был закрыт, и началась работа над rc ядром, с 6.1-rc1 как первым из rc версий, конечно. Деревья -rc (также известные как предварительные патчи) в основном занимаются слиянием патчей и исправлением (регрессий и других) ошибок, что в конечном итоге приводит к тому, что главные менеджеры (Линус Торвальдс и Эндрю Мортон) определяют как “стабильное” дерево ядра.
Количество rc ядер или предварительных патчей варьируется; обычно, однако, этот “окно исправлений” длится от 6 до 10 недель, после чего выпускается новое стабильное ядро. В предыдущем блоке вывода мы видим, что восемь кандидатов на выпуск (от 6.1-rc1 до 6.1-rc8) в конечном итоге привели к стабильному релизу дерева v6.1 11 декабря 2022 года, что заняло в общей сложности 70 дней или 10 недель. См. раздел 6.x на сайте https://kernelnewbies.org/LinuxVersions, чтобы подтвердить это.
Почему Рисунок 2.1 начинается с ядра 2.6.12? Ответ прост: это первая версия, с которой ведется история ядра в Git. Другой вопрос может быть о том, почему на рисунке показано ядро 5.4 (LTS)? Потому что оно тоже является ядром с длительной поддержкой (предполагаемая дата окончания поддержки - декабрь 2025 года), и именно это ядро мы использовали в первом издании этой книги!
Просмотр журнала Git ядра через его страницу на GitHub
Журнал ядра можно увидеть более наглядно через страницу релизов/тегов на GitHub-дереве Линуса здесь – https://github.com/torvalds/linux/tags: ![[figure22.png]]
Рисунок 2.2: См. тег v6.1 с кандидатами на выпуск -rc’n’, которые в конечном итоге привели к ядру v6.1, показанному выше (читайте снизу вверх)
Рисунок 2.2 показывает нам тег ядра v6.1 на этом урезанном скриншоте. Как мы до этого дошли? Несколько раз нажав на кнопку “Next” (не показана здесь), вы попадете на оставшиеся страницы, где можно увидеть кандидатов на выпуск v6.1-rc’n’. Либо просто перейдите по ссылке https://github.com/torvalds/linux/tags?after=v6.2-rc4.
Предыдущий скриншот частично показывает, как два из различных кандидатов на выпуск v6.1-rc’n’, 6.1-rc7 и 6.1-rc8, в конечном итоге привели к выпуску дерева LTS 6.1 12 декабря 2022 года.
Работа никогда не останавливается: как видно, к началу января 2023 года был выпущен кандидат на выпуск v6.2-rc3, который в конечном итоге привел к ядру v6.2 19 февраля 2023 года. Затем, 6 марта 2023 года вышел кандидат на выпуск 6.3-rc1, за которым последовали еще шесть, что в итоге привело к выпуску стабильного ядра 6.3 23 апреля 2023 года. И так продолжается…
К тому времени, когда вы это читаете, ядро будет уже значительно продвинуто вперед. Но это нормально – ядро 6.1 LTS будет поддерживаться довольно долго (помните, предполагаемая дата окончания поддержки - декабрь 2026 года), и таким образом, это очень значимый релиз для продуктов и проектов!
Кратко о рабочем процессе разработки ядра
Обобщенно, беря за пример серию ядер 6.x, рабочий процесс разработки ядра выглядит следующим образом. Вы можете одновременно обратиться к Рисунку 2.3, где мы схематично показываем, как ядро 6.0 эволюционирует в 6.1 LTS.
-
Выпускается стабильная версия 6.x (для наших целей считаем x равным 0). Таким образом, открывается 2-недельное окно слияния для основного ядра 6.x+1.
-
Окно слияния остается открытым примерно две недели, и новые патчи сливаются в основное ядро различными менеджерами подсистем, которые долгое время тщательно принимали патчи от участников и обновляли свои деревья.
-
Когда проходит около двух недель, окно слияния закрывается.
-
Теперь начинается период “исправления ошибок”; начинаются rc (или основные, предварительные патчи) ядра. Они развиваются следующим образом: выпускаются 6.x+1-rc1, 6.x+1-rc2, …, 6.x+1-rcn. Этот процесс может занимать от 6 до 8 недель.
-
Затем следует период “окончательной доработки”, обычно около недели. Приходит стабильный релиз, и выпускается новое стабильное ядро 6.x+1.
-
Релиз передается “стабильной команде”:
-
Значительные исправления ошибок или проблем безопасности приводят к выпуску 6.x+1.y: 6.x+1.1, 6.x+1.2, …, 6.x+1.n. Мы будем в основном работать над ядром 6.1.25, что делает значение y равным 25.
-
Релиз поддерживается до следующего стабильного релиза или до даты окончания поддержки (EOL), которая для 6.1 LTS предполагается как декабрь 2026 года. Исправления ошибок и безопасности будут применяться вплоть до этого срока.
-
…и весь процесс повторяется. ![[figure23.png]] Рисунок 2.3: Как ядро 6.x становится ядром 6.x+1 (пример здесь особенно для того, как 6.0 эволюционирует в 6.1)
Так что, если вы пытаетесь отправить патч, но пропустили окно слияния, вам (или скорее менеджеру подсистемы, который продвигает вашу серию патчей как часть нескольких других) придется подождать до следующего окна слияния, которое наступит примерно через 2,5-3 месяца. Эй, таковы правила; мы не так уж спешим.
Упражнение
Пройдитесь по тому, что мы сделали – следуя эволюции ядра – для текущего последнего стабильного ядра. (Полезный совет: еще один способ увидеть историю выпусков ядра: https://en.wikipedia.org/wiki/Linux_kernel_version_history).
Таким образом, когда вы теперь видите релизы ядра Linux, имена и процесс, связанный с ними, будут иметь смысл. Теперь давайте перейдем к рассмотрению различных типов деревьев исходного кода ядра.
Исследование типов деревьев исходного кода ядра
Существует несколько типов деревьев исходного кода ядра Linux. Одним из ключевых является ядро с длительной поддержкой (LTS). Оно просто является “специальным” выпуском в том смысле, что разработчики ядра будут продолжать вносить важные исправления ошибок и уязвимостей безопасности в него до определенной даты окончания поддержки (EOL). По соглашению, следующее ядро, которое будет “помечено” как LTS-релиз, обычно является последним, выпущенным в году, как правило, в декабре.
“Жизнь” ядра LTS обычно составляет минимум 2 года, и ее можно продлить на несколько лет. Ядро 6.1.y LTS, которое мы будем использовать на протяжении всей этой книги, является 23-м LTS ядром и имеет предполагаемый срок службы 4 года – с декабря 2022 по декабрь 2026 года.
Этот фрагмент изображения со страницы Википедии об истории версий ядра Linux (https://en.wikipedia.org/wiki/Linux_kernel_version_history) говорит сам за себя: ![[figure24.png]] Рисунок 2.4: Обзор разработки, поддерживаемых (стабильных) и стабильных LTS ядер и их дат EOL; с 6.1 LTS мы работаем (источник изображения: Википедия)
Интересно, что ядро 5.4 LTS будет поддерживаться до декабря 2025 года, а 5.10 LTS будет поддерживаться так же долго, как и 6.1 LTS, до декабря 2026 года.
Обратите внимание, что в репозитории существует несколько типов релизных ядер. Здесь мы приводим неполный список, упорядоченный от наименее до наиболее стабильного (следовательно, их срок службы от самого короткого до самого длинного):
-
-next trees: Это действительно передовой край (острие стрелы!), деревья подсистем с новыми патчами, собранными здесь для тестирования и обзора. Это то, над чем будет работать участник ядра upstream. Если вы собираетесь вносить свои патчи в ядро (вносить вклад), вам нужно работать с последним -next tree.
-
Предварительные патчи, также известные как -rc или mainline: Это кандидаты на выпуск ядер, которые генерируются перед релизом.
-
Стабильные ядра: Как подразумевает название, это рабочая часть. Эти ядра обычно выбираются дистрибутивами и другими проектами (по крайней мере, на начальном этапе). Их также называют ванильными ядрами.
-
Ядра дистрибутивов и LTS: Ядра дистрибутивов (очевидно) предоставляются дистрибутивами. Они обычно начинаются с базового ванильного/стабильного ядра. Ядра LTS – это специально поддерживаемые на протяжении более длительного времени ядра, что делает их особенно полезными для промышленных/производственных проектов и продуктов. Особенно в отношении дистрибутивов корпоративного класса, часто можно заметить, что многие из них используют “старые” ядра. Это может быть даже в случае с некоторыми производителями Android. Теперь, даже если команда uname -r показывает, что версия ядра, например, основана на 4.x, это не обязательно означает, что оно старое. Нет, дистрибутив/производитель/OEM обычно имеет команду разработчиков ядра (или аутсорсит ее), которая периодически обновляет старое ядро новыми и актуальными патчами, особенно критическими исправлениями безопасности и ошибок. Таким образом, хотя версия может казаться устаревшей, это не обязательно так!
Обратите внимание, что это требует огромного объема работы со стороны команды разработчиков ядра, и все еще очень сложно поддерживать последнее стабильное ядро; поэтому действительно лучше работать с ванильными ядрами.
В этой книге мы будем работать с одним из последних ядер LTS с самым длительным сроком поддержки. На момент написания это ядро 6.1.x LTS, с предполагаемой датой окончания поддержки (EOL) в декабре 2026 года, что позволяет содержанию этой книги оставаться актуальным и полезным на протяжении многих лет!
Здесь требуется упомянуть еще два типа деревьев исходного кода ядра: Super LTS (SLTS) и ядра производителей чипов (SoC). Ядра SLTS поддерживаются даже дольше, чем ядра LTS, проектом Civil Infrastructure Platform (https://www.cip-project.org/), который является проектом Linux Foundation. Цитируя с их сайта:
“Проект CIP направлен на создание открытого исходного ‘базового слоя’ промышленного программного обеспечения для использования и реализации программных строительных блоков в проектах гражданской инфраструктуры. В настоящее время системы гражданской инфраструктуры создаются с нуля, с минимальным повторным использованием существующих программных блоков. Проект CIP намерен создать повторно используемые строительные блоки, которые соответствуют требованиям безопасности, надежности и другим требованиям промышленной и гражданской инфраструктуры.”
На самом деле, на момент написания последние ядра CIP – SLTS v6.1 и SLTS v6.1-rt – основаны на ядре 6.1 LTS, и их предполагаемая дата окончания поддержки (EOL) – август 2033 года (10 лет)! Также ядра SLTS 5.10 и SLTS 5.10-rt имеют предполагаемую дату EOL в январе 2031 года. Для получения последней информации смотрите Вики здесь: https://wiki.linuxfoundation.org/civilinfrastructureplatform/start#kernel_maintainership.
Ядра LTS – новый мандат
Быстрое и важное обновление! В сентябре 2023 года на саммите Open Source в Бильбао, Испания, Джонатан Корбет в своей известной лекции “kernel report” сделал важное объявление: ядра LTS отныне будут поддерживаться всего два года.
Вот несколько ресурсов, на случай если вы захотите изучить это самостоятельно:
Доклад о ядре, Джон Корбет, Сентябрь 2023, OSS EU, Испания; YouTube: https://osseu2023.sched.com/event/4a09976cdb50e939601160ecdb45bc05#video_stream; Презентация (PDF): https://lwn.net/talks/2023/kr-osseu.pdf
Поддержка ядер Linux на длительный срок станет намного короче, Лиам Проувен, The Register, Сентябрь 2023: https://www.theregister.com/2023/09/26/linux_kernel_report_2023/
Это может стать сюрпризом для многих. Почему только два года? Вкратце, были даны две причины:
-
Во-первых, зачем поддерживать серию ядер на протяжении многих лет, если люди на самом деле их не используют? Многие корпоративные поставщики ядер, а также производители SoC, поддерживают свои собственные ядра.
-
Далее, к сожалению, серьезная проблема: усталость мейнтейнеров. Сообщество ядра тяжело поддерживать все эти ядра LTS – на данный момент их 7 основных версий (4.14, 4.19, 5.4, 5.10, 5.15, 6.1 и 6.6) – в постоянном состоянии! Кроме того, нагрузка только увеличивается со временем. Например, серия 4.14 LTS получила около 300 обновлений и почти 28 000 коммитов. Кроме того, старые ошибки в конце концов проявляются, и требуется много работы для их исправления в современных ядрах LTS. Не только это, но и новые ошибки, которые появляются в более поздних ядрах, должны быть исправлены и затем перенесены в старые, но все еще поддерживаемые ядра LTS. Все это складывается.
На данный момент кажется, что ядро, с которым мы работаем здесь – 6.1 LTS – будет поддерживаться обычное время, до декабря 2026 года.
Продолжая, давайте кратко обсудим упомянутый выше второй тип деревьев исходного кода ядра: производители чипсетов/силикона SoC склонны поддерживать свои собственные ядра для различных плат/silicon, которые они поддерживают. Они обычно основывают свое ядро на существующем ванильном ядре LTS (не обязательно на самом последнем!) и затем развивают его, добавляя свои специфические для производителя патчи, пакеты поддержки плат (BSP), драйверы и так далее. Конечно, со временем различия – между их ядром и последним стабильным – могут стать довольно значительными, что приводит к сложным проблемам поддержки, например, к необходимости постоянно переносить критические исправления безопасности/ошибок на них.
При работе над проектом, использующим немного кремния, возможно, лучший подход – это основывать свою работу на существующих промышленных решениях, таких как проект Yocto (https://www.yoctoproject.org/), который отлично справляется с поддержкой недавних ядер LTS с применением слоев производителя в синхронизации с ключевыми исправлениями безопасности/ошибок. Например, на момент написания последний стабильный релиз Yocto – Nanbield 4.3 – поддерживает как ядро 6.1 LTS, так и более новое не-LTS ядро 6.5; конечно, конкретная версия может варьироваться в зависимости от архитектуры (семейства процессоров).
Таким образом, существует множество типов деревьев исходного кода ядра. Тем не менее, я отсылаю вас к странице релизов на kernel.org для получения деталей о типах релизных ядер: https://www.kernel.org/releases.html. Для еще более подробной информации посетите раздел “Как работает процесс разработки” (https://www.kernel.org/doc/html/latest/process/2.Process.html#how-the-development-process-works).
Запрос репозитория, https://www.kernel.org/, в неинтерактивном, скриптуемом виде можно выполнить с помощью curl. Следующий вывод отражает состояние Linux на 06 декабря 2023 года:
$ curl -L https://www.kernel.org/finger_banner
Последняя стабильная версия ядра Linux: 6.6.4
Последняя версия mainline ядра Linux: 6.7-rc4
Последняя стабильная версия 6.6 ядра Linux: 6.6.4
Последняя стабильная версия 6.5 ядра Linux: 6.5.13 (EOL)
Последняя долгосрочная версия 6.1 ядра Linux: 6.1.65
Последняя долгосрочная версия 5.15 ядра Linux: 5.15.141
Последняя долгосрочная версия 5.10 ядра Linux: 5.10.202
Последняя долгосрочная версия 5.4 ядра Linux: 5.4.262
Последняя долгосрочная версия 4.19 ядра Linux: 4.19.300
Последняя долгосрочная версия 4.14 ядра Linux: 4.14.331
Последняя версия linux-next ядра Linux: next-20231206
$
К тому времени, когда вы это прочитаете, крайне вероятно – фактически, это определенно – что ядро эволюционировало дальше, и появились более поздние версии. Для такой книги, как эта, лучшее, что я могу сделать, это выбрать близкую к последней стабильную версию LTS с самой долгой предполагаемой датой EOL на момент написания: 6.1.x LTS.
Конечно, это уже произошло! Ядро 6.6 было выпущено 29 октября 2023 года и, на момент написания (прямо перед отправкой в печать), оно действительно было отмечено как LTS-ядро с предполагаемой датой окончания поддержки в декабре 2026 года (та же, что и у ядра 6.1 LTS).
Чтобы продемонстрировать эту мысль, обратите внимание, что первое издание этой книги использовало ядро 5.4.0, поскольку на момент его написания серия ядер 5.4 была LTS-серией с самым длительным сроком поддержки. Сегодня, когда я пишу это, ядро 5.4 LTS всё ещё поддерживается с предполагаемой датой окончания поддержки (EOL) в декабре 2025 года, а последняя стабильная версия - 5.4.268.
Какое ядро мне использовать?
Учитывая все виды и типы ядер, которые мы рассмотрели, действительно возникает вопрос, какое ядро мне следует использовать? Ответ на это неоднозначен, поскольку он зависит от окружения. Это для встраиваемых систем, десктопа или сервера? Это для нового проекта или для устаревшего? Какой предполагаемый период обслуживания? Является ли это ядром SoC, поддерживаемым производителем? Какие требования к безопасности? Тем не менее, “правильный ответ”, прямо из уст старших разработчиков ядра, следующий:
Используйте последнее стабильное обновление. Это самое стабильное, самое безопасное, лучшее ядро, которое мы можем создать на данный момент. Это лучшее, что мы можем предложить. Вам следует использовать именно его. – Джон Корбет, сентябрь 2023
Совет: Перейдите по ссылке https://kernel.org/; видите большую желтую кнопку с номером релиза ядра внутри? Это последнее стабильное ядро на сегодняшний день.
Вам необходимо использовать все стабильные/LTS-релизы, чтобы система была безопасной и стабильной. Если вы попытаетесь вручную выбрать случайные патчи, вы не исправите все известные и неизвестные проблемы, а скорее получите потенциально менее безопасную систему, содержащую известные ошибки. – Грег Кроа-Хартман
Статья блога Грега Кроа-Хартмана “Какое стабильное ядро мне использовать”, август 2018 года (http://kroah.com/log/blog/2018/08/24/what-stable-kernel-should-i-use/), эхом отражает эти мысли.
Теперь, когда мы вооружены знаниями о номенклатуре версий ядра и типах деревьев исходного кода ядра, определенно пора начинать наше путешествие по сборке нашего ядра.
Шаги для сборки ядра из исходного кода
Как удобный и быстрый справочник, ниже приведены основные ключевые шаги, необходимые для сборки ядра Linux из исходного кода. Поскольку объяснение каждого из них довольно подробное, вы можете вернуться к этому обзору, чтобы увидеть общую картину. Шаги следующие:
- Получение дерева исходного кода ядра Linux через один из следующих вариантов:
- Загрузка определенного дерева исходного кода ядра в виде сжатого файла
- Клонирование дерева Git (ядра)
- Распаковка дерева исходного кода ядра в какое-либо место в вашем домашнем каталоге (этот шаг можно пропустить, если вы получили ядро путем клонирования дерева Git).
- Конфигурация: Получение начальной точки для конфигурации вашего ядра (подход варьируется). Затем отредактируйте ее, выбирая опции поддержки ядра, необходимые для нового ядра. Рекомендуемый способ сделать это - с помощью
make menuconfig
. - Сборка образа ядра, загружаемых модулей и любых необходимых файлов Device Tree Blobs (DTB) с помощью
make [-j'n'] all
. Это создает сжатый образ ядра(arch/<arch>/boot/[b|z|u]{Ii}mage)
, несжатый образ ядра – vmlinux, файл System.map, объекты модулей ядра и любые настроенные файлы DTB. - Установка только что собранных модулей ядра (на x86) с помощью
sudo make [INSTALL_MOD_PATH=<prefix-dir>] modules_install
. Этот шаг по умолчанию устанавливает модули ядра в /lib/modules/$(uname -r)/ (переменную окружения INSTALL_MOD_PATH можно использовать для изменения этого). - Загрузчик (x86): Настройка загрузчика GRUB и образа initramfs, ранее называвшегося initrd:
sudo make [INSTALL_PATH=</new/boot/dir>] install
- Это создает и устанавливает образ initramfs или initrd под /boot (переменную окружения INSTALL_PATH можно использовать для изменения этого).
- Обновляет конфигурационный файл загрузчика для загрузки нового ядра (первый пункт).
- Настройка меню загрузчика GRUB (опционально).
Эта глава, будучи первой из двух по теме сборки ядра, охватывает шаги с 1 по 3, включая много необходимого фонового материала. Следующая глава охватит оставшиеся шаги, с 4 по 7. Итак, начнем с шага 1.
Шаг 1 – Получение дерева исходного кода ядра Linux
В этом разделе мы рассмотрим два основных способа, которыми вы можете получить дерево исходного кода ядра Linux:
- Загрузка и распаковка определенного дерева исходного кода ядра из публичного репозитория ядра Linux: https://www.kernel.org.
- Клонирование дерева исходного кода Линуса Торвальдса (или других) – например, дерева Git linux-next.
Как вы решаете, какой подход использовать? Для большинства разработчиков, работающих над проектом или продуктом, решение уже принято – проект использует очень конкретную версию ядра Linux. Таким образом, вы загрузите то конкретное дерево исходного кода ядра, возможно, примените к нему специфичные для проекта патчи, если это необходимо, и будете использовать его.
Для тех, кто намерен вносить вклад или отправлять код в основное ядро, второй подход – клонирование дерева Git – является правильным. Конечно, здесь есть свои нюансы; мы описали некоторые детали в разделе “Исследование типов деревьев исходного кода ядра”.
В следующем разделе мы продемонстрируем оба подхода к получению дерева исходного кода ядра. Сначала мы опишем подход, при котором конкретное дерево исходного кода ядра (не дерево Git) загружается из репозитория ядра. Мы выбираем ядро Linux 6.1.25 LTS для этой цели. Таким образом, для всех практических целей в этой книге этот подход следует использовать. Во втором подходе мы клонируем дерево Git.
Загрузка определенного дерева ядра
Во-первых, где находится исходный код ядра? Короткий ответ заключается в том, что он находится на публичном сервере репозитория ядра, доступном по адресу https://www.kernel.org. На главной странице этого сайта отображается последняя стабильная версия ядра Linux, а также последние релизы с длительной поддержкой и linux-next. На следующем скриншоте показан вид сайта на 25 апреля 2023 года. Даты отображаются в формате гггг-мм-дд: ![[figure25.png]] Рисунок 2.5: Сайт kernel.org (на 25 апреля 2023 года) с выделенным ядром 6.1 LTS
Краткое напоминание: мы также предоставляем PDF-файл, содержащий полноцветные изображения скриншотов/диаграмм, используемых в этой книге. Вы можете скачать его здесь: https://packt.link/gbp/9781803232225.
Существует множество способов загрузить сжатый файл с исходным кодом ядра с этого сервера и/или его зеркал. Рассмотрим два из них:
-
Интерактивный и, возможно, самый простой способ заключается в посещении указанного выше сайта и простом нажатии на соответствующую ссылку tarball в вашем веб-клиенте. Браузер загрузит файл изображения в формате .tar.xz на вашу систему.
-
Также можно загрузить любое дерево исходного кода ядра в сжатом виде, перейдя по адресу https://mirrors.edge.kernel.org/pub/linux/kernel/ и выбрав основную версию; на практике, для ядер основной версии 6 URL будет https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/; просмотрите или выполните поиск на этой странице для нужного вам ядра. Например, взгляните на следующий скриншот:
![[figure26.png]] Рисунок 2.6: Частичный скриншот с kernel.org, выделяющий ядро (6.1.25 LTS), которое мы загрузим и будем использовать
Файлы .tar.gz и .tar.xz имеют одинаковое содержание; отличается только тип сжатия. Обычно файлы .tar.xz загружаются быстрее, так как они меньше по размеру.
- Альтернативно, вы можете загрузить дерево исходного кода ядра из командной строки, используя утилиту wget. Также можно использовать мощную утилиту curl. Например, чтобы загрузить сжатый файл с исходным кодом стабильного ядра 6.1.25 LTS, введите следующую команду одной строкой:
wget --https-only -O ~/Downloads/linux-6.1.25.tar.xz https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.1.25.tar.xz
Это безопасно загрузит сжатое дерево исходного кода ядра 6.1.25 в папку ~/Downloads на вашем компьютере. Итак, вперед, загрузите исходный код ядра 6.1.25 (LTS) на вашу систему!
Клонирование дерева Git
Для разработчиков, работающих над проектом и желающих вносить свой вклад в upstream, необходимо работать с самой последней версией кодовой базы ядра Linux. В сообществе разработчиков ядра существуют тонкие различия в том, что именно считается последней версией. Как упоминалось ранее, дерево linux-next, и некоторые конкретные ветки или теги в нем, используются для этой цели.
В этой книге мы не собираемся углубляться в детали настройки дерева linux-next. Этот процесс уже хорошо документирован, смотрите раздел “Дополнительное чтение” этой главы за подробными ссылками. Подробная страница о том, как именно следует клонировать дерево linux-next, находится здесь: Работа с linux-next, https://www.kernel.org/doc/man-pages/linux-next.html, и, как там упоминается, дерево linux-next, http://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git, является местом хранения патчей, нацеленных на следующее окно слияния ядра. Если вы занимаетесь разработкой ядра на передовом крае, вы, вероятно, захотите работать с этим деревом, а не с основным деревом Линуса Торвальдса или деревом исходного кода из общего репозитория ядра на https://www.kernel.org.
Для наших целей клонирование основного репозитория Git ядра Linux (по сути, дерева Git Линуса Торвальдса) будет более чем достаточно. Сделайте это так:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
Команда git clone может занять некоторое время. Кроме того, вы можете указать, что хотите получить последнюю стабильную версию дерева Git ядра, выполнив git clone следующим образом; пока что, и только если вы намерены работать с этим основным деревом Git, мы просто сделаем это и клонируем стабильное дерево Git ядра со всей его богатой историей (снова введите это одной строкой):
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
Теперь перейдите в каталог, куда оно было извлечено:
cd linux-stable
Если вы намерены работать с этим деревом Git, пропустите раздел “Шаг 2 – извлечение дерева исходного кода ядра”, так как операция git clone в любом случае извлечет дерево исходного кода. Вместо этого продолжите с разделом “Шаг 3 – конфигурирование ядра Linux”, который следует за ним. Это подразумевает, что версия дерева исходного кода ядра, которую вы используете, будет значительно отличаться от версии 6.1.25, которую мы используем в этой книге. Поэтому я предлагаю рассматривать эту часть как демонстрацию того, как получить последнее стабильное дерево Git через git, и оставить это.
Наконец, еще один способ загрузки данного ядра предоставляют разработчики ядра, предлагающие скрипт для безопасной загрузки данного дерева исходного кода ядра Linux с проверкой его PGP-подписи. Скрипт доступен здесь: https://git.kernel.org/pub/scm/linux/kernel/git/mricon/korg-helpers.git/tree/get-verified-tarball.
Шаг 2 – Извлечение дерева исходного кода ядра
В предыдущем разделе, на шаге 1, вы узнали, как именно можно получить дерево исходного кода ядра Linux. Один из способов — и тот, который мы используем в этой книге — это просто загрузить сжатый исходный файл с сайта kernel.org (или одного из его зеркал). Другой способ — использовать Git для клонирования недавнего дерева исходного кода ядра.
Итак, я предполагаю, что к этому моменту вы уже получили сжатое дерево исходного кода ядра 6.1.25 (LTS) на вашем Linux-компьютере. Теперь, когда оно у вас есть, перейдем к шагу 2, простому шагу, где мы узнаем, как его извлечь.
Обратите внимание, что клонирование полного дерева ядра Linux — это операция, требующая времени, сетевого трафика и дискового пространства! Убедитесь, что у вас достаточно свободного места на диске (как минимум несколько гигабайт). Выполнение команды git clone –depth n <…>, где n — целое число, может быть полезным для ограничения глубины истории (коммитов) и, таким образом, сокращения объема загрузки и использования диска. Как упоминается в руководстве по git-clone для опции –depth: “Создать поверхностное клонирование с историей, обрезанной до указанного количества коммитов”. Также, для сведения, чтобы отменить “поверхностное клонирование” и загрузить все, просто выполните git pull –unshallow.
Как было сказано ранее, этот раздел предназначен для тех, кто загрузил конкретное сжатое дерево исходного кода ядра Linux из репозитория, https://www.kernel.org, и намерен его собрать. В этой книге мы в основном работаем с серией ядер 6.1 с длительной поддержкой, в частности, с ядром 6.1.25 LTS. С другой стороны, если вы выполнили git clone для основного дерева Git ядра Linux, как показано в предыдущем разделе, вы можете безопасно пропустить этот раздел и перейти к следующему — Шагу 3 – Конфигурирование ядра Linux.
Итак, теперь, когда загрузка завершена, перейдем дальше. Следующий шаг — извлечение дерева исходного кода ядра — помните, это файл, сжатый и упакованный в архив (обычно .tar.xz). На риск повторения, мы предполагаем, что к этому моменту вы загрузили исходный код ядра версии 6.1.25 в виде сжатого файла в каталог ~/Downloads:
$ cd ~/Downloads ; ls -lh linux-6.1.25.tar.xz
-rw-rw-r-- 1 c2kp c2kp 129M Apr 20 16:13 linux-6.1.25.tar.xz
Простой способ распаковать и извлечь этот файл — использовать повсеместно распространенную утилиту tar:
tar xf ~/Downloads/linux-6.1.25.tar.xz
Это извлечет дерево исходного кода ядра в каталог с именем linux-6.1.25 внутри каталога ~/Downloads. Но что, если мы хотим извлечь его в другой каталог, скажем, `~/kernels? Сделаем это:
mkdir -p ~/kernels
tar xf ~/Downloads/linux-6.1.25.tar.xz --directory=~/kernels/
Это извлечет исходный код ядра в каталог ~/kernels/linux-6.1.25/. Как удобство и хорошая практика, давайте установим переменную окружения, указывающую на корень нашего нового дерева исходного кода ядра:
export LKP_KSRC=~/kernels/linux-6.1.25
Обратите внимание, что в дальнейшем мы будем предполагать, что переменная LKP_KSRC содержит путь к нашему дереву исходного кода ядра 6.1.25 LTS.
Хотя вы всегда можете использовать графический файловый менеджер, такой как Nautilus, для извлечения сжатого файла, я настоятельно рекомендую вам ознакомиться с использованием командной строки Linux для выполнения этих операций.
Не забудьте про tldr, когда вам нужно быстро найти наиболее часто используемые опции для общих команд! Например, для tar просто выполните tldr tar, чтобы посмотреть общие команды tar, или посмотрите здесь: https://tldr.inbrowser.app/pages/common/tar.
Заметили? Мы можем извлечь дерево исходного кода ядра в любой каталог в нашем домашнем каталоге или в другом месте. Это отличается от старых времен, когда дерево всегда извлекалось в место, доступное для записи только root, часто в /usr/src/.
Если вы хотите просто продолжить с рецептом сборки ядра, пропустите следующий раздел и двигайтесь дальше. Если вас это интересует (я на это очень надеюсь!), следующий раздел — это краткое, но важное отступление, в котором мы рассмотрим структуру и расположение дерева исходного кода ядра.
Краткий обзор дерева исходного кода ядра
Представьте себе! Теперь весь исходный код ядра Linux доступен у вас на системе! Отлично — давайте быстро взглянем на него:
![[figure27.png]]
Рисунок 2.7: Корень чистого дерева исходного кода ядра Linux 6.1.25
Отлично! Каков его размер? Быстрое выполнение команды du -h . внутри корня извлеченного дерева исходного кода ядра покажет, что это дерево (напомню, его версия — 6.1.25) составляет примерно 1.5 гигабайта!
К вашему сведению, ядро Linux выросло и продолжает расти в плане количества строк кода (SLOCs). Текущие оценки приближаются к 30 миллионам SLOCs. Конечно, понимайте, что не весь этот код будет компилироваться при сборке ядра.
Как мы можем узнать, какая именно версия ядра Linux это код, просто глядя на исходники? Это просто: один из быстрых способов — это проверить первые несколько строк Makefile проекта. Кстати, ядро использует Makefile повсюду; большинство каталогов имеют свой собственный. Мы будем ссылаться на этот Makefile, который находится в корне дерева исходного кода ядра, как на Makefile верхнего уровня:
$ head Makefile
# SPDX-License-Identifier: GPL-2.0
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 25
EXTRAVERSION =
NAME = Hurr durr I'ma ninja sloth
# *DOCUMENTATION*
# To see a list of typical targets execute "make help"
# More info can be located in ./README
Ясно, что это исходный код ядра версии 6.1.25. Мы рассмотрели значение тегов VERSION, PATCHLEVEL, SUBLEVEL и EXTRAVERSION — они соответствуют непосредственно номенклатуре w.x.y.z — в разделе “Понимание номенклатуры выпуска ядра Linux”. Тег NAME — это просто прозвище, данное релизу (смотрите сюда — ну что я могу сказать: это юмор ядра. Лично мне больше нравилось название для ядер 5.x — это было “Dare mighty things”!).
Итак, давайте теперь получим общее представление о структуре этого дерева исходного кода ядра на высоте 10 000 футов. Следующая таблица суммирует общее разделение и назначение наиболее важных файлов и директорий в корне дерева исходного кода ядра Linux. Свяжите это с Рисунком 2.7:
Имя файла или директории | Назначение |
---|---|
Файлы верхнего уровня | |
README | Файл README проекта. Он информирует нас о том, где хранится официальная документация ядра — спойлер: она находится в директории, называемой Documentation — и как начать её использовать. Современная документация ядра также доступна онлайн и выполнена очень хорошо: https://www.kernel.org/doc/html/latest/ . Документация действительно важна; это аутентичный материал, написанный самими разработчиками ядра. Обязательно прочитайте этот короткий файл README первым! См. больше ниже, пункт [1]. |
COPYING | Этот файл детализирует условия лицензии, под которыми выпущен исходный код ядра. Большинство файлов исходного кода ядра выпущены под хорошо известной лицензией GNU GPL v2 (обозначается как GPL-2.0). Современная тенденция — использовать легко поисковые идентификаторы лицензий SPDX, согласованные с отраслевыми стандартами. Вот полный список: https://spdx.org/licenses/ . См. больше ниже, пункт [2]. |
MAINTAINERS | ЧАВО: что-то не так с компонентом ядра (или файлом) XYZ — с кем связаться, чтобы получить поддержку? Именно это и предоставляет этот файл — список всех подсистем ядра вместе с их ответственными. Это распространяется вплоть до уровня отдельных компонентов, таких как конкретный драйвер или файл, а также их статуса, кто в настоящее время их поддерживает, почтового списка, веб-сайта и так далее. Очень полезно! Есть даже вспомогательный скрипт для поиска человека или команды, с кем нужно связаться: scripts/get_maintainer.pl. См. больше, пункт [3]. |
Makefile | Это Makefile верхнего уровня ядра; система сборки ядра Kbuild, а также модули ядра используют этот Makefile для сборки. |
Директория основной подсистемы | Назначение |
kernel/ | Основная подсистема ядра: здесь находится код, отвечающий за множество ключевых функций ядра, включая управление жизненным циклом процессов/потоков, планирование задач процессора, блокировки, cgroups, таймеры, прерывания, сигнализацию, модули, трассировку, примитивы RCU, [e]BPF и многое другое. |
mm/ | Основная часть кода управления памятью (mm) находится здесь. Мы рассмотрим немного этого в Главе 6, “Основы внутреннего устройства ядра – Процессы и потоки”, и затронем связанные темы в Главе 7, “Внутреннее устройство управления памятью – Основы”, и Главе 8, “Выделение памяти ядра для авторов модулей – Часть 1”. |
fs/ | Здесь реализованы две ключевые функции файловой системы: слой абстракции – виртуальный коммутатор файловых систем ядра (VFS) – и отдельные драйверы файловых систем (например, ext[2 |
block/ | Код, лежащий в основе пути ввода-вывода блоков для VFS/FS. Включает код, реализующий кэш страниц, общий слой ввода-вывода блоков, планировщики ввода-вывода, новые функции blk-mq и так далее. |
net/ | Полная реализация стека сетевых протоколов, строго по стандартам RFC – https://whatis.techtarget.com/definition/Request-for-Comments-RFC . Включает высококачественные реализации TCP, UDP, IP и многих других сетевых протоколов. Хотите увидеть реализацию на уровне кода TCP/IP для IPv4? Она здесь: net/ipv4/, смотрите исходные файлы tcp.c и ip.c, среди прочих. |
ipc/ | Код подсистемы межпроцессного взаимодействия (IPC); реализация механизмов IPC, таких как очереди сообщений SysV и POSIX, разделяемая память, семафоры и так далее. |
sound/ | Код аудиоподсистемы, также известный как слой Advanced Linux Sound Architecture (ALSA). |
virt/ | Код виртуализации (гипервизора); здесь реализована популярная и мощная виртуальная машина ядра (KVM). |
Архитектура/Инфраструктура/Драйверы/Разное
| Директория/Файл | Назначение |
| ————— | ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————- |
| Documentation/ | Официальная документация ядра находится здесь; важно с ней ознакомиться. Файл README ссылается на её онлайн-версию. |
| LICENSES/ | Текст всех лицензий, категоризированный по различным разделам. См. пункт [2]. |
| arch/ | Здесь находится код, специфичный для архитектуры (под архитектурой подразумевается процессор). Linux начался как небольшой хобби-проект для i386. Сейчас это, вероятно, самая портированная ОС. См. порты архитектур в пункте [4] списка, следующем за этой таблицей. |
| certs/ | Поддержка кода для генерации подписанных модулей; это мощная функция безопасности, которая при правильном использовании гарантирует, что даже вредоносные руткиты не смогут просто загрузить любой модуль ядра, который они захотят. |
| crypto/ | Эта директория содержит реализацию на уровне ядра шифров (алгоритмов шифрования/дешифрования или преобразований) и API ядра для обслуживания потребителей, требующих криптографических услуг. |
| drivers/ | Здесь находится код драйверов устройств на уровне ядра. Это считается неядерной областью; она классифицируется на множество типов драйверов. Это область, куда чаще всего вносятся вклады; также этот код занимает наибольшее дисковое пространство в дереве исходного кода. |
| include/ | Эта директория содержит заголовочные файлы ядра, независимые от архитектуры. Также есть некоторые, специфичные для архитектуры, под arch/
/include/.... |
| init/ | Код инициализации ядра, независимый от архитектуры; возможно, это ближайшее, что мы получаем к основной функции ядра: init/main.c:start_kernel(), где функция start_kernel() считается ранней точкой входа на языке C во время инициализации ядра. (Давайте воспользуемся отличным инструментом для просмотра кода от Bootlin, чтобы увидеть это для версии 6.1.25 здесь:
[https://elixir.bootlin.com/linux/v6.1.25/source/init/main.c#L936](https://elixir.bootlin.com/linux/v6.1.25/source/init/main.c#L936)
.) |
| io_uring/ | Инфраструктура ядра для реализации нового фреймворка быстрого ввода-вывода io_uring; см. пункт [5]. |
| lib/ | Ближайший эквивалент библиотеки для ядра. Важно понимать, что ядро не поддерживает общие библиотеки, как это делают приложения в пользовательском пространстве. Некоторый код здесь автоматически связывается с файлом изображения ядра и таким образом доступен ядру во время выполнения. В lib/ содержатся различные полезные компоненты: [де]компрессия, контрольные суммы, работа с битовыми картами, математические операции, строковые функции, алгоритмы работы с деревьями и так далее. |
| rust/ | Инфраструктура ядра для поддержки языка программирования Rust; см. пункт [6]. |
| samples/ | Примеры кода для различных функций и механизмов ядра; полезно для обучения! |
| scripts/ | Здесь размещены различные скрипты, некоторые из которых используются при сборке ядра, многие для других целей, таких как статический/динамический анализ, отладка и т.д. В основном это скрипты на Bash и Perl. (К сведению, и особенно для целей отладки, я рассмотрел множество этих скриптов в книге "Отладка ядра Linux", 2022.) |
| security/ | Здесь находятся модули безопасности ядра Linux (LSM), фреймворк обязательного контроля доступа (MAC), который стремится установить более строгий контроль доступа приложений пользователя к пространству ядра, чем это делает ядро по умолчанию. Стандартная модель называется дискреционным контролем доступа (DAC). В настоящее время Linux поддерживает несколько LSM; наиболее известные из них - SELinux, AppArmor, Smack, Tomoyo, Integrity и Yama. Обратите внимание, что LSM по умолчанию отключены. |
| tools/ | Здесь находится исходный код различных инструментов режима пользователя, в основном приложений или скриптов, которые имеют "тесную связь" с ядром и поэтому должны находиться в конкретном коде ядра. Perf, современный инструмент профилирования процессора, инструментарий eBPF и некоторые инструменты трассировки служат отличными примерами. |
| usr/ | Поддержка кода для генерации и загрузки образа initramfs; это позволяет ядру выполнять код пользовательского пространства во время инициализации ядра. Часто это требуется; мы рассматриваем initramfs в Главе 3, "Сборка ядра Linux 6.x из исходного кода – Часть 2", раздел "Понимание фреймворка initramfs". |
Таблица 2.2: Структура дерева исходного кода ядра Linux
Ниже приведены некоторые важные пояснения из таблицы:
-
README: В этом файле также упоминается документ, на который нужно ссылаться для получения информации о минимально приемлемых версиях программного обеспечения для сборки и запуска ядра: Documentation/process/changes.rst. Интересно, что ядро предоставляет скрипт на Awk (scripts/ver_linux), который выводит версии текущего программного обеспечения на системе, на которой он выполняется, помогая вам проверить, приемлемы ли установленные у вас версии.
-
Лицензирование ядра: Не вдаваясь в юридические подробности (и без того понятно, что я не юрист), вот прагматическая суть дела. Поскольку ядро выпускается под лицензией GNU GPL-2.0 (GNU GPL - это Общая публичная лицензия GNU), любой проект, который напрямую использует кодовую базу ядра, автоматически попадает под эту лицензию. Это свойство “производного произведения” GPL-2.0. С юридической точки зрения, такие проекты или продукты теперь должны выпускать свое программное обеспечение ядра под теми же условиями лицензии. На практике ситуация намного сложнее; многие коммерческие продукты, работающие на ядре Linux, содержат в себе проприетарный код пользовательского и/или пространства ядра. Обычно это делается путем рефакторинга кода ядра (чаще всего, драйверов устройств) в формат загружаемого модуля ядра (LKM). Возможно выпускать модуль ядра (LKM) под моделью двойной лицензии. LKM является темой главы 4, “Написание вашего первого модуля ядра – Часть 1”, и главы 5, “Написание вашего первого модуля ядра – Часть 2”, и там мы рассматриваем некоторую информацию о лицензировании модулей ядра.
Некоторые люди, предпочитающие проприетарные лицензии, умудряются выпускать свой код ядра в рамках модуля ядра, который не лицензируется по условиям GPL-2.0; технически это, возможно, возможно, но это, по меньшей мере, считается крайне антисоциальным и может даже перейти грань незаконности. Заинтересованные могут найти больше ссылок на лицензирование в документе “Дополнительное чтение” этой главы.
-
MAINTAINERS: Просто взгляните на этот файл в корне вашего дерева исходного кода ядра! Интересные вещи… Чтобы показать, как он полезен, давайте запустим вспомогательный скрипт на Perl: scripts/get_maintainer.pl. Обратите внимание, что, строго говоря, он предназначен для выполнения только на дереве Git. Здесь мы просим скрипт показать ответственных за кодовую базу планирования задач процессора ядра, указав файл или директорию через ключ -f:
$ scripts/get_maintainer.pl --nogit -f kernel/sched
Ingo Molnar <mingo@redhat.com> (maintainer:SCHEDULER)
Peter Zijlstra <peterz@infradead.org> (maintainer:SCHEDULER)
Juri Lelli <juri.lelli@redhat.com> (maintainer:SCHEDULER)
Vincent Guittot <vincent.guittot@linaro.org> (maintainer:SCHEDULER)
Dietmar Eggemann <dietmar.eggemann@arm.com> (reviewer:SCHEDULER)
Steven Rostedt <rostedt@goodmis.org> (reviewer:SCHEDULER)
Ben Segall <bsegall@google.com> (reviewer:SCHEDULER)
Mel Gorman <mgorman@suse.de> (reviewer:SCHEDULER)
Daniel Bristot de Oliveira <bristot@redhat.com> (reviewer:SCHEDULER)
Valentin Schneider <vschneid@redhat.com> (reviewer:SCHEDULER)
linux-kernel@vger.kernel.org (open list:SCHEDULER)
- Порты архитектур Linux (CPU): На момент версии 6.1, ОС Linux была портирована на все эти процессоры. Большинство из них имеют MMU. Вы можете видеть код, специфичный для архитектуры, в папке arch/, где каждая директория представляет собой конкретную архитектуру процессора:
$ cd ${LKP_KSRC} ; ls arch/
alpha/ arm64/ ia64/ m68k/ nios2/ powerpc/
sh/ x86/ arc/ csky/ Kconfig microblaze/
openrisc/ riscv/ sparc/ x86_64/ arm/ hexagon/
loongarch/ mips/ parisc/ s390/ um/ xtensa/
Фактически, при кросс-компиляции переменная окружения ARCH устанавливается в имя одной из этих папок, чтобы скомпилировать ядро для этой архитектуры. Например, при сборке цели “foo” для AArch64, обычно делают что-то вроде make ARCH=arm64 CROSS_COMPILE=<…> foo.
Как разработчик ядра или драйверов, изучение дерева исходного кода ядра - это то, к чему вам придется привыкнуть (и даже начать получать удовольствие!). Поиск конкретной функции или переменной может быть сложной задачей, когда код составляет около 30 миллионов строк! Обязательно научитесь использовать эффективные инструменты для просмотра кода. Я рекомендую бесплатные и открытые инструменты ctags и cscope. На самом деле, в Makefile верхнего уровня ядра есть цели именно для этого:
make [ARCH=<cpu>] tags ; make [ARCH=<cpu>] cscope
Обязательно сделайте это! (Когда cscope используется с ARCH, установленным в null (по умолчанию), он создает индекс для x86[_64]. Например, чтобы сгенерировать теги, относящиеся к AArch64, выполните команду make ARCH=arm64 cscope.) Также, к вашему сведению, существует множество других инструментов для просмотра кода, конечно же; ещё один хороший инструмент - это opengrok.
- io_uring: Не преувеличивая, можно сказать, что io_uring и eBPF считаются двумя из новых “волшебных” функций, которые предоставляет современная система Linux (папка io_uring здесь - это поддержка ядра для этой функции)! Причина, по которой энтузиасты баз данных и сетевых технологий так радуются io_uring, проста: производительность. Этот фреймворк значительно улучшает показатели производительности в реальных ситуациях с высоким вводом-выводом, как для дисковых, так и для сетевых нагрузок. Его архитектура с общим (между пользовательским и пространством ядра) кольцевым буфером, схема нулевого копирования и способность использовать гораздо меньше системных вызовов по сравнению с типичными старыми фреймворками AIO, включая режим опроса, делают его завидной особенностью. Так что, если ваши приложения в пользовательском пространстве хотят попасть на этот действительно быстрый путь ввода-вывода, ознакомьтесь с io_uring. В разделе “Дополнительное чтение” этой главы есть полезные ссылки.
make [ARCH=<cpu>] tags ; make [ARCH=<cpu>] cscope
- Rust в ядре: Да, действительно, было много шума вокруг того факта, что базовая поддержка языка программирования Rust была добавлена в ядро Linux (начиная с версии 6.0). Почему? Rust имеет хорошо разрекламированное преимущество перед даже нашим уважаемым языком C: безопасность памяти. Факт заключается в том, что даже сегодня одна из крупнейших проблем безопасности, связанных с программированием на C/C++ – как для ОС/драйверов, так и для приложений пользовательского пространства – коренится в проблемах безопасности памяти (например, широко известный дефект переполнения буфера BoF). Эти проблемы могут возникать, когда разработчики создают ошибки, связанные с повреждением памяти (баги!) в коде на C/C++. Это приводит к уязвимостям в программном обеспечении, которые умелые хакеры постоянно ищут и эксплуатируют! Сказав все это, на данный момент Rust имеет очень ограниченное присутствие в ядре – ядро не использует его в основном коде. Текущая поддержка Rust в ядре предназначена для будущей возможности написания модулей на Rust. (Конечно, здесь есть немного образца кода на Rust: samples/rust/.) Использование Rust в ядре со временем, безусловно, будет расти… В разделе “Дополнительное чтение” есть ссылки на эту тему – обязательно ознакомьтесь, если вас это интересует.
Мы завершили шаг 2, извлечение дерева исходного кода ядра! В качестве бонуса вы также узнали основы структуры исходного кода ядра. Теперь перейдем к шагу 3 процесса и узнаем, как настроить ядро Linux перед его сборкой.
Шаг 3 – Конфигурирование ядра Linux
Конфигурирование ядра, возможно, является самым критическим шагом в процессе сборки ядра. Одна из многих причин, почему Linux считается выдающейся ОС, это ее универсальность. Распространено заблуждение, что существуют отдельные кодовые базы ядра Linux для серверов корпоративного класса, дата-центров, рабочих станций и маленьких встраиваемых устройств на Linux – нет, они все используют одно и то же унифицированное дерево исходного кода ядра Linux! Таким образом, тщательная настройка ядра для конкретного случая использования (сервер, десктоп, встраиваемое устройство или гибрид/кастом) является мощной функцией и требованием. Именно это мы и рассматриваем здесь.
Вероятно, вы обнаружите, что эта тема – создание рабочей конфигурации ядра – имеет тенденцию к длинным и извилистым обсуждениям, но в конечном итоге это того стоит; потратьте время и усилия, чтобы прочитать это.
Также, чтобы собрать ядро, необходимо выполнить шаг конфигурации ядра в любом случае. Даже если вы считаете, что вам не нужно вносить никаких изменений в существующую или стандартную конфигурацию, очень важно выполнить этот шаг хотя бы один раз в рамках процесса сборки. В противном случае будут отсутствовать определенные заголовочные файлы, которые генерируются автоматически здесь, и это может вызвать проблемы позже. Как минимум, следует выполнить команду make old[def]config. Это настроит конфигурацию ядра на основе текущей системы, запрашивая у пользователя ответы только на новые опции конфигурации.
Далее мы рассмотрим немного необходимого фона по системе сборки ядра.
Примечание: если вы совсем новичок в настройке ядра, следующие детали могут показаться вам сначала немного подавляющими. В этом случае я предлагаю вам пропустить их при первом чтении, перейти к практическим аспектам настройки ядра и затем вернуться к этому разделу.
Минимальное понимание системы сборки Kconfig/Kbuild
Инфраструктура, которую использует ядро Linux для конфигурации, известна как система Kconfig, а для сборки используется инфраструктура Kbuild. Не вдаваясь в мрачные подробности, система Kconfig + Kbuild связывает сложный процесс конфигурации и сборки ядра, разделяя работу на логические потоки:
- Kconfig – инфраструктура для конфигурации ядра; она состоит из двух логических частей:
-
Язык Kconfig: используется для указания синтаксиса в различных файлах Kconfig[.*], которые фактически определяют “меню”, где выбираются опции конфигурации ядра.
-
Парсеры Kconfig: инструменты, которые умно анализируют файлы Kconfig[.*], определяют зависимости и автоматический выбор, и генерируют систему меню. Среди них широко используется make menuconfig, который внутренне вызывает инструмент mconf (код находится в scripts/kconfig).
-
- Kbuild – поддерживающая инфраструктура для сборки исходного кода в бинарные компоненты ядра. В основном используется рекурсивный стиль сборки make, начинающийся с Makefile верхнего уровня ядра, который, в свою очередь, рекурсивно анализирует содержимое сотен Makefile, встроенных в подкаталоги внутри исходного кода (по мере необходимости).
Диаграмма, пытающаяся передать информацию о системе Kconfig/Kbuild (упрощенным образом), можно увидеть на Рисунке 2.8. Некоторые детали еще не рассмотрены; тем не менее, вы можете держать это в уме, читая следующие материалы.
Чтобы помочь вам лучше понять, давайте рассмотрим несколько ключевых компонентов, входящих в систему Kconfig/Kbuild:
- Символы CONFIG_FOO
- Файлы спецификации меню, названные Kconfig[.*]
- Makefile(ы)
- Общий файл конфигурации ядра – .config – сам по себе.
Цели этих компонентов можно кратко суммировать следующим образом:
| Компонент Kconfig/Kbuild | Краткое описание назначения |
| —————————————- | ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————– |
| Kconfig: Символ конфигурации: CONFIG_FOO | Каждый настраиваемый элемент ядра FOO представлен макросом CONFIG_FOO. В зависимости от выбора пользователя, макрос будет разрешаться в одно из значений y, m или n:
- y=yes : это означает включение конфигурации или функции FOO непосредственно в образ ядра
- m=module : это означает сборку в виде отдельного объекта, модуля ядра (файл .ko)
- n=no : это означает, что функция не будет собираться
Обратите внимание, что CONFIG_FOO — это алфавитно-цифровая строка. Скоро мы увидим способ поиска точного имени опции конфигурации через интерфейс make menuconfig. |
| Kconfig: Kconfig.*files | Здесь определяется символ CONFIG_FOO. Синтаксис Kconfig указывает его тип (булевый, трисостояние, [альфа]цифровой и т.д.) и дерево зависимостей. Кроме того, для интерфейса конфигурации на основе меню (вызывается через make [menu |
| Kbuild: Makefile(s) | Система Kbuild использует подход рекурсивного make с Makefile. Makefile в корне дерева исходного кода ядра называется Makefile верхнего уровня, обычно с Makefile в каждой подпапке для сборки исходного кода там. В исходном коде ядра 6.1 более 2700 Makefile! |
| Файл .config | В конечном итоге, конфигурация ядра сводится к этому файлу; .config — это итоговый файл конфигурации ядра. Он генерируется и хранится в корневой папке дерева исходного кода ядра как простой текстовый файл ASCII. Храните его в безопасности, так как это ключевая часть вашего продукта. Обратите внимание, что имя файла конфигурации можно переопределить с помощью переменной окружения KCONFIG_CONFIG. |
Таблица 2.3: Основные компоненты системы сборки Kconfig+Kbuild
Как работает система Kconfig+Kbuild – минимальный обзор
Теперь, когда мы знаем некоторые детали, вот упрощенное объяснение того, как все это связано и работает:
-
Сначала пользователь (вы) настраивает ядро, используя какую-либо систему меню, предоставленную Kconfig.
-
Директивы конфигурации ядра, выбранные через этот интерфейс меню, записываются в несколько автоматически сгенерированных заголовочных файлов и итоговый файл .config, используя синтаксис CONFIG_FOO={y m}, или CONFIG_FOO просто комментируется (что означает “не собирать FOO вообще”). - Далее, Makefile для каждого компонента Kbuild (вызываются через Makefile верхнего уровня ядра) обычно указывают директиву FOO следующим образом:
obj-$(CONFIG_FOO) += FOO.o
- Компонент FOO может быть чем угодно – ядерной функцией, драйвером устройства, файлововой системой, директивой отладки и так далее. Помните, значение CONFIG_FOO может быть y, m или отсутствовать; в зависимости от этого, компонент FOO либо собирается в образ ядра (когда его значение y), либо как модуль (когда его значение m)! Если он комментируется, он вообще не собирается, что просто. По сути, вышеупомянутая директива Makefile во время сборки преобразуется в одну из трех форм для данного компонента ядра FOO:
obj-y += FOO.o # собирать функцию FOO в образ ядра
obj-m += FOO.o # собирать функцию FOO как отдельный модуль ядра (файл foo.ko)
<если CONFIG_FOO отсутствует> # НЕ собирать функцию FOO
Чтобы увидеть пример этого в действии, проверьте файл Kbuild (подробнее о файлах Kconfig в разделе “Понимание файлов Kconfig*”) в корне дерева исходного кода ядра:
$ cat Kconfig
…
# Kbuild для корневой директории ядра
…
# Обычное рекурсивное спускание в директории
# ---------------------------------------------------------------------------
obj-y += init/
obj-y += usr/
obj-y += arch/$(SRCARCH)/
obj-y += $(ARCH_CORE)
obj-y += kernel/
[ … ]
obj-$(CONFIG_BLOCK) += block/
obj-$(CONFIG_IO_URING) += io_uring/
obj-$(CONFIG_RUST) += rust/
obj-y += $(ARCH_LIB)
[ … ]
obj-y += virt/
obj-y += $(ARCH_DRIVERS)
Интересно! Мы буквально видим, как Makefile верхнего уровня будет спускаться в другие директории, большинство из которых установлены на obj-y; фактически, это означает, что их нужно включить в сборку (в некоторых случаях это параметризуется, становясь obj-y или obj-m в зависимости от того, какую пользователь выбрал опцию).
Отлично. Давайте продолжим; ключевым моментом является получение рабочего файла .config. Как это сделать? Мы делаем это итеративно. Начинаем с “по умолчанию” конфигурации – тема следующего раздела – и постепенно дорабатываем до пользовательской конфигурации.
Получение конфигурации по умолчанию
Так как же выбрать начальную конфигурацию ядра? Существует несколько методов; вот несколько распространенных:
- Не указывать ничего: Kconfig автоматически загрузит конфигурацию ядра по умолчанию (поскольку у всех настроек ядра есть значения по умолчанию).
- Использовать существующую конфигурацию ядра дистрибутива.
- Создать пользовательскую конфигурацию на основе модулей ядра, которые в данный момент загружены в память.
Первый подход имеет преимущество в простоте. Ядро само обработает детали, предоставив вам конфигурацию по умолчанию. Минус в том, что конфигурация по умолчанию может быть очень большой (это так, когда вы собираете Linux для десктопа или серверной системы на базе x86_64); по умолчанию включается огромное количество опций, на всякий случай, что может значительно увеличить время сборки и размер образа ядра. Обычно, конечно, затем от вас ожидается ручная настройка ядра до желаемых параметров.
Это поднимает вопрос, где хранится конфигурация ядра по умолчанию? Система Kconfig использует схему приоритетов для получения конфигурации по умолчанию, если она не указана. Список приоритетов и их порядок (первый - наивысший приоритет) следующий:
- .config
- /lib/modules/$(uname -r)/.config
- /etc/kernel-config
- /boot/config-$(uname -r)
- ARCH_DEFCONFIG (если определено)
- arch/${ARCH}/defconfig
Из списка видно, что система Kconfig сначала проверяет наличие файла .config в корне дерева исходного кода ядра; если он найден, она берет все значения конфигурации оттуда. Если его нет, она затем смотрит на путь /lib/modules/$(uname -r)/.config. Если он найден, значения из этого файла будут использоваться как значения по умолчанию. Если и его нет, проверяется следующий файл в порядке приоритетов и так далее… Это показано на Рисунке 2.8.
Для более детального изучения инфраструктуры Kconfig и Kbuild ядра, мы рекомендуем обратиться к следующим отличным документам:
- Исследование ядра Linux: Секреты Kconfig/kbuild, Цао Цзин, opensource.com, октябрь 2018: https://opensource.com/article/18/10/kbuild-andkconfig
- Презентация слайдов: Погружение в Kbuild, Цао Цзин, Fujitsu, август 2018: https://events19.linuxfoundation.org/wp-content/uploads/2017/11/A-Diveinto-Kbuild-Cao-Jin-Fujitsu.pdf
Рисунок (вдохновленный статьями Цао Цзина), который пытается передать информацию о системе Kconfig/Kbuild ядра, показан здесь. Диаграмма содержит больше информации, чем было рассмотрено до сих пор; не беспокойтесь, мы к этому вернемся.
![[figure28.png]] Рисунок 2.8: Упрощенное представление системы Kconfig/Kbuild ядра
Итак, давайте теперь разберемся, как именно получить рабочую конфигурацию ядра!
Получение хорошего начального пункта для конфигурации ядра
Это приводит нас к очень важному моменту: хотя эксперименты с конфигурацией ядра могут быть полезны в качестве учебного упражнения, для производственной системы критически важно, чтобы ваша пользовательская конфигурация основывалась на проверенной, известной, протестированной и работающей конфигурации ядра.
Здесь, чтобы помочь вам понять нюансы выбора валидной отправной точки для конфигурации ядра, мы рассмотрим три подхода к получению начальной точки для типичной конфигурации ядра:
- Первый, простой (но не оптимальный) подход, где вы просто эмулируете существующую конфигурацию ядра дистрибутива.
- Затем, более оптимизированный подход, где конфигурация ядра основывается на загруженных в память модулях ядра существующей системы. Это подход localmodconfig.
- Наконец, несколько слов о подходе для типичного проекта встраиваемого Linux.
Давайте рассмотрим каждый из этих подходов немного подробнее. В плане конфигурации ядра, которое вы загрузили и извлекли на предыдущих двух шагах, пока ничего не делайте; прочитайте следующие разделы, и затем в разделе “Начало работы с подходом localmodconfig” мы фактически начнем процесс.
Конфигурация ядра, используя конфигурацию дистрибутива как отправную точку
Типичная целевая система для использования этого подхода - это настольный компьютер или сервер на базе Linux x86_64. Давайте настроим ядро на все значения по умолчанию:
$ make mrproper
CLEAN scripts/basic
CLEAN scripts/kconfig
CLEAN include/config include/generated .config
Чтобы начать с чистого листа, мы сначала выполняем make mrproper; будьте осторожны, это очищает практически все, включая .config, если он существует.
Затем мы выполняем шаг make defconfig, который, как показывает вывод команды make help (попробуйте сами! См. Рисунок 2.10), дает нам новую конфигурацию:
$ make defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
[ … ]
HOSTLD scripts/kconfig/conf
*** Конфигурация по умолчанию основана на 'x86_64_defconfig'
#
# конфигурация записана в .config
#
Сначала выполняется сборка утилиты mconf (в scripts/kconfig), а затем генерируется конфигурация. Вот она:
$ ls -l .config
-rw-rw-r-- 1 c2kp c2kp 136416 апр 29 08:12 .config
Готово: теперь у нас есть конфигурация ядра “все по умолчанию”, сохраненная в .config!
Что делать, если нет файла defconfig для вашей архитектуры в arch/${ARCH}/configs? Тогда, по крайней мере на x86_64, вы можете просто скопировать существующую конфигурацию ядра по умолчанию дистрибутива:
cp /boot/config-$(uname -r) ${LKP_KSRC}/.config
Здесь мы просто копируем файл конфигурации существующего дистрибутива Linux (в данном случае, это наша гостевая виртуальная машина Ubuntu 22.04 LTS) в файл .config в корне дерева исходного кода ядра, тем самым делая конфигурацию дистрибутива отправной точкой, которую затем можно дополнительно редактировать. Как уже упоминалось, минус этого быстрого подхода в том, что конфигурация склонна быть большой, что приводит к образу ядра с большим размером.
Также, к вашему сведению, как только конфигурация ядра генерируется любым способом (например, через make defconfig), каждый параметр конфигурации ядра FOO отображается как пустой файл в include/config.
Настройка ядра через подход localmodconfig
Типичная целевая система для использования этого подхода - это (обычно x86_64) настольный компьютер или сервер на базе Linux. Этот второй подход более оптимизирован по сравнению с предыдущим – хороший вариант, когда целью является начать с конфигурации ядра, основанной на вашей существующей работающей системе, и, следовательно, (обычно) относительно компактной по сравнению с типичной конфигурацией по умолчанию на настольном или серверном Linux.
Здесь мы предоставляем системе Kconfig снимок текущих модулей ядра, работающих в системе, просто перенаправляя вывод команды lsmod в временный файл, а затем передавая этот файл процессу сборки. Это можно сделать следующим образом:
lsmod > /tmp/lsmod.now
cd ${LKP_KSRC}
make LSMOD=/tmp/lsmod.now localmodconfig
Утилита lsmod просто перечисляет все модули ядра, которые в данный момент находятся в памяти системного ядра. Мы подробнее рассмотрим это в Главе 4, “Написание вашего первого модуля ядра – Часть 1”.
Мы сохраняем вывод этой команды в временный файл, который затем передаем через переменную окружения LSMOD в цель localmodconfig Makefile. Задача этой цели состоит в том, чтобы настроить ядро таким образом, чтобы включить только базовую функциональность плюс функциональность, предоставляемую этими модулями ядра, и исключить остальное, фактически давая нам разумное подобие текущего ядра (или того ядра, которое представляет вывод lsmod). Мы используем именно эту технику для настройки нашего ядра 6.1 в предстоящем разделе “Начало работы с подходом localmodconfig”. Мы также показываем только этот подход как шаг 1 (1 в круге) на Рисунке 2.8.
Конфигурация ядра для типичных встраиваемых систем Linux
Типичная целевая система для использования этого подхода обычно представляет собой небольшую встраиваемую систему Linux. Цель здесь - начать с проверенной, известной, протестированной и рабочей конфигурации ядра для нашего проекта встраиваемого Linux. Как же именно мы можем этого достичь?
Перед тем как продолжить, позвольте мне упомянуть: начальное обсуждение здесь будет касаться старого подхода к конфигурированию (для архитектуры AArch32 или ARM-32) встраиваемого Linux; затем мы рассмотрим “правильный” и современный подход для современных платформ.
Интересно, что для AArch32, по крайней мере, сама база кода ядра содержит известные, проверенные и рабочие файлы конфигурации ядра для различных известных аппаратных платформ.
Предположим, что наша цель - система на базе ARM-32, нам просто нужно выбрать ту конфигурацию, которая соответствует (или наиболее близка к) нашей целевой плате. Эти файлы конфигурации ядра находятся в дереве исходного кода ядра в директории arch/<arch>/configs/
. Файлы конфигурации имеют формат <platform-name>_defconfig
.
Для примера, взглянем на скриншот, показывающий для ARM-32 существующие специфичные для платы файлы кода ядра под arch/arm/mach-<foo>
и файлы конфигурации платформы под arch/arm/configs в базе кода ядра Linux версии 6.1.25:
![[figure29.png]] Рисунок 2.9: Содержимое директорий arch/arm и arch/arm/configs на ядре Linux 6.1.25
Ого, довольно много! Директории arch/arm/mach-<foo>
представляют аппаратные платформы (платы или машины), для которых Linux был портирован (обычно производителем чипсета); код, специфичный для платы, находится в этих директориях.
Аналогично, рабочие файлы конфигурации ядра по умолчанию для этих платформ также предоставляются ими и находятся в папке arch/arm/configs в формате <foo>_defconfig
, как это четко видно в нижней части Рисунка 2.9.
Таким образом, например, если вы настраиваете ядро Linux для аппаратной платформы, содержащей, скажем, SoC i.MX 7 от NXP, не начинайте с файла конфигурации ядра x86_64 по умолчанию. Это не сработает. Даже если вам удастся это сделать, ядро не будет собираться/работать чисто. Выберите подходящий файл конфигурации ядра: для нашего примера, возможно, файл imx_v6_v7_defconfig будет хорошей отправной точкой. Вы можете скопировать этот файл в .config в корне вашего дерева исходного кода ядра и затем уточнить его под конкретные нужды вашего проекта.
В качестве другого примера, Raspberry Pi (https://www.raspberrypi.org/) - это очень популярная платформа для хобби и производства. Файл конфигурации ядра, используемый в качестве базы для него в его дереве исходного кода, это arch/arm/configs/bcm2835_defconfig. Имя файла отражает тот факт, что платы Raspberry Pi используют SoC на базе Broadcom 2835. Детали по компиляции ядра для Raspberry Pi можно найти здесь: https://www.raspberrypi.org/documentation/linux/kernel/building.md. Однако, подождите, мы рассмотрим хотя бы часть этого в Главе 3, “Сборка ядра Linux 6.x из исходного кода – Часть 2”, в разделе “Сборка ядра для Raspberry Pi”.
Современный подход – использование Device Tree
Слово предостережения! Для AArch32, как мы видели, вы найдете файлы конфигурации, специфичные для платформы, под arch/arm/configs, а также код ядра, специфичный для платы, под arch/arm/mach-<foo>
, где foo - это название платформы. Однако на самом деле этот подход - хранение конфигурации и исходных файлов ядра, специфичных для платы, внутри кодовой базы ОС Linux - считается абсолютно неправильным для ОС! Линус Торвальдс ясно дал понять, что “ошибки”, допущенные в старых версиях Linux, когда ARM-32 была популярной архитектурой, не должны повторяться для других архитектур. Тогда как же подойти к этому вопросу? Ответ для современных (32 и 64-битных) архитектур ARM и PPC - использовать современный подход Device Tree (DT).
Очень упрощенно, DT содержит все детали топологии аппаратного обеспечения платформы. Это фактически схема платы или платформы; это не код, а описание аппаратной платформы, аналогичное VHDL. Код и драйверы, специфичные для BSP, все еще нужно писать, но теперь есть аккуратный способ их “обнаружения” или перечисления ядром при загрузке, когда оно анализирует DTB (Device Tree Blob), переданный загрузчиком. DTB генерируется в процессе сборки путем вызова DTC (Device Tree Compiler) на исходных файлах DT для платформы.
Теперь, в большинстве встраиваемых проектов (по крайней мере для ARM и PPC) используется DT с большим успехом. Это также помогает производителям оригинального оборудования (OEM) и производителям дизайна оборудования/поставщикам (ODM/vendors), позволяя использовать по сути одно и то же ядро с платформоспецифическими настройками, встроенными в DT. Подумайте о десятках моделей Android-телефонов от популярных OEM с в основном одинаковыми компонентами, но с небольшими аппаратными различиями; обычно одного ядра достаточно! Это значительно облегчает бремя поддержки. Для любопытных - исходные файлы DT - файлы .dts можно найти здесь: arch/<arch>/boot/dts
.
Урок о том, чтобы держать специфичные для платы вещи вне кодовой базы ядра, насколько это возможно, был хорошо усвоен для AArch64 (ARM-64). Сравните его чистые, хорошо организованные и не загроможденные папки конфигурации и DTS (в arch/arm64/configs/ есть только один файл - defconfig) с AArch32. Даже файлы DTS (смотрите под arch/arm64/boot/dts/) организованы лучше, чем у AArch32:
6.1.25 $ ls arch/arm64/configs/
defconfig
6.1.25 $ ls arch/arm64/boot/dts/
actions/ amazon/ apm/ bitmain/ exynos/ intel/
marvell/ nuvoton/ realtek/ socionext/ tesla/ xilinx/
allwinner/ amd/ arm/ cavium/ hisilicon/ Makefile microchip/
qcom/ rockchip/ synaptics/ toshiba/ altera/
amlogic/ apple/ broadcom/ freescale/ lg/ mediatek/ nvidia/ renesas/ sprd/ ti/
Таким образом, с современными встраиваемыми проектами и DT, как подходить к задачам ядра/BSP? Существует несколько подходов; работа над BSP:
- Выполняется внутренней командой BSP или платформы.
- Предоставляется в виде “пакета BSP” от поставщика (часто это производитель чипсетов, с которым вы сотрудничаете) вместе с референсным оборудованием.
- Передается на аутсорсинг внешней компании или консультанту, с которым вы сотрудничаете. Существует несколько компаний в этой области – среди них Siemens (бывший Mentor Graphics), Timesys и WindRiver.
- Сейчас часто, когда проект собирается и интегрируется с помощью сложного программного обеспечения для сборки, такого как Yocto или Buildroot, поставщики вносят вклад в слои BSP, которые затем интегрируются в продукт командой сборки.
Дизайн: Немного в стороне от темы, но я считаю это важным: когда работаешь над проектами (особенно встраиваемыми), команды часто проявляют нежелательную тенденцию напрямую использовать API SDK поставщиков для выполнения работы, специфичной для устройства, в своих приложениях и драйверах. На первый взгляд это может показаться нормальным; однако это может стать огромной проблемой, когда становится ясно, что требования меняются, сами устройства меняются, и таким образом ваш плотно связанный софт просто ломается! Вы привязали приложения (и драйверы) к аппаратному обеспечению устройства практически без разделения.
Конечно, решение заключается в использовании слабо связанной архитектуры, с чем-то вроде HAL (Hardware Abstraction Layer), чтобы позволить приложениям взаимодействовать с устройствами без проблем. Это также позволяет изменять код, специфичный для устройства, не затрагивая более высокие уровни (приложения). Проектирование таким образом может показаться очевидным в теории, фактически используя идею сокрытия информации, но на практике это может быть сложно обеспечить; всегда держите это в уме. Факт в том, что модель устройств в Linux поощряет такое слабое связывание. В разделе “Дополнительное чтение” есть хорошие ссылки на эти подходы к дизайну, в разделе “Общие онлайн и книжные ресурсы…” (здесь: https://github.com/PacktPublishing/Linux-Kernel-Programming_2E/blob/main/Further_Reading.md#generic-onlineand-book-resources–miscellaneous-very-useful).
Итак, это завершает обзор трех подходов к установке начальной точки для конфигурации ядра.
Просмотр всех доступных опций конфигурации
На самом деле, в отношении конфигурации ядра, мы только коснулись поверхности. В системе Kconfig закодировано множество других методов для явного создания конфигурации ядра определенным образом! Как это делается? Через цели конфигурации для make. Вы можете увидеть их, выполнив команду make help в корне вашего дерева исходного кода ядра; они находятся под заголовком “Цели конфигурации”:
![[figure210.png]] Рисунок 2.10: Вывод команды make help на x86_64 (ядро 6.1.25) с выделенными интересными строками
Давайте также поэкспериментируем с несколькими другими подходами, начнем с oldconfig:
$ make mrproper
[ … ]
$ make oldconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/confdata.o
[ … ]
HOSTLD scripts/kconfig/conf
#
# используются значения по умолчанию, найденные в /boot/config-5.19.0-41-generic
#
[ … ]
* Перезапуск конфигурации...
[ … ]
*
Поддержка Control Group (CGROUPS) [Y/?] y
По умолчанию снижать задержку динамического изменения (CGROUP_FAVOR_DYNMODS) [N/y/?] (NEW) << здесь ожидается ввод >>
[ … ]
Это работает, конечно, но вам придется нажимать клавишу Enter (возможно, несколько раз), чтобы принять значения по умолчанию для любых и всех вновь обнаруженных опций конфигурации ядра… (или вы можете явно указать значение; мы рассмотрим больше о новых конфигурациях ядра в следующем разделе). Это довольно нормально, но немного раздражает. Есть более простой способ: использование цели olddefconfig; как говорится в строке помощи – “То же самое, что и oldconfig, но устанавливает новые символы в их значения по умолчанию без запроса”:
$ make olddefconfig
#
# используются значения по умолчанию, найденные в /boot/config-5.19.0-41-generic
#
.config:10301:предупреждение: значение символа 'm' недопустимо для ANDROID_BINDER_IPC
.config:10302:предупреждение: значение символа 'm' недопустимо для ANDROID_BINDERFS
#
# конфигурация записана в .config
#
Готово.
Просмотр и установка новых конфигураций ядра
Еще один быстрый эксперимент: мы очищаем все и затем копируем конфигурацию ядра дистрибутива. Если вы хотите сохранить существующий .config, сделайте резервную копию.
$ make mrproper
$ cp /boot/config-5.19.0-41-generic .config
cp: перезаписать '.config'? y
$
Теперь у нас в .config находятся значения по умолчанию дистрибутива. Подумайте об этом: мы сейчас используем ядро дистрибутива 5.19.0-41-generic, но собираемся построить новое ядро версии 6.1.25. Таким образом, новое ядро наверняка будет иметь по крайней мере несколько новых конфигураций. В таких случаях, когда вы пытаетесь настроить ядро, система Kconfig начнет задавать вам вопросы: она отобразит каждую новую опцию конфигурации и доступные значения, которые вы можете для нее установить, с значением по умолчанию в квадратных скобках, в окне консоли. От вас ожидается выбор значений для новых опций конфигурации, с которыми она сталкивается. Вы увидите это как серию вопросов и приглашение отвечать на них в командной строке.
Ядро предоставляет два интересных механизма для просмотра всех новых конфигураций ядра:
- listnewconfig – список новых опций
- helpnewconfig – список новых опций и справочный текст
Запуск первого просто перечисляет каждую новую переменную конфигурации ядра:
$ make listnewconfig
[ … ]
CONFIG_CGROUP_FAVOR_DYNMODS=n
CONFIG_XEN_PV_MSR_SAFE=y
CONFIG_PM_USERSPACE_AUTOSLEEP=n
[ … ]
CONFIG_TEST_DYNAMIC_DEBUG=n
Их много – у меня получилось 108 новых конфигураций – поэтому я обрезал вывод здесь.
Мы можем видеть все новые конфигурации, хотя вывод не очень полезен для понимания того, что они конкретно означают. Запуск цели helpnewconfig решает эту проблему – теперь вы можете видеть “Справку” (из файла Kconfig опции конфигурации) для каждой новой конфигурации ядра:
$ make helpnewconfig
[ … ]
CONFIG_CGROUP_FAVOR_DYNMODS:
Эта опция по умолчанию включает опцию монтирования favordynmods, которая снижает
задержки динамических изменений cgroup, таких как миграции задач и включение/выключение
контроллеров, за счет увеличения стоимости операций в горячих путях, таких как форки и
выходы.
Если вы не уверены, скажите N.
Символ: CGROUP_FAVOR_DYNMODS [=n]
Тип: bool
Определено в init/Kconfig:959
Описание: По умолчанию снижать задержку динамического изменения
[ … ]
CONFIG_TEST_DYNAMIC_DEBUG:
Этот модуль регистрирует обратный вызов трассировщика для подсчета включенных pr_debugs в функции do_debugging,
затем изменяет их включение, вызывает функцию и сравнивает счетчики.
Если вы не уверены, скажите N.
Символ: TEST_DYNAMIC_DEBUG [=n]
[ … ]
$
Не беспокойтесь о понимании синтаксиса Kconfig пока; мы рассмотрим его в разделе “Настройка меню ядра, Kconfig и добавление нашего собственного пункта меню”.
Переменная окружения LMC_KEEP
Также, заметили ли вы на Рисунке 2.10, что цели localmodconfig и localyesconfig могут опционально включать переменную окружения под названием LMC_KEEP (LMC означает LocalModConfig)?
Её значение прямолинейно: установка LMC_KEEP в некоторые значения, разделенные двоеточием, заставляет систему Kconfig сохранять исходные конфигурации для указанных путей. Пример может выглядеть так: “drivers/usb:drivers/gpu:fs”. Фактически, это говорит: “Оставь эти модули включенными.”
Эта функция была введена в ядре версии 5.8 (коммит здесь: https://github.com/torvalds/linux/commit/c027b02d89fd42ecee911c39e9098b9609a5ca0b). Чтобы использовать её, вы можете выполнить команду конфигурации так:
make LSMOD=/tmp/mylsmod \
LMC_KEEP="drivers/usb:drivers/gpu:fs" \
localmodconfig
Настройка конфигурации через скрипт streamline_config.pl
Интересно, что ядро предоставляет множество вспомогательных скриптов, которые могут выполнять полезные задачи по обслуживанию, отладке и другим операциям в директории scripts/. Хорошим примером в контексте нашего обсуждения является скрипт на Perl scripts/kconfig/streamline_config.pl. Он идеально подходит для ситуаций, когда в ядре вашего дистрибутива включено слишком много модулей или встроенных функций ядра, и вам нужно только то, что вы используете сейчас – то, что обеспечивают текущие загруженные модули, как в случае с localmodconfig. Запустите этот скрипт со всеми модулями, которые вы хотите загрузить, сохраняя его вывод в итоге в .config. Затем выполните make oldconfig, и конфигурация будет готова!
В качестве дополнения, вот как оригинальный автор этого скрипта – Стивен Ростедт – описывает, что он делает (https://github.com/torvalds/linux/blob/master/scripts/kconfig/streamline_config.pl):
[…]
# Вот что я сделал со своим дистрибутивом Debian.
# cd /usr/src/linux-2.6.10
# cp /boot/config-2.6.10-1-686-smp .config
# ~/bin/streamline_config > config_strip
# mv .config config_sav
# mv config_strip .config
# make oldconfig
[…]
Вы можете попробовать это, если хотите.
Начало работы с подходом localmodconfig
Теперь (наконец-то!) давайте перейдем к практике и создадим разумно размерную базовую конфигурацию ядра для нашего ядра LTS версии 6.1.25, используя технику localmodconfig. Как упоминалось, этот подход, основанный только на существующих модулях ядра, хорош, когда целью является получение отправной точки для конфигурации ядра на системе на базе x86, настроенной под текущий хост.
Перед тем как продолжить, хорошей идеей будет очистить дерево исходного кода, особенно если вы проводили эксперименты, которые мы рассматривали ранее. Будьте осторожны: эта команда удалит всё, включая .config:
make mrproper
Как было описано ранее, давайте сначала получим снимок текущих загруженных модулей ядра и затем передадим его системе сборки, указав цель localmodconfig, вот так:
lsmod > /tmp/lsmod.now
cd ${LKP_KSRC}
make LSMOD=/tmp/lsmod.now localmodconfig
Не забывайте: конфигурация ядра, выполняемая сейчас, подходит для типичных систем x86_64 десктоп/сервер, как учебный подход. Этот подход лишь предоставляет отправную точку, и даже она может быть не идеальной. Для реальных проектов вам придется тщательно проверять и настраивать каждый аспект конфигурации ядра; аудит вашего точного аппаратного и программного обеспечения является ключевым. Снова же, для встраиваемых целей подход другой (как мы обсуждали в разделе “Конфигурация ядра для типичных встраиваемых систем Linux”).
Теперь, когда вы выполняете команду make […] localmodconfig, показанную выше, вполне возможно, даже вероятно, что будут различия в опциях конфигурации между ядром, которое вы сейчас настраиваете (версия 6.1.25), и ядром, которое вы сейчас используете на машине сборки (в моем случае, ядро хоста - $(uname -r) = 5.19.0-41-generic). В таких случаях, как объяснялось в разделе “Просмотр и установка новых конфигураций ядра”, система Kconfig будет задавать вам вопросы по каждой новой конфигурации; нажатие Enter принимает значение по умолчанию.
Теперь давайте воспользуемся командой localmodconfig.
Подсказка будет заканчиваться суффиксом (NEW), что фактически сообщает вам, что это новая опция конфигурации ядра, и она требует вашего ответа о том, как её настроить.
Введите следующие команды (если они еще не выполнены):
$ uname -r
5.19.0-41-generic
$ lsmod > /tmp/lsmod.now
$ make LSMOD=/tmp/lsmod.now localmodconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
[ ... ]
using config: '/boot/config-5.19.0-41-generic'
System keyring enabled but keys "debian/canonical-certs.pem" not found.
Resetting keys to default value.
*
* Перезапуск конфигурации...
*
* Поддержка Control Group
*
Поддержка Control Group (CGROUPS) [Y/?] y
По умолчанию снижать задержку динамического изменения (CGROUP_FAVOR_DYNMODS) [N/y/?] (NEW) ↵
Контроллер памяти (MEMCG) [Y/n/?] y
[ … ]
Оппортунистический сон в пользовательском пространстве (PM_USERSPACE_AUTOSLEEP) [N/y/?] (NEW) ↵
[ … ]
Мульти-генерационный LRU (LRU_GEN) [N/y/?] (NEW) ↵
[ … ]
Rados block device (RBD) (BLK_DEV_RBD) [N/m/y/?] n
Драйвер блочных устройств в пользовательском пространстве (Экспериментальный) (BLK_DEV_UBLK) [N/m/y/?] (NEW) ↵
[ … ]
Клавиатура Pine64 PinePhone (KEYBOARD_PINEPHONE) [N/m/y/?] (NEW) ↵
[ … ]
Драйвер пин-контроллера и GPIO для Intel Meteor Lake (PINCTRL_METEORLAKE) [N/m/y/?] (NEW) ↵
[ … ]
Тест DYNAMIC_DEBUG (TEST_DYNAMIC_DEBUG) [N/m/y/?] (NEW) ↵
[ … ]
#
# конфигурация записана в .config
#
$ ls -l .config
-rw-rw-r-- 1 c2kp c2kp 170136 апр 29 09:56 .config
После многократного нажатия клавиши Enter (↵) – мы выделили это в блоке вывода выше, показав лишь несколько из многих новых опций, с которыми мы столкнулись – опрос милосердно заканчивается, и система Kconfig записывает вновь сгенерированную конфигурацию в файл .config в текущем рабочем каталоге. Обратите внимание, что мы сократили предыдущий вывод, так как он слишком объемный и нецелесообразно воспроизводить его полностью.
Предыдущие шаги обеспечивают генерацию файла .config через подход localmodconfig. Прежде чем мы закончим этот раздел, вот несколько дополнительных моментов, на которые стоит обратить внимание:
- Чтобы начать с чистого листа, выполните make mrproper или make distclean в корне дерева исходного кода ядра, это полезно, когда вы хотите начать процесс сборки ядра с нуля; будьте уверены, это однажды пригодится! Обратите внимание, что это также удаляет файл(ы) конфигурации ядра. Сделайте резервную копию перед началом, если это необходимо.
- Здесь, в этой главе, все шаги по конфигурации ядра и снимки экрана, связанные с этим, были выполнены на виртуальной машине Ubuntu 22.04 LTS на базе x86_64, которую мы используем как хост для сборки совершенно нового ядра Linux LTS 6.1. Точные названия, наличие и содержание пунктов меню, а также внешний вид и ощущение системы меню (интерфейс пользователя) могут и будут различаться в зависимости от (а) архитектуры (CPU) и (б) версии ядра.
- Как упоминалось ранее, на производственной системе или в проекте платформенная или BSP команда, или даже компания-вендор BSP для встраиваемого Linux, если вы с ними сотрудничаете, предоставит хорошо известный, работающий и протестированный файл конфигурации ядра. Используйте его как отправную точку, скопировав его в файл .config в корне дерева исходного кода ядра. В качестве альтернативы могут быть использованы программы для сборки, такие как Yocto или Buildroot.
По мере накопления опыта в сборке ядра вы поймете, что усилия по правильной настройке конфигурации ядра с первого раза выше; и, конечно, время, необходимое для первого построения, довольно длительное. Но после того как это сделано правильно, процесс обычно становится намного проще – это рецепт, который можно повторять.
Теперь давайте узнаем, как использовать полезный и интуитивно понятный интерфейс пользователя для тонкой настройки нашей конфигурации ядра.
Настройка конфигурации ядра через интерфейс make menuconfig
Отлично, у нас теперь есть начальный файл конфигурации ядра (.config), сгенерированный с помощью цели Makefile localmodconfig, как было подробно показано в предыдущем разделе, что является хорошей отправной точкой. Обычно мы теперь уточняем конфигурацию ядра. Один из способов сделать это – фактически рекомендуемый способ – через цель Makefile menuconfig. Эта цель заставляет систему Kbuild генерировать довольно сложную исполняемую программу на C (scripts/kconfig/mconf), которая предоставляет конечному пользователю аккуратный интерфейс пользователя на основе меню. Это шаг 2 на Рисунке 2.8. В следующем блоке вывода, когда (внутри корня нашего дерева исходного кода ядра) мы впервые вызываем команду, система Kbuild собирает исполняемый файл mconf и запускает его:
$ make menuconfig
UPD scripts/kconfig/.mconf-cfg
HOSTCC scripts/kconfig/mconf.o
HOSTCC scripts/kconfig/lxdialog/checklist.o
[…]
HOSTLD scripts/kconfig/mconf
Конечно, картинка стоит тысячи слов, поэтому вот как выглядит интерфейс menuconfig на моей виртуальной машине.
![[figure211.png]]
Рисунок 2.11: Основное меню для конфигурации ядра через make menuconfig (на x86-64)
Кстати, вам не нужно запускать вашу VM в режиме GUI, чтобы использовать этот подход; он также работает в окне терминала при использовании SSH-логина с хоста – еще одно преимущество этого подхода UI для редактирования нашей конфигурации ядра!
Как опытные разработчики или любой, кто достаточно пользовался компьютером, хорошо знают, что вещи могут и действительно идут не так. Рассмотрим, например, следующий сценарий – запуск make menuconfig впервые на только что установленной системе Ubuntu:
$ make menuconfig
UPD scripts/kconfig/.mconf-cfg
HOSTCC scripts/kconfig/mconf.o
YACC scripts/kconfig/zconf.tab.c
/bin/sh: 1: bison: not found
scripts/Makefile.lib:196: рецепт для цели 'scripts/kconfig/zconf.tab.c' не удался
make[1]: *** [scripts/kconfig/zconf.tab.c] Ошибка 127
Makefile:539: рецепт для цели 'menuconfig' не удался
make: *** [menuconfig] Ошибка 2
$
Не паникуйте. Внимательно прочитайте сообщения об ошибках. Строка после YACC […] дает подсказку: /bin/sh: 1: bison: not found. Ага! Так что установите bison следующей командой:
sudo apt install bison
Теперь все должно быть в порядке. Почти; снова, на только что созданной виртуальной машине Ubuntu, make menuconfig жалуется, что не установлен flex. Так что мы установили его (как вы и догадались: через sudo apt install flex). Также, специально для Ubuntu, вам нужно установить пакет libncurses5-dev. На Fedora используйте sudo dnf install ncurses-devel.
Если вы прочитали и следовали инструкциям в главе “Настройка рабочего пространства ядра” в Интернете, у вас уже должны быть установлены все необходимые пакеты. Если нет, пожалуйста, обратитесь к ней сейчас и установите все требуемые пакеты. Помните, как посеешь…
Полезный совет: запуск скрипта Bash
/ch1/pkg_install4ubuntu_lkp.sh (на системе Ubuntu) установит все необходимые пакеты.
Продолжая, фреймворк Kconfig+Kbuild с открытым исходным кодом предоставляет пользователю подсказки через свой UI. Посмотрите на Рисунок 2.11; вы часто увидите символы, предшествующие меню (например, [], <>, --, (), и так далее); значение этих символов следующее:
[.]
: Внутренняя функция ядра, булевская опция. Она либо включена, либо выключена; показанный ‘.’ будет заменен на * или пробел:[*]
: Включено, функция скомпилирована и встроена в образ ядра (y)[ ]
: Выключено, не собирается вообще (n)
- <.>: Функция, которая может находиться в одном из трех состояний. Это известно как трисостояние; показанный ‘.’ будет заменен на *, M или пробел:
<*>
: Включено, функция скомпилирована и встроена в образ ядра (y)<M>
: Модуль, функция скомпилирована и собрана как модуль ядра (LKM) (m)< >
: Выключено, не собирается вообще (n)
{.}
: Существует зависимость для этой опции конфигурации; следовательно, она должна быть собрана или скомпилирована либо как модуль (m), либо в образ ядра (y).-*-
: Зависимость требует, чтобы этот элемент был скомпилирован в ядро (y).(...)
: Подсказка: требуется ввод алфавитно-цифрового значения. Нажмите Enter, находясь на этой опции, и появится окно ввода.<Menu name>
—>: Следует подменю. Нажмите Enter на этом элементе, чтобы перейти к подменю.
Эмпирический подход ключевой. Давайте проведем несколько экспериментов с интерфейсом make menuconfig, чтобы увидеть, как он работает. Именно это мы и узнаем в следующем разделе.
Пример использования интерфейса make menuconfig
Чтобы получить представление о том, как использовать систему меню Kbuild через удобную команду menuconfig, давайте включим довольно интересную опцию конфигурации ядра. Она называется “Kernel .config support” и позволяет просматривать содержимое конфигурации ядра во время его работы! Полезно, особенно во время разработки и тестирования. По соображениям безопасности, она обычно отключена в продакшене.
Остаются несколько назойливых вопросов:
- Вопрос: Где это находится?
- Ответ: Это находится как элемент в главном меню “General Setup” (мы скоро это увидим).
- Вопрос: Каково значение по умолчанию?
- Ответ: Значение по умолчанию
<M>
, что означает, что по умолчанию она будет собрана как модуль ядра.
- Ответ: Значение по умолчанию
В качестве учебного эксперимента мы установим её значение на [*] (или y), встроив её непосредственно в ядро. Таким образом, она будет всегда включена. Давайте это сделаем!
- Запустите интерфейс конфигурации ядра:
make menuconfig
Вы должны увидеть интерфейс терминала, как на Рисунке 2.11. Первый элемент обычно является подменю с названием “General Setup —>”; нажмите Enter, находясь на нем; это приведет вас в подменю “General Setup”, в котором отображается множество элементов; перемещайтесь (нажимая стрелку вниз) к элементу с названием “Kernel .config support”:
![[figure212.png]] Рисунок 2.12: Снимок экрана с элементами меню General Setup, с выделенным нужным элементом (на x86-64)
-
Мы видим на предыдущем снимке экрана, что мы настраиваем ядро версии 6.1.25 на x86, выделенный пункт меню - это “Kernel .config support”, и, исходя из префикса
<M>
, это пункт меню с тремя состояниями, который по умолчанию установлен на выбор<M>
для “модуля”. -
Оставив выбранным пункт “Kernel .config support”, используйте клавишу со стрелкой вправо, чтобы перейти к кнопке < Help > на нижней панели инструментов, и нажмите Enter, находясь на кнопке < Help >. Или просто нажмите ? на опции! Экран должен выглядеть примерно так:
![[figure213.png]] Рисунок 2.13: Конфигурация ядра через make menuconfig; пример экрана помощи (с выделенным именем макроса конфигурации ядра)
Экран помощи довольно информативен. Действительно, многие экраны помощи конфигурации ядра очень хорошо заполнены и полезны. К сожалению, некоторые из них таковыми не являются.
- Хорошо, следующим шагом нажмите Enter, находясь на кнопке < Exit >, чтобы вернуться на предыдущий экран.
- Измените значение, нажав пробел; это переключает значение текущего пункта меню между <> (всегда включено), < > (выключено) и
<M>
(модуль). Оставьте его на <>, что означает “всегда включено”. -
Далее, хотя теперь это включено, возможность фактически просматривать конфигурацию ядра предоставляется через псевдофайл в procfs; следующий пункт ниже этого - это соответствующий:
[ ] Включить доступ к .config через /proc/config.gz
- Вы можете видеть, что по умолчанию это выключено ([ ]); включите его, перейдя к нему и нажав пробел. Теперь оно отображается как [*]:
![[figure214.png]] Рисунок 2.14: Усеченный снимок экрана, показывающий, как мы включили возможность просмотра конфигурации ядра
- Отлично, на этом пока все; нажмите клавишу со стрелкой вправо или Tab, перейдите к кнопке < Exit > и нажмите Enter, находясь на ней; вы вернетесь на экран главного меню. Повторите это, снова нажав < Exit >; интерфейс спросит вас, хотите ли вы сохранить эту конфигурацию. Выберите < Yes > (нажав Enter на кнопке Yes):
![[figure215.png]] Рисунок 2.15: Приглашение сохранить измененную конфигурацию ядра
-
Новая конфигурация ядра теперь сохранена в файле .config. Давайте быстро это проверим. Надеюсь, вы заметили, что точное название конфигураций ядра, которые мы изменили – это макрос, как видит его исходный код ядра – это:
- CONFIG_IKCONFIG для опции “Kernel .config support”.
- CONFIG_IKCONFIG_PROC для опции “Enable access to .config through /proc/config.gz”.
Как мы это узнали? Это указано в левом верхнем углу экрана помощи! Смотрите еще раз на Рисунок 2.13.
Готово. Конечно, реальный эффект не будет виден, пока мы не соберем и не загрузим это ядро. Теперь, что именно достигается включением этой функции? Когда она включена, настройки конфигурации текущего работающего ядра можно просмотреть в любое время двумя способами:
- Запустив скрипт scripts/extract-ikconfig.
- Прямо прочитав содержимое псевдофайла /proc/config.gz. Конечно, он сжат gzip; сначала разархивируйте его, а затем читайте. Команда zcat /proc/config.gz сделает это!
Для дальнейшего изучения, почему бы не изменить еще несколько параметров в конфигурации ядра по умолчанию (нашего ядра Linux 6.1.25 для архитектуры x86-64)? Пока не беспокойтесь о точном значении каждой из этих опций конфигурации ядра; это просто для того, чтобы получить практику с системой Kconfig. Итак, запустите make menuconfig и в нем внесите изменения, следуя формату, показанному ниже.
Формат:
- Конфигурация ядра, с которой мы работаем:
- Что это значит
- Куда нужно перейти
- Название пункта меню (и макрос конфигурации ядра CONFIG_FOO в скобках)
- Значение по умолчанию
- Значение, на которое нужно изменить
Хорошо, вот что попробовать; начнем с:
Локальная версия:
- Значение: строка, которая будет добавлена к версии ядра. Возьмем uname –r в качестве примера; фактически, это компонент “z” или EXTRAVERSION в номенклатуре версии ядра w.x.y.z).
- Перейти к: General Setup.
- Пункт меню: Локальная версия – добавить к версии ядра (CONFIG_LOCALVERSION); нажмите Enter один раз здесь, и вы получите окно ввода.
- Значение по умолчанию: NULL.
- Изменить на: что угодно; считается хорошей практикой добавлять дефис перед локальной версией; например, -lkp-kernel.
Далее:
-
Частота таймера. Подробности об этом параметре вы узнаете в Главе 10, “Планировщик CPU – Часть 1”:
- Значение: частота, с которой срабатывает прерывание таймера (аппаратное).
-
Перейти к: Processor type and features Частота таймера (250 Гц) —> . Продолжайте прокручивать, пока не найдете второй пункт меню. - Пункт меню: Частота таймера (CONFIG_HZ).
- Значение по умолчанию: 250 Гц.
- Изменить на: 300 Гц.
Просматривайте экраны помощи для каждой конфигурации ядра, с которой вы работаете. Отлично; когда закончите, сохраните изменения и выйдите из интерфейса.
Проверка конфигурации ядра в файле конфигурации
Но где сохраняется новая конфигурация ядра? Это важно повторить: конфигурация ядра записывается в простой текстовый файл ASCII в корне дерева исходного кода ядра, который называется .config. То есть, она сохраняется в ${LKP_KSRC}/.config.
Как упоминалось ранее, каждый параметр конфигурации ядра связан с переменной конфигурации формы CONFIG_<FOO>
, где <FOO>
, конечно, заменяется на соответствующее имя. Внутри ядра эти переменные становятся макросами, которые используются системой сборки и самим исходным кодом ядра.
Таким образом, чтобы проверить, вступят ли в силу изменения конфигурации ядра, которые мы только что внесли, давайте соответствующим образом выполним команду grep для файла конфигурации ядра:
$ grep -E "CONFIG_IKCONFIG|CONFIG_LOCALVERSION|CONFIG_HZ_300" .config
CONFIG_LOCALVERSION="-lkp-kernel"
# CONFIG_LOCALVERSION_AUTO is not set
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
CONFIG_HZ_300=y
Ага! Файл конфигурации теперь отражает тот факт, что мы действительно изменили соответствующие параметры конфигурации ядра; значения также отображаются.
Предупреждение: Лучше всего НЕ пытаться редактировать файл .config вручную. Существует множество взаимозависимостей, о которых вы можете не знать; всегда используйте систему меню Kbuild (мы рекомендуем использовать make menuconfig) для его редактирования. Сказав это, существует также неинтерактивный способ сделать это с помощью скрипта. Мы узнаем об этом позже. Тем не менее, использование интерфейса make menuconfig действительно является лучшим способом.
Так что теперь, я ожидаю, что вы изменили конфигурацию ядра в соответствии с только что увиденными значениями.
Во время нашего быстрого приключения с системой Kconfig/Kbuild до сих пор произошло многое под капотом. В следующем разделе мы рассмотрим некоторые оставшиеся моменты: немного больше о Kconfig/Kbuild, поиск внутри системы меню, чистое визуализирование различий между исходным и модифицированным файлами конфигурации ядра, использование скрипта для редактирования конфигурации, вопросы безопасности и советы по их решению; еще многое предстоит узнать!
Конфигурация ядра – дальнейшее изучение
Создание или редактирование файла .config в корне дерева исходного кода ядра через интерфейс make menuconfig или другие методы не является финальным шагом в работе системы Kconfig с конфигурацией. Нет, теперь она внутренне вызывает скрытую цель под названием syncconfig, которая ранее неправильно называлась silentoldconfig. Эта цель заставляет Kconfig генерировать несколько заголовочных файлов, которые затем используются в процессе сборки ядра.
Эти файлы включают некоторые мета-заголовки в include/config, а также файл заголовка include/generated/autoconf.h, который хранит конфигурацию ядра в виде макросов C, что позволяет как Makefile ядра, так и коду ядра принимать решения на основе доступности функций ядра.
Теперь, когда мы достаточно глубоко погрузились в тему, взгляните еще раз на Рисунок 2.8, на высокоуровневую диаграмму (вдохновленную статьями Цао Цзина), которая пытается передать информацию о системе Kconfig/Kbuild ядра. На этой диаграмме в части Kconfig показан только общий интерфейс make menuconfig; обратите внимание, что существует несколько других подходов к UI, таких как make config и make {x | g | n}config. Они здесь не показаны. |
Поиск в интерфейсе menuconfig
Продолжая, что делать, если при запуске make menuconfig вы ищете конкретный параметр конфигурации ядра, но не можете его найти? Нет проблем: система интерфейса menuconfig имеет функцию поиска параметра конфигурации. Подобно знаменитому редактору vi (да, [g]vi[m] все еще наш любимый текстовый редактор!), нажмите клавишу / (косая черта), чтобы вызвать диалоговое окно поиска, затем введите искомый термин с префиксом CONFIG_ или без него, и выберите кнопку < Ok >, чтобы начать поиск.
На следующих двух снимках экрана показаны диалоговое окно поиска и диалоговое окно результатов. В качестве примера, мы искали термин vbox: ![[figure216.png]] Рисунок 2.16: Конфигурация ядра через make menuconfig: поиск параметра конфигурации
Диалоговое окно результатов поиска на Рисунке 2.17 для предыдущего запроса довольно интересно. Оно раскрывает несколько деталей о параметрах конфигурации:
- Директива конфигурации. Просто добавьте префикс CONFIG_ к тому, что показано в Symbol:.
- Тип конфигурации (Булевый, трисостояние, алфавитно-цифровой и так далее).
- Строка подсказки.
- Важно для того, чтобы вы могли его найти, его местоположение в системе меню.
- Его внутренние зависимости (Depends on:), если таковые имеются.
- Файл Kconfig и номер строки n в нем (Defined at <path/to/foo.Kconfig*:n>), где определена данная конфигурация ядра. Мы подробнее рассмотрим это в последующих разделах.
- Любые параметры конфигурации, которые он автоматически выбирает (Selects:), если сам выбран.
![[figure217.png]] Рисунок 2.17: Конфигурация ядра через make menuconfig: усеченный снимок экрана диалога результатов из предыдущего поиска
Вся информация, управляющая отображением меню и выбором опций, содержится в текстовом файле ASCII, используемом системой Kbuild – этот файл обычно называется Kconfig. На самом деле их несколько. Их точные имена и местоположения указаны в строке Defined at ….
Просмотр различий в конфигурации
Когда файл конфигурации ядра .config готов к записи, система Kconfig проверяет, существует ли он уже, и если да, создает его резервную копию под именем .config.old. Зная это, мы всегда можем сравнить эти два файла, чтобы увидеть изменения, которые мы только что внесли. Однако использование обычной утилиты diff для этого делает различия довольно трудными для интерпретации. Ядро предоставляет более удобный способ, специализированный консольный скрипт, который предназначен именно для этого. Скрипт scripts/diffconfig внутри дерева исходного кода ядра полезен для этого. Передайте ему параметр –help, чтобы увидеть экран использования.
Давайте попробуем:
$ scripts/diffconfig .config.old .config
HZ 250 -> 300
HZ_250 y -> n
HZ_300 n -> y
LOCALVERSION "" -> "-lkp-kernel"
$
Если вы изменили конфигурацию ядра, как показано в предыдущем разделе, вы должны увидеть вывод, похожий на тот, что показан в предыдущем блоке кода, через скрипт diffconfig ядра. Он четко показывает нам, какие именно параметры конфигурации ядра мы изменили и как. На самом деле, вам даже не нужно передавать параметры .config*; по умолчанию он использует их.
Использование скрипта конфигурации ядра для просмотра/редактирования конфигурации ядра
Иногда возникает необходимость напрямую редактировать или запрашивать конфигурацию ядра, проверяя или изменяя данный параметр конфигурации. Мы научились делать это через отличный интерфейс make menuconfig. Здесь мы узнаем, что есть, возможно, более простой и, что важнее, неинтерактивный и, следовательно, поддающийся скриптам способ сделать то же самое – через Bash-скрипт внутри исходного кода ядра: scripts/config.
Запуск его без параметров приведет к отображению полезного экрана помощи; обязательно проверьте его. Пример поможет понять его использование.
Возможность просмотреть текущую конфигурацию ядра очень полезна, поэтому давайте убедимся, что эти параметры конфигурации включены. Просто для примера, сначала явно отключим соответствующие параметры конфигурации ядра, а затем включим их:
$ scripts/config --disable IKCONFIG --disable IKCONFIG_PROC
$ grep IKCONFIG .config
# CONFIG_IKCONFIG is not set
# CONFIG_IKCONFIG_PROC is not set
$ scripts/config --enable IKCONFIG --enable IKCONFIG_PROC
$ grep IKCONFIG .config
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
Вуаля, готово.
Осторожно: этот скрипт может изменять .config, но нет гарантии, что то, что вы ему поручаете, действительно правильно. Проверка корректности конфигурации ядра будет выполнена только при следующей сборке. В случае сомнений, сначала проверьте все зависимости через файлы Kconfig* или запустив make menuconfig, затем используйте scripts/config соответствующим образом, и затем протестируйте сборку, чтобы убедиться, что все в порядке.
Конфигурация ядра для безопасности
Перед завершением, краткое замечание о чем-то критически важном: безопасности ядра. Хотя технологии укрепления безопасности в пространстве пользователя значительно развились, технологии укрепления безопасности в пространстве ядра все еще догоняют. Тщательная настройка параметров конфигурации ядра действительно играет ключевую роль в определении уровня безопасности данного ядра Linux; проблема в том, что существует так много опций и мнений, что часто трудно определить, что является хорошей идеей с точки зрения безопасности, а что нет.
Александр Попов написал очень полезный Python-скрипт под названием kconfig-hardened-check. Его можно запустить для проверки и сравнения заданной конфигурации ядра, через обычный файл конфигурации, с набором предопределенных предпочтений по укреплению безопасности, полученных из различных проектов безопасности ядра Linux:
- Проект по самозащите ядра (Kernel Self Protection Project, KSPP; ссылка: https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project)
- Последний публичный патч grsecurity
- CLIP OS
- Модуль безопасности блокировки (security lockdown LSM)
- Прямая обратная связь от разработчиков ядра Linux
Вы можете клонировать проект kconfig-hardened-check с его GitHub-репозитория по адресу https://github.com/a13xp0p0v/kconfig-hardened-check и попробовать его! Кстати, моя книга “Отладка ядра Linux” подробно описывает использование этого скрипта. Ниже приведен снимок экрана с его экраном помощи, который поможет вам начать работу с ним: ![[figure218.png]] Рисунок 2.18: Снимок экрана с экраном помощи супер-скрипта kconfig-hardened-check
Полезный совет: с помощью скрипта kconfig-hardened-check можно легко сгенерировать файл конфигурации ядра с учетом безопасности, например, для AArch64:
kconfig-hardened-check -g ARM64 > my_kconfig_hardened
(Файл вывода называется фрагментом конфигурации.) А что если у вас уже есть существующий файл конфигурации ядра для вашего продукта? Можем ли мы их объединить? Конечно, можем! Ядро предоставляет для этого скрипт: scripts/kconfig/merge_config.sh. Запустите его, передав в качестве параметров путь к исходному (возможно, небезопасному) файлу конфигурации ядра, а затем путь к только что сгенерированному безопасному фрагменту конфигурации ядра; результатом будет объединение обоих (дополнительные параметры для merge_config.sh позволяют вам управлять процессом; обязательно проверьте их. Пример можно найти здесь: https://github.com/a13xp0p0v/kconfig-hardenedcheck#generating-a-kconfig-fragment-with-the-security-hardening-options).
Также вы наверняка узнаете, что существуют новые плагины GCC
(CONFIG_GCC_PLUGINS), которые предоставляют некоторые крутые функции безопасности, специфичные для архитектуры. Например, автоматическая инициализация локальных/кучевых переменных, генерация энтропии при загрузке и т.д. Однако они часто не отображаются в меню. Обычно они находятся здесь: Общие опции, зависящие от архитектуры | Плагины GCC, так как поддержка не установлена по умолчанию. По крайней мере, на x86 попробуйте установить пакет gcc-<ver#>-plugin-dev
, где ver#
- это номер версии GCC, и затем попробуйте снова настроить конфигурацию.
Различные советы – конфигурация ядра
Несколько оставшихся советов по конфигурации ядра:
- При сборке ядра x86 для виртуальной машины с использованием VirtualBox (как мы делаем здесь), при настройке ядра может быть полезно установить CONFIG_ISO9660_FS=y; это позволит VirtualBox монтировать виртуальный CD с дополнениями гостевой ОС и установить (довольно полезные!) дополнения гостевой ОС. Обычно это улучшает производительность в VM и обеспечивает лучшую графику, возможности USB, обмен буфером обмена и файлами.
- При сборке пользовательского ядра иногда мы хотим писать/собирать программы eBPF (это продвинутая тема, не рассматриваемая здесь) или что-то подобное. Для этого требуются некоторые заголовочные файлы внутри ядра. Вы можете явно обеспечить это, установив в конфигурации ядра CONFIG_IKHEADERS=y (или на m; с версии 5.2). Это приведет к созданию файла /sys/kernel/kheaders.tar.xz, который можно извлечь в другое место, чтобы предоставить заголовки.
- Далее, говоря об eBPF, современные ядра имеют возможность генерировать некоторую отладочную информацию, называемую метаданными формата типа BPF (BTF). Это можно включить, выбрав в конфигурации ядра CONFIG_DEBUG_INFO_BTF=y. Это также требует установки инструмента pahole. Больше информации о метаданных BTF можно найти в официальной документации ядра здесь: https://www.kernel.org/doc/html/next/bpf/btf.html.
- Когда эта опция включена, другая конфигурация ядра – CONFIG_MODULE_ALLOW_BTF_MISMATCH – становится актуальной при сборке модулей ядра. Это тема, которую мы подробно рассмотрим в следующих двух главах. Если CONFIG_DEBUG_INFO_BTF включен, хорошей идеей будет установить эту последнюю конфигурацию на Yes, так как в противном случае ваши модули могут не загрузиться, если метаданные BTF не совпадают при загрузке.
-
Далее, сборка ядра теоретически должна проходить без ошибок или даже предупреждений. Чтобы обеспечить это, установите CONFIG_WERROR=y, чтобы предупреждения трактовались как ошибки. В знакомом интерфейсе make menuconfig это находится в разделе “General Setup Compile the kernel with warnings as errors”, и по умолчанию обычно отключено. - Здесь есть интересный скрипт: scripts/get_feat.pl; его экран помощи показывает, как вы можете использовать его для вывода матрицы поддержки функций ядра для машины или для заданной архитектуры. Например, чтобы увидеть матрицу поддержки функций ядра для AArch64, выполните:
scripts/get_feat.pl --arch arm64 ls
- Существует неофициальная “база данных” всех доступных конфигураций ядра – и версий ядра, на которых они поддерживаются – на сайте проекта Linux Kernel Driver database (LKDDb) здесь: https://cateee.net/lkddb/web-lkddb/.
- Конфигурация загрузки ядра: При загрузке вы всегда можете переопределить некоторые функции ядра с помощью мощных параметров командной строки ядра. Они подробно документированы здесь: https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html. Хотя это очень полезно, иногда нам нужно передавать больше параметров в формате ключ-значение, по сути, в форме key=value, расширяя командную строку ядра. Это можно сделать, заполнив небольшой файл конфигурации ядра, называемый конфигурацией загрузки. Эта функция конфигурации загрузки зависит от того, что конфигурация ядра BOOT_CONFIG установлена на y. Она находится в меню “General Setup” и обычно включена по умолчанию.
- Её можно использовать двумя способами: либо прикрепив конфигурацию загрузки к образу initrd или initramfs (мы рассмотрим initrd в следующей главе), либо встроив конфигурацию загрузки непосредственно в ядро. Для второго варианта вам нужно будет создать файл конфигурации загрузки, передать директиву CONFIG_BOOT_CONFIG_EMBED_FILE=”x/y/z” в конфигурацию ядра и пересобрать ядро. Обратите внимание, что параметры командной строки ядра имеют приоритет над параметрами конфигурации загрузки. При загрузке, если эта функция включена и используется, параметры конфигурации загрузки видны через /proc/bootconfig. Подробности о конфигурации загрузки можно найти в официальной документации ядра здесь: https://elixir.bootlin.com/linux/v6.1.25/source/Documentation/admin-guide/bootconfig.rst Вы наверняка столкнетесь с множеством других полезных настроек конфигурации ядра и скриптов, включая те, что предназначены для укрепления безопасности ядра; будьте внимательны. Отлично! Вы только что завершили первые три шага сборки ядра Linux – это довольно значимое достижение. Конечно, мы завершив оставшиеся четыре шага процесса сборки ядра в следующей главе. Мы закончим эту главу финальным разделом, в котором вы узнаете еще один полезный навык – как настраивать меню пользовательского интерфейса ядра.
Настройка меню ядра, Kconfig, и добавление нашего собственного пункта меню
Предположим, вы разработали драйвер устройства, новый экспериментальный модульный класс планирования, пользовательский callback для debugfs (файловая система отладки) или какую-то другую крутую функцию ядра. Однажды вы это сделаете. Как вы сообщите об этом другим членам команды, вашему клиенту или сообществу? Обычно вы создадите новую конфигурацию ядра (макрос) и позволите людям выбирать её либо как встроенную, либо как модуль ядра, чтобы они могли её собрать и использовать. В рамках этого процесса вам нужно будет определить новую конфигурацию ядра и вставить новый пункт меню в подходящее место конфигурационного меню ядра. Для этого полезно сначала лучше понять, что такое файлы Kconfig* и где они находятся. Давайте разберемся.
Понимание файлов Kconfig*
Файлы Kconfig* содержат метаданные, которые интерпретируются системой конфигурации и сборки ядра – Kconfig/Kbuild, – позволяя ей собирать и (условно) отображать меню, которые вы видите при запуске интерфейса menuconfig, принимать выборы и так далее.
Например, файл Kconfig, находящийся в корне дерева исходного кода ядра, используется для заполнения начального экрана интерфейса menuconfig. Взгляните на него; он работает, подключая различные другие файлы Kconfig в разных папках дерева исходного кода ядра. В дереве исходного кода ядра существует множество файлов Kconfig* (более 1700 для версии 6.1.25)! Каждый из них обычно определяет одно меню, что помогает понять, насколько сложна система сборки.
В качестве реального примера, давайте найдем запись в Kconfig, которая определяет следующие пункты меню: Google Devices | Поддержка Google Virtual NIC (gVNIC). Google Cloud использует виртуальную сетевую карту (NIC); она называется Google Virtual NIC. Вероятно, их серверы на базе Linux будут использовать её. Её местоположение в интерфейсе menuconfig выглядит так: -> Драйверы устройств -> Поддержка сетевых устройств -> Поддержка драйверов Ethernet -> Google Devices
Вот снимок экрана, показывающий эти пункты меню:
![[figure219.png]] Рисунок 2.19: Частичный снимок экрана, показывающий пункт Google Devices в интерфейсе make menuconfig (для x86/6.1.25)
Как мы узнаем, какой файл Kconfig определяет эти пункты меню? Экран помощи для данной конфигурации это раскрывает! Так что, находясь на соответствующем пункте меню, выберите кнопку < Help > и нажмите Enter; здесь экран помощи говорит (среди прочего):
Defined at drivers/net/ethernet/google/Kconfig:5
Это и есть файл Kconfig, описывающий это меню! Давайте посмотрим на него:
$ cat drivers/net/ethernet/google/Kconfig
#
# Конфигурация сетевого устройства Google
#
config NET_VENDOR_GOOGLE
bool "Google Devices"
default y
help
If you have a network (Ethernet) device belonging to this class, say Y.
[ … ]
Это простая и понятная запись Kconfig; обратите внимание на ключевые слова языка Kconfig: config, bool, default, и help. Мы выделили их инвертированными цветами. Вы можете видеть, что это устройство включено по умолчанию. Синтаксис мы рассмотрим скоро. Таблица, суммирующая наиболее важные файлы Kconfig* и подменю, которые они обслуживают в интерфейсе Kbuild:
| Меню | Расположение файла Kconfig для него |
| ———————————————————————————————————————————————————————————————————————————- | ——————————————- |
| Главное меню, начальный экран интерфейса menuconfig UI | Kconfig |
| Общие настройки + Включить поддержку загружаемых модулей | init/Kconfig |
| Типы процессоров и функции + Опции шины + Бинарные эмуляции (название этого меню обычно зависит от архитектуры; здесь это относится к x86[_64]; в общем случае, файл Kconfig находится здесь: arch/
/Kconfig) | arch/
/Kconfig* |
| Управление питанием | kernel/power/Kconfig* |
| Драйверы прошивки | drivers/firmware/Kconfig* |
| Виртуализация | arch/
/kvm/Kconfig* |
| Общие опции, зависящие от архитектуры | arch/Kconfig* |
| Включить слой блочных устройств + Планировщики ввода-вывода | block/Kconfig* |
| Форматы исполняемых файлов | fs/Kconfig.binfmt |
| Опции управления памятью | mm/Kconfig* |
| Поддержка сетевых функций | net/Kconfig, net/
/Kconfig |
| Драйверы устройств | drivers/Kconfig, drivers/
/Kconfig |
| Файловые системы | fs/Kconfig, fs/
/Kconfig |
| Опции безопасности | security/Kconfig, security/
/Kconfig |
| Криптографический API | crypto/Kconfig, crypto/
/Kconfig |
| Библиотечные процедуры | lib/Kconfig, lib/
/Kconfig |
| Отладка ядра (подразумевает отладку ядра) | lib/Kconfig.debug, lib/Kconfig.* |
Таблица 2.4: Подменю конфигурации ядра и соответствующие им файлы Kconfig, определяющие их*
Обычно один файл Kconfig управляет одним меню, хотя их может быть несколько. Теперь давайте перейдем к фактическому добавлению пункта меню.
Создание нового пункта меню в меню “General Setup”
В качестве простого примера, давайте добавим наш собственный булевский фиктивный параметр конфигурации в меню “General Setup”. Мы хотим, чтобы имя конфигурации было, скажем, CONFIG_LKP_OPTION1. Как видно из предыдущей таблицы, соответствующий файл Kconfig для редактирования - это init/Kconfig, так как это мета-файл, определяющий меню “General Setup”.
Давайте приступим (предполагается, что вы находитесь в корне дерева исходного кода ядра):
- Опционально: Для безопасности всегда делайте резервную копию редактируемого файла Kconfig:
cp init/Kconfig init/Kconfig.orig
- Теперь откройте и отредактируйте файл init/Kconfig:
vi init/Kconfig
Прокрутите вниз до подходящего места в файле; здесь мы решили вставить наш пользовательский пункт меню между LOCALVERSION_AUTO и BUILD_SALT. На следующем снимке экрана показан наш новый пункт (файл init/Kconfig редактируется с помощью vim):
![[figure220.png]] Рисунок 2.20: Редактирование 6.1.25:init/Kconfig и вставка нашего собственного пункта меню (выделено)
Кстати, я предоставил предыдущий эксперимент в виде патча к исходному файлу init/Kconfig версии 6.1.25 в репозитории GitHub нашей книги. Найдите файл патча здесь: ch2/Kconfig.patch.
Новый пункт начинается с ключевого слова config, за которым следует часть FOO вашей новой переменной конфигурации CONFIG_LKP_OPTION1. Пока что просто прочитайте утверждения, которые мы сделали в файле Kconfig относительно этого пункта. Подробнее о языке/синтаксисе Kconfig мы поговорим в разделе “Несколько деталей о языке Kconfig”, который следует.
-
Сохраните файл и выйдите из редактора.
-
(Пере)настроим ядро: запустите make menuconfig. Затем перейдите к нашему новому пункту меню в разделе “General Setup Тестовый случай для книги LKP 2e/Глава 2: создание …”. Включите эту функцию. Обратите внимание, как на Рисунке 2.21 она выделена и по умолчанию отключена, как мы и указали через строку default n.
make menuconfig
[...]
Вот соответствующий вывод:
![[figure221.png]] Рисунок 2.21: Конфигурация ядра через make menuconfig, показывающая наш новый пункт меню (до его включения)
- Теперь включите его, переключив его с помощью пробела, затем сохраните и выйдите из системы меню.
Находясь там, попробуйте нажать кнопку < Help >. Вы увидите текст «Помощь», который мы в файле init/Kconfig.
- Проверим, была ли наша функция выбрана:
$ grep "LKP_OPTION1" .config CONFIG_LKP_OPTION1=y $ grep "LKP_OPTION1" include/generated/autoconf.h $
Мы обнаруживаем, что она действительно была установлена как включенная (y) в нашем файле .config, но еще не была включена во внутренний автоматически сгенерированный заголовочный файл ядра. Это произойдет, когда мы соберем ядро.
Теперь проверим это через полезный неинтерактивный метод скрипта конфигурации. Мы рассмотрели это в разделе “Использование скрипта конфигурации ядра для просмотра/редактирования конфигурации ядра”.
$ scripts/config -s LKP_OPTION1
y
Ага, она включена, как и ожидалось (опция -s эквивалентна –state). Ниже мы отключаем её через опцию -d, запрашиваем её состояние (-s), затем снова включаем через опцию -e, и снова запрашиваем её (просто для обучения!):
$ scripts/config -d LKP_OPTION1 ; scripts/config -s LKP_OPTION1
n
$ scripts/config -e LKP_OPTION1 ; scripts/config -s LKP_OPTION1
y
- Сборка ядра. Не беспокойтесь; полные детали по сборке ядра приведены в следующей главе. Вы можете пропустить это на данный момент или всегда можете перейти к Главе 3, “Сборка ядра Linux 6.x из исходников – Часть 2”, а затем вернуться к этой точке.
make -j4
Далее, в последних версиях ядер, после шага сборки, каждый параметр конфигурации ядра, который включен (либо y, либо m), появляется как пустой файл в include/config; это происходит и с нашим новым параметром, конечно:
$ ls -l include/config/LKP_* -rw-r--r-- 1 c2kp c2kp 0 апр 29 11:56 include/config/LKP_OPTION1
- После завершения, проверьте заголовочный файл autoconf.h на наличие нашего нового параметра конфигурации:
$ grep LKP_OPTION1 include/generated/* 2>/dev/null
include/generated/autoconf.h:#define CONFIG_LKP_OPTION1 1
include/generated/rustc_cfg:--cfg=CONFIG_LKP_OPTION1
include/generated/rustc_cfg:--cfg=CONFIG_LKP_OPTION1="y"
Это сработало (начиная с версии 6.0 даже Rust знает о нем!). Да; однако, при работе над реальным проектом или продуктом, чтобы использовать этот новый параметр конфигурации ядра, нам, как правило, потребуется дополнительный шаг, а именно настройка нашей записи конфигурации в Makefile, относящемся к коду, который использует этот параметр конфигурации. Вот быстрый пример, как это может выглядеть. Представим, что мы написали какой-то код ядра в файле на C с именем lkp_options.c. Теперь нам нужно убедиться, что он будет скомпилирован и встроен в образ ядра! Как? Вот один из способов: в верхнеуровневом Makefile ядра (или в собственном Makefile) следующая строка гарантирует, что он будет скомпилирован в ядро во время сборки; добавьте её в конец соответствующего Makefile:
obj-${CONFIG_LKP_OPTION1} += lkp_option1.o
Не беспокойтесь пока о довольно странном синтаксисе Makefile ядра. Следующие несколько глав обязательно прольют свет на это. Также мы рассмотрели этот конкретный синтаксис в разделе “Как работает система Kconfig+Kbuild – минимальный подход”.
Кроме того, вы должны понимать, что тот же самый параметр конфигурации может использоваться как обычный макрос C в части кода ядра; например, мы могли бы сделать что-то вроде этого в нашем коде ядра (или модуля) на C:
#ifdef CONFIG_LKP_OPTION1
do_our_thing();
#endif
Стоит отметить, что сообщество разработчиков ядра Linux разработало и строго придерживается определенных строгих руководящих принципов стиля кодирования. В этом контексте, руководящие принципы гласят, что условную компиляцию следует избегать, когда это возможно; если требуется использовать символ Kconfig в качестве условия, тогда делайте это так:
if (IS_ENABLED(CONFIG_LKP_OPTION1))
do_our_thing();
Руководство по стилю кодирования ядра Linux можно найти здесь: https://www.kernel.org/doc/html/latest/process/coding-style.html. Я призываю вас часто обращаться к ним и, конечно же, следовать им!
Несколько деталей о языке Kconfig
Наш использование языка Kconfig до сих пор (Рисунок 2.20) — это лишь вершина айсберга. Фактически, система Kconfig использует язык Kconfig (или синтаксис) для выражения и создания меню с помощью простых директив ASCII-текста. Язык включает в себя записи меню, атрибуты, зависимости, ограничения видимости, текст помощи и так далее.
Ядро документирует конструкции и синтаксис языка Kconfig здесь: https://www. kernel.org/doc/html/v6.1/kbuild/kconfig-language.html#kconfig-language. Обратитесь к этому документу для получения полной информации.
Краткое упоминание о наиболее распространенных конструкциях Kconfig приведено в следующей таблице:
| Конструкция | Значение |
| ——————————————— | ————————————————————————————————————————————————————————————————————– |
| config <FOO>
| Указывает имя пункта меню вида CONFIG_FOO
; просто используйте часть FOO
после ключевого слова config. |
| Атрибуты меню | |
| bool ["<description>"]
| Указывает, что опция конфигурации является булевой; её значение в .config будет либо y (встроено в образ ядра), либо не будет существовать (будет отображаться как закомментированная запись). |
| tristate ["<description>"]
| Указывает, что опция конфигурации является трисостоянием; её значение в .config будет либо y, либо m (собрано как модуль ядра), либо не будет существовать (будет отображаться как закомментированная запись). |
| int ["<description>"]
| Указывает, что опция конфигурации принимает целочисленное значение. |
| range x-y
| Для целого числа, чей допустимый диапазон от x до y. |
| default <value>
| Указывает значение по умолчанию; используйте y, m, n или другое, в зависимости от необходимости. |
| prompt "<description>" [if <expr>]
| Ввод подсказки с описательным предложением (может быть условным); у записи меню может быть максимум одна подсказка. |
| depends on "expr"
| Определяет зависимость для пункта меню; может иметь
несколько с помощью синтаксиса зависимости от FOO1 && FOO2 && (FOO3 || FOO4). |
| select <config> [if "expr"]
| Определяет обратную зависимость. |
| help “my awesome help text … bleh bleh bleh “ | Текст, который отображается при выборе кнопки < Help >. |
Таблица 2.5: Kconfig, несколько конструкций
Для лучшего понимания синтаксиса, приведем несколько примеров из файла lib/Kconfig.debug (файл, который описывает пункты меню для подменю “Kernel Hacking” – это фактически означает отладку ядра – в UI). Не забывайте, что вы также можете просмотреть его онлайн: https://elixir.bootlin.com/linux/v6.1.25/source/lib/Kconfig.debug):
- Пример с простым и понятным параметром (опция CONFIG_DEBUG_INFO):
config DEBUG_INFO
bool
help
Выбран вариант отладочной информации ядра, отличный от "None"
в выборе "Debug information" ниже, что означает, что отладочная
информация будет генерироваться для целей сборки.
- Далее рассмотрим опцию CONFIG_FRAME_WARN. Обратите внимание на синтаксис диапазона и условных значений по умолчанию:
config FRAME_WARN
int "Предупреждать о стековых фреймах больше чем"
range 0 8192
default 0 if KMSAN
default 2048 if GCC_PLUGIN_LATENT_ENTROPY
default 2048 if PARISC
default 1536 if (!64BIT && XTENSA)
default 1280 if KASAN && !64BIT
default 1024 if !64BIT
default 2048 if 64BIT
help
Сообщить компилятору о предупреждении во время сборки для стековых фреймов,
превышающих этот размер.
Установка слишком низкого значения вызовет множество предупреждений.
Установка на 0 отключает предупреждение.
- Следующая опция CONFIG_HAVE_DEBUG_STACKOVERFLOW является простым булевым значением; она либо включена, либо выключена (ядро либо имеет возможность обнаруживать переполнения стека в пространстве ядра, либо нет). Опция CONFIG_DEBUG_STACKOVERFLOW также является булевой. Обратите внимание, как она зависит от двух других опций, разделенных оператором логического И (&&):
config HAVE_DEBUG_STACKOVERFLOW
bool
config DEBUG_STACKOVERFLOW
bool "Проверять переполнения стека"
depends on DEBUG_KERNEL && HAVE_DEBUG_STACKOVERFLOW
help
Установите Y здесь, если вы хотите проверять переполнения стека ядра,
IRQ и исключений (если ваша архитектура их использует). Эта опция
будет показывать подробные сообщения, если свободное пространство стека
упадет ниже определенного предела. [...]
Еще одна полезная вещь: во время конфигурирования ядра (через обычный интерфейс make menuconfig), нажатие на < Help > не только показывает некоторый (обычно полезный) текст помощи, но также отображает текущие значения во время выполнения различных опций конфигурации. То же самое можно увидеть, просто выполнив поиск по опции конфигурации (через клавишу /, как упоминалось ранее). Например, введите / и поищите конфигурацию ядра с именем KASAN; вот что я вижу при этом поиске.
![[figure222.png]] Рисунок 2.22: Частичный снимок экрана, показывающий опцию конфигурации KASAN; можно увидеть, что по умолчанию она выключена
Если вы не знаете, KASAN — это Kernel Address SANitizer — блестящая технология, основанная на компиляторе, помогающая обнаруживать дефекты повреждения памяти; я подробно рассматриваю её в книге “Отладка ядра Linux”.
Внимательно посмотрите на строку “Depends on:”; она показывает зависимости, а также их текущее значение. Важно отметить, что пункт меню вообще не будет отображаться в UI, если зависимости не будут выполнены.
Отлично! Это завершает наше рассмотрение файлов Kconfig, создание или редактирование пользовательского пункта меню в конфигурации ядра, немного синтаксиса языка Kconfig и, собственно, эту главу.
Итог
В этой главе вы сначала узнали о номенклатуре выпусков (или версий) ядра Linux (помните, выпуски ядра Linux основаны на времени, а не на функциях!), о различных типах ядер Linux (-next деревья, -rc/mainline деревья, стабильные, LTS, SLTS, дистрибутивы, пользовательские встраиваемые) и базовом рабочем процессе разработки ядра. Затем вы узнали, как получить для себя дерево исходного кода ядра Linux и как извлечь сжатое дерево исходного кода на диск. По пути вы даже получили быстрый обзор структуры дерева исходного кода ядра, чтобы его структура стала более понятной.
После этого, критически важно, вы узнали, как подходить к шагу конфигурации ядра и выполнять его — ключевой шаг в процессе сборки ядра! Кроме того, вы узнали, как настраивать меню ядра, добавляя свои собственные записи, и немного о системе Kconfig/Kbuild и связанных с ней файлах Kconfig, которые она использует, среди прочего.
Знание того, как получать и настраивать ядро Linux, является полезным навыком. Мы только начали это долгое и захватывающее путешествие. Вы поймете, что с большим опытом и знаниями о внутренних механизмах ядра, драйверах и аппаратном обеспечении целевой системы ваша способность тонко настраивать ядро под цели вашего проекта будет только улучшаться.
Мы на полпути к созданию пользовательского ядра; я предлагаю вам переварить этот материал, попробовать выполнить шаги из этой главы практически, поработать над вопросами/упражнениями и просмотреть раздел “Дополнительное чтение”. Затем, в следующей главе, давайте на самом деле соберем ядро 6.1.25 и проверим его!
Упражнение
Следуя почти точно шагам, которые вы изучили в этой главе, я бы хотел, чтобы вы теперь сделали то же самое для другого ядра, скажем, ядра Linux 6.0.y, где y — это самый высокий номер (на момент написания, это 19)! Конечно, если хотите, можете работать с любым другим ядром:
- Перейдите на сайт https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/ и найдите релизы 6.0.y.
- Загрузите последнее дерево исходного кода ядра Linux v6.0.y.
- Извлеките его на диск.
- Настройте ядро (начните с подхода localmodconfig, затем при необходимости настройте конфигурацию ядра. В качестве дополнительного упражнения вы можете также запустить скрипт streamline_config.pl).
- Покажите “дельту” — различия между оригинальным и новым файлом конфигурации ядра (подсказка: используйте скрипт diffconfig ядра для этого).
Вопросы
По завершении этой главы, вот список вопросов, чтобы проверить ваше понимание материала главы: https://github.com/PacktPublishing/Linux-Kernel-Programming_2E/blob/main/questions/ch2_qs_assignments.txt. Некоторые из этих вопросов с ответами можно найти в репозитории GitHub книги: https://github.com/PacktPublishing/Linux-Kernel-Programming_2E/tree/master/solutions_to_assgn.
Дополнительное чтение
Чтобы помочь вам углубиться в тему, мы предоставляем довольно подробный список онлайн-ресурсов и ссылок (а иногда даже книг) в документе “Дополнительное чтение” в репозитории GitHub этой книги. Он доступен здесь: https://github.com/PacktPublishing/Linux-Kernel-Programming_2E/blob/master/Further_Reading.md.
Узнайте больше на Discord
Чтобы присоединиться к сообществу Discord этой книги – где вы можете делиться отзывами, задавать вопросы автору и узнавать о новых релизах – следуйте QR-коду ниже: https://packt.link/SecNet
![[figure223.png]]