Перейти к содержанию

Загрузка ядра 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