برنامه نویسی

اجرای Raspberry Pi OS در یک Docker Container

Summarize this content to 400 words in Persian Lang

مقدمه

گاهی اوقات، آزمایش کار بر روی سیستم عامل Raspberry Pi بدون اجرای آن بر روی سخت افزار واقعی بسیار آسان تر است. مواردی مانند Ansible Playbooks یا خوشه Kubernetes را در بیشتر موارد می توان در یک محیط مجازی آزمایش کرد.

تعداد زیادی آموزش و سایر تصاویر Docker وجود دارد که سیستم عامل Raspberry Pi را با استفاده از QEMU اجرا می کنند، اما متأسفانه، همه آنها از تصویر سیستم عامل در زمان اجرا به عنوان کارت SD استفاده می کنند. از این رو، آنها از نصب حجم برای به اشتراک گذاری فایل سیستم پشتیبانی نمی کنند.

تصویر

واضح ترین راه حل استخراج تصویر سیستم عامل و اشتراک گذاری پوشه استخراج شده با ماشین مجازی است. خوشبختانه، QEMU چنین گزینه‌ای را در اختیار شما قرار می‌دهد -virtfs پرچم:

-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید اکنون سعی کنیم تمام فایل های سیستم عامل را از یک توزیع سیستم عامل Raspberry Pi دانلود و استخراج کنیم:

curl https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz \
| unxz -c – >/tmp/sd.img

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تصویر استخراج شده را می توان با بازرسی کرد fdisk دستور:

$ fdisk -l /tmp/sd.img
Disk /tmp/sd.img: 2732 MB, 2864709632 bytes, 5595136 sectors
43712 cylinders, 4 heads, 32 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/tmp/sd.img1 64,0,1 1023,3,32 8192 1056767 1048576 512M c Win95 FAT32 (LBA)
/tmp/sd.img2 1023,3,32 1023,3,32 1056768 5595135 4538368 2216M 83 Linux

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همانطور که می بینیم، تصویر دارای دو پارتیشن است. اولین مورد، پارتیشن بوت، از نوع خود است FAT32و دومی از نوع است ext4.

ما می توانیم آن پارتیشن ها را با استفاده از loop دستگاه با mount، sfdisk، و jq در یک خط:

mount -o loop,offset=”$(sfdisk -J /tmp/sd.img | jq ‘.partitiontable.sectorsize * .partitiontable.partitions[1].start’)” /tmp/sd.img /tmp/sd

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ترکیب همه آنها با هم در یک Dockerfile چیزی شبیه به این خواهد بود:

# syntax=docker/dockerfile:1.3-labs
FROM alpine:latest AS image

ADD https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz /sd.img.xz

RUN –security=insecure \
apk add –no-cache –virtual=.tools \
jq \
sfdisk \
&& unxz -ck /sd.img.xz >/tmp/sd.img \
&& mkdir -p /tmp/sd \
&& mount -o loop,offset=”$(sfdisk -J /tmp/sd.img | jq ‘.partitiontable.sectorsize * .partitiontable.partitions[1].start’)” /tmp/sd.img /tmp/sd \
&& mount -o loop,offset=”$(sfdisk -J /tmp/sd.img | jq ‘.partitiontable.sectorsize * .partitiontable.partitions[0].start’)” /tmp/sd.img /tmp/sd/boot/firmware \
&& cp -pr /tmp/sd /media/sd \
&& umount /tmp/sd/boot/firmware /tmp/sd \
&& rm -rf /tmp/sd /tmp/sd.img \
&& apk del .tools

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نصب ولوم ها به یک حالت ممتاز نیاز دارد. به همین دلیل است که ما نیاز داریم –security=insecure پرچم بعد از RUN دستورالعمل، که فقط در مشخصات آزمایشگاهی موجود است.

هسته

حال، اگر بخواهیم تصویر استخراج شده را با استفاده از QEMU اجرا کنیم:

qemu-system-aarch64 \
-serial mon:stdio \
-nographic \
-no-reboot \
-machine virt \
-cpu cortex-a72 \
-m 1G \
-smp 4 \
-device virtio-net-device,netdev=net0 \
-netdev user,id=net0 \
-kernel /media/sd/boot/firmware/kernel8.img \
-virtfs local,id=boot,mount_tag=boot,multidevs=remap,path=/media/sd/boot/firmware,security_model=none \
-virtfs local,id=root,mount_tag=root,multidevs=remap,path=/media/sd,security_model=none \
-append “console=ttyAMA0,115200 root=root rootflags=cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L rootfstype=9p rootwait”

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هسته یک وحشت ایجاد می کند زیرا نمی تواند ریشه نوع را نصب کند 9p:

