Когда собираешь и тестируешь свой Linux для одноплатника достаточно долго, начинаешь замечать, что деплой Linux на SD-карту — монотонная повторяющаяся последовательность действий, занимающая ценное время, в которой легко совершить ошибку. К тому же больно видеть, как исчерпывает свой ресурс SD-карта и слот для неё.

Часто при embedded-разработке эти проблемы решают при помощи сетевой загрузки Linux.

В этой статье я расскажу, как организовать сетевую загрузку для Raspberry Pi и собрать минимальное ядро Linux, поддерживающее сетевую загрузку.

Сетевая загрузка рассматривается для Raspberry Pi 3 Model В и Raspberry Pi 4 Model B, которые я далее называю общим термином Raspberry Pi или более ласково — малинка.

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

Тема сетевой загрузки довольно многогранна и затрагивает несколько уровней стека — от протоколов локальной сети до особенностей загрузчика Raspberry Pi. Я старался изложить материал максимально просто и последовательно, но если у вас нет базовых знаний о работе локальных сетей (DHCP, TFTP), протоколах TCP/IP или процессе загрузки Linux, некоторые моменты могут показаться сложными.

Статья является продолжением моей предыдущей статьи, где я рассказывал, как создать минимальный Linux для Raspberry Pi, который грузится с SD-карты.

В свой репозиторий я поместил исходный код Docker-образов, упрощающий сборку минимального Linux и настройку окружения для сетевой загрузки.

Надеюсь, что статья сэкономит вам время, которое вы сможете потратить на свой увлекательный проект.

Основы сетевой загрузки Linux

Загрузка Linux

Вы не поймёте сетевую загрузку, пока не разберётесь, как загружается Linux.

Основные компоненты, которые участвуют в загрузке:

  • Загрузчик. Программа, которая помещает образы файлов ядра Linux, initramfs в оперативную память и передаёт управлению ядру Linux.

  • Ядро Linux. Бинарный код операционной системы, который выполняется в пространстве ядра при запуске операционной системы.

  • Initramfs. cpio-архив, содержащий модули ядра, пользовательские приложения и программы, необходимые для сложных сценариев загрузки, которые не может обеспечить ядро, например, загрузка с зашифрованного раздела. Для простых сценариев initramfs может быть не нужен, но большинство современных дистрибутивов используют загрузку с initramfs, но нам initramfs не нужен.

  • Device Tree и Device Tree Overlays. Файлы, которые описывают конфигурацию конкретной платформы.

  • Параметры командной строки. Вы можете кастомизировать поведение ядра Linux при его компиляции, но также поведение можно кастомизировать и при запуске с помощью командной строки. Кастомизация при помощи командной строки не увеличивает размер ядра.

  • Корневая файловая система. Файловая система, содержащая пользовательские приложения и программы, конфигурационные файлы, необходимые для функционирования операционной системы. Корневая файловая система может располагаться на разделах диска, в компьютерной сети, в файле.

Если кратко, то загрузка Linux выглядит следующим образом:

  1. Загрузчик загружает в оперативную память ядро, Device Tree c Device Tree Overlays или без, а также опционально initramfs.

  2. Загрузчик передаёт управление ядру, передавая адреса в памяти Device Tree и initramfs, параметры командной строки Linux.

  3. Ядро монтирует корневую файловую систему и продолжает загрузку.

  4. Ядро запускает первый процесс c PID = 1, который выполняет дальнейшую инициализацию операционной системы в пространстве пользователя.

Сетевая загрузка Linux

Существует несколько сценариев сетевой загрузки. Я рассматриваю только один - канонический TFTP + NFS-root.

  1. Выполняется код загрузчика, который находит в сети DHCP и TFTP-сервера.

  2. Выполняется дозагрузка загрузчика и его конфигурационных файлов из сети.

  3. Загрузчик загружает ядро Linux и опционально файл initramfs.

  4. Передаётся управление ядру, где в параметрах командной строки указывается расположение корневой файловой системы на сервере NFS.

  5. Ядро само поднимает сетевой интерфейс и монтирует NFS-ресурс.

  6. Выполняется дальнейшая загрузка операционной системы с NFS-ресурса.

