호스트 머신으로 M1 MacOS를 사용한다. homebrew로 설치한 QEMU를 이용한다.
MacOS에는 Parallels Desktop 설치하여 Ubuntu를 설치하였다.
MacOS Version: Ventura 13.3
Ubuntu Version: Ubuntu 22.04.2 LTS
Linux Kernel, u-boot, busybox 빌드와 initramfs 구축은 Ubuntu에서 수행한다.
apt에서 설치하는 방법과 crosstool-ng로 직접 만드는 방법이 있다.
aarch64-linux-gnu-*** 를 사용할 수 있게 된다.
sudo apt update
sudo apt install gcc-aarch64-linux-gnu
리눅스 커널 소스를 본가인 torvals에서 다운로드 받는다.
git clone --depth 1 -b v6.2 https://github.com/torvalds/linux.git
cd linux
리눅스 커널 소스를 QEMU용으로 빌드하기 위해서 소스코드에 준비되어 있는 config를 이용하는 것이 편하다.
arch/arm64/configs 안에 defconfig가 저장되어 있다.
make 를 입력/실행하면 .config에 설정을 머지할 수 있다.
리눅스 원조 torvalds에는 defconfig 밖에 없다. Rust for Linux에서 사용하는 qemu-busybox-min.config를 arch/arm64/configs에 저장한다.
## qemu-busybox-min.config
CONFIG_PCI_HOST_GENERIC=y
CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_GPIOLIB=y
CONFIG_GPIO_PL061=y
CONFIG_KEYBOARD_GPIO=y
CONFIG_CMDLINE="console=ttyAMA0 nokaslr rdinit=/sbin/init"
defconfig와 추가 config 파일로 리눅스 빌드용 .config를 만든다.
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make qemu-busybox-min.config
리눅스 커널을 gdb로 디버그를 하려 한다면, make menuconfig 에서 DEBUG_KERNEL와 DEBUG_INFO를 체크하고, RELOCATABLE를 무효한다.
설정이 완료되면, build한다.
make -j$(nproc)
빌드가 완료되면 arch/arm64/boot/에 커널 이미지(Image,Image.gz)가 생성된다.
소스를 다운로드 받은 후, 크로스 컴파일로 빌드한다.
git clone https://github.com/u-boot/u-boot.git
cd u-boot
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu
make defconfig
make -j$(nproc)
u-boot 폴더 밑에 생성된 파일 중 u-boot.bin을 이용한다.
busybox는 대표적인 리눅스 CLI명령를 저용량으로 대용하는 도구이다.
쉘을 포함한 리눅스 동작이 가능한 파일시스템을 메모리에 로드하여 커널을 부팅하면 루트 파일시스템을 찾아 대체할 수 있다. 외부저장장치에 있거나 네트워크 경로에 있는 파일시스템을 새로운 root로 전환할 수 있다.
github에서 소스를 다운로드 받고, 리눅스 빌드와 동일하게 크로스 컴파일과 아키텍쳐를 설정하여 make install을 수행한다. 정상 빌드가 끝나면 ./_install 디텍터리에 bin,sbin 등 필요한 디렉터리 안에 바이너리가 생성된다.
git clone https://github.com/mirror/busybox.git
cd busybox
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
config까지 완료되면 make menuconfig로 추가해야 할 설정이 있다.
Settins-->Build Static binary(no share libs)를 설정해야 라이브러리를 배치하는 수고를 덜 수 있다.
리눅스 init process로 sbin/init를 실행하기 위해서 /etc/init.d/rcS 파일을 작성해야 한다. 또한 터미널 tty를 이용하기 위한 디바이스 노드도 작성한다.
#!/bin/sh
#/etc/init.d/rcS
mount -v --bind /dev /dev
mount -v --bind /dev/pts /dev/pts
mount -vt proc proc /proc
mount -vt sysfs sysfs /sys
mount -vt tmpfs tmpfs /run
/sbin/mdev -s
rcS파일은 chmod +x로 실행 가능하도록 만들어 준다.
mkdir proc sys dev run etc dev/pts etc/init.d
sudo mknod -m 666 dev/null c 1 3
sudo mknod -m 600 dev/console c 5 1
cpio로 디렉터리를 아카이브 파일로 변환해야 한다.
make install로 생성된 _install 디렉터리로 이동하여 new ASCII포맷으로 아카이브 생성한다.
find . | cpio -H newc -ov --owner root:root -F ../initramfs.cpio
gzip으로 압축한다.
cd ../
gzip initramfs.cpio
initramfs.cpio.gz를 u-boot에서 인식할 수 있도록 u-boot/tools/mkimage 툴을 이용하여 파일을 변환한다. u-boot대응 이미지파일의 이름은 uRamdisk로 한다.
./u-boot/tools/mkimage -A arm64 -O linux -T ramdisk -d initramfs.cpio.gz uRamdisk
파일 시스템 구성
문제 없이 진행했다면 아래의 파일을 가지고 있을 것이다.
리눅스 커널 이미지 (Image)
램디스크 u-boot용 이미지(uRamdisk)
u-boot 파일(u-boot.bin)
QEMU(u-boot)에는 두가지 파일 시스템을 연결해야 한다. u-boot 부트 파일 시스템과 리눅스 전용 루트 파일시스템이다. 이 두가지 파일 시스템을 하나의 이미지 파일을 파티션하여 구성할 수 있다.
sd카드 인터페이스로 인식할 수 있도록 1024MB 용량으로 파일을 만든다.
dd if=/dev/zero of=sdcard.img bs=1M count=1024
sdcard.img 안에 두 개의 파일 시스템을 구성한다.
fdisk sdcard.img
Command (m for help): n
Partition number (1-4, default 1): 1
First sector (2048-xxxxxx, default 2048): 2048
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-xxxxxx, default xxxxxxx): +100M
Created a new partition 1 of type 'Linux' and of size 100 MiB.
Partition #1 contains a vfat signature.
Do you want to remove the signature? [Y]es/[N]o: Y
The signature will be removed by a write command.
Command (m for help): n
Partition type
p primary (1 primary, 0 extended, 3 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (2-4, default 2):
First sector (206848-xxxxxx, default 206848):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (206848-xxxxxxx, default xxxxxxx):
Created a new partition 2 of type 'Linux' and of size 1.8 GiB.
Partition #2 contains a ext4 signature.
Do you want to remove the signature? [Y]es/[N]o: Y
The signature will be removed by a write command.
Command (m for help): t
Partition number (1,2, default 2): 1
Hex code (type L to list all codes): b
Changed type of partition 'Linux' to 'W95 FAT32'.
Command (m for help): p
Disk sdcard.img: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xfa869c97
Device Boot Start End Sectors Size Id Type
sdcard.img1 2048 206847 204800 100M b W95 FAT32
sdcard.img2 206848 2097151 1890304 923M 83 Linux
Command (m for help): w
# FAT32 for boot partition
$ sudo mkfs.vfat -F 32 -n boot /dev/loop5p1
# ext4 for root partition
$ sudo mkfs.ext4 -L root /dev/loop5p2
sdcard.img 파일의 각각의 파티션에 파일 및 디렉터리를 추가한다.
W95 FAT32(100M)에 Image(리눅스 커널 이미지), uRamdisk(initramfs u-boot용 이미지), boot.src(u-boot 스크립트 u-boot용 이미지, 추후 설명)를 추가한다.
Linux(923M)에 리눅스 루트 파일시스템을 추가한다. 리눅스 루트 파일시스템을 우분투에서 buildroot를 크로스 컴파일로 빌드하면 만들 수 있다. initramfs를 만드는 과정에서 busybox에서 만든 _install를 리눅스 루트파일시스템으로 사용할 수 있지만 buildroot은 다양한 바이너리와 라이브러리로 파일시스템을 만들 수 있다는 장점이 있다. 용량이 크기 때문에 initramfs에는 적합하지 않기 때문에 용량이 큰 외부저장장치로 사용한다.
sdcard.img를 /dev/loop(n)에 추가한다.
sudo losetup -P -f --show /media/psf/Parallels/sim-aarch64/sdcard.img
위의 명령어를 실행하면 sdcard.img 파일이 연결된 디바이스가 표시된다.
예: /dev/loop5
파티션 2개이기 때문에 /dev/loop5p1와 loop5p2로 구성되어 있다는 것을 확인할 수 있다.
sudo fdisk /dev/loop5
Command (m for help): p
Device Boot Start End Sectors Size Id Type
/dev/loop5p1 2048 206847 204800 100M b W95 FAT32
/dev/loop5p2 206848 2097151 1890304 923M 83 Linux
각각 부트, 루트로 마운트한다.
mkdir -pv /mnt/{boot,root}
sudo mount /dev/loop5p1 /mnt/boot
sudo mount /dev/loop5p2 /mnt/root
/mnt/boot와 /mnt/root에 해당 파일을 복사한다.
파일 복사 완료 후 해제한다.
sudo umount /mnt/boot
sudo umount /mnt/root
sudo losetup -d /dev/loop5
# save as boot_cmd.txt
fatload virtio 0:1 ${kernel_addr_r} Image
fatload virtio 0:1 ${ramdisk_addr_r} uRamdisk
setenv bootargs "console=ttyAMA0 nokaslr rdinit=/sbin/init"
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr} 첫번째 줄은 Image파일을 kernel_addr_r 메모리 주소에 로드한다. kernel_addr_r 메모리 주소는 u-boot에서 아래와 같이 입력하면 알 수 있다.md ${kernel_addr_r} u-boot는 다양한 타겟을 지원하는데 각각의 보드에 맞는 메모리 주소를 가지고 있다.#!/bin/busybox sh
echo "Mounting Proc and Sysfs"
# Mount the /proc and /sys filesystems.
mount -t devtmpfs devtempfs /dev
mount -t proc none /proc
mount -t sysfs none /sys
# Mount the root filesystem
mount -t ext4 /dev/vda2 /mnt/newroot
# Switch mount point
mount -n -o move /sys /mnt/newroot/sys
mount -n -o move /proc /mnt/newroot/proc
mount -n -o move /dev /mnt/newroot/dev
# Execute new mount rootfilesystem
exec switch_root -c /dev/console /mnt/newroot /sbin/init mkimage로 boot_cmd.txt를 boot.scr로 변환한다../u-boot/tools/mkimage -A arm64 -O linux -T script -C none -n "aarch64" -d boot_cmd.txt boot.scr#!/bin/bash
UBOOT_BIN="./u-boot.bin"
SD_IMG="./sdcard.img"
DIRECTORY="./"
qemu-system-aarch64 \
-M virt \
-smp 1 \
-m 1024M \
-cpu cortex-a53 \
-bios "${UBOOT_BIN}" \
-nographic \
-device virtio-blk,drive=drive0 \
-drive id=drive0,if=none,format=raw,media=disk,file="$SD_IMG" qemu-system-aarch64는 virt 가상 머신으로 동작시킬 것이며, virtio 블럭 디바이스로 이미지 파일을 연결한다.=> ls virtio 0:1 /
594864 u-boot.bin
1182781 uRamdisk
0 tmp
38711808 Image
309 boot.scr ls virtio 0:2 / 를 입력하면 리눅스 루트 파일시스템 내부의 파일을 확인 가능하다.