[ 1.144617] Disabling rootwait; root= is invalid.
[ 1.153539] VFS: Cannot open root device “root” or unknown-block(0,0): error -19
[ 1.154459] Please append a correct “root=” boot option; here are the available partitions:

[ 1.156259] List of all bdev filesystems:
[ 1.156335] ext3
[ 1.156346] ext2
[ 1.156386] ext4
[ 1.156414] vfat
[ 1.156440] msdos
[ 1.156469] f2fs
[ 1.156497] [ 1.156625] Kernel panic – not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حتی اگر 9pfs پشتیبانی در هسته لینوکس وجود دارد، هسته سیستم عامل Raspberry Pi با پرچم های مربوطه ساخته نشده است. بیایید سعی کنیم آن را با بازسازی هسته خود برطرف کنیم:

FROM ubuntu:latest AS kernel

ENV ARCH=arm64
ENV CROSS_COMPILE=aarch64-linux-gnu-

WORKDIR /tmp/kernel

ADD https://github.com/raspberrypi/linux/archive/refs/tags/stable_20241008.tar.gz /kernel.tar.gz

RUN tar –strip-components=1 -xzf /kernel.tar.gz \
&& apt-get update \
&& apt-get install –mark-auto –no-install-recommends -y \
bc \
bison \
flex \
gcc \
gcc-aarch64-linux-gnu \
libc6-dev \
libc6-dev-arm64-cross \
libssl-dev \
make \
&& make O=/tmp/build defconfig \
&& scripts/config –file /tmp/build/.config \
–set-val CONFIG_9P_FS y \
–set-val CONFIG_9P_FS_POSIX_ACL y \
–set-val CONFIG_9P_FS_SECURITY y \
–set-val CONFIG_NETWORK_FILESYSTEMS y \
–set-val CONFIG_NET_9P y \
–set-val CONFIG_NET_9P_VIRTIO y \
–set-val CONFIG_PCI y \
–set-val CONFIG_PCI_HOST_COMMON y \
–set-val CONFIG_PCI_HOST_GENERIC y \
–set-val CONFIG_VIRTIO_PCI y \
–set-val CONFIG_VIRTIO_BLK y \
–set-val CONFIG_VIRTIO_NET y \
&& make O=/tmp/build -j 3 Image.gz \
&& rm -rf /tmp/kernel \
&& mkdir -p /media/sd/boot/firmware \
&& cp /tmp/build/arch/$ARCH/boot/Image.gz /media/sd/boot/firmware/kernel8.img

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیکربندی

اکنون، دستور بالا باید سیستم عامل را بوت کند، که هنوز به طور کامل مقداردهی اولیه نمی شود:

[FAILED] Failed to start systemd-re…ount Root and Kernel File Systems.
See ‘systemctl status systemd-remount-fs.service’ for details.
[ TIME ] Timed out waiting for device /dev/disk/by-partuuid/385cce61-01.

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این به دلیل سوابق نادرست در /etc/fstab همانطور که ما فایل سیستم را ارائه می دهیم 9pfs. که می توان با بازنویسی پیش پخت برطرف شد /etc/fstab:

COPY <
proc /proc proc defaults 0 0
boot /boot/firmware 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 2
root / 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 1
EOF

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همچنین به روز رسانی گزینه های بوت در آن منطقی است cmdline.txt بنابراین می توانیم از این فایل مجددا استفاده کنیم و در نتیجه رفتار Raspberry Pi را شبیه سازی کنیم:

COPY <
console=ttyAMA0,115200 init=/usr/lib/raspberrypi-sys-mods/firstboot root=root rootflags=cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L rootfstype=9p rootwait
EOF

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون، در نهایت می‌توانیم سیستم‌عامل را راه‌اندازی کنیم و وارد شویم. با این حال، چند سرویس هنوز در مقداردهی اولیه نیستند:

[FAILED] Failed to start rpi-eeprom…k for Raspberry Pi EEPROM updates.
[FAILED] Failed to start resize2fs_…root filesystem to fill partition.

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

از آنجایی که ما در یک محیط مجازی در حال اجرا هستیم، نیازی به اجرا نیست rpi-eeprom-update.service که در حال به روز رسانی سیستم عامل Raspberry Pi است. سرویس دوم سعی می‌کند سیستم فایل ریشه را گسترش دهد، اما از آنجایی که ما از طریق 9P به آن دسترسی داریم، شکست می‌خورد. بیایید هر دوی آنها را غیرفعال کنیم:

RUN rm \
/media/sd/etc/init.d/resize2fs_once \
/media/sd/etc/systemd/system/multi-user.target.wants/rpi-eeprom-update.service

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سخت افزار

QEMU شبیه سازی بردهای Raspberry Pi خارج از جعبه را فراهم می کند. متأسفانه، هنگام اجرا از Docker چندان کارآمد نیست، به خصوص که داکر در یک محیط مجازی در MacOS نیز اجرا می شود. علاوه بر این، از دستگاه های PCI که برای اشتراک فایل سیستم مورد نیاز هستند، پشتیبانی نمی کند.

در عوض، می‌توانیم از پلتفرم عمومی استفاده کنیم virt، که برای اجرا در یک محیط مجازی مانند Docker برای مک بهینه شده است.

در این مورد، ما باید به صراحت CPU را مشخص کنیم (-smp 4) و رم (-m 1G) منابع موجود برای ماشین مجازی. همچنین منطقی است که به آن پایبند باشید cortex-a72 CPU همانطور که در یک برد واقعی استفاده می شود.

نقطه ورود

بخش آخر دریافت پشتیبانی برای cmdline.txt همانطور که سیستم عامل Raspberry Pi به این فایل متکی است. در این لحظه، محتویات این فایل با راه اندازی مجدد کانتینر بازیابی می شود. به طوری که، اگر ما را تغییر دهیم -append پارامتر برای خواندن از فایل، کمک زیادی نمی کند:

-append “$(cat /media/sd/boot/firmware/cmdline.txt)”

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

دستور QEMU باید در یک اسکریپت پیچیده شود تا ماشین مجازی در راه اندازی مجدد نرم افزاری راه اندازی مجدد شود. در این مورد، سیستم عامل یا یک کاربر می تواند آن را تغییر دهد cmdline.txt و این تغییرات را در ظرف فعلی اعمال کنید.

هیچ راهی برای رهگیری راه اندازی مجدد کاربر و متمایز ساختن آنها از وحشت هسته وجود ندارد. تنها گزینه ممکن خواندن پورت سریال و راه اندازی مجدد فرآیند زمانی که هسته بازده است reboot: Restarting system. که با استفاده از آن قابل دستیابی است expect:

COPY –chmod=755 <<‘EOF’ /usr/local/bin/rpi
#!/usr/bin/expect

while {true} {
set reboot false

spawn -noecho qemu-system-aarch64 \
-serial mon:stdio \
-nographic \
-no-reboot \
-machine virt \
-cpu cortex-a72 \
-m 1G \
-smp 4 \
-device virtio-net-device,netdev=net0 \
-netdev user,id=net0 \
-kernel /media/sd/boot/firmware/kernel8.img \
-virtfs local,id=boot,mount_tag=boot,multidevs=remap,path=/media/sd/boot/firmware,security_model=none \
-virtfs local,id=root,mount_tag=root,multidevs=remap,path=/media/sd,security_model=none \
-append “[exec cat /media/sd/boot/firmware/cmdline.txt] panic=-1”

interact {
-o -reset

-nobuffer “reboot: Restarting system” {
set reboot true
expect eof
return
}
}

lassign [wait] pid spawn_id os_error exit_code

if {$exit_code != 0 || !$reboot} {
exit $exit_code
}
}
EOF

CMD [“rpi”]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اسکریپت بالا، ما آن را اصلاح کردیم -append استدلال برای اضافه کردن panic=-1 پارامتر هسته در این مورد، QEMU با راه اندازی مجدد خارج می شود و expect اسکریپت دوباره خوانده می شود cmdline.txt. بنابراین سیستم‌عامل یا کاربر می‌تواند تغییراتی را در آن فایل انجام دهد و سپس راه‌اندازی مجدد شود تا تغییرات را درست مانند برد معمولی Raspberry Pi انجام دهد.

نتایج

در اینجا Dockerfile کامل شامل تمام دستورالعمل های بالا آمده است:

Dockerfile

# syntax=docker/dockerfile:1.3-labs
FROM alpine:latest AS image

ADD https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz /sd.img.xz

RUN –security=insecure \
apk add –no-cache –virtual=.tools \
jq \
sfdisk \
&& unxz -ck /sd.img.xz >/tmp/sd.img \
&& mkdir -p /tmp/sd \
&& mount -o loop,offset=”$(sfdisk -J /tmp/sd.img | jq ‘.partitiontable.sectorsize * .partitiontable.partitions[1].start’)” /tmp/sd.img /tmp/sd \
&& mount -o loop,offset=”$(sfdisk -J /tmp/sd.img | jq ‘.partitiontable.sectorsize * .partitiontable.partitions[0].start’)” /tmp/sd.img /tmp/sd/boot/firmware \
&& cp -pr /tmp/sd /media/sd \
&& umount /tmp/sd/boot/firmware /tmp/sd \
&& rm -rf /tmp/sd /tmp/sd.img \
&& apk del .tools

