Raspbian: сборка образа. Часть 2

Автор: Евгений Голышев

Статья была опубликована в 236-м выпуске журнала Linux Format. По истечении полугода с момента публикации права на статью переходят ее автору, поэтому материал публикуется в нашем блоге.

Тема сборки минимально функциональной версии Raspbian продолжается. Эту часть я хочу начать с обсуждения несправедливо забытого термина JeOS (читается как juice). Этот термин, который, кстати, расшифровывается как «just enough operating system», был очень популярен лет десять назад, когда производители коммерческих дистрибутивов GNU/Linux были увлечены разработкой инструментов для индивидуальной настройки [customization] операционных систем. JeOS – это подход, согласно которому для решения какой-то конкретной задачи достаточно взять минимально возможную версию операционной системы и установить поверх нее зависимости, необходимые для запуска одного конкретного приложения, решающего эту задачу. Таким образом, решение получается самодостаточным и максимально эффективным с точки зрения производительности. В зависимости от инструмента, помогающего кастомизировать операционные системы, конечный результат был пригоден для запуска как на реальной, так и в виртуальной машине. Самым ярким из этих инструментов был SUSE Studio, который неоднократно освещался на страницах Linux Format (к примеру, в LXF125 и LXF138). Он одним из первых начал широко использовать термин «виртуальное устройство [virtual appliance]», который означает операционную систему узкого назначения вкупе с целевым приложением, пригодную для запуска в виртуальной машине. Но времена меняются, и всё чаще для решении какой-то конкретной задачи выделяется целый Raspberry Pi или любой другой одноплатный компьютер. Терминология здесь еще недостаточно проработана, и необходимо это исправить. Назовем, к примеру, подготовленный в первой части этого руководства образ минимально функциональной версии Raspbian термином JeOSI (предлагаю произносить его как juicy). По аналогии с JeOS новый термин расшифровывается как «just enough operating system image». С переводом будет немного сложнее. Дело в том, что словосочетание «just enough» в данном случае является наречием, а не существительным, что делает эти термины больше похожими на слоганы, чем на что-то другое. После нескольких часов размышлений я не придумал ничего лучше, чем «достаточно просто операционной системы» [можно также перевести как «ОС в обрез», – прим. ред.] для JeOS и «достаточно просто образа операционной системы» для JeOSI (как рекламный слоган популярного в 1990-х растворимого напитка Invite – «просто добавь воды»). На самом деле куда важнее, что JeOSI может сбить с толку тем, что перекликается с сетевой моделью OSI или некоммерче­ской организацией Open Source Initiative, название которой также часто сокращается до OSI; но более удачного термина я пока предложить не могу.

Итак, пора определиться с целями второй части учебника.

Показанный в первой части подход позволяет собирать минимально функциональные образы для Raspberry Pi на основе Debian-подобных дистрибутивов. Прежде чем написать статью, я успел опробовать этот подход на Raspbian, Devuan и Ubuntu. Однако именно Raspbian использовался в качестве примера для демонстрации сборки образа на протяжении всей первой части руководства, т.к. он является «родным» для RPi. Таким образом, выбор в пользу этого дистрибутива был исключительно символическим, и нет никаких препятствий в использовании любого другого основанного на Debian дистрибутива. Экспериментируйте.

Особенность второй части учебника заключается в том, что она в основном состоит из набора не зависящих друг от друга рецептов, которые (каждый по-своему) предлагают сделать полученный в предыдущей части образ более полезным с практической точки зрения. Поэтому эту часть учебника можно читать как от начала до конца, так и выборочно, переходя только к тем рецептам, которые представляют наибольший интерес, без риска для понимания идущего за ними (или перед ними) материала. Вторая часть учебника предполагает наличие образа, который у вас должен был появиться после работы с первой частью. Создайте где-нибудь две директории – boot и rootfs – и смонтируйте в них загрузочный и корневой раздел соответственно.

