Top.Mail.Ru
Linux kernel. Programming. Глава 2. Сборка ядра Linux 6.x из исходного кода – Часть 1


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-система, будь то суперкомпьютер или крошечное встраиваемое устройство, имеет три обязательных компонента:

Кроме того, у нее есть два необязательных компонента:

В этих двух главах мы будем заниматься только сборкой ядра ОС (Linux) из исходного кода. Мы не углубляемся в детали корневой файловой системы. В следующей главе мы узнаем, как минимально настроить загрузчик GNU GRUB, специфичный для x86.

Полный процесс сборки ядра — по крайней мере, для x86[_64] — требует в общей сложности шесть или семь шагов. Кроме необходимых подготовительных шагов, здесь мы рассмотрим первые три, а остальные — в следующей главе.

В этой главе мы рассмотрим следующие темы:

Вы можете задаться вопросом: что насчет сборки ядра 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:

Обратите внимание, что ядра дистрибутивов могут не строго следовать этим конвенциям; это зависит от них. Регулярные или “ванильные” ядра, которые выпускаются на сайте https://www.kernel.org/, действительно следуют этим конвенциям (по крайней мере, пока Линус Торвальдс не решит их изменить).

Исторически, в ядрах до версии 2.6 (то есть, теперь это древние вещи), минорный номер имел особое значение; если он был четным, это указывало на стабильный релиз ядра, а если нечетным — на нестабильный или бета-релиз. Сейчас это уже не так.

В рамках интересного упражнения по настройке ядра мы позже изменим компонент localversion (также известный как -EXTRAVERSION) ядра, которое мы соберем.

Релизы “пальцы и пальцы ног”

Далее, важно понять простой факт: с современными ядрами Linux, когда изменяется основной и/или минорный номер, это не означает, что произошло что-то грандиозное или ключевое в плане нового дизайна, архитектуры или функционала; нет, это просто, по словам Линуса, органическая эволюция.

Текущая используемая номенклатура версии ядра является слабо временной, а не основанной на функциях. Таким образом, новый основной номер появляется время от времени. Как часто именно? Линус любит называть это моделью “пальцы и пальцы ног”; когда у него заканчиваются пальцы рук и ног для подсчета минорного номера (компонента x от w.x.y), он обновляет основной номер с w на w+1. Следовательно, после итерации над 20 минорными номерами – от 0 до 19 – мы получаем новый основной номер.

На практике это происходит с ядра 3.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.

  1. Выпускается стабильная версия 6.x (для наших целей считаем x равным 0). Таким образом, открывается 2-недельное окно слияния для основного ядра 6.x+1.

  2. Окно слияния остается открытым примерно две недели, и новые патчи сливаются в основное ядро различными менеджерами подсистем, которые долгое время тщательно принимали патчи от участников и обновляли свои деревья.

  3. Когда проходит около двух недель, окно слияния закрывается.

  4. Теперь начинается период “исправления ошибок”; начинаются rc (или основные, предварительные патчи) ядра. Они развиваются следующим образом: выпускаются 6.x+1-rc1, 6.x+1-rc2, …, 6.x+1-rcn. Этот процесс может занимать от 6 до 8 недель.

  5. Затем следует период “окончательной доработки”, обычно около недели. Приходит стабильный релиз, и выпускается новое стабильное ядро 6.x+1.

  6. Релиз передается “стабильной команде”:

    • Значительные исправления ошибок или проблем безопасности приводят к выпуску 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 года.

Обратите внимание, что в репозитории существует несколько типов релизных ядер. Здесь мы приводим неполный список, упорядоченный от наименее до наиболее стабильного (следовательно, их срок службы от самого короткого до самого длинного):

Обратите внимание, что это требует огромного объема работы со стороны команды разработчиков ядра, и все еще очень сложно поддерживать последнее стабильное ядро; поэтому действительно лучше работать с ванильными ядрами.

В этой книге мы будем работать с одним из последних ядер 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 отныне будут поддерживаться всего два года.

Вот несколько ресурсов, на случай если вы захотите изучить это самостоятельно:

Это может стать сюрпризом для многих. Почему только два года? Вкратце, были даны две причины:

На данный момент кажется, что ядро, с которым мы работаем здесь – 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 из исходного кода. Поскольку объяснение каждого из них довольно подробное, вы можете вернуться к этому обзору, чтобы увидеть общую картину. Шаги следующие:

  1. Получение дерева исходного кода ядра Linux через один из следующих вариантов:
    • Загрузка определенного дерева исходного кода ядра в виде сжатого файла
    • Клонирование дерева Git (ядра)
  2. Распаковка дерева исходного кода ядра в какое-либо место в вашем домашнем каталоге (этот шаг можно пропустить, если вы получили ядро путем клонирования дерева Git).
  3. Конфигурация: Получение начальной точки для конфигурации вашего ядра (подход варьируется). Затем отредактируйте ее, выбирая опции поддержки ядра, необходимые для нового ядра. Рекомендуемый способ сделать это - с помощью make menuconfig.
  4. Сборка образа ядра, загружаемых модулей и любых необходимых файлов Device Tree Blobs (DTB) с помощью make [-j'n'] all. Это создает сжатый образ ядра (arch/<arch>/boot/[b|z|u]{Ii}mage), несжатый образ ядра – vmlinux, файл System.map, объекты модулей ядра и любые настроенные файлы DTB.
  5. Установка только что собранных модулей ядра (на x86) с помощью sudo make [INSTALL_MOD_PATH=<prefix-dir>] modules_install . Этот шаг по умолчанию устанавливает модули ядра в /lib/modules/$(uname -r)/ (переменную окружения INSTALL_MOD_PATH можно использовать для изменения этого).
  6. Загрузчик (x86): Настройка загрузчика GRUB и образа initramfs, ранее называвшегося initrd: sudo make [INSTALL_PATH=</new/boot/dir>] install
    • Это создает и устанавливает образ initramfs или initrd под /boot (переменную окружения INSTALL_PATH можно использовать для изменения этого).
    • Обновляет конфигурационный файл загрузчика для загрузки нового ядра (первый пункт).
  7. Настройка меню загрузчика GRUB (опционально).

Эта глава, будучи первой из двух по теме сборки ядра, охватывает шаги с 1 по 3, включая много необходимого фонового материала. Следующая глава охватит оставшиеся шаги, с 4 по 7. Итак, начнем с шага 1.

Шаг 1 – Получение дерева исходного кода ядра Linux

В этом разделе мы рассмотрим два основных способа, которыми вы можете получить дерево исходного кода ядра Linux:

Как вы решаете, какой подход использовать? Для большинства разработчиков, работающих над проектом или продуктом, решение уже принято – проект использует очень конкретную версию ядра 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.

Существует множество способов загрузить сжатый файл с исходным кодом ядра с этого сервера и/или его зеркал. Рассмотрим два из них:

![[figure26.png]] Рисунок 2.6: Частичный скриншот с kernel.org, выделяющий ядро (6.1.25 LTS), которое мы загрузим и будем использовать

Файлы .tar.gz и .tar.xz имеют одинаковое содержание; отличается только тип сжатия. Обычно файлы .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

Ниже приведены некоторые важные пояснения из таблицы:

  1. README: В этом файле также упоминается документ, на который нужно ссылаться для получения информации о минимально приемлемых версиях программного обеспечения для сборки и запуска ядра: Documentation/process/changes.rst. Интересно, что ядро предоставляет скрипт на Awk (scripts/ver_linux), который выводит версии текущего программного обеспечения на системе, на которой он выполняется, помогая вам проверить, приемлемы ли установленные у вас версии.

  2. Лицензирование ядра: Не вдаваясь в юридические подробности (и без того понятно, что я не юрист), вот прагматическая суть дела. Поскольку ядро выпускается под лицензией GNU GPL-2.0 (GNU GPL - это Общая публичная лицензия GNU), любой проект, который напрямую использует кодовую базу ядра, автоматически попадает под эту лицензию. Это свойство “производного произведения” GPL-2.0. С юридической точки зрения, такие проекты или продукты теперь должны выпускать свое программное обеспечение ядра под теми же условиями лицензии. На практике ситуация намного сложнее; многие коммерческие продукты, работающие на ядре Linux, содержат в себе проприетарный код пользовательского и/или пространства ядра. Обычно это делается путем рефакторинга кода ядра (чаще всего, драйверов устройств) в формат загружаемого модуля ядра (LKM). Возможно выпускать модуль ядра (LKM) под моделью двойной лицензии. LKM является темой главы 4, “Написание вашего первого модуля ядра – Часть 1”, и главы 5, “Написание вашего первого модуля ядра – Часть 2”, и там мы рассматриваем некоторую информацию о лицензировании модулей ядра.

    Некоторые люди, предпочитающие проприетарные лицензии, умудряются выпускать свой код ядра в рамках модуля ядра, который не лицензируется по условиям GPL-2.0; технически это, возможно, возможно, но это, по меньшей мере, считается крайне антисоциальным и может даже перейти грань незаконности. Заинтересованные могут найти больше ссылок на лицензирование в документе “Дополнительное чтение” этой главы.

  3. 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)
  1. Порты архитектур 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.

  1. io_uring: Не преувеличивая, можно сказать, что io_uring и eBPF считаются двумя из новых “волшебных” функций, которые предоставляет современная система Linux (папка io_uring здесь - это поддержка ядра для этой функции)! Причина, по которой энтузиасты баз данных и сетевых технологий так радуются io_uring, проста: производительность. Этот фреймворк значительно улучшает показатели производительности в реальных ситуациях с высоким вводом-выводом, как для дисковых, так и для сетевых нагрузок. Его архитектура с общим (между пользовательским и пространством ядра) кольцевым буфером, схема нулевого копирования и способность использовать гораздо меньше системных вызовов по сравнению с типичными старыми фреймворками AIO, включая режим опроса, делают его завидной особенностью. Так что, если ваши приложения в пользовательском пространстве хотят попасть на этот действительно быстрый путь ввода-вывода, ознакомьтесь с io_uring. В разделе “Дополнительное чтение” этой главы есть полезные ссылки.
make [ARCH=<cpu>] tags ; make [ARCH=<cpu>] cscope
  1. 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/Kbuild (упрощенным образом), можно увидеть на Рисунке 2.8. Некоторые детали еще не рассмотрены; тем не менее, вы можете держать это в уме, читая следующие материалы.

Чтобы помочь вам лучше понять, давайте рассмотрим несколько ключевых компонентов, входящих в систему Kconfig/Kbuild:

Цели этих компонентов можно кратко суммировать следующим образом:

| Компонент 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 – минимальный обзор

Теперь, когда мы знаем некоторые детали, вот упрощенное объяснение того, как все это связано и работает:

   obj-$(CONFIG_FOO) += FOO.o
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. Как это сделать? Мы делаем это итеративно. Начинаем с “по умолчанию” конфигурации – тема следующего раздела – и постепенно дорабатываем до пользовательской конфигурации.

Получение конфигурации по умолчанию

Так как же выбрать начальную конфигурацию ядра? Существует несколько методов; вот несколько распространенных:

Первый подход имеет преимущество в простоте. Ядро само обработает детали, предоставив вам конфигурацию по умолчанию. Минус в том, что конфигурация по умолчанию может быть очень большой (это так, когда вы собираете Linux для десктопа или серверной системы на базе x86_64); по умолчанию включается огромное количество опций, на всякий случай, что может значительно увеличить время сборки и размер образа ядра. Обычно, конечно, затем от вас ожидается ручная настройка ядра до желаемых параметров.

Это поднимает вопрос, где хранится конфигурация ядра по умолчанию? Система Kconfig использует схему приоритетов для получения конфигурации по умолчанию, если она не указана. Список приоритетов и их порядок (первый - наивысший приоритет) следующий:

Из списка видно, что система Kconfig сначала проверяет наличие файла .config в корне дерева исходного кода ядра; если он найден, она берет все значения конфигурации оттуда. Если его нет, она затем смотрит на путь /lib/modules/$(uname -r)/.config. Если он найден, значения из этого файла будут использоваться как значения по умолчанию. Если и его нет, проверяется следующий файл в порядке приоритетов и так далее… Это показано на Рисунке 2.8.

Для более детального изучения инфраструктуры Kconfig и Kbuild ядра, мы рекомендуем обратиться к следующим отличным документам:

Рисунок (вдохновленный статьями Цао Цзина), который пытается передать информацию о системе Kconfig/Kbuild ядра, показан здесь. Диаграмма содержит больше информации, чем было рассмотрено до сих пор; не беспокойтесь, мы к этому вернемся.

![[figure28.png]] Рисунок 2.8: Упрощенное представление системы Kconfig/Kbuild ядра

Итак, давайте теперь разберемся, как именно получить рабочую конфигурацию ядра!

Получение хорошего начального пункта для конфигурации ядра

Это приводит нас к очень важному моменту: хотя эксперименты с конфигурацией ядра могут быть полезны в качестве учебного упражнения, для производственной системы критически важно, чтобы ваша пользовательская конфигурация основывалась на проверенной, известной, протестированной и работающей конфигурации ядра.

Здесь, чтобы помочь вам понять нюансы выбора валидной отправной точки для конфигурации ядра, мы рассмотрим три подхода к получению начальной точки для типичной конфигурации ядра:

Давайте рассмотрим каждый из этих подходов немного подробнее. В плане конфигурации ядра, которое вы загрузили и извлекли на предыдущих двух шагах, пока ничего не делайте; прочитайте следующие разделы, и затем в разделе “Начало работы с подходом 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:

Дизайн: Немного в стороне от темы, но я считаю это важным: когда работаешь над проектами (особенно встраиваемыми), команды часто проявляют нежелательную тенденцию напрямую использовать 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 начнет задавать вам вопросы: она отобразит каждую новую опцию конфигурации и доступные значения, которые вы можете для нее установить, с значением по умолчанию в квадратных скобках, в окне консоли. От вас ожидается выбор значений для новых опций конфигурации, с которыми она сталкивается. Вы увидите это как серию вопросов и приглашение отвечать на них в командной строке.

Ядро предоставляет два интересных механизма для просмотра всех новых конфигураций ядра:

Запуск первого просто перечисляет каждую новую переменную конфигурации ядра:

$ 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 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; вы часто увидите символы, предшествующие меню (например, [], <>, --, (), и так далее); значение этих символов следующее:

Эмпирический подход ключевой. Давайте проведем несколько экспериментов с интерфейсом make menuconfig, чтобы увидеть, как он работает. Именно это мы и узнаем в следующем разделе.

Пример использования интерфейса make menuconfig

Чтобы получить представление о том, как использовать систему меню Kbuild через удобную команду menuconfig, давайте включим довольно интересную опцию конфигурации ядра. Она называется “Kernel .config support” и позволяет просматривать содержимое конфигурации ядра во время его работы! Полезно, особенно во время разработки и тестирования. По соображениям безопасности, она обычно отключена в продакшене.

Остаются несколько назойливых вопросов:

В качестве учебного эксперимента мы установим её значение на [*] (или y), встроив её непосредственно в ядро. Таким образом, она будет всегда включена. Давайте это сделаем!

  1. Запустите интерфейс конфигурации ядра:
    make menuconfig
    

    Вы должны увидеть интерфейс терминала, как на Рисунке 2.11. Первый элемент обычно является подменю с названием “General Setup —>”; нажмите Enter, находясь на нем; это приведет вас в подменю “General Setup”, в котором отображается множество элементов; перемещайтесь (нажимая стрелку вниз) к элементу с названием “Kernel .config support”:

![[figure212.png]] Рисунок 2.12: Снимок экрана с элементами меню General Setup, с выделенным нужным элементом (на x86-64)

  1. Мы видим на предыдущем снимке экрана, что мы настраиваем ядро версии 6.1.25 на x86, выделенный пункт меню - это “Kernel .config support”, и, исходя из префикса <M>, это пункт меню с тремя состояниями, который по умолчанию установлен на выбор <M> для “модуля”.

  2. Оставив выбранным пункт “Kernel .config support”, используйте клавишу со стрелкой вправо, чтобы перейти к кнопке < Help > на нижней панели инструментов, и нажмите Enter, находясь на кнопке < Help >. Или просто нажмите ? на опции! Экран должен выглядеть примерно так:

![[figure213.png]] Рисунок 2.13: Конфигурация ядра через make menuconfig; пример экрана помощи (с выделенным именем макроса конфигурации ядра)

Экран помощи довольно информативен. Действительно, многие экраны помощи конфигурации ядра очень хорошо заполнены и полезны. К сожалению, некоторые из них таковыми не являются.

  1. Хорошо, следующим шагом нажмите Enter, находясь на кнопке < Exit >, чтобы вернуться на предыдущий экран.
  2. Измените значение, нажав пробел; это переключает значение текущего пункта меню между <> (всегда включено), < > (выключено) и <M> (модуль). Оставьте его на <>, что означает “всегда включено”.
  3. Далее, хотя теперь это включено, возможность фактически просматривать конфигурацию ядра предоставляется через псевдофайл в procfs; следующий пункт ниже этого - это соответствующий:

    [ ] Включить доступ к .config через /proc/config.gz

  4. Вы можете видеть, что по умолчанию это выключено ([ ]); включите его, перейдя к нему и нажав пробел. Теперь оно отображается как [*]:

![[figure214.png]] Рисунок 2.14: Усеченный снимок экрана, показывающий, как мы включили возможность просмотра конфигурации ядра

  1. Отлично, на этом пока все; нажмите клавишу со стрелкой вправо или Tab, перейдите к кнопке < Exit > и нажмите Enter, находясь на ней; вы вернетесь на экран главного меню. Повторите это, снова нажав < Exit >; интерфейс спросит вас, хотите ли вы сохранить эту конфигурацию. Выберите < Yes > (нажав Enter на кнопке Yes):

![[figure215.png]] Рисунок 2.15: Приглашение сохранить измененную конфигурацию ядра

  1. Новая конфигурация ядра теперь сохранена в файле .config. Давайте быстро это проверим. Надеюсь, вы заметили, что точное название конфигураций ядра, которые мы изменили – это макрос, как видит его исходный код ядра – это:

    • CONFIG_IKCONFIG для опции “Kernel .config support”.
    • CONFIG_IKCONFIG_PROC для опции “Enable access to .config through /proc/config.gz”.

Как мы это узнали? Это указано в левом верхнем углу экрана помощи! Смотрите еще раз на Рисунок 2.13.

Готово. Конечно, реальный эффект не будет виден, пока мы не соберем и не загрузим это ядро. Теперь, что именно достигается включением этой функции? Когда она включена, настройки конфигурации текущего работающего ядра можно просмотреть в любое время двумя способами:

Для дальнейшего изучения, почему бы не изменить еще несколько параметров в конфигурации ядра по умолчанию (нашего ядра Linux 6.1.25 для архитектуры x86-64)? Пока не беспокойтесь о точном значении каждой из этих опций конфигурации ядра; это просто для того, чтобы получить практику с системой Kconfig. Итак, запустите make menuconfig и в нем внесите изменения, следуя формату, показанному ниже.

Формат:

Хорошо, вот что попробовать; начнем с:

Локальная версия:

Далее:

Просматривайте экраны помощи для каждой конфигурации ядра, с которой вы работаете. Отлично; когда закончите, сохраните изменения и выйдите из интерфейса.

Проверка конфигурации ядра в файле конфигурации

Но где сохраняется новая конфигурация ядра? Это важно повторить: конфигурация ядра записывается в простой текстовый файл 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 для предыдущего запроса довольно интересно. Оно раскрывает несколько деталей о параметрах конфигурации:

![[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:

Вы можете клонировать проект 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, и затем попробуйте снова настроить конфигурацию.

Различные советы – конфигурация ядра

Несколько оставшихся советов по конфигурации ядра:

   scripts/get_feat.pl --arch arm64 ls

Настройка меню ядра, 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”.

Давайте приступим (предполагается, что вы находитесь в корне дерева исходного кода ядра):

  1. Опционально: Для безопасности всегда делайте резервную копию редактируемого файла Kconfig:
   cp init/Kconfig init/Kconfig.orig
  1. Теперь откройте и отредактируйте файл 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”, который следует.

  1. Сохраните файл и выйдите из редактора.

  2. (Пере)настроим ядро: запустите make menuconfig. Затем перейдите к нашему новому пункту меню в разделе “General Setup Тестовый случай для книги LKP 2e/Глава 2: создание …”. Включите эту функцию. Обратите внимание, как на Рисунке 2.21 она выделена и по умолчанию отключена, как мы и указали через строку default n.
   make menuconfig
   [...]

Вот соответствующий вывод:

![[figure221.png]] Рисунок 2.21: Конфигурация ядра через make menuconfig, показывающая наш новый пункт меню (до его включения)

  1. Теперь включите его, переключив его с помощью пробела, затем сохраните и выйдите из системы меню.

Находясь там, попробуйте нажать кнопку < Help >. Вы увидите текст «Помощь», который мы в файле init/Kconfig.

  1. Проверим, была ли наша функция выбрана:
    $ 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
  1. Сборка ядра. Не беспокойтесь; полные детали по сборке ядра приведены в следующей главе. Вы можете пропустить это на данный момент или всегда можете перейти к Главе 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
  1. После завершения, проверьте заголовочный файл 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):

  1. Пример с простым и понятным параметром (опция CONFIG_DEBUG_INFO):
config DEBUG_INFO
   bool
   help
     Выбран вариант отладочной информации ядра, отличный от "None" 
     в выборе "Debug information" ниже, что означает, что отладочная 
     информация будет генерироваться для целей сборки.
  1. Далее рассмотрим опцию 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 отключает предупреждение.
  1. Следующая опция 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)! Конечно, если хотите, можете работать с любым другим ядром:

  1. Перейдите на сайт https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/ и найдите релизы 6.0.y.
  2. Загрузите последнее дерево исходного кода ядра Linux v6.0.y.
  3. Извлеките его на диск.
  4. Настройте ядро (начните с подхода localmodconfig, затем при необходимости настройте конфигурацию ядра. В качестве дополнительного упражнения вы можете также запустить скрипт streamline_config.pl).
  5. Покажите “дельту” — различия между оригинальным и новым файлом конфигурации ядра (подсказка: используйте скрипт 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]]