Этот сценарий не лишён недостатков, в частности, в области безопасности, так отсутствует аутентификация на уровне DHCP, TFTP и NFS-серверов, но так как мы будем его использовать на локальном компьютере для отладки — это вполне допустимый вариант.

Описанный сценарий похож на PXE-загрузку, но это не PXE-загрузка, хотя и выполняются похожие действия.

Особенности загрузки Linux на Raspberry Pi

В статье рассматривается сетевая загрузка только на Raspberry Pi. Загрузка на нём немного отличается от большинства платформ, но я рассказываю про сетевую загрузку на малинке, потому что предыдущая моя статья была посвящена именно Raspberry Pi и он является широко распространённой платформой, имеющей лучшую поддержку сообществом, чем, например, Orange Pi.

Одним из отличий Raspberry Pi 3 Model B от Raspberry Pi 4 Model является наличие у последнего EEPROM-чипа, в который помещается первичный загрузчик по функционалу близкий к тому, что в Raspberry Pi 3 располагается в файле bootcode.bin на SD-карте.

Что нужно для сетевой загрузки

Для организации сетевой загрузки Linux необходимо:

  • настроить firmware встраиваемого устройства на загрузку по сети,

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

  • установить и настроить DHCP-сервер,

  • установить и настроить TFTP-сервер,

  • установить и настроить NFS-сервер,

  • настроить корневую файловую систему, являющуюся ресурсом NFS.

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

Я не претендую на универсальность или идеальность решения, а просто рассказываю, что сделал и почему. Всегда рад конструктивным комментариям, ведь обоснованная критика делает мир лучше.

Сетевая загрузка операционной системы базируется на нескольких сетевых протоколах: DHCP, TFTP, NFS.

При сетевой загрузке необходимо решить следующие задачи:

  • получить сетевые параметры:

    • IP-адрес клиента,

    • IP- адрес и маска сети,

    • IP-адрес шлюза,

    • IP-адрес TFTP-сервера,

  • загрузить firmware / kernel / DTB с TFTP-сервера,

  • обеспечить доступ операционной системы к файлам, хранящимся на сетевом диске.

DHCP-сервер

Основная задача DHCP-сервера — выдавать IP-адреса и другие параметры сети (маска подсети, IP-адрес шлюза по умолчанию, IP-адреса DNS-серверов) для устройств, подключаемых в IP-сеть. DHCP-сервер запускается на одном из устройств сети и обслуживает один или несколько сетевых интерфейсов (или подсетей).

DHCP является протоколом прикладного уровня и работает поверх UDP. При этом он активно использует широковещательные механизмы уровней L2 и L3, поскольку клиенты на начальном этапе не имеют IP-адреса, что часто усложняет понимание протокола для новичков.

TFTP-сервер

Основная задача TFTP-сервера — предоставлять по запросу файлы, хранящиеся на сервере. Так как TFTP расшифровывается как Trivial File Transfer Protocol, никакой речи об аутентификации, проверки целостности не идёт. В более современных подходах к сетевой загрузке используется HTTPS, но в нашем случае можно обойтись и TFTP.

NFS-сервер

Основная задача NFS-сервера — позволить клиенту работать с файловой системой на сервере. Обычно он оформлен в виде служб для systemd и/или другой системы инициализации. Но мы не будем отходить от концепции минимальности и простоты и запустим все необходимые демоны и программы из shell-скрипта.

Ядро Linux c сетевой загрузкой

Для сетевой загрузки Linux необходима платформа, которая содержит устройства для доступа к сети и ядро, поддерживающее сетевую загрузку.

Так как у Raspberry Pi есть сетевой адаптер Ethernet, нам нужно разобраться, как добавить поддержку сетевой загрузки в ядро Linux.

Если вы используете стандартное ядро Linux, входящее в состав Raspberry Pi OS, то ничего в ядро доставлять не нужно, достаточно добавить нужные параметры командной строки Linux, но я в статье рассматриваю минимальный Linux и кастомное ядро, которое компилируется из исходного кода, поэтому нам придётся указать несколько параметров в файле .config.