$ sudo mkdir /mnt/boot /mnt/rootfs
$ LOOP_DEV=$(sudo losetup --partscan --show --find raspbian-stretch.img)
$ sudo mount ${LOOP_DEV}p1 /mnt/boot
$ sudo mount ${LOOP_DEV}p2 /mnt/rootfs

А теперь – вперед.

Поддержка сети

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

SUSE Studio был пионером и отличным примером решения в области кастомизации операционных систем.
SUSE Studio был пионером и отличным примером решения в области кастомизации операционных систем.

Raspbian использует системный менеджер systemd, который, помимо всего прочего, пытается решить проблему с именованием сетевых интерфейсов. Таким образом, в рамках systemd 197, который вышел 7 января 2013 г., в udev была добавлена поддержка т. н. «предсказуемых имен сетевых интерфейсов [Predictable Network Interface Names]». И первым делом я предлагаю отключить эту поддержку. Во-первых, по иронии судьбы предсказуемые имена не всегда предсказуемы. Во-вторых, в случае RPi и его единственного проводного сетевого интерфейса нет никакой проблемы с именованием. В-третьих, я хочу, чтобы этот учебник был применим ко всем производным от Debian дистрибутивам, которые могут по тем или иным причинам не использовать systemd.

Чтобы отключить поддержку предсказуемых имен сетевых интерфейсов, необходимо добавить два следующие параметра к командной строке ядра, которая содержится в cmdline.txt на загрузочном разделе.

net.ifnames=0 biosdevname=0

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

$ sudo sh -c "echo 'net.ifnames=0 biosdevname=0 $(cat /mnt/cmdline.txt)' > /mnt/boot/cmdline.txt"

Если на данном этапе вы запишете образ на SD-карту, загрузите с нее устройство и выполните

$ ip link show

то увидите в списке всем хорошо знакомый eth0.

Следующим шагом необходимо разобраться с «именем хоста [hostname]». Дело в том, что debootstrap (см. первую часть учебника) назвал chroot-окружение именем системы, в которой оно собиралось, поэтому две ваши машины – RPi и та, на которой происходила сборка окружения – сейчас имеют одинаковые имена. Это необходимо исправить. Для этого придумайте новое имя для RPi и добавьте его в /etc/hostname и /etc/hosts, к примеру, следующим образом:

$ sudo sh -c "echo raspbian-jeosi > /mnt/rootfs/etc/hostname"
$ sudo sed -i '2i 127.0.0.1\traspbian-jeosi' /mnt/rootfs/etc/hosts

В данном случае в качес тве имени использовалось raspbian-jeosi. Назовите машину с учетом ваших личных вкусов и предпочтений, но помните, что согласно RFC 952 имена хостов не должны превышать 24-х символов; в качестве символов, из которых разрешается составлять имена – буквы латинского алфавита в верхнем и нижнем регистре, цифры, знак минуса (-) и точка (.).

В заключение необходимо установить пакеты, которые содержат DHCP-клиент, ряд вспомогательных утилит типа ping, всё необходимое для взаимодействия устройства с другими устройствами в сети (на базе стека протокола TCP/IP, разумеется) и SSH-сервер, ради которого всё это затевалось

$ sudo chroot /mnt/rootfs apt-get update
$ sudo chroot /mnt/rootfs apt-get install netbase net-tools iscdhcp-client inetutils-ping openssh-server

а также отредактировать конфигурационный файл /etc/network/interfaces, добавив в него следующее содержимое:

auto lo
iface lo inet loopback
auto eth0
allow-hotplug eth0
iface eth0 inet dhcp

И хотя после всех этих манипуляций образ наконец имеет всё необходимое, чтобы работать в Сети, остается одна небольшая про­блема с подключением к SSH-серверу. Дело в том, что OpenSSH, который достался дистрибутиву от Debian, сконфигурирован таким образом, чтобы не разрешать вход от имени root с парольной аутентификацией. Решить эту проблему можно одним из двух способов: либо измените в /etc/ssh/sshd_config текущее значение PermitRootLogin на yes, включив тем самым возможность входа как root по паролю (что настоятельно не рекомендуется), либо перейдите к следующему разделу и заведите еще одного пользователя в системе, на которого не будет накладываться это ограничение.