RUN rm \
/media/sd/etc/init.d/resize2fs_once \
/media/sd/etc/systemd/system/multi-user.target.wants/rpi-eeprom-update.service

COPY <
proc /proc proc defaults 0 0
boot /boot/firmware 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 2
root / 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 1
EOF

COPY <
console=ttyAMA0,115200 init=/usr/lib/raspberrypi-sys-mods/firstboot root=root rootflags=cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L rootfstype=9p rootwait
EOF

FROM ubuntu:latest AS kernel

ENV ARCH=arm64
ENV CROSS_COMPILE=aarch64-linux-gnu-

WORKDIR /tmp/kernel

ADD https://github.com/raspberrypi/linux/archive/refs/tags/stable_20241008.tar.gz /kernel.tar.gz

RUN tar –strip-components=1 -xzf /kernel.tar.gz \
&& apt-get update \
&& apt-get install –mark-auto –no-install-recommends -y \
bc \
bison \
flex \
gcc \
gcc-aarch64-linux-gnu \
libc6-dev \
libc6-dev-arm64-cross \
libssl-dev \
make \
&& make O=/tmp/build defconfig \
&& scripts/config –file /tmp/build/.config \
–set-val CONFIG_9P_FS y \
–set-val CONFIG_9P_FS_POSIX_ACL y \
–set-val CONFIG_9P_FS_SECURITY y \
–set-val CONFIG_NETWORK_FILESYSTEMS y \
–set-val CONFIG_NET_9P y \
–set-val CONFIG_NET_9P_VIRTIO y \
–set-val CONFIG_PCI y \
–set-val CONFIG_PCI_HOST_COMMON y \
–set-val CONFIG_PCI_HOST_GENERIC y \
–set-val CONFIG_VIRTIO_PCI y \
–set-val CONFIG_VIRTIO_BLK y \
–set-val CONFIG_VIRTIO_NET y \
&& make O=/tmp/build -j 3 Image.gz \
&& rm -rf /tmp/kernel \
&& mkdir -p /media/sd/boot/firmware \
&& cp /tmp/build/arch/$ARCH/boot/Image.gz /media/sd/boot/firmware/kernel8.img

FROM alpine:latest

RUN apk add –no-cache \
expect \
qemu-system-aarch64

COPY –from=image /media/sd /media/sd
COPY –from=kernel /media/sd /media/sd
COPY –chmod=755 <<‘EOF’ /usr/local/bin/rpi
#!/usr/bin/expect

while {true} {
set reboot false

spawn -noecho qemu-system-aarch64 \
-serial mon:stdio \
-nographic \
-no-reboot \
-machine virt \
-cpu cortex-a72 \
-m 1G \
-smp 4 \
-device virtio-net-device,netdev=net0 \
-netdev user,id=net0 \
-kernel /media/sd/boot/firmware/kernel8.img \
-virtfs local,id=boot,mount_tag=boot,multidevs=remap,path=/media/sd/boot/firmware,security_model=none \
-virtfs local,id=root,mount_tag=root,multidevs=remap,path=/media/sd,security_model=none \
-append “[exec cat /media/sd/boot/firmware/cmdline.txt] panic=-1”

interact {
-o -reset

-nobuffer “reboot: Restarting system” {
set reboot true
expect eof
return
}
}

lassign [wait] pid spawn_id os_error exit_code

if {$exit_code != 0 || !$reboot} {
exit $exit_code
}
}
EOF

CMD [“rpi”]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برخی از ویژگی‌ها مانند فعال کردن SSH و تغییر رمز عبور پیش‌فرض کاربر را ندارد، اما این ویژگی‌ها را می‌توانید در سایر آموزش‌ها یا اسناد رسمی پیدا کنید.

با وجود این، قبلاً در مخزن اصلی پیاده سازی و آزمایش شده است که من را بر آن داشت تا این مقاله را بنویسم. و البته، یک وجود دارد dokmic/rpi تصویر آماده استفاده منتشر شده در Docker Hub که از هر دو معماری ARM64 و ARM پشتیبانی می کند و دارای چندین گزینه پیکربندی برای سفارشی کردن سیستم عامل Raspberry Pi شبیه سازی شده است.

ترجمه این مقاله فقط با کسب اجازه مجاز است.

مقدمه