Обычно сетевая загрузка осуществляется из Ethernet-сети. Использование WiFi-сети теоретически возможно, но из-за особенностей аутентификации и авторизации в сети это более сложный процесс, и в статье я рассматривать его не буду.

Моя реализация сетевой загрузки

Я ставил целью получить простое и воспроизводимое решение. Поэтому у меня один Docker-образ на основе Alpine Linux, где в качестве TFTP и DHCP-серверов используется BusyBox, а в качестве NFS-сервера nfs-utils.

Исходный код Docker-образа для сборки минимального Linux с поддержкой сетевой загрузки и Docker-образа с серверами приведён в моём репозитории на Github. Я специально разместил их в одном репозитории, чтобы вы скорее могли перейти к экспериментам.

Для сетевой загрузки нужно минимум конфигурационных данных. Нам необходимо настроить сетевой интерфейс, DHCP, TFTP и NFS-сервера.

Настройка сетевого интерфейса хоста

Чтобы DHCP-сервер, запущенный в Docker-контейнере, мог выдавать IP-адреса, Docker-контейнер нужно запускать в привилегированном режиме с поддержкой хост-сети. Такое возможно только на хостах с операционной системой Linux.

Если вы используете современный дистрибутив на основе Debian и внешний Ethernet-адаптер, как я, то при подключении адаптера в USB-порт он распознаётся в системе, и вам в графическом интерфейсе нужно назначить для него IP-адрес и ещё несколько параметров сети.

Настройка сетевой загрузки на Raspberry Pi.

Включение поддержки сетевой загрузки отличается у Raspberry Pi 3 Model B и у Raspberry Pi 4 Model B.

Сетевая загрузка Raspberry Pi 3 Model B

По умолчанию Raspberry Pi 3 Model B не поддерживает сетевую загрузку. Чтобы он мог загружаться из сети, необходимо изменить OTP-бит в SoC. Операция эта необратимая, вы можете изменить этот бит только один раз.

Код начальной загрузки из сети у третьей малинки прописан в ROM-области SoC, и изменить его нельзя. Когда я выполнял сетевую загрузку, загрузка работала нестабильно, и я не смог определить конкретную причину такого поведения.

Чтобы добиться стабильной сетевой загрузки, я использовал вариант с SD-картой, на которой был один файл bootcode.bin

К сожалению, я изменил OTP-бит, и другой третьей малинки у меня нет, поэтому я не могу сказать, нужно ли изменять его при использовании сетевой загрузки с SD-картой.

Сетевая загрузка Raspberry Pi 4 Model B

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

Настройка DHCP-сервера

Настройка DHCP-сервера сводится к одному файлу /etc/udhcpd.conf в Docker-контейнере. Если у вас есть базовые знания протокола DHCP, конфигурация понятна без комментариев.

start       192.168.7.100
end         192.168.7.150
max_leases  51
interface   enxYYYYYYYYYYYY

option subnet 255.255.255.0
option router 192.168.7.1
option dns    192.168.7.1

option tftp    192.168.7.1

На всякий случай приведу объяснение опций конфигурационного файла.

  • start, end — начало и конец пула IP-адресов, которые может выдавать DHCP-сервер.

  • max_leases — размер пула IP-адресов.

  • interface — имя сетевого интерфейса, где запускается DHCP-сервер. В нашем случае это внешняя сетевая карта. Вместо YYYYYYYYYYYY нужно указывать её MAC-адрес.

  • option subnet — маска IP-сети.

  • option router — IP-адрес шлюза (gateway).

  • option dns — IP-адрес DNS-сервера. -option tftp — IP-адрес TFTP-сервера.

Опции router и dns можно не указывать, но я их указал для верности.

Настройка TFTP-сервера

Одной из особенностей TFTP-сервера, входящего в состав BusyBox, является то, что TFTP-сервер нужно запускать с помощью суперсервера inetd.

TFTP-сервер называется tftpd.

