В прошлый раз мы видели, как загрузить Ubuntu для RISC-V на плате QEMU virt
и установить среду разработки для сборки на C и RISC-V. Это было весело и все такое, но ничто не сравнится с компиляцией собственного ядра Linux и утилит пользовательского пространства и загрузкой этого ядра на виртуальной (или физической) плате RISC-V. Поэтому мы сделаем это сегодня!
В документации по RISC-V действительно описывается процесс, как это сделать, но, к сожалению, он очень краток и пропускает много деталей, поэтому может не подойти для читателей-новичков в мире встраиваемых систем. По крайней мере, мне потребовалось много возиться, дополнительно гуглить и экспериментировать, чтобы в итоге все заработало. Таким образом, эта статья пытается восполнить пробелы в официальных документах, чтобы новичкам было проще разобраться. Поехали!
Подготовка
Подходящая среда Linux. Если вы работаете на Windows / macOS, запустите полноценную виртуальную машину Linux с гипервизором VirtualBox или VMware. WSL2 на Windows может работать или не работать, и в этой статье не будет поддерживаться.
В качестве эталонного дистрибутива используется Ubuntu 22.04. Возможно, вам придется адаптировать инструкции и команды соответствующим образом, если вы используете другой дистрибутив Linux. Или, чтобы избежать лишних хлопот, все равно запустите виртуальную машину Ubuntu.
Предполагается, что вы уже хорошо знакомы с командами и администрированием Linux. Если вы получите ошибку типа gpg2: command not found
на полпути, от вас будут ожидать, что вы догадаетесь sudo apt install gnupg2
вместо того, чтобы жаловаться 😉
Настройка хоста, кросс-компиляция Linux и BusyBox для RISC-V
Основная статья: Запуск 64- и 32-битной RISC-V Linux на QEMU
Обновить метаданные репозитория:
$ sudo apt update
Установите зависимости сборки для Linux и BusyBox:
$ sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev
gawk build-essential bison flex texinfo gperf libtool patchutils bc
zlib1g-dev libexpat-dev git
Вам также потребуется установить qemu-system
для эмуляции платы RISC-V virt
:
$ sudo apt install -y qemu-system
Linux
Зайдите на kernel.org и загрузите последнее стабильное ядро или любое другое достаточно свежее ядро по вашему выбору. Например, на момент написания статьи (2022-08-14) последним стабильным ядром является 5.19.1:
$ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.19.1.tar.xz
Получите также соответствующую сигнатуру ядра. Для такой важной вещи, как ядро ОС, лучше всего проверить его подлинность и целостность:
$ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.19.1.tar.sign
Распакуйте tarball ядра (но пока не извлекайте архив):
$ unxz linux-5.19.1.tar.xz
На сайте https://kernel.org/category/signatures.html объясняется, как проверить подпись в tarball ядра. Сначала установите gnupg2
:
$ sudo apt install gnupg2
Теперь импортируйте ключи Линуса Торвальдса и Грега Кроа-Хартмана, создателя Linux и ведущего разработчика ядра (соответственно):
$ gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org
Доверьте импортированные ключи. Замените хэши, показанные ниже, в соответствии с результатами, полученными от предыдущей команды:
$ gpg2 --tofu-policy good 38DBBDC86092693E
$ gpg2 --tofu-policy good 79BE3E4300411886
Теперь проверьте:
$ gpg2 --trust-model tofu --verify linux-5.19.1.tar.sign
Ожидаемый результат:
gpg: assuming signed data in 'linux-5.19.1.tar'
gpg: Signature made Thu Aug 11 11:22:54 2022 UTC
gpg: using RSA key 647F28654894E3BD457199BE38DBBDC86092693E
gpg: Good signature from "Greg Kroah-Hartman <gregkh@kernel.org>" [full]
gpg: gregkh@kernel.org: Verified 1 signatures in the past 0 seconds. Encrypted
0 messages.
Теперь распакуйте tarball:
$ tar xvf linux-5.19.1.tar
Войдите в дерево исходных текстов:
$ pushd linux-5.19.1/
Чтобы выполнить кросс-компиляцию для RISC-V, нам нужен кросс-компилятор. Установите gcc-riscv64-linux-gnu
:
$ sudo apt install -y gcc-riscv64-linux-gnu
Теперь настройте ядро для RISC-V:
$ make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
И соберите его (это может занять некоторое время):
$ make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)
Теперь мы можем покинуть дерево исходников:
$ popd
BusyBox
Перейдите на сайт busybox.net для получения исходного кода BusyBox. Последняя версия на момент написания статьи (2022-08-14) — 1.35.0.
Загрузите сжатый tarball:
$ wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
И хэш SHA256, чтобы проверить его целостность:
$ wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2.sha256
Существует также файл подписи для проверки подписи tarball, но мы не будем рассматривать его здесь.
Проверьте контрольную сумму:
$ sha256sum -c busybox-1.35.0.tar.bz2.sha256
Ожидаемый результат:
busybox-1.35.0.tar.bz2: OK
Распакуйте архив:
$ tar xvf busybox-1.35.0.tar.bz2
Теперь войдите в дерево источника:
$ pushd busybox-1.35.0/
Настройте и соберите для RISC-V — убедитесь, что полученный двоичный файл статически связан:
$ CROSS_COMPILE=riscv64-linux-gnu- LDFLAGS=--static make defconfig
$ CROSS_COMPILE=riscv64-linux-gnu- LDFLAGS=--static make -j$(nproc)
Теперь мы можем покинуть дерево исходных текстов:
$ popd
Подготовка виртуального диска, rootfs
Прежде чем мы сможем загрузить нашу плату virt
, нам нужно подготовить образ диска с корневой файловой системой (rootfs). Корневая файловая система будет в основном предоставлена BusyBox, хотя нам потребуется создать несколько дополнительных каталогов для точек монтирования, скриптов запуска и тому подобного.
Проще всего это сделать с помощью dd
— давайте создадим образ виртуального диска busybox
размером 1 ГБ:
$ dd if=/dev/zero of=busybox bs=1M count=1024
Отформатируйте его с помощью файловой системы ext4
(или другой поддерживаемой файловой системы по вашему выбору):
$ mkfs.ext4 busybox
Создайте точку монтирования rootfs
:
$ mkdir -p rootfs
Теперь смонтируйте наш виртуальный диск в только что созданную директорию монтирования:
$ sudo mount busybox rootfs
Теперь мы можем установить Busybox на этот rootfs:
$ sudo CROSS_COMPILE=riscv64-linux-gnu- LDFLAGS=--static make -C busybox-1.35.0/ install CONFIG_PREFIX=../rootfs
Создайте несколько каталогов для монтирования ключевых файловых систем, таких как procfs
, sysfs
и devtmpfs
для правильной загрузки BusyBox:
$ sudo mkdir -p rootfs/proc rootfs/sys rootfs/dev
Убедитесь, что /etc/fstab
существует, чтобы заглушить предупреждение при выключении питания:
$ sudo mkdir -p rootfs/etc
$ sudo touch rootfs/etc/fstab
Создайте каталог /etc/init.d
для сценариев запуска:
$ sudo mkdir -p rootfs/etc/init.d
BusyBox запускает скрипт /etc/init.d/rcS
при запуске системы. Давайте заполним его и сделаем исполняемым:
$ sudo bash -c "cat > rootfs/etc/init.d/rcS" << EOF
#!/bin/sh
echo "Hello Embedded World!"
echo "Hello RISC-V World!"
mount -t proc proc /proc
mount -t sysfs sysfs /sys
ip addr add 10.0.2.15/24 dev eth0
ip link set dev eth0 up
ip route add default via 10.0.2.2 dev eth0
EOF
$ sudo chmod +x rootfs/etc/init.d/rcS
Размонтируйте наш виртуальный диск:
$ sudo umount rootfs
Теперь перейдем к самому интересному!
Загрузка нашей платы RISC-V virt
Запустим наш эмулятор:
$ qemu-system-riscv64
-nographic
-machine virt
-kernel linux-5.19.1/arch/riscv/boot/Image
-append "root=/dev/vda ro console=ttyS0"
-drive file=busybox,format=raw,id=hd0
-device virtio-blk-device,drive=hd0
-netdev user,id=eth0
-device virtio-net-device,netdev=eth0
Большинство опций покажутся вам знакомыми, если вы следили за нашей предыдущей статьей, поэтому мы просто расскажем о том, что нового:
Поскольку пользовательское пространство BusyBox очень легкое, он должен полностью загрузиться примерно за 1 секунду. Вот что вы должны увидеть:
...
Hello Embedded World!
Hello RISC-V World!
Please press Enter to activate this console.
Нажмите Enter в ответ на запрос. Вы должны попасть в оболочку root.
Давайте поиграем. Просмотрите список запущенных процессов:
# ps aux
Пример вывода:
PID USER TIME COMMAND
1 0 0:00 init
2 0 0:00 [kthreadd]
3 0 0:00 [rcu_gp]
4 0 0:00 [rcu_par_gp]
5 0 0:00 [netns]
6 0 0:00 [kworker/0:0-eve]
7 0 0:00 [kworker/0:0H-ev]
8 0 0:00 [kworker/u2:0-ev]
9 0 0:00 [mm_percpu_wq]
10 0 0:00 [rcu_tasks_trace]
11 0 0:00 [ksoftirqd/0]
12 0 0:00 [rcu_sched]
13 0 0:00 [migration/0]
14 0 0:00 [kworker/0:1-eve]
15 0 0:00 [cpuhp/0]
16 0 0:00 [kdevtmpfs]
17 0 0:00 [inet_frag_wq]
18 0 0:00 [khungtaskd]
19 0 0:00 [oom_reaper]
20 0 0:00 [writeback]
21 0 0:00 [kcompactd0]
22 0 0:00 [kblockd]
23 0 0:00 [ata_sff]
24 0 0:00 [rpciod]
25 0 0:00 [kworker/0:1H-ev]
26 0 0:00 [xprtiod]
27 0 0:00 [kswapd0]
28 0 0:00 [kworker/u2:1-ev]
29 0 0:00 [nfsiod]
30 0 0:00 [uas]
31 0 0:00 [mld]
32 0 0:00 [ipv6_addrconf]
39 0 0:00 [jbd2/vda-8]
40 0 0:00 [ext4-rsv-conver]
48 0 0:00 -/bin/sh
49 0 0:00 init
50 0 0:00 init
51 0 0:00 init
52 0 0:00 ps aux
Это действительно легкая система! Вы даже можете запустить top
для просмотра процессов и загрузки процессора в реальном времени:
# top
Нажмите q
, чтобы выйти.
Давайте посмотрим, сколько памяти мы используем по сравнению с доступной:
# free -m
Пример вывода:
total used free shared buff/cache available
Mem: 108 10 95 0 3 95
Swap: 0 0 0
Опять же, очень легкий — VM имеет около 128MB памяти, а мы использовали только 10MB. Сравните это с сервером Ubuntu 22.04 на RISC-V, который не загружается с 512MB памяти из-за недостаточного количества памяти 😉
Проверьте, сколько дискового пространства используется / доступно:
# df -Th
Пример вывода:
Filesystem Type Size Used Available Use% Mounted on
/dev/root ext4 973.4M 1.7M 904.5M 0% /
devtmpfs devtmpfs 53.0M 0 53.0M 0% /dev
Мы выделили 1 ГБ для нашего виртуального диска, а корневая файловая система BusyBox занимает менее 2 МБ.
Давайте также протестируем сеть. Попробуйте пинговать хост:
# ping -c5 10.0.2.2
Пример вывода:
PING 10.0.2.2 (10.0.2.2): 56 data bytes
64 bytes from 10.0.2.2: seq=0 ttl=255 time=16.085 ms
64 bytes from 10.0.2.2: seq=1 ttl=255 time=3.775 ms
64 bytes from 10.0.2.2: seq=2 ttl=255 time=3.810 ms
64 bytes from 10.0.2.2: seq=3 ttl=255 time=3.770 ms
64 bytes from 10.0.2.2: seq=4 ttl=255 time=4.322 ms
--- 10.0.2.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 3.770/6.352/16.085 ms
Отлично! Давайте посмотрим, можем ли мы связаться с серверами из Интернета. Вот публичный IP-адрес для google.com: 142.250.204.46
:
# ping -c5 142.250.204.46
Пример вывода:
PING 142.250.204.46 (142.250.204.46): 56 data bytes
64 bytes from 142.250.204.46: seq=0 ttl=255 time=15.262 ms
64 bytes from 142.250.204.46: seq=1 ttl=255 time=8.313 ms
64 bytes from 142.250.204.46: seq=2 ttl=255 time=8.136 ms
64 bytes from 142.250.204.46: seq=3 ttl=255 time=11.132 ms
64 bytes from 142.250.204.46: seq=4 ttl=255 time=8.752 ms
--- 142.250.204.46 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 8.136/10.319/15.262 ms
Давайте также посмотрим информацию о процессоре через procfs
:
# cat /proc/cpuinfo
Выход:
processor : 0
hart : 0
isa : rv64imafdc
mmu : sv57
Итак, наша эмулируемая плата имеет одно процессорное ядро RISC-V с одним аппаратным потоком (hart), и процессорное ядро поддерживает спецификацию RV64IMAFDC ISA, где «IMAFD» можно упростить до просто «G», чтобы получить RV64GC («G» для общих расширений, «C» для сжатых инструкций). Вы можете прочитать больше о спецификации RISC-V ISA на GitHub, которая является очень модульной с минимальной базовой ISA плюс множество дополнительных расширений.
Поиграйте еще немного, затем выключите питание платы:
# poweroff
Вот и все — поздравляем! Вы успешно скомпилировали собственное ядро Linux и минимальное пользовательское пространство BusyBox, и загрузили его на виртуальной плате RISC-V virt
с QEMU.
Следующие шаги
Если эта статья заставила вас жаждать большего, вот несколько вещей, которые вы можете попробовать для продолжения вашего приключения. Список ни в коем случае не является исчерпывающим:
- Если вы немного поиграли, вы могли заметить, что разрешение DNS не работает внутри платы. Попробуйте выяснить причину и устранить ее
- Попробуйте следовать этой статье (или официальной документации по RISC-V) с реальной, физической RISC-V SoC. В конце концов, физическое оборудование — это реальная вещь.
- Если вам удастся загрузить свой собственный встроенный Linux на физическом оборудовании RISC-V, попробуйте сделать с ним что-нибудь полезное, например, превратить его в IoT-проект как часть умного дома.