گاهی اوقات، آزمایش کار بر روی سیستم عامل Raspberry Pi بدون اجرای آن بر روی سخت افزار واقعی بسیار آسان تر است. مواردی مانند Ansible Playbooks یا خوشه Kubernetes را در بیشتر موارد می توان در یک محیط مجازی آزمایش کرد.

تعداد زیادی آموزش و سایر تصاویر Docker وجود دارد که سیستم عامل Raspberry Pi را با استفاده از QEMU اجرا می کنند، اما متأسفانه، همه آنها از تصویر سیستم عامل در زمان اجرا به عنوان کارت SD استفاده می کنند. از این رو، آنها از نصب حجم برای به اشتراک گذاری فایل سیستم پشتیبانی نمی کنند.

تصویر

واضح ترین راه حل استخراج تصویر سیستم عامل و اشتراک گذاری پوشه استخراج شده با ماشین مجازی است. خوشبختانه، QEMU چنین گزینه‌ای را در اختیار شما قرار می‌دهد -virtfs پرچم:

-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید اکنون سعی کنیم تمام فایل های سیستم عامل را از یک توزیع سیستم عامل Raspberry Pi دانلود و استخراج کنیم:

curl https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz \
| unxz -c - >/tmp/sd.img
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تصویر استخراج شده را می توان با بازرسی کرد fdisk دستور:

$ fdisk -l /tmp/sd.img 
Disk /tmp/sd.img: 2732 MB, 2864709632 bytes, 5595136 sectors
43712 cylinders, 4 heads, 32 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device     Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/tmp/sd.img1    64,0,1      1023,3,32         8192    1056767    1048576  512M  c Win95 FAT32 (LBA)
/tmp/sd.img2    1023,3,32   1023,3,32      1056768    5595135    4538368 2216M 83 Linux
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همانطور که می بینیم، تصویر دارای دو پارتیشن است. اولین مورد، پارتیشن بوت، از نوع خود است FAT32و دومی از نوع است ext4.

ما می توانیم آن پارتیشن ها را با استفاده از loop دستگاه با mount، sfdisk، و jq در یک خط:

mount -o loop,offset="$(sfdisk -J /tmp/sd.img | jq '.partitiontable.sectorsize * .partitiontable.partitions[1].start')" /tmp/sd.img /tmp/sd
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ترکیب همه آنها با هم در یک Dockerfile چیزی شبیه به این خواهد بود:

# syntax=docker/dockerfile:1.3-labs
FROM alpine:latest AS image

ADD https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz /sd.img.xz

RUN --security=insecure \
  apk add --no-cache --virtual=.tools \
    jq \
    sfdisk \
  && unxz -ck /sd.img.xz >/tmp/sd.img \
  && mkdir -p /tmp/sd \
  && mount -o loop,offset="$(sfdisk -J /tmp/sd.img | jq '.partitiontable.sectorsize * .partitiontable.partitions[1].start')" /tmp/sd.img /tmp/sd \
  && mount -o loop,offset="$(sfdisk -J /tmp/sd.img | jq '.partitiontable.sectorsize * .partitiontable.partitions[0].start')" /tmp/sd.img /tmp/sd/boot/firmware \
  && cp -pr /tmp/sd /media/sd \
  && umount /tmp/sd/boot/firmware /tmp/sd \
  && rm -rf /tmp/sd /tmp/sd.img \
  && apk del .tools
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نصب ولوم ها به یک حالت ممتاز نیاز دارد. به همین دلیل است که ما نیاز داریم --security=insecure پرچم بعد از RUN دستورالعمل، که فقط در مشخصات آزمایشگاهی موجود است.

هسته

حال، اگر بخواهیم تصویر استخراج شده را با استفاده از QEMU اجرا کنیم:

qemu-system-aarch64 \
  -serial mon:stdio \
  -nographic \
  -no-reboot \
  -machine virt \
  -cpu cortex-a72 \
  -m 1G \
  -smp 4 \
  -device virtio-net-device,netdev=net0 \
  -netdev user,id=net0 \
  -kernel /media/sd/boot/firmware/kernel8.img \
  -virtfs local,id=boot,mount_tag=boot,multidevs=remap,path=/media/sd/boot/firmware,security_model=none \
  -virtfs local,id=root,mount_tag=root,multidevs=remap,path=/media/sd,security_model=none \
  -append "console=ttyAMA0,115200 root=root rootflags=cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L rootfstype=9p rootwait"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هسته یک وحشت ایجاد می کند زیرا نمی تواند ریشه نوع را نصب کند 9p:

[    1.144617] Disabling rootwait; root= is invalid.
[    1.153539] VFS: Cannot open root device "root" or unknown-block(0,0): error -19
[    1.154459] Please append a correct "root=" boot option; here are the available partitions:
...
[    1.156259] List of all bdev filesystems:
[    1.156335]  ext3
[    1.156346]  ext2
[    1.156386]  ext4
[    1.156414]  vfat
[    1.156440]  msdos
[    1.156469]  f2fs
[    1.156497] 
[    1.156625] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حتی اگر 9pfs پشتیبانی در هسته لینوکس وجود دارد، هسته سیستم عامل Raspberry Pi با پرچم های مربوطه ساخته نشده است. بیایید سعی کنیم آن را با بازسازی هسته خود برطرف کنیم:

FROM ubuntu:latest AS kernel

ENV ARCH=arm64
ENV CROSS_COMPILE=aarch64-linux-gnu-

WORKDIR /tmp/kernel

ADD https://github.com/raspberrypi/linux/archive/refs/tags/stable_20241008.tar.gz /kernel.tar.gz

RUN tar --strip-components=1 -xzf /kernel.tar.gz \
  && apt-get update \
  && apt-get install --mark-auto --no-install-recommends -y \
    bc \
    bison \
    flex \
    gcc \
    gcc-aarch64-linux-gnu \
    libc6-dev \
    libc6-dev-arm64-cross \
    libssl-dev \
    make \
  && make O=/tmp/build defconfig \
  && scripts/config --file /tmp/build/.config \
    --set-val CONFIG_9P_FS y \
    --set-val CONFIG_9P_FS_POSIX_ACL y \
    --set-val CONFIG_9P_FS_SECURITY y \
    --set-val CONFIG_NETWORK_FILESYSTEMS y \
    --set-val CONFIG_NET_9P y \
    --set-val CONFIG_NET_9P_VIRTIO y \
    --set-val CONFIG_PCI y \
    --set-val CONFIG_PCI_HOST_COMMON y \
    --set-val CONFIG_PCI_HOST_GENERIC y \
    --set-val CONFIG_VIRTIO_PCI y \
    --set-val CONFIG_VIRTIO_BLK y \
    --set-val CONFIG_VIRTIO_NET y \
  && make O=/tmp/build -j 3 Image.gz \
  && rm -rf /tmp/kernel \
  && mkdir -p /media/sd/boot/firmware \
  && cp /tmp/build/arch/$ARCH/boot/Image.gz /media/sd/boot/firmware/kernel8.img
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیکربندی

اکنون، دستور بالا باید سیستم عامل را بوت کند، که هنوز به طور کامل مقداردهی اولیه نمی شود:

[FAILED] Failed to start systemd-re…ount Root and Kernel File Systems.
See 'systemctl status systemd-remount-fs.service' for details.
[ TIME ] Timed out waiting for device /dev/disk/by-partuuid/385cce61-01.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این به دلیل سوابق نادرست در /etc/fstab همانطور که ما فایل سیستم را ارائه می دهیم 9pfs. که می توان با بازنویسی پیش پخت برطرف شد /etc/fstab:

COPY <
proc /proc proc defaults 0 0
boot /boot/firmware 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 2
root / 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 1
EOF
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همچنین به روز رسانی گزینه های بوت در آن منطقی است cmdline.txt بنابراین می توانیم از این فایل مجددا استفاده کنیم و در نتیجه رفتار Raspberry Pi را شبیه سازی کنیم:

COPY <
console=ttyAMA0,115200 init=/usr/lib/raspberrypi-sys-mods/firstboot root=root rootflags=cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L rootfstype=9p rootwait
EOF
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون، در نهایت می‌توانیم سیستم‌عامل را راه‌اندازی کنیم و وارد شویم. با این حال، چند سرویس هنوز در مقداردهی اولیه نیستند:

[FAILED] Failed to start rpi-eeprom…k for Raspberry Pi EEPROM updates.
[FAILED] Failed to start resize2fs_…root filesystem to fill partition.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

از آنجایی که ما در یک محیط مجازی در حال اجرا هستیم، نیازی به اجرا نیست rpi-eeprom-update.service که در حال به روز رسانی سیستم عامل Raspberry Pi است. سرویس دوم سعی می‌کند سیستم فایل ریشه را گسترش دهد، اما از آنجایی که ما از طریق 9P به آن دسترسی داریم، شکست می‌خورد. بیایید هر دوی آنها را غیرفعال کنیم:

RUN rm \
  /media/sd/etc/init.d/resize2fs_once \
  /media/sd/etc/systemd/system/multi-user.target.wants/rpi-eeprom-update.service
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سخت افزار

