윈도우에서 라즈베리파이 커널 빌드

WSL이나 qemu등을 사용하지 않고 MSYS2에서 라즈베리파이 커널 빌드와 busybox를 initd로 사용하여 부팅가능한 SD카드 만드는 과정을 기록한다. MSYS2에서 만들면 발생하는 문제점으로는 라즈베리파이가 부팅 된 이후 특별히 할 수 있는것이 없다.

WSL을 사용하거나 qemu에서 HAXM 가속으로 리눅스를 부팅해서 작업하는것이 시간도 몇배나 절약되고 자잘한 문제가 덜 발생한다. 예로 윈도우에서 빌드한 툴체인에서 libc를 static 라이브러리로 링크하면 _Unwind_Resume등을 찾을 수 없어서 링크가 불가능하여 라즈베리파이에서 구동되는 컴파일러를 빌드할 수 없는 등 할 수 없는 작업들이 많다. (LDPlayer같은 프로그램을 사용한다면 하이퍼바이저를 사실상 사용할 수 없기때문에 WSL을 사용할 수 없다.)

만약 부팅가능한 SD 카드를 만들고 젠투 리눅스같은 배포본을 설치 할 예정이라면 MSYS2에서도 시도할만하다.

MSYS2를 설치하고 MSYS2 MSYS를 관리자 권한으로 실행. 이유로는 fsuitl 명령에서 관리자 권한이 필요. MSYS에서 아래 패키지를 추가로 설치.

pacman -S gcc make python3
pacman -S flex bison libtool autoconf automake texinfo unzip help2man bc patch
pacman -S ncurses-devel openssl-devel
pacman -S vim

아래 명령을 실행하여 심볼릭 링크를 윈도우 단축아이콘 형식을 사용하도록 변경. (커널 압축 해제 또는 일부 명령에서 링크 대상보다 링크가 먼저 생성되는 경우가 발생하여 네이티브 방식은 오류가 발생)

export MSYS=winsymlinks:lnk