Пользователи

На данный момент в системе есть только один пользователь – root. Этого было достаточно, чтобы, не отвлекаясь на лишние детали, убедиться, что новоиспеченная система находится в рабочем состоянии. Теперь настало время обратить внимание на опущенные ранее детали.

В настоящее время разработчики дистрибутивов делятся на два лагеря, когда речь заходит о пользователе root (напомню, что по отношению к нему часто используется термин «суперполь­зователь»). Более консервативные следуют практике наличия по крайней мере двух учетных записей в системе: root’а для административных задач и непривилегированного пользователя для всех остальных. Этот канонический подход дошел до нас в почти неизменном виде с самых первых версий UNIX; но он не лишен недостатков. На мой взгляд, наиболее ярким из них является необходимость постоянного контроля за тем, чтобы сессия root’а была вовремя закрыта. Таким образом, всегда есть риск забыться и продолжить решение своих повседневных задач от лица суперпользователя, что может привести к серьезным негативным последствиям – ведь безграничные возможности, скрытые в учетной записи root’а, обладают поистине разрушительной силой, которую не каждый способен держать под контролем.

Debian является примером одного из консервативных с этой точки зрения дистрибутивов. Ubuntu, в свою очередь, это один из первых дистрибутивов, который взял на вооружение подход, при котором все административные задачи решаются посредством sudo. Суть этого подхода заключается в том, чтобы

  • не устанавливать пароль для суперпользователя, оставляя его учетную запись заблокированной, или заблокировать учетную запись явно, указав вместо пароля (или перед паролем) восклицательный знак (!);
  • установить программу sudo;
  • добавить первого пользователя (т. е. того, который был создан во время установки системы) в группу sudo, дав ему тем самым неограниченные права при выполнении любых операций в системе.
Получив дополнительные права через sudo, не забывайте, что с великой силой приходит великая ответственность.
Получив дополнительные права через sudo, не забывайте, что с великой силой приходит великая ответственность.

С одной стороны, такая практика противоречит философии программы – дать каждому пользователю как можно меньше привилегий, но так, чтобы их было достаточно для решения поставленных перед этими пользователями задач; а с другой – помогает решить проблему с закрытием сессии root.

В официальном образе Raspbian используется именно этот подход. Какой подход будет использоваться в вашем образе – решать вам, но для начала необходимо завести в системе еще одного пользователя, от имени которого будет осуществляться вход, в том числе и через SSH.

Итак, чтобы создать пользователя с именем pi, выполните

$ sudo chroot /mnt/rootfs useradd -m -s /bin/bash pi

Заметьте, что useradd и другие смежные с ней программы, в том виде, в котором они используются в этом разделе, специфичны для всех систем, в которых используется shadow для управления пользователями и группами. В отличие от coreutils, интерфейс программ (и даже название самих программ) для управления пользователями и группами сильно варьируется от реализации к реализации, поэтому всё описанное здесь может отличаться от того, что принято в других Unix-подобных операционных системах и даже других дистрибутивах GNU/Linux.

Опция -m позволяет указать, что в /home необходимо создать домашнюю директорию пользователя с его именем, а опция -s говорит, что оболочкой по умолчанию должна стать Bash. Можно было бы посредством опции -p указать пароль, но это небезопасно. Во-первых, вышеприведенная команда останется в истории, к которой может получить доступ администратор или злоумышленник, если пользователь, от имени которого она выполнялась, будет скомпрометирован. Во-вторых, процесс создания пользователя будет фигурировать в списке процессов, из которого администратор может получить все переданные useradd параметры. В-третьих, пароль на экране может быть просто подсмотрен из-за плеча. Даже если эта и другие приведенные в данной статье команды выполняются на персональной машине в пустом и запертом помещении, я всё равно предлагаю пойти правильным путем и установить пароль пользователя отдельно. В конце концов, чтобы стать настоящим хакером, нужно быть немного шизофреником.

$ sudo chroot /mnt/rootfs passwd pi