QEMU شبیه سازی بردهای Raspberry Pi خارج از جعبه را فراهم می کند. متأسفانه، هنگام اجرا از Docker چندان کارآمد نیست، به خصوص که داکر در یک محیط مجازی در MacOS نیز اجرا می شود. علاوه بر این، از دستگاه های PCI که برای اشتراک فایل سیستم مورد نیاز هستند، پشتیبانی نمی کند.

در عوض، می‌توانیم از پلتفرم عمومی استفاده کنیم virt، که برای اجرا در یک محیط مجازی مانند Docker برای مک بهینه شده است.

در این مورد، ما باید به صراحت CPU را مشخص کنیم (-smp 4) و رم (-m 1G) منابع موجود برای ماشین مجازی. همچنین منطقی است که به آن پایبند باشید cortex-a72 CPU همانطور که در یک برد واقعی استفاده می شود.

نقطه ورود

بخش آخر دریافت پشتیبانی برای cmdline.txt همانطور که سیستم عامل Raspberry Pi به این فایل متکی است. در این لحظه، محتویات این فایل با راه اندازی مجدد کانتینر بازیابی می شود. به طوری که، اگر ما را تغییر دهیم -append پارامتر برای خواندن از فایل، کمک زیادی نمی کند:

-append "$(cat /media/sd/boot/firmware/cmdline.txt)"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

دستور QEMU باید در یک اسکریپت پیچیده شود تا ماشین مجازی در راه اندازی مجدد نرم افزاری راه اندازی مجدد شود. در این مورد، سیستم عامل یا یک کاربر می تواند آن را تغییر دهد cmdline.txt و این تغییرات را در ظرف فعلی اعمال کنید.

هیچ راهی برای رهگیری راه اندازی مجدد کاربر و متمایز ساختن آنها از وحشت هسته وجود ندارد. تنها گزینه ممکن خواندن پورت سریال و راه اندازی مجدد فرآیند زمانی که هسته بازده است reboot: Restarting system. که با استفاده از آن قابل دستیابی است expect:

COPY --chmod=755 <<'EOF' /usr/local/bin/rpi
#!/usr/bin/expect

while {true} {
  set reboot false

  spawn -noecho qemu-system-aarch64 \
    -serial mon:stdio \
    -nographic \
    -no-reboot \
    -machine virt \
    -cpu cortex-a72 \
    -m 1G \
    -smp 4 \
    -device virtio-net-device,netdev=net0 \
    -netdev user,id=net0 \
    -kernel /media/sd/boot/firmware/kernel8.img \
    -virtfs local,id=boot,mount_tag=boot,multidevs=remap,path=/media/sd/boot/firmware,security_model=none \
    -virtfs local,id=root,mount_tag=root,multidevs=remap,path=/media/sd,security_model=none \
    -append "[exec cat /media/sd/boot/firmware/cmdline.txt] panic=-1"

  interact {
    -o -reset

    -nobuffer "reboot: Restarting system" {
      set reboot true
      expect eof
      return
    }
  }

  lassign [wait] pid spawn_id os_error exit_code

  if {$exit_code != 0 || !$reboot} {
    exit $exit_code
  }
}
EOF