Inetd слушает порты, прописанные в его конфигурации, и как только приходит пакет для нужного порта, inetd запускает процесс TFTP-сервера и передаёт TFTP-серверу сокет, на котором был обнаружен пакет.

В файле /etc/inetd.conf для tftpd прописывается строка.

69 dgram udp nowait root /usr/sbin/tftpd tftpd  -l /var/tftpd
conf

Ошибки inetd логирует в syslog, поэтому, если хотите увидеть ошибки, необходимо запустить syslogd перед запуском inetd.

По умолчанию syslogd логирует сообщения в файл /var/log/messages. Чтобы их посмотреть, можно выполнить команду:

# cat /var/log/messages

Настройка NFS-сервера

Настройка NFS-сервера хранится в единственном файле /etc/exports

/nfs/rootfs  *(rw,fsid=0,sync,no_subtree_check,no_auth_nlm,insecure,no_root_squash,no_all_squash,crossmnt)

Чтобы не усложнять статью, я не буду подробно разбирать параметры.

Опции командной строки Linux для монтирования NFS-директории в качестве корневой файловой системы

Для того, чтобы NFS-директория монтировалась в качестве корневой файловой системы, в командную строку Linux необходимо прописать следующие параметры

root=/dev/nfs nfsroot=192.168.7.1:/nfs/rootfs/ vers=3 rw ip=dhcp

Конфигурация ядра для сетевой загрузки

Чтобы ядро могло продолжить сетевую загрузку с NFS, необходимо его собрать с правильной конфигурацией. Обычно ядра в дистрибутивах уже собраны соответствующим образом, но мы собираем минимальное ядро на основе tinyconfig, поэтому для Raspberry Pi 4 Model B необходимо добавить следующие опции:

CONFIG_OF_ALL_DTBS=y

CONFIG_ARCH_BCM=y
CONFIG_ARCH_BCM2835=y

CONFIG_RASPBERRYPI_FIRMWARE=y
CONFIG_BCM2835_MBOX=y
CONFIG_MAILBOX=y

CONFIG_DMADEVICES=y
CONFIG_DMA_BCM2835=y
CONFIG_CMA=y
CONFIG_DMA_CMA=y

CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_SHARE_IRQ=y
CONFIG_SERIAL_8250_EXTENDED=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_BCM2835AUX=y

CONFIG_TTY=y

CONFIG_TMPFS=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_PROC_FS=y
CONFIG_SYSFS=y

CONFIG_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y

CONFIG_PRINTK=y
CONFIG_EARLY_PRINTK=y
CONFIG_PRINTK_TIME=y

CONFIG_BCMGENET=y
CONFIG_MDIO_BCM_UNIMAC=y

CONFIG_NET=y
CONFIG_NETDEVICES=y

CONFIG_INET=y
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
CONFIG_IP_PNP_BOOTP=y

CONFIG_NFS_FS=y
CONFIG_FILE_LOCKING=y
CONFIG_MULTIUSER=y

CONFIG_ROOT_NFS=y

CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y

Для Raspberry Pi 3 Model B необходимо добавить поддержку сетевого адаптера и USB, так как у него Ethernet реализован через USB Ethernet-контроллер:

CONFIG_USB_SUPPORT=y
CONFIG_USB_ARCH_HAS_HCD=y
CONFIG_USB=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_EHCI_HCD_PLATFORM=y

CONFIG_USB_DWC2=y
CONFIG_USB_DWC2_HOST=y
CONFIG_USB_DWCOTG=y

CONFIG_USB_USBNET=y
CONFIG_USB_NET_DRIVERS=y
CONFIG_USB_NET_SMSC95XX=y

Инструкция, как собрать и запустить Linux c сетевой загрузкой

Сборка окружения для сетевой загрузки

  1. Создаём директорию для окружения:

    $ mkdir boot-server && cd boot-server
    
  2. Создаём файл entrypoint.sh, который будет запускать все необходимые серверы:

    cat << 'EOF' > entrypoint.sh
    #!/bin/sh
    
    set -e
    set -u
    
    echo "Starting network services..."
    
    echo "[1/7] Starting syslog daemon..."
    syslogd
    
    echo "[2/7] Starting udhcpd (DHCP server) in foreground..."
    udhcpd -S
    
    echo "[3/7] Starting inetd..."
    inetd
    
    echo "[4/7] Starting rpcbind (portmapper)..."
    rpcbind
    
    echo "[5/7] Starting rpc.statd (lock manager)..."
    rpc.statd
    
    echo "[6/7] Starting rpc.mountd (mount requests handler)..."
    rpc.mountd
    
    echo "[7/7] Starting nfsd with 8 threads..."
    rpc.nfsd 8
    
    echo "Applying NFS exports from /etc/exports..."
    exportfs -rav
    
    echo "All services started successfully!"
    
    exec tail -f /dev/null
    
    EOF
    
    $ sudo chmod +x entrypoint.sh
    
  3. Создаём Dockerfile:

    $ cat << 'EOF' > Dockerfile
    
    FROM alpine
    
    COPY entrypoint.sh /entrypoint.sh
    
    RUN apk add --no-cache busybox-extras nfs-utils
    
    RUN touch /var/lib/udhcpd/udhcpd.leases 
    
    ENTRYPOINT ["/entrypoint.sh"]
    
    EOF
    
  4. Собираем Docker-образ:

    $ sudo docker build -t boot-server .
    
  5. Создаём директорию для файлов конфигурации:

    $ mkdir config
    
  6. Создаём файл конфигурации для TFTP-сервера. Не забудьте указать имя вашего сетевого интерфейса вместо enxYYYYYYYYYYYY:

    $ cat << 'EOF' > config/udhcpd.conf
    start       192.168.7.100
    end         192.168.7.150
    max_leases  51
    interface   enxYYYYYYYYYYYY
    
    option subnet 255.255.255.0
    option router 192.168.7.1
    option dns    192.168.7.1
    
    option tftp    192.168.7.1
    EOF
    
  7. Создаём файл конфигурации для DHCP-сервера:

    $ echo "69 dgram udp nowait root /usr/sbin/tftpd tftpd  -l /var/tftpd" > config/inetd.conf
    
  8. Создаём файл конфигурации для NFS-сервера:

    $ echo "/nfs/rootfs  *(rw,fsid=0,sync,no_subtree_check,no_auth_nlm,insecure,no_root_squash,no_all_squash,crossmnt)" > config/exports
    
  9. Создаём директории, где будут располагаться данные для TFTP и NFS-серверов:

    $ mkdir -p data/{boot,nfs} 
    

Сборка Linux

Далее я собираю только Linux c downstream-ядром. Сборка upstream аналогична, за иключением того, откуда берётся исходный код.

  1. Cоздаём директорию для окружения сборки Linux:

    $ cd ..
    
    $ mkdir linux-builder && cd linux-builder
    
  2. Создаём Dockerfile:

    $ cat << 'EOF' > Dockerfile
    FROM debian:bookworm
    
    RUN apt update && apt install -y --no-install-recommends \
     build-essential crossbuild-essential-arm64 wget xz-utils cpio flex bison bc file tree \
       	ncurses-dev libelf-dev libssl-dev git ca-certificates parted kpartx dosfstools e2fsprogs mount \
        && apt clean \
        && rm -rf /var/lib/apt/lists/*
    
    ENV ARCH=arm64
    ENV CROSS_COMPILE=aarch64-linux-gnu-
    
    WORKDIR /workspace
    
    EOF
    
  3. Собираем Docker-образ:

    $ sudo docker build -t linux-builder .
    
  4. Запускаем Docker-контейнер в интерактивном режиме:

    $ sudo docker run -it --rm --entrypoint /bin/bash -v $(pwd):/workspace -v $(pwd)/../boot-server/data:/deploy linux-builder
    
  5. Собираем ядро:

    # mkdir /build && cd /build
    
    # git clone --depth=1 -b rpi-6.12.y https://github.com/raspberrypi/linux.git linux
    
    # cd linux
    
    # make tinyconfig && \
    ./scripts/config \
    -e OF_ALL_DTBS \
    -e ARCH_BCM -e ARCH_BCM2835 -e RASPBERRYPI_FIRMWARE -e BCM2835_MBOX -e CONFIG_MAILBOX \
    -e DMADEVICES -e DMA_BCM2835 -e CMA -e DMA_CMA \
    -e SERIAL_8250 -e SERIAL_8250_SHARE_IRQ -e SERIAL_8250_EXTENDED -e SERIAL_8250_CONSOLE -e SERIAL_8250_BCM2835AUX \
    -e TTY \
    -e TMPFS -e DEVTMPFS -e DEVTMPFS_MOUNT -e PROC_FS -e SYSFS \
    -e BINFMT_ELF -e BINFMT_SCRIPT \
    -e PRINTK -e EARLY_PRINTK -e PRINTK_TIME \
    -e BCMGENET -e MDIO_BCM_UNIMAC \
    -e NET -e NETDEVICES \
    -e INET -e IP_PNP -e IP_PNP_DHCP -e IP_PNP_BOOTP \
    -e NFS_FS -e FILE_LOCKING -e MULTIUSER \
    -e ROOT_NFS \
    -e IKCONFIG -e IKCONFIG_PROC \
    -e USB_SUPPORT -e USB_ARCH_HAS_HCD -e CONFIG_USB -e USB_EHCI_HCD -e USB_EHCI_HCD_PLATFORM \
    -e USB_DWC2 -e USB_DWC2_HOST -e USB_DWCOTG \
    -e USB_USBNET -e USB_NET_DRIVERS -e USB_NET_SMSC95XX
    
    # make olddefconfig
    
    # make -j$(nproc)
    
  6. Собираем BusyBox:

    # cd /build
    
    # wget https://mirrors.slackware.com/slackware/slackware64-current/source/a/mkinitrd/busybox-1.37.0.tar.bz2
    
    # tar xjvf busybox-1.37.0.tar.bz2
    
    # cd busybox-1.37.0
    
    # make defconfig
    
    # sed -i 's/^CONFIG_SHA256_HWACCEL=y/# CONFIG_SHA256_HWACCEL is not set/' .config
    
    # sed -i 's/^CONFIG_SHA1_HWACCEL=y/# CONFIG_SHA1_HWACCEL is not set/' .config
    
    # mkdir -p /deploy/nfs/rootfs/{bin,sbin,etc,proc,sys,usr/{bin,sbin},dev,run,tmp,var}
    
    # make CONFIG_STATIC=y CONFIG_PREFIX=/deploy/nfs/rootfs -j$(nproc) install
    
    # rm /deploy/nfs/rootfs/sbin/init
    
    # cat << 'EOF' > /deploy/nfs/rootfs/sbin/init
    #!/bin/sh
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t tmpfs tmpfs /run
    mount -t tmpfs tmpfs /tmp
    mdev -s
    TTY=/dev/$(head -1 /sys/class/tty/console/active)
    echo 0 > /proc/sys/kernel/printk
    printf "\033c" > $TTY
    cat << INNER > $TTY
    Welcome to ArtyomSoft Robinson Linux for Raspberry Pi
    TTY: $(basename $TTY)
    Time: $(date)
    Kernel version: $(uname -r)
    INNER
    exec setsid sh -c "exec sh <'$TTY' >'$TTY' 2>'$TTY'"
    EOF
    
    # chmod +x /deploy/nfs/rootfs/sbin/init
    
  7. Наполняем директорию для начальной сетевой загрузки:

    # cd /build
    
    cat << 'EOF' > /deploy/boot/config.txt
    
    kernel=kernel8.img
    arm_64bit=1
    enable_uart=1
    uart_2ndstage=1
    
    [pi3]
    core_freq=250
    
    [pi4]
    
    EOF
    
    # cat > "/deploy/boot/cmdline.txt" <<EOF
    earlycon console=serial0,115200 root=/dev/nfs nfsroot=192.168.7.1:/nfs/rootfs/ vers=3 rw ip=dhcp rootwait
    EOF
    
    # wget -O firmware.tar.xz https://github.com/raspberrypi/firmware/releases/download/1.20250915/raspi-firmware_1.20250915.orig.tar.xz
    
    # tar -xvf firmware.tar.xz --strip-components=2 -C /deploy/boot
    
    # rm firmware.tar.xz
    
    # cp /build/linux/arch/arm64/boot/Image /deploy/boot/kernel8.img
    
    # cp /build/linux/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb /deploy/boot
    
    # cp /build/linux/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb /deploy/boot
    
  8. Завершаем работу Docker-контейнера или открываем новое терминальное окно.

Запуск окружения для сетевой загрузки

  1. Настраиваем сетевой интерфейс на хосте (у нас это IP 192.168.7.1 и маска 255.255.255.0).

  2. Подключаем UART-адаптер к малинке. В Raspberry Pi 3 и Raspberry Pi 4 UART выведен на пины GPIO. Распиновка и схема подключения приведены ниже. Нас интересуют пины 6, 8, 10.

    Распиновка GPIO Raspberry Pi
    Распиновка GPIO Raspberry Pi
    Подключение USB-UART-адаптера
    Подключение USB-UART-адаптера
  3. Подключаем малинку к Ethernet-порту хаба или компьютера.

  4. Запускаем эмулятор терминала в отдельном терминальном окне.

    sudo picocom -b 115200 /dev/ttyUSB0
    
  5. Подаём питание на малинку.

  6. Запускаем Docker-контейнер в detach-режиме:

    # cd ../boot-server/
    
    sudo docker run \
    -v $(pwd)/config/inetd.conf:/etc/inetd.conf \
    -v $(pwd)/config/udhcpd.conf:/etc/udhcpd.conf \
    -v $(pwd)/config/exports:/etc/exports \
    -v $(pwd)/data/boot:/var/tftpd \
    -v $(pwd)/data/nfs:/nfs \
    --privileged --network host --name boot-server -d --rm boot-server
    
  7. Проверяем логи:

    # sudo docker exec simple-network-boot-server cat /var/log/messages
    
  8. Подаём питание на малинку и наблюдаем сетевую загрузку в эмуляторе терминала.

Вывод отладочной информации при загрузке

Загрузка и вывод отладочной информации через UART отличаются в различных версиях Raspberry Pi.

Отладочную информацию выводит:

  • вторичный загрузчик (bootcode.bin в Raspberry Pi 3 Model B, EEPROM в Raspberry Pi 4 Model B и выше),

  • основной загрузчик (start.elf),

  • ядро до инициализации драйверов UART (earlyboot) и после инициализации.

Как увидеть, с какими параметрами собрано ядро

Часто при отладке ядра необходимо знать, с какими параметрами собрано ядро. В большинстве дистрибутивов это файл .config, который находится в директории /boot. Но есть другой вариант, когда файл конфигурации доступен по пути /proc/config.gz. Для этого в конфигурацию ядра необходимо добавить две опции CONFIG_IKCONFIG=y и
CONFIG_IKCONFIG_PROC=y

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

$ zcat /proc/config.gz | less

Использование Wireshark для отладки

С помощью программы Wireshark вы можете проанализировать процесс сетевой загрузки, какие пакеты отправляются между Raspberry Pi-серверами. Программа Wireshark дополняет, а не заменяет отладочные сообщения по UART или полученные с помощью команды dmesg.

Выводы

Если вы дочитали до конца и выполнили действия, описываемые в статье, то вы разобрались:

  • с сетевой загрузкой, компонентами и протоколами, которые в ней участвуют при использовании Raspberry Pi 3 и 4,

  • как настроить ядро Linux для сетевой загрузки,

  • как настроить окружение для сетевой загрузки.

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

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

Понимание сетевой загрузки, изложенной в статье, должно помочь вам в освоении других сценариев, понимании PXE, iPXЕ, загрузки по https, загрузки с использованием WiFi.

© 2026 ООО «МТ ФИНАНС»

Комментарии (0)