Загрузка ядра Linux
Загрузчик (bootloader) - Программа, которая запускается каждый раз, когда компьютер инициализирует загрузочное устройство во время его включения или сброса, и которая загружает образ ядра в память.
В Arduino используется optiboot. Он ожидает сигналов по UART и если есть сообщение - пытается записать новую прошивку.
Chain-loading - более сложный процесс загрузки, который разбивается на 2-3 этапа загрузки Состоит из: загрузчик первого этапа, загрузчик второго этапа и т.д.
Legacy загрузчик
Процедура загрузки Intel схожа с выбором блока подпрограммы в ПЛК (Organization Block в Siemens, например OB87). Т. к. после включения может быть адресован только 1 МБ памяти, то процессор обращается скрытое смешение к команде по адресу 0xFFFFFFF0 = вектор сброса. В свою очередь, материнская плата гарантирует, что команда в векторе сброса является переходом к ячейке памяти, сопоставленной с точкой входа в BIOS.
BIOS выполняет самодиагностику, ищет загрузочную запись, запрашивает MBR (master boot record) и помещает загрузочный сектор диска в 0x7c00 и начинает выполнение. Программа-загруpчик обычно не помещается в 440 байт (другие байты: 4b disk signature, 2b nulls, 4x16b partition table, 2b MBR Signature) и используется для загрузки 2nd stage bootloader, например GRUB (GRand Unified Bootloader). Когда уже он загрузит initrd и образ ядра - передается управление Linux.
Initrd - устаревший способ создания RAM-диска. У него есть некоторые недостатки, которые были исправлены в initramfs
Device tree
Дерево устройств — это древовидная структура данных с узлами, описывающими физические устройства в системе
Device Tree Blob (.dtb) - бинарное представление dts создается компилятором dtc. Двоичный файл, который загружается загрузчиком и анализируется ядром во время загрузки.
Сборка dtb по dts и обратно:
dtc -I dts -O dtb -o /path/my_tree.dtb /arch/arm/boot/dts/my_tree.dts
dtc -I dtb -O dts -o /path/my_tree.dts /path/my_tree.dtb
Также можно использовать в работающей системе:
dtc -I fs /proc/device-tree -o ...
UBoot
Основной open source загрузчик для встраеваемых устройств. Может загружать GRUB или уже Linux.
При необходимости можно собрать SPL версию загрузчика (с флагом CONFIG_SPL_BUILD), который нужен для ограниченной памяти. Этот загрузчик уже загрузит UBoot.
UBoot может загрузить ОС по TFTP.
Systemd
Набор компонентов, который используется для инициализации и настройки системы Linux. systemctl = ui.
Можно описать свой сервис myapplication.service и поместить его в /etc/systemd/system:
[Unit]
Description=Launch my application
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/bin/fooapplication
[Install]
WantedBy=multi-user.target
Далее включить и запустить:
systemctl enable myapplication.service
systemctl start myapplication.service
Инструмент systemd-analyze, позволяет выявить проблемы с производительностью и другую важную информацию systemd
Выполнение задания
Аналогично предыдущему занятию скачиваем все исходники.
KERNEL_VERSION=v6.18.8
Настраиваем кросс-компиляцию. Для примера взял aarch64, который в OrangePi (linux и busybox нужно будет перекомпилировать):
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
UBOOT_VERSION=v2026.01
git clone --branch $UBOOT_VERSION https://github.com/u-boot/u-boot build/u-boot
make qemu_arm64_defconfig
make -j$(nproc)
ls -lh u-boot.bin
Запускаем отладку:
qemu-system-aarch64 -M virt -kernel u-boot/u-boot -m 512M -cpu cortex-a57 -nographic
poweroffчтобы выйти
Собираем linux:
make defconfig
./scripts/config -e ARM64 -e CMDLINE_FORCE -e BLK_DEV_INITRD -e SERIAL_AMBA_PL011_CONSOLE -e SERIAL_AMBA_PL011 -e OF -e USE_BUILTIN_DTB -e ARCH_VIRT -e CMDLINE_BOOL -e CMDLINE="console=ttyAMA0"
make olddefconfig
make all dtbs
Проверка загрузки linux:
qemu-system-aarch64 \
-M virt -m 512M -cpu cortex-a57 \
-kernel linux-v6.18.8/arch/arm64/boot/Image \
-nographic
система не увидит root, для остановки в соседнем терминале выполнить
kill -9 $(pgrep -f qemu)
Пересобрать Busybox. Важно отметить, что целевой файл должен быть под ARM aarch64:
file busybox
busybox: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=..., for GNU/Linux 3.7.0, stripped
В init добавить TTY=/dev/ttyAMA0:
cat > rootfs/init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs tmpfs /run
mount -t tmpfs tmpfs /tmp
mkdir -p /dev/shm
mount -t tmpfs shm /dev/shm
mdev -s
if [ -c /dev/fb0 ]; then
TTY=/dev/tty1
elif [ -c /dev/ttyAMA0 ]; then
TTY=/dev/ttyAMA0
else
TTY=/dev/ttyS0
fi
echo 0 > /proc/sys/kernel/printk
printf "\033c" > $TTY
cat << INNER > $TTY
===============================
Minimal Linux by Vlad Ryabchevsky
Kernel: $(uname -r)
Time: $(date)
===============================
INNER
exec setsid sh -c "exec sh <'$TTY' >'$TTY' 2>'$TTY'"
EOF
chmod +x rootfs/init
cd rootfs
find . | cpio -o -H newc | gzip > ../rootfs.cpio.gz
cd ..
Проверка загрузки всей системы (загрузка из блочного устрйоства):
qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 512M \
-kernel linux-v6.18.8/arch/arm64/boot/Image \
-initrd initramfs.cpio.gz \
-nographic
poweroff -n -f
Для эмуляции загрузки по TFTP создадим папку tftp. В нее нужно скопировать image, initrd и dtb (его можно сгенерировать самим qemu):
cp linux-v6.18.8/arch/arm64/boot/Image tftp/.
cp rootfs.cpio.gz tftp/.
qemu-system-aarch64 -M virt,dumpdtb=tftp/virt.dtb
Теперь запускаем ВМ, доабвляем ей сетевую папку и виртуальный сетевой интерфейс. Далее загружаем файлики и при помощи booti запускаем ОС:
qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 512M \
-kernel u-boot/u-boot \
-nographic -netdev user,id=n0,tftp=$(pwd)/tftp \
-device virtio-net-device,netdev=n0
tftpboot 0x41000000 virt.dtb
tftpboot 0x42000000 Image
tftpboot 0x45000000 initramfs.cpio.gz
booti 0x42000000 0x45000000:12168b 0x4100000