CMD ["rpi"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اسکریپت بالا، ما آن را اصلاح کردیم -append استدلال برای اضافه کردن panic=-1 پارامتر هسته در این مورد، QEMU با راه اندازی مجدد خارج می شود و expect اسکریپت دوباره خوانده می شود cmdline.txt. بنابراین سیستم‌عامل یا کاربر می‌تواند تغییراتی را در آن فایل انجام دهد و سپس راه‌اندازی مجدد شود تا تغییرات را درست مانند برد معمولی Raspberry Pi انجام دهد.

نتایج

در اینجا Dockerfile کامل شامل تمام دستورالعمل های بالا آمده است:

Dockerfile

# syntax=docker/dockerfile:1.3-labs
FROM alpine:latest AS image

ADD https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz /sd.img.xz

RUN --security=insecure \
  apk add --no-cache --virtual=.tools \
    jq \
    sfdisk \
  && unxz -ck /sd.img.xz >/tmp/sd.img \
  && mkdir -p /tmp/sd \
  && mount -o loop,offset="$(sfdisk -J /tmp/sd.img | jq '.partitiontable.sectorsize * .partitiontable.partitions[1].start')" /tmp/sd.img /tmp/sd \
  && mount -o loop,offset="$(sfdisk -J /tmp/sd.img | jq '.partitiontable.sectorsize * .partitiontable.partitions[0].start')" /tmp/sd.img /tmp/sd/boot/firmware \
  && cp -pr /tmp/sd /media/sd \
  && umount /tmp/sd/boot/firmware /tmp/sd \
  && rm -rf /tmp/sd /tmp/sd.img \
  && apk del .tools

RUN rm \
  /media/sd/etc/init.d/resize2fs_once \
  /media/sd/etc/systemd/system/multi-user.target.wants/rpi-eeprom-update.service

COPY <
proc /proc proc defaults 0 0
boot /boot/firmware 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 2
root / 9p cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L 0 1
EOF

COPY <
console=ttyAMA0,115200 init=/usr/lib/raspberrypi-sys-mods/firstboot root=root rootflags=cache=mmap,msize=104857600,posixacl,trans=virtio,version=9p2000.L rootfstype=9p rootwait
EOF

FROM ubuntu:latest AS kernel

ENV ARCH=arm64
ENV CROSS_COMPILE=aarch64-linux-gnu-

WORKDIR /tmp/kernel

ADD https://github.com/raspberrypi/linux/archive/refs/tags/stable_20241008.tar.gz /kernel.tar.gz

RUN tar --strip-components=1 -xzf /kernel.tar.gz \
  && apt-get update \
  && apt-get install --mark-auto --no-install-recommends -y \
    bc \
    bison \
    flex \
    gcc \
    gcc-aarch64-linux-gnu \
    libc6-dev \
    libc6-dev-arm64-cross \
    libssl-dev \
    make \
  && make O=/tmp/build defconfig \
  && scripts/config --file /tmp/build/.config \
    --set-val CONFIG_9P_FS y \
    --set-val CONFIG_9P_FS_POSIX_ACL y \
    --set-val CONFIG_9P_FS_SECURITY y \
    --set-val CONFIG_NETWORK_FILESYSTEMS y \
    --set-val CONFIG_NET_9P y \
    --set-val CONFIG_NET_9P_VIRTIO y \
    --set-val CONFIG_PCI y \
    --set-val CONFIG_PCI_HOST_COMMON y \
    --set-val CONFIG_PCI_HOST_GENERIC y \
    --set-val CONFIG_VIRTIO_PCI y \
    --set-val CONFIG_VIRTIO_BLK y \
    --set-val CONFIG_VIRTIO_NET y \
  && make O=/tmp/build -j 3 Image.gz \
  && rm -rf /tmp/kernel \
  && mkdir -p /media/sd/boot/firmware \
  && cp /tmp/build/arch/$ARCH/boot/Image.gz /media/sd/boot/firmware/kernel8.img

FROM alpine:latest

RUN apk add --no-cache \
  expect \
  qemu-system-aarch64

COPY --from=image /media/sd /media/sd
COPY --from=kernel /media/sd /media/sd
COPY --chmod=755 <<'EOF' /usr/local/bin/rpi
#!/usr/bin/expect

while {true} {
  set reboot false

  spawn -noecho qemu-system-aarch64 \
    -serial mon:stdio \
    -nographic \
    -no-reboot \
    -machine virt \
    -cpu cortex-a72 \
    -m 1G \
    -smp 4 \
    -device virtio-net-device,netdev=net0 \
    -netdev user,id=net0 \
    -kernel /media/sd/boot/firmware/kernel8.img \
    -virtfs local,id=boot,mount_tag=boot,multidevs=remap,path=/media/sd/boot/firmware,security_model=none \
    -virtfs local,id=root,mount_tag=root,multidevs=remap,path=/media/sd,security_model=none \
    -append "[exec cat /media/sd/boot/firmware/cmdline.txt] panic=-1"

  interact {
    -o -reset

    -nobuffer "reboot: Restarting system" {
      set reboot true
      expect eof
      return
    }
  }

  lassign [wait] pid spawn_id os_error exit_code

  if {$exit_code != 0 || !$reboot} {
    exit $exit_code
  }
}
EOF

CMD ["rpi"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برخی از ویژگی‌ها مانند فعال کردن SSH و تغییر رمز عبور پیش‌فرض کاربر را ندارد، اما این ویژگی‌ها را می‌توانید در سایر آموزش‌ها یا اسناد رسمی پیدا کنید.

با وجود این، قبلاً در مخزن اصلی پیاده سازی و آزمایش شده است که من را بر آن داشت تا این مقاله را بنویسم. و البته، یک وجود دارد dokmic/rpi تصویر آماده استفاده منتشر شده در Docker Hub که از هر دو معماری ARM64 و ARM پشتیبانی می کند و دارای چندین گزینه پیکربندی برای سفارشی کردن سیستم عامل Raspberry Pi شبیه سازی شده است.

ترجمه این مقاله فقط با کسب اجازه مجاز است.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا