Как удалить лишние прошивки Linux

linux

Разработчики Linux поддерживают не только множество драйверов устройств в исходном коде ядра, но и большую коллекцию прошивок к устройствам в пакете linux-firmware. Но когда размер системы имеет значение — нужно отсечь лишнее. Модули ядра легко отключить в make menuconfig, а вот выбрать нужные прошивки сложнее. Поэтому в этой статье мы напишем скрипт, который позволит вам извлечь из полугигабайтной коллекции прошивок нужные именно вашей машине мегабайты.

С железом нередко бывает так, что одного драйвера в пространстве ядра ОС для его работы недостаточно. Нужна также прошивка (firmware), которая загружается в само устройство. Точный формат и назначения прошивки зачастую известны только производителю: иногда это программа для микроконтроллера или FPGA, а иногда просто набор данных. Пользователю это не важно, главное, что устройство не работает, если ОС не загрузит в него прошивку.

В свободных операционных системах прошивки нередко вызывают споры. Многие из них распространяются под несвободными лицензиями и без исходного кода. Авторы OpenBSD и ряда дистрибутивов GNU/Linux считают это проблемой и со свободой, и с безопасностью и принципиально не включают такие прошивки в установочный образ.

Как удалить лишние прошивки в Linux

Если у вас есть устройство, которое требует прошивки, и вам нужно, чтобы оно работало, вопрос лицензии прошивки становится чисто академическим — от необходимости иметь ее в системе и загружать вы никуда не уйдете.

Полный набор из linux-firmware занимает более 500 Мбайт в распакованном виде. При этом каждой отдельно взятой системе требуется только небольшая часть этих файлов, остальное — мертвый груз.

Даже в современном мире с дисками на несколько терабайт еще много случаев, когда размер имеет значение: встраиваемые системы, образы для загрузки через PXE и подобное. Хорошо, если о board support package позаботился кто-то другой, но это не всегда так.

РЕКОМЕНДУЕМ:
Как создать свой сервис для Linux

Если вы точно знаете полный список нужного железа, можно извлечь файлы вручную. Впрочем, даже в этом случае найти нужные файлы может быть непросто — linux-firmware представляет собой не очень структурированную кучу файлов, и списка соответствия файлов именам модулей ядра там нет. А если вы хотите дать пользователям возможность легко собрать свой образ, тут и вовсе нет выбора — нужно автоматическое решение.

В этой статье я расскажу о своем способе автоматической сборки. Он неидеален, но автоматизирует большую часть работы, что уже неплохо. Писать скрипт будем на Python 3.

Примеры кода в статье упрощенные. Готовый и работающий скрипт ты можешь найти на GitHub.

К примеру, можно им просмотреть список прошивок для включенных в .config драйверов сетевых карт Realtek.

Прошивки в Linux

В ядре Linux нет глобального списка прошивок и кода для их загрузки. Каждый модуль загружает свои прошивки с помощью функций из семейства request_firmware. Логично, если учесть, что процедура загрузки прошивки у каждого устройства разная. Соответственно, информация о нужных прошивках разбросана по множеству отдельных файлов с исходным кодом.

На первый взгляд, некоторую надежду дает опция сборки FIRMWARE_IN_KERNEL. Увы, на деле она встраивает в файл с ядром только файлы, которые вы явно укажите в EXTRA_FIRMWARE. Так что файлы все равно сначала придется найти.

Поиск по вызовам request_firmware() тоже не очень перспективен. Некоторые модули поддерживают несколько разных прошивок, да и имя файла часто хранится в переменной. В качестве примера можно посмотреть на фрагмент кода из драйвера сетевой карты Intel e100.

К счастью для нас, модули должны указывать нужные им файлы прошивок с помощью макроса MODULE_FIRMWARE(). Пример можно найти в e100. Этот макрос определен в файле include/linux/module.h.

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

В ряде случаев можно было бы обойтись одной modinfo. Если у вас есть собранное ядро и возможность его загрузить, вы можете просмотреть вывод modinfo для каждого нужного модуля. Это не всегда удобно или вообще возможно, так что мы продолжим искать решение, для которого понадобится только исходный код ядра.

Здесь и далее будем считать, что все ненужные модули отключены в конфиге сборки ядра (Kconfig). Если мы собираем образ для конкретной системы или ограниченного набора систем, это вполне логичное предположение.

Поиск исходников модулей

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

Поиск всех включенных в конфиг модулей

Это самая простая часть. Конфиг сборки ядра имеет простой формат «ключ — значение» вроде CONFIG_IWLWIFI=m. Значение может быть n (не собирать), y (встроить в ядро) или m (собрать в виде модуля).

Нас интересуют только ключи, а какое значение там, y или m, нам не важно. Поэтому мы можем выгрести нужные строки регулярным выражением (.*)=(?:y|m). В модуле re из Python синтаксис (?:...) используется для незахватывающих групп (non-capturing group), так что захвачена будет только часть в скобках из (.*)=.

Поиск нужных каталогов и файлов исходников

В каждом Makefile можно найти выражения вида obj-$(CONFIG_SOMETHING) += .... Переменные CONFIG_SOMETHING разрешаются в их значения из конфига и, соответственно, добавляются в списки целей obj-y (встроить в ядро) и obj-m (оставить модулем).

В файлах верхнего уровня (вроде drivers/net/wireless/intel/Makefile) в качестве значения фигурирует подкаталог с модулем, вроде obj-$(CONFIG_IWLWIFI) += iwlwifi/. Внутри каталога с модулем ( drivers/net/wireless/intel/iwlwifi/Makefile) упоминается конечный объектный файл, наподобие obj-$(CONFIG_IWLWIFI) += iwlwifi.o. Мы ограничимся случаем с целью-каталогом.

Цели-каталоги мы будем искать по выражению obj-\$\((.*)\)\s+\+=\s+(.*)/(?:\n|$) в Makefile.

А что про цели-файлы? Если мы говорим о модулях из нескольких файлов в каталоге, то их файлы *.c мы обработаем при обходе самого каталога. Существуют модули из одного файла, в основном для старых устройств вроде drivers/net/ethernet/intel/e100.c. Таких модулей весьма немного, так что мы игнорируем их существование. При желании можно добавить обработку подобных случаев, но я не уверен, практично ли это. Лучше мы чуть позже сделаем так, чтобы их прошивки были включены по умолчанию, а не выключены.

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

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

Осталось собрать воедино функцию для построения списка файлов из указанного каталога, в которых есть ссылки на прошивки.

Очень экономит время библиотека glob из стандартной поставки Python 3. С версии 3.5 у нее есть поддержка рекурсии. Некоторые модули содержат вложенный каталог с исходниками, а порой и не один. Если в таком каталоге есть свой Makefile, то это независимый модуль, что мы обрабатываем в первой ветке условия. Однако, если Makefile отсутствует, это «просто» подкаталог, и нам нужны все файлы *.c из него и всех подкаталогов. Модуль glob умеет это не хуже, чем шелл, и выражение **/*.c отлично работает (если не забыть recursive=True).

Извлечение данных о прошивках

Остается последнее — извлечь собственно имена файлов прошивок. Будем прогонять файлы с исходным кодом через препроцессор из GCC, чтобы вместо имен макросов получить готовые строки имен файлов из linux-firmware.

Вообще, остановить GCC после стадии препроцессора можно опцией -E. Увы, для этого нужен правильный список каталогов с заголовочными файлами. Некоторые модули, вроде упомянутого iwlwifi, содержат множество вложенных каталогов и используют, кроме стандартных заголовков ядра, еще и свои общие файлы, что сильно усложняет дело.

Однако в Makefile ядра есть цели для файлов *.i — это как раз файлы *.c после обработки препроцессором.

К примеру, make drivers/net/wireless/intel/iwlwifi/cfg/9000.i превратит файл 9000.c в голый C без директив и макросов. Макрос MODULE_FIRMWARE генерирует для полей структуры данных о модуле уникальные имена, которые можно узнать по подстроке __UNIQUE_ID_firmware. Эти строки мы и будем извлекать.

Выражение вида const char* hello = "hello" " " "world" — немного непривычный, но вполне законный способ записи строковых констант, эквивалентный const char* hello = "hello world". Нам придется это учесть.

Из всех способов выполнить внешнюю программу мы воспользовались самым быстрым и грязным — subprocess.run([command], shell=True, capture_output=True). В таких скриптах, где Python, по сути, отбирает работу у sh, я каждый раз радуюсь, что этот способ там есть.

РЕКОМЕНДУЕМ:
Как защититься от руткитов в Linux

Заключение

Может быть, извлечение данных о прошивках из кода ядра и достаточно экзотичная задача. Но даже если сам скрипт вам не пригодится, вполне вероятно, вы узнали что-то новое об организации исходного кода ядра Linux и сможете применить мой опыт в своих скриптах автоматизации.

Понравилась статья? Поделиться с друзьями:
Комментарии: 1
  1. Виктор

    Спасибо за статью: интересно и с подробностями!

Добавить комментарий