커널 소스 및 모듈에서 대소문자 구분이 필요한 파일들이 있으며 crosstool-ng 또한 대소문자를 구분할 수 있는 파일 시스템에서만 구동이 가능하므로 먼저 아래와같이 폴더를 만들고 대소문자 구분을 하도록 변경한다. (자세한 내용: https://learn.microsoft.com/ko-kr/windows/wsl/case-sensitivity)

아래 명령 실행 이후엔 관리자 권한이 없어도 된다

mkdir x-tools
mkdir cross-build
fsutil file setCaseSensitiveInfo x-tools
fsutil file setCaseSensitiveInfo cross-build
mkdir ~/cross-build/src

cross-build 폴더에 crosstool-ng 소스를 다운받고 빌드한다. 배포 페이지는 https://crosstool-ng.github.io/ 이곳. pacman으로 crosstool-ng를 설치할 수 있지만 너무 구버전이라 직접 패치할 내용이 많으므로 사용하지 않는것이 편하다. 아래와같이 다운받고 crosstool 빌드. (253W 전력 제한을 설정한 i9-13900K에서 약 20분 정도 소요)

cd cross-build
wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.26.0.tar.xz
tar xf crosstool-ng-1.26.0.tar.xz
cd crosstool-ng-1.26.0
./bootstrap
./configure --enable-local
make -j`nproc`

cross-build 폴더에 aarch64-gcc 폴더를 생성하여 crosstool 설정을 실행

cd ~/cross-build
mkdir aarch64-gcc
cd aarch64-gcc
../crosstool-ng-1.26.0/ct-ng menuconfig

아래 항목의 값을 변경. zstd는 https://github.com/crosstool-ng/crosstool-ng/issues/1974#issuecomment-1575624878 이 댓글을 확인하여 패치를 crosstool에 적용하거나 제외. crosstool-ng-1.26.0/packages/zstd/1.5.5에 0000-msys-static-install.patch 같은 이름으로 패치파일을 생성하면 build 시 자동으로 패치가 적용 됨.

Target options
  Target Architecture: arm
  Bitness: 64-bit
Operating System
  Target OS: linux
C-library
  Enable obsolete libcrypt: Y
C compiler
  Enable GRAPHITE loop optimisations: N
  Enable LTO: Y
    Support LTO compression with zstd: N

Save & Exit 후 아래 명령을 실행하여 툴체인을 빌드. 약 67분 정도 소요

../crosstool-ng-1.26.0/ct-ng build

이후부터는 아래와같이 환경변수 두개를 설정한 환경에서 작업을 진행

export MSYS=winsymlinks:lnk
export PATH=$PATH:~/x-tools/aarch64-unknown-linux-gnu/bin

라즈베리파이의 커널을 빌드 할 준비. 깃허브 https://github.com/raspberrypi/linux/tags 에서 최신 안정버전을 확인가능

cd ~/cross-build/src
wget https://github.com/raspberrypi/linux/archive/refs/tags/stable_20240529.tar.gz
tar xf stable_20240529.tar.gz
cd linux-stable_20240529
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- bcm2712_defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- scripts
mv scripts/asn1_compiler.exe scripts/asn1_compiler
mv scripts/dtc/dtc.exe scripts/dtc/dtc
mkdir -p /usr/include/linux
echo "#define LINUX_VERSION_CODE 328209" > /usr/include/linux/version.h
echo "#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))" >> /usr/include/linux/version.h

scripts 폴더 내 빌드되는 실행파일에 확장자 exe가 붙어서 확장자를 제거하지 않으면 문제가 발생하므로 scripts를 먼저 make 한 후 확장자를 제거하고 빌드 중간에 필요한 헤더를 임의로 생성. 이후 커널빌드

make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- -j`nproc`
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- INSTALL_DTBS_PATH=~/aarch64-root dtbs_install
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH=~/aarch64-root modules_install
cp arch/arm64/boot/Image.gz ~/aarch64-root/kernel8.img
cp ~/aarch64-root/broadcom/bcm2712-rpi-5-b.dtb ~/aarch64-root/
rm -rf ~/aarch64-root/broadcom

busybox의 빌드를 준비. 최신 코드는 https://www.busybox.net/ 이곳에서 확인가능

cd ~/cross-build/src
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- menuconfig

아래 옵션을 변경

Settings
  What Kind of applet links to install: as script wrappers
  /bin/sh applet links: as script wrappers

NTFS나 fat32는 심볼릭 링크를 만들 수 없으므로 script wrapper로 busybox 명령을 생성하도록 변경. 아래 명령으로 busybox를 빌드하고 aarch64-root 폴더에 복사

make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- -j`nproc`
make ARCH=arm64 CROSS_COMPILE=aarch64-unknown-linux-gnu- install
cp -r _install/* ~/aarch64-root/

dropbear 빌드. 최신 버전은 https://matt.ucc.asn.au/dropbear/releases/ 이곳에서 확인 가능

cd ~/cross-build/src
wget https://matt.ucc.asn.au/dropbear/releases/dropbear-2024.85.tar.bz2
tar xf dropbear-2024.85.tar.bz2
cd dropbear-2024.85
mkdir build
cd build
../configure --prefix=/usr --exec-prefix=/usr --sysconfdir=/etc --localstatedir=/var --host=aarch64 --disable-harden --disable-zlib --enable-bundled-libtom --disable-wtmp --disable-lastlog CC=aarch64-unknown-linux-gnu-gcc STRIP=aarch64-unknown-linux-gnu-strip STATIC=1
make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp" SCPPROGRESS=1 -j`nproc`
make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp" SCPPROGRESS=1 strip
make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp" SCPPROGRESS=1 install DESTDIR=~/aarch64-root

config.txt 및 cmdline.txt 생성

echo "root=/dev/mmcblk0p1 rootwait console=tty1" > ~/aarch64-root/cmdline.txt
echo "disable_overscan=1" > ~/aarch64-root/config.txt
echo "dtoverlay=vc4-kms-v3d" >> ~/aarch64-root/config.txt
echo "max_framebuffers=2" >> ~/aarch64-root/config.txt

기본폴더 생성

mkdir ~/aarch64-root/dev
mkdir ~/aarch64-root/etc
mkdir ~/aarch64-root/etc/init.d
mkdir ~/aarch64-root/etc/default
mkdir ~/aarch64-root/etc/network
mkdir ~/aarch64-root/etc/network/if-down.d
mkdir ~/aarch64-root/etc/network/if-post-down.d
mkdir ~/aarch64-root/etc/network/if-pre-up.d
mkdir ~/aarch64-root/etc/network/if-up.d
mkdir ~/aarch64-root/etc/dropbear
mkdir ~/aarch64-root/mnt
mkdir ~/aarch64-root/proc
mkdir ~/aarch64-root/root
mkdir ~/aarch64-root/run
mkdir ~/aarch64-root/sys
mkdir ~/aarch64-root/tmp
mkdir ~/aarch64-root/var
mkdir ~/aarch64-root/var/run

libc 런타임 복사

mkdir ~/aarch64-root/lib64
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/ld-linux-aarch64.so.1 ~/aarch64-root/lib/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libc.so.6 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libcrypt.so.1 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libdl.so.2 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libm.so.6 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libnss_dns.so.2 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libnss_files.so.2 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libpthread.so.0 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libresolv.so.2 ~/aarch64-root/lib64/
cp ~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot/lib/libutil.so.1 ~/aarch64-root/lib64/

etc 폴더 내용 생성 및 init 시스템 스크립트 생성. 주의사항으로 cat >> … 라인만 복사&붙여넣은 후 엔터를 누르고 스크립트 본문은 이후에 따로 복사&붙여넣기 해야 하며 ^D는 컨트롤 + D. 대부분 buildroot에서 생성하는 스크립트를 옮겨 온 것

echo "root:x:0:0:root:/root:/bin/sh" > ~/aarch64-root/etc/passwd
echo "root::10933:0:99999:7:::" > ~/aarch64-root/etc/shadow
echo "raspberrypi" > ~/aarch64-root/etc/hostname
cat >> ~/aarch64-root/etc/inittab
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/mkdir -p /run/lock/subsys
::sysinit:/sbin/swapon -a
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
#console::respawn:/sbin/getty -L  console 0 vt100 # GENERIC_SERIAL
tty1::respawn:/sbin/getty -L  tty1 0 vt100 # HDMI console

# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
^D
cat >> ~/aarch64-root/etc/init.d/rcS
#!/bin/sh

# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do

     # Ignore dangling symlinks (if any).
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set start
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i start
            ;;
    esac
done
^D
cat >> ~/aarch64-root/etc/init.d/rcK
#!/bin/sh

# Stop all init scripts in /etc/init.d
# executing them in reversed numerical order.
#
for i in $(ls -r /etc/init.d/S??*) ;do

     # Ignore dangling symlinks (if any).
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set stop
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i stop
            ;;
    esac
done
^D
cat >> ~/aarch64-root/etc/init.d/S40network
#!/bin/sh
#
# Start the network....
#

# Debian ifupdown needs the /run/network lock directory
mkdir -p /run/network

case "$1" in
  start)
        printf "Starting network: "
        /sbin/ifup -a
        [ $? = 0 ] && echo "OK" || echo "FAIL"
        ;;
  stop)
        printf "Stopping network: "
        /sbin/ifdown -a
        [ $? = 0 ] && echo "OK" || echo "FAIL"
        ;;
  restart|reload)
        "$0" stop
        "$0" start
        ;;
  *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
esac

exit $?
^D
cat >> ~/aarch64-root/etc/init.d/S50dropbear
#!/bin/sh
#
# Starts dropbear sshd.
#

# Allow a few customizations from a config file
test -r /etc/default/dropbear && . /etc/default/dropbear

start() {
        DROPBEAR_ARGS="$DROPBEAR_ARGS -R"

        # If /etc/dropbear is a symlink to /var/run/dropbear, and
        #   - the filesystem is RO (i.e. we can not rm the symlink),
        #     create the directory pointed to by the symlink.
        #   - the filesystem is RW (i.e. we can rm the symlink),
        #     replace the symlink with an actual directory
        if [ -L /etc/dropbear \
             -a "$(readlink /etc/dropbear)" = "/var/run/dropbear" ]
        then
                if rm -f /etc/dropbear >/dev/null 2>&1; then
                        mkdir -p /etc/dropbear
                else
                        echo "No persistent location to store SSH host keys. New keys will be"
                        echo "generated at each boot. Are you sure this is what you want to do?"
                        mkdir -p "$(readlink /etc/dropbear)"
                fi
        fi

        printf "Starting dropbear sshd: "
        umask 077

        start-stop-daemon -S -q -p /var/run/dropbear.pid \
                --exec /usr/sbin/dropbear -- $DROPBEAR_ARGS
        [ $? = 0 ] && echo "OK" || echo "FAIL"
}
stop() {
        printf "Stopping dropbear sshd: "
        start-stop-daemon -K -q -p /var/run/dropbear.pid
        [ $? = 0 ] && echo "OK" || echo "FAIL"
}
restart() {
        stop
        start
}

case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart|reload)
        restart
        ;;
  *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
esac

exit $?
^D
cat >> ~/aarch64-root/etc/default/dropbear
# allow empty password
DROPBEAR_ARGS="-B"
cat >> ~/aarch64-root/etc/network/if-pre-up.d/wait_iface
#!/bin/sh

# In case we have a slow-to-appear interface (e.g. eth-over-USB),
# and we need to configure it, wait until it appears, but not too
# long either. IF_WAIT_DELAY is in seconds.

if [ "${IF_WAIT_DELAY}" -a ! -e "/sys/class/net/${IFACE}" ]; then
    printf "Waiting for interface %s to appear" "${IFACE}"
    while [ ${IF_WAIT_DELAY} -gt 0 ]; do
        if [ -e "/sys/class/net/${IFACE}" ]; then
            printf "\n"
            exit 0
        fi
        sleep 1
        printf "."
        : $((IF_WAIT_DELAY -= 1))
    done
    printf " timeout!\n"
    exit 1
fi
^D
cat >> ~/aarch64-root/etc/fstab
/dev/root / vfat rw,noauto 0 1
proc /proc proc defaults 0 0
devpts /dev/pts devpts defaults,gid=5,mode=620,ptmxmode=0666 0 0
tmpfs /dev/shm tmpfs mode=1777 0 0
tmpfs /tmp tmpfs mode=1777 0 0
tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0
sysfs /sys sysfs defaults 0 0
^D
cat >> ~/aarch64-root/etc/network/interfaces
# interface file auto-generated by buildroot

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
  wait-delay 15
  hostname $(hostname)
^D

udhcpc 스크립트 복사

mkdir -p ~/aarch64-root/usr/share/udhcpc
cp ~/cross-build/src/busybox-1.36.1/examples/udhcp/simple.script ~/aarch64-root/usr/share/udhcpc/default.script

아래 파일은 ldd를 임의로 생성. ldd /usr/sbin/dropbear 와 같은 방식으로 사용

cat >> ~/aarch64-root/usr/bin/ldd
#!/bin/sh
LD_TRACE_LOADED_OBJECTS=1 /lib/ld-linux-aarch64.so.1 \$1
^D

SD 카드로 복사 (F드라이브 일 경우) ext2 파티션을 만든 후 별도의 리눅스 배포본을 설치하려면 미리 256메가 등 적당한 크기의 파일시스템으로 포맷하여 복사하여 부팅 후 설치

cp -r ~/aarch64-root/* /f/

루트 파일시스템을 tarball 파일로 묶기

tar -C ~/aarch64-root --exclude bcm* --exclude cmdline.txt --exclude config.txt --exclude kernel8.img --exclude linuxrc --exclude overlays -cvf ~/rootfs.tar .

여기까지. SD카드로 부팅되고 root 계정으로 암호없이 로그인이 되고 ssh 터미널도 구동된다. 윈도우의 MSYS환경에서는 여기까지가 최선인 것으로 보이므로 이것으로 끝.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

*
*

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.