На данном этапе образ имеет всё необходимое, чтобы считаться приверженцем консервативного подхода. Чтобы заставить его следовать подходу, при котором все административные задачи решаются через sudo, сначала установим саму программу, а затем добавим пользователя в соответствующую группу.

$ sudo chroot /mnt/rootfs apt-get update
$ sudo chroot /mnt/rootfs apt-get install sudo
$ sudo chroot /mnt/rootfs usermod -aG sudo pi

Теперь пользователь pi может временно заимствовать права суперпользователя для тех вещей, которые выполняются через sudo.

Финальный штрих – блокировка учетной записи root. Для этого выполните

$ sudo chroot /mnt/rootfs passwd -l root

Данная команда поставит восклицательный знак (!) перед хэшем пароля, и это обозначит, что учетная запись заблокирована.

Загляните в «теневой файл паролей [shadowed password file]» /etc/shadow, чтобы в этом убедиться.

На этом тему можно было бы считать закрытой, но остается еще одна вещь, которая не дает мне покоя. Дело в том, что за полтора десятка лет существования Ubuntu успело подрасти целое поколение линуксоидов, которые убеждены, что sudo – это не более чем просто способ временного получения прав суперпользователя, и если оставить всё как есть, то наш учебник только поддержит это распространенное заблуждение. Я хочу внести свой посильный вклад в то, чтобы исправить сложившуюся ситуацию. Для этого я предлагаю решить одну простую, но очень актуальную для RPi задачу, суть которой заключается в том, чтобы дать пользователю возможность выключать или перезагружать свое устройство через Android-приложение нажатием одной кнопки. Таким образом, потребуется посредством sudo дать пользователю право выполнять команды systemctl poweroff и systemctl reboot. Более того, необходимо не только дать пользователю право выполнения этих команд, но и при этом не спрашивать его пароль. Дело в том, что Android-приложение SSH Button, которое я предлагаю использовать для создания кнопок выключения и перезагрузки устройства, как и любое подобное приложение, очень примитивно. Оно подключается к SSH-серверу, выполняет указанную программу и анализирует код завершения [exit status]. Если в результате своей работы программа переходит в режим, в котором она ожидает каких-то действий от пользователя (к примеру, ввода пароля), то SSH Button считает выполнение программы неудачным. Всё это создает идеальные условия для того, чтобы копнуть чуть глубже возможности, предлагаемые sudo, чем я и предлагаю заняться прямо сейчас.

Правила, согласно которым sudo принимает решение, кто и что в праве делать, находятся в /etc/sudoers. Однако, вместо того чтобы редактировать этот файл напрямую, я предлагаю добавить новое правило в виде отдельного файла через подключаемую директорию /etc/sudoers.d. Создайте файл, к примеру, с именем shutdown со следующим содержимым.

pi raspbian-jeosi =NOPASSWD: /usr/bin/systemctl halt,/usr/bin/systemctl reboot

Все файлы в этой директории должны иметь права 0440 (-r–r—–).

$ sudo chmod 0440 /mnt/rootfs/etc/sudoers.d/shutdown

Теперь установите SSH Button на любое устройство под управлением Android. Затем создайте в нем кнопку и укажите в ее настройках адрес вашего RPi, логин и пароль пользователя pi и одну из разрешенных этому пользователю команд — systemctl poweroff или systemctl reboot.

Удобный способ выполнить любую команду на RPi с мобильного устройства.
Удобный способ выполнить любую команду на RPi с мобильного устройства.

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

Доступный только на чтение корень

Продвинутые владельцы одноплатников, и тем более те, кто создает на их основе продукты, предпочитают (очевидно, если это не противоречит поставленной задаче) делать корневую файловую систему доступной только на чтение. В таком случае можно не переживать за целостность данных из-за внезапного отключения питания.

Доступный только на чтение корень – это умышленное ограничение функциональности системы для повышения ее живучести. Но основная проблема здесь не в том, как этого добиться, а в том, как минимизировать последствия от этого ограничения: ведь будет очень обидно, если демон, предназначенный для решения той самой единственной задачи, потеряет способность вести журнал… Тому, как сделать корень доступным только на чтение и не терять логи в этих условиях, посвящена большая часть этого раздела. (В силу того, что материал руководства выходит далеко за пределы журналирования, разговор на эту тему не будет исчерпывающим, поэтому я настоятельно рекомендую обратиться к статье «Записки демонов» из LXF91 за более детальными подробностями по этому вопросу.)

Традиционно в Unix-подобных операционных системах журналированием занимается демон syslogd. В Debian, к примеру, используется одна из его реализаций под названием rsyslog. Когда сообщение доходит до syslogd, возможен один из пяти вариантов развития событий:

  • сообщение может быть добавлено в файл;
  • сообщение может быть выдано на терминал любого указанного пользователя;
  • сообщение может быть записано в FIFO (именованный канал);
  • сообщение может быть перенаправлено syslogd, находящемуся на другой машине;
  • сообщение может быть проигнорировано.

Самый распространенный первый вариант не подходит, т.к. корневая файловая система доступна только на чтение. Можно, конечно, передавать логи на другую машину, но в некоторых случаях наличие еще одной машины может оказаться избыточным. К счастью, другая реализация syslogd, разработанная в рамках проекта Busybox, поддерживает использование так называемого «кольцевого буфера [circular buffer]» для хранения логов. Таким образом, создается иллюзия того, что логи пишутся в обычном режиме так, как если бы корневая файловая система не была доступна только на чтение.

В Raspbian (и других дистрибутивах, основанных на Debian) реализация syslogd от проекта Busybox находится в пакете busybox-syslogd.

$ sudo chroot /mnt/rootfs apt-get install busybox-syslogd

Если в системе уже был установлен rsyslog, то установка busybox-syslogd приведет к его удалению, т. к. busybox-syslogd возьмет на себя функции rsyslog.

Теперь логи подавляющего большинства демонов в вашей системе будут сохраняться в памяти. busybox-syslogd сконфигурирован в Raspbian таким образом, чтобы использовать для этих целей буфер размером 64 КБ. Однако, если произойдет внезапное отключение питания, то все логи вылетят в трубу. Конечно, самым надежным во всех случаях подходом является хранение логов на отдельной машине, и busybox-syslogd это тоже умеет.

Несмотря на то, что подавляющее большинство демонов используют syslogd для журналирования, есть демоны, которые занимаются этим вопросом самостоятельно. К примеру, Nginx ведет два типа журналов: журнал доступа [access log] и журнал ошибок [error log], и по умолчанию не пользуется услугами syslogd для этих целей. Тем не менее, модуль Nginx ngx_http_log_module, который занимается журналированием, поддерживает использование syslogd. В Debian и его производных Nginx сконфигурирован так, чтобы журнал доступа сохранялся в /var/log/nginx/access. log, а журнал ошибок – в /var/log/nginx/error.log. Очевидно, что в файловой системе, доступной только на чтение, Nginx не сможет писать в access.log и error.log, поэтому необходимо попросить web-сервер регистрировать события через syslogd. Для этого отредактируйте /etc/nginx/nginx.conf так, чтобы директивы access_log и error_log выглядели следующим образом.

access_log syslog:server=unix:/dev/log,nohostname;
error_log syslog:server=unix:/dev/log,nohostname;

И в заключение, необходимо сделать корневой раздел доступным только на чтение. Для этого добавьте параметр ro к командной строке ядра, которая находится в файле cmdline.txt на загрузочном разделе.

Сборка ядра

Сейчас система основана на Raspbian’овском ядре, которое латается и пакетируется самими разработчиками одноплатников, поэтому оно, как ни что другое, отлично поддерживает RPi. Пакет raspberrypi-kernel, установка которого обсуждалась в первой части этого руководства, содержит ядро, предназначенное для решения широкого круга задач, что открывает огромные возможности для оптимизаций. Таким образом, в данном разделе речь пойдет о том, как самостоятельно собрать ядро – для того, чтобы получить возможность подогнать его под решение конкретной задачи.

Теперь осталось определиться, у кого мы возьмем ядро, которое будем здесь собирать: у Линуса, разработчиков RPi или у когото еще. Предлагаю собирать именно «ванильное» ядро, т. к. этот подход является наиболее дистрибутивонезависимым. Базовая поддержка RPi появилась в upstream’е достаточно давно. К примеру, RPi 2 поддерживается Linux’ом, начиная с версии 4.5, которая вышла 13-го марта 2016 г., а RPi 3 – начиная с версии 4.8, которая вышла 2 октября того же года. Безусловно, RPi поддерживается «ванильным», т. е. стандартным, ядром не настолько хорошо, как Raspbian’овским, но для решения некоторых задач этим можно пренебречь.

В качестве примера я возьму Linux 4.14 — последний на момент написания статьи выпуск с длительным сроком поддержки. Так, этот раздел не потеряет своей актуальности ни на грамм, пока не закончится жизненный цикл выпуска 4.14, которое при участии Google будет сопровождаться до 2023 г. Тем не менее, я призываю вас брать самую последнюю на момент чтения этого руководства версию ядра и применять к ней описанные здесь рецепты.

А теперь к делу. Загрузите с kernel.org архив с интересующей вас версией ядра Linux. К примеру, им оказался linux-4.14.52.tar.xz. Распакуйте его.

$ tar xJvf linux-4.14.52.tar.xz

Затем установите необходимые для конфигурации и сборки ядра пакеты (в Debian и производных от него это build-essential, libncurses5-dev и gcc-arm-linux-gnueabihf). После этого выполните следующие команды, чтобы приступить к конфигурации целевого ядра, ради которой мы здесь все собрались.

$ ARCH=arm make bcm2835_defconfig
$ ARCH=arm make menuconfig
menuconfig поможет сконфигурировать ядро для решения конкретной задачи.
menuconfig поможет сконфигурировать ядро для решения конкретной задачи.

На этом этапе, как правило, из ядра выкидывается всё, что не способствует выполнению поставленной задачи, и добавляется всё, чего не хватает. Руководство не предлагает в каче­стве примера какую-то конкретную задачу, поэтому здесь всё зависит от вас. Я настоятельно рекомендую не зацикливаться на этом шаге, двинуться дальше, а потом еще раз вернуться к этому разделу после просмотра доклада «Tuning Linux For Embedded Systems: When Less is More» Даррена Гарта [Darren Hart], где он ставит перед собой цель собрать минимально возможное ядро для встраиваемого устройства.

После того как с конфигурацией будет покончено, можно начинать сборку:

$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- chrt -i 0 make -j4 deb-pkg

В результате получится несколько Deb-пакетов. Сейчас нас интересует linux-image, полное название у которого в моем случае – linux-image-4.14.52_4.14.52-1_armhf.deb. Установите его, предварительно удалив пакет с Raspbian’овским ядром:

$ sudo chroot /mnt/rootfs apt-get purge raspberrypi-kernel
$ sudo cp linux-image-4.14.52_4.14.52-1_armhf.deb /mnt/rootfs
$ sudo chroot /mnt/rootfs dpkg -i linux-image-4.14.52_4.14.52-1_armhf.deb
$ sudo rm /mnt/rootfs/linux-image-4.14.52_4.14.52-1_armhf.deb

Затем удалите с загрузочного раздела всё, что относилось к Raspbian’овскому ядру и загрузите на него новое ядро и dtb-файлы:

$ sudo rm /mnt/boot/{cmdline.txt,bcm283*,kernel7.img}
$ sudo cp /mnt/rootfs/boot/vmlinuz-4.14.52 /mnt/boot/zImage
$ sudo cp /mnt/rootfs/usr/lib/linux-image-4.14.52/bcm283* /mnt/boot

Для загрузки «ванильного» ядра потребуется загрузчик, в качестве которого я предлагаю использовать Das U-Boot. Последней стабильной версией U-Boot на момент написания статьи является 2018.05. Загрузите архив с исходниками U-Boot, укажите файл конфигурации, которая соответствует вашему устройству, и запустите сборку.

Что касается файла конфигурации, то в моем случае это rpi_3_32b_defconfig, т. к. я хочу запустить на Raspberry Pi 3 Model B собранное под ARMv7 ядро Linux, поскольку мы имеем дело с 32-битной пакетной базой Raspbian. Подсмотрите в директории configs все возможные варианты.

$ curl -O ftp://ftp.denx.de/pub/u-boot/u-boot-2018.05.tar.bz2
$ tar xjvf u-boot-2018.05.tar.bz2
$ cd u-boot-2018.05
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- chrt -i 0 make rpi_3_32b_defconfig
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- chrt -i 0 make -j4

Теперь подготовьте файл boot.scr со следующим содержимым:

mmc dev 0
setenv fdtfile bcm2837-rpi-3-b.dtb
setenv bootargs earlyprintk console=tty0 console=ttyAMA0
root=/dev/mmcblk0p2 rootwait init=/bin/systemd
fatload mmc 0:1 ${kernel_addr_r} zImage
fatload mmc 0:1 ${fdt_addr_r} ${fdtfile}
bootz ${kernel_addr_r} - ${fdt_addr_r}

Этот файл содержит список команд, которые U-Boot должен выполнить. Такой способ конфигурации U-Boot делает загрузчик очень гибким.

Есть три места, на которые в этом пункте стоит обратить пристальное внимание.

  • 2-я строка содержит имя DTB-файла, соответствующего устройству, на котором планируется загрузка ядра. Указанный в этой строке файл, в числе прочих DTB-файлов, был получен при сборке ядра и уже находится на корневом и загрузочном разделах. Укажите здесь DTB-файл, соответствующий вашему устройству.
  • 3-я строка содержит командную строку ядра.
  • 4-я строка содержит имя двоичного файла ядра (zImage).

Теперь необходимо скомпилировать boot.scr в boot.scr.uimg. Для этого потребуется программа mkimage, которую можно найти в пакете u-boot-tools во всех Debian-подобных дистрибутивах или в uboot-tools в Fedora.

$ mkimage -A arm -O linux -T script -C none -n boot.scr -d boot.scr boot.scr.uimg

Наконец, нужно перекинуть двоичный файл загрузчика, который был получен после сборки U-Boot, и boot.scr.uimg на загрузочный раздел:

$ sudo u-boot.bin /mnt/boot/kernel7.img
$ sudo boot.scr.uimg /mnt/boot

Очистка образа

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

Существует идиома, которая решает эту проблему. Она стала популярной за счет повсеместного использования в Dockerfile’ах, т.к. Docker-образы, как и образы для одноплатников, тоже нуждаются в очистке от «строительного мусора». Выглядит эта идиома следующим образом.

$ sudo apt-get clean
$ sudo rm -rf /var/lib/apt/lists/*

Первая команда удаляет кэшированные двоичные пакеты, а вторая – индексные файлы. В нашем случае эти команды будут выглядеть так:

$ sudo chroot /mnt/rootfs apt-get clean
$ sudo rm -rf /mnt/rootfs/var/lib/apt/lists/*

Заключение

Несмотря на то, что есть достаточно большое количество скриптов, автоматизирующих весь описанный в этом руководстве процесс, я убежден в большой пользе понимания, что у них под капотом. Во-первых, эти скрипты нуждаются в контрибьюторах – и где как не здесь их выращивать. Во-вторых, спускаясь на уровень сборки своей системы, хоть и на основе двоичных пакетов, есть возможность лучше понять GNU/Linux, а где как не здесь этим заниматься. Я старался выжимать максимум из каждого раздела этой части руководства, выстраивая как можно более длинные цепочки из смежных тем для того, чтобы сделать материал как можно более энциклопедическим. Теперь ваша очередь – сдуйте пыль со своего одноплатника и выжмите из него максимум. Удачного хакинга.

Leave a Reply

Your email address will not be published. Required fields are marked *