PC 환경에서 arm64 커널을 에뮬레이션해보자.
목적
사용자 OS로 64bit x86 PC Window 환경에서 virtualbox를 사용하여 호스트 OS용도로 우분투 리눅스를 사용하고 있는데, 이 우분투에서 QEMU를 사용하여 게스트 OS로 arm64 커널을 사용하게 한다.
- 게스트 OS로 arm 연합인 Linaro에서 안정적으로 제공하는 커널인 LSK(Linaro Stable Kernel) 커널을 사용한다. 그리고 Linaro Releases 사이트에서 같이 제공하는 여러 가지 형태의 루트파일시스템을 사용한다.
- 게스트 OS에서 인터넷이 동작해야 한다.
- 게스트 OS에서 gcc 컴파일 환경이 있어야 한다.
테스트 환경 조건
- 사용자 OS
- x86 아키텍처(intel 64bit)를 사용하는 PC
- Window 8 또는 10 (반드시 64비트 운영체제여야 함)
- 호스트 OS로 리눅스를 사용하기 위해 VirutalBox 5.1 사용
- 호스트 OS
- Ubuntu 64bit 14.04 이상 (반드시 64비트 운영체제여야 함)
- QEMU 2.9 이상
- 게스트 OS (arm64)
- LSK 4.4 이상
- LSK 4.14 추천
- LSK 4.4 이상
사전 준비 작업
다음 항목들이 사전에 준비되어 있어야 한다. 이 항목들의 설치 방법은 설명하지 않는다.
- 64bit Windows 10
- VirtualBox 5.1
- 64bit Ubuntu 14.4 이상
- kernel 4.14 이상의 빌드환경을 위해서는 Ubuntu 64bit 18.04 권장
- Linaro 툴체인 5.3 이상
- Ubuntu 18.04의 경우 gcc 7.3 권장
- gcc 컴파일러 4.8.4
- Ubuntu 18.04의 경우 gcc 7.3 권장
- QEMU 2.9 이상
- Guest OS 실행 시 콘솔 출력이 안되는 경우 qemu 소스를 다운로드 및 빌드 후 설치 권장
- Ubuntu 18.04의 경우 QEMU 2.11 권장
- 기타 툴 (각자 호스트 OS 환경에 맞춰…)
- gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat libsdl1.2-dev xterm lib32stdc++6 lib32z1 libssl-dev device-tree-compiler vim autogen autoconf libtool flex bison ncurses-dev uuid-dev python-dev python-crypto libsdl2-dev libbsd libattr-devel screen qemu-utils cloud-utils bridge-utils dnsmasq uml-utilities
에뮬레이션 모드 vs VM 가속화 모드(KVM)
QEMU는 에뮬레이션 모드와 VM 가속화 모드가 있다는 것을 미리 알아두어야 혼동이되지 않는다.
- 에뮬레이션 모드
- 호스트 OS용 아키텍처와 게스트 OS용 아키텍처 코드를 실행할 수 있다.
- 예: x86 pc 호스트 환경에서 aarch64 커널을 구동
- 가속화 모드(KVM 모드)
- “–kvm” 플래그를 사용하여 호스트와 게스트 os가 동일한 아키텍처인 경우 VM을 가속화할 수 있다.
- 예: aarch64 호스트(임베디드 등) 환경에서 arm64 커널(aarch64) 구동
1. VirtualBox 주요 네트워크 설정
네트웍 환경
QEMU를 사용하면서 가장 어려운 설정이 네트워크 부분이다. 다음 그림과 같은 네트워크 환경으로 구성할 예정이다.
- 여러 가지 제약 조건이 걸려있기 때문에 잘 이해하고 준비해두어야 한다.
VirtualBox 환경 설정
아래 그림과 같이 VirtualBox에 NAT 네트워크를 20.0.2.0/24 네트워크 대역으로 만든다.
- VirtualBox의 default NAT 네트워크 대역은 10.0.2.x/24를 사용하는데 이를 변경하여 20.0.2.x/24 대역을 사용한다.
- 인터넷 연결이 제한된 호스트 전용 네트워크는 구성하지 않는다.
VirtualBox에서 Ubuntu OS 네트워크 설정
64비트용 우분투 OS의 네트워크 설정을 다음과 같이 변경한다.
Ubuntu 부팅 후 네트워크 설정
- eth0
- enp0s3 (우분투 15.10 이상부터 적용)
- /etc/network/interface (전통적 방법)
- Network Manager
/etc/network/interfaces 파일 수정
호스트 OS의 브리지 설정 및 NAT forwading을 위해 /etc/network/interface 파일을 다음과 같이 수정한다.
- 게스트 OS를 위한 promisc 설정
- enp0s3(eth0) 인터페이스에 반드시 PROMISC가 설정되어 있어야 bridge 내에 추가되는 tap0 인터페이스에 enp0s3로 수신되는 모든 패킷을 tap 인터페이스도 브릿지되어 전달될 수 있도록 한다.
- 만일 promisc가 enp0s3(eth0)에 설정되지 않은 경우 enp0s3가 수신한 패킷 중 dest MAC이 enp0s3의 MAC이 아닌 경우 filter 처리하므로 게스트 OS의 네트웍 드라이버에서 사용하는 MAC이 tap0를 통해 수신할 수 없다.
- 게스트 OS가 자신의 패킷을 전송할 수는 있어도 자신에게 오는 인터넷 패킷을 수신할 수 없다.
- 게스트 OS를 위한 NAT 설정
- 아래 스크립트의 가장 마지막 두 줄에 해당한다.
$ sudo vi /etc/network/interfaces auto lo iface lo inet loopback auto enp0s3 iface enp0s3 inet manual address 0.0.0.0 netmask 255.255.255.0 auto virbr0 iface virbr0 inet static address 20.0.2.4 netmask 255.255.255.0 gateway 20.0.2.2 bridge_ports enp0s3 bridge_stp off up /sbin/ifconfig enp0s3 promisc up sysctl net.ipv4.ip_forward=1 up iptables -t nat -A POSTROUTING -o virbr0 -j MASQUERADE
Network Manager 동작 정지
네트워크 매니저가 동작하지 못하게 /etc/NetworkManager/NetworkManager.conf 파일을 다음과 같이 수정한다.
- managed=false와 같이 수정한다.
$ sudo vi /etc/NetworkManager/NetworkManger.conf [main] plugins=ifupdown,keyfile,ofono dns=dnsmasq no-auto-default=08:00:27:15:4C:52, [ifupdown] managed=false
DNS Server 설정
최근에는 dns 설정을 위해 /etc/resolv.conf 파일의 직접적인 수정은 허용하지 않는다. 따라서 다음과 같은 동작을 수행한다.
- 아래 예에서는 dns server로 168.126.63.1을 사용하였다. (적절히 사용자가 선호하는 dns server를 1개 이상 등록한다)
$ sudo vi /etc/resolvconf/resolv.conf.d/tail nameserver 168.126.63.1 $ sudo service resolvconf restart
등록이 잘 되었는지 확인해본다.
$ cat /etc/resolv.conf nameserver 127.0.0.1 nameserver 168.126.63.1
호스트 OS 네트웍 테스트
다음과 같이 우분트를 리부팅한 후 네트웍이 정상 동작하는지 확인한다.
$ sudo reboot (리부팅) $ ifconfig enp0s3 Link encap:Ethernet HWaddr 08:00:27:59:bc:f3 UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1 ... virbr0 Link encap:Ethernet HWaddr 08:00:27:59:bc:f3 inet addr:20.0.2.4 Bcast:20.0.2.255 Mask:255.255.255.0 inet6 addr: fe80::a00:27ff:fe59:bcf3/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 ... $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 20.0.2.2 0.0.0.0 UG 0 0 0 virbr0 20.0.2.0 * 255.255.255.0 U 0 0 0 virbr0 link-local * 255.255.0.0 U 1000 0 0 virbr0 $ ping hello.net PING hello.net (52.216.83.66) 56(84) bytes of data. 64 bytes from 52.216.83.66: icmp_seq=1 ttl=37 time=186 ms 64 bytes from 52.216.83.66: icmp_seq=2 ttl=37 time=187 ms
탭(tap) 인터페이스 생성
게스트 OS가 호스트 OS와의 통신 뿐만 아니라 외부 인터넷망과 연동하려면 tap 인터페이스를 준비하여야 한다.
탭 인터페이스 생성 스크립트 준비
호스트 OS에 tap0 인터페이스를 만들고 브리지에 등록하는 스크립트를 준비한다.
- 호스트 OS 버전에 따라 이더넷 인터페이스 명이 달라지므로 아래 ENET 변수를 사용하는 호스트 이더넷 인터페이스명에 맞게 수정한다.
- enp0s3 (우분투 15.04 이상)
- eth0
- 호스트 OS 커널이 tap 인터페이스를 만들 수 있도록 tun 디바이스가 준비되어 있어야 한다.
- 확인: 호스트 OS인 우분투에 /dev/net/tun 파일이 존재해야 한다.
- 주의: tap0 인터페이스는 사용자 권한에서 접근이 가능해야 하므로 현재 사용자명을 사용하여 생성한다.
- 아래 스크립트에서 sudo tunctl -u {WHOAMI} 부분을 참고한다.
$ mkdir qq2 $ cd qq2 $ vi tap.sh WHOAMI=$(whoami) echo add tap0 to virbr0 bridge echo ------------------------------------------------------ sudo tunctl -d tap0 sudo tunctl -u ${WHOAMI} sudo ifconfig tap0 0.0.0.0 up sudo brctl addif virbr0 tap0 $ chmod 755 ./tap.sh
탭 인터페이스 생성 및 체크
tap.sh 스크립트를 동작시켜 tap 인터페이스를 생성한다.
$ ./tap.sh add tap0 to virbr0 bridge ------------------------------------------------------ [sudo] password for jake: Set 'tap0' nonpersistent Set 'tap0' persistent and owned by uid 1000
네트워크 동작 확인
다음 명령들을 통해 tap 인터페이스 생성 후 브리지에 잘 포함되었는지와 네트웍이 정상 동작하는 것을 확인한다.
- 사용하는 리눅스 패키지 및 버전에 따라 enp0s3 대신 eth0가 나올 수도 있다.
$ ifconfig
enp0s3 Link encap:Ethernet HWaddr 08:00:27:59:bc:f3
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1
...
tap0 Link encap:Ethernet HWaddr 02:e4:fd:72:d5:13
UP BROADCAST MULTICAST MTU:1500 Metric:1
...
virbr0 Link encap:Ethernet HWaddr 02:e4:fd:72:d5:13
inet addr:20.0.2.4 Bcast:20.0.2.255 Mask:255.255.255.0
...
$ brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.02e4fd72d513 no enp0s3
tap0
$ ping hello.net
PING hello.net (52.216.18.242) 56(84) bytes of data.
64 bytes from 52.216.18.242: icmp_seq=1 ttl=33 time=239 ms
64 bytes from 52.216.18.242: icmp_seq=2 ttl=33 time=235 ms
$ route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 20.0.2.2 0.0.0.0 UG 0 0 0 virbr0
20.0.2.0 * 255.255.255.0 U 0 0 0 virbr0
2. 게스트 OS용 커널과 루트 파일 시스템 준비
이제 QEMU에서 작동시킬 게스트 OS용 Linaro 커널과 루트 파일 시스템을 준비한다.
다음 그림과 같은 디렉토리 구성을 사용할 계획이다.
Linaro 커널 다운로드
유저 작업 디렉토리에서 다음과 같이 Linaro 스테이블 커널 v4.9 또는 v4.14에 대한 소스를 git 툴을 사용하여 다운로드 한다.
- –depth 1을 사용하여 다운 받는 용량을 줄였다.
- -b <브랜치 명> 플래그를 사용하여 해당 브랜치를 선택한다.
- lsk-4.9 또는 lsk-4.14 디렉토리가 만들어진다.
- 관련 사이트: Linaro Stable Kernel (LSK) | Linaro.org
$ git clone --depth 1 -b linux-linaro-lsk-v4.9 https://git.linaro.org/kernel/linux-linaro-stable.git/ lsk-4.9 or $ git clone --depth 1 -b linux-linaro-lsk-v4.14 https://git.linaro.org/kernel/linux-linaro-stable.git/ lsk-4.14
다음 사이트에서 적절한 루트 파일 시스템용 이미지 또는 압축 파일을 다운받는다. 패키지들은 여러 가지 형태인데 이 글에서는 openembedded와 debian 계열의 패키지를 선택하여 설명한다.
- https://releases.linaro.org | Linaro.org
예)
Linaro release에서 제공하는 루트 파일 시스템들은 크게 두 가지로 형태로 나뉜다.
- 1) 압축된 완성 이미지 디스크
- 파티션이 1~2개로 구성된 이미지 디스크 형태로 루트 파일들이 포함되어 있고, 보통 압축 시 다음과 같은 이름 형태를 사용하여 제공한다.
- *.img.gz,
- *.img.xz
- *.rootfs.ext4.gz
- *.rootfs.ext4.xz
- *.rootfs.tar.gz
- *.rootfs.tar.xz
- 아래 샘플 두 가지는 압축된 이미지 디스크 파일로 제공한다.
- 15.07월: vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img.gz
- 특징: openembedded 패킹 + vexpress64 HW + 커널 v4.2 + gcc + lamp(apache2 + mysqld)
- 17.02월: rpb-console-image-lava-juno-20170223094505-36.rootfs.ext4.gz
- 특징: linaro lava 패킹 + juno HW + 커널 v4.9.39 + gcc
- 15.07월: vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img.gz
- 파티션이 1~2개로 구성된 이미지 디스크 형태로 루트 파일들이 포함되어 있고, 보통 압축 시 다음과 같은 이름 형태를 사용하여 제공한다.
- 2) 압축된 루트 파일들
- 루트 파일 시스템을 만들기 위한 압축 파일들만이 제공된다.
- *.tar.gz
- *.tar.xz
- 아래 샘플은 루트 파일들을 압축하여 제공하므로 번거롭더라도 실제로 사용하기 전에 파티션을 가진 이미지 디스크를 만들고, 그 안에 복사하여 준비한다.
- 18.04월: linaro-stretch-developer-20180416-89.tar.gz
- 특징: debian 패킹 + gcc v6.3
- 18.04월: linaro-stretch-developer-20180416-89.tar.gz
- 루트 파일 시스템을 만들기 위한 압축 파일들만이 제공된다.
Linaro Release에서 사용하는 단어 예를 설명한다.
- console & minimal
- gcc 및 기본 툴만 탑재
- desktop
- desktop에 준하는 더 많은 툴을 탑재
- lamp
- gcc + apache2 + mysql 데몬 탑재 (웹 서비스 스택)
- lava
- 리눅스 오토메이션 테스트 용
- stretch
- 데비안 리눅스의 배포판 코드명
- kernel v4.9 LTS 기반으로 만들었다.
1) 압축된 완성 이미지 디스크로 준비하는 과정
유저 작업 디렉토리에서 다시 qq2 디렉토리로 이동 후 루트 파일 시스템이 포함된 이미지를 다운로드하고, 적절한 압축 해제 유틸리티를 사용하여 압축을 해제한다.
- 주의: gzip에서 -k 옵션을 사용하지 않으면 압축이 풀린 후 기존 압축된 파일은 자동으로 삭제된다.
$ cd qq2 $ wget https://releases.linaro.org/openembedded/aarch64/15.07/vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img.gz $ gzip -d vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img.gz
이미지 정보 확인
이미지 정보에서 Start에 해당하는 숫자들이 mount할 때 필요한 정보이다.
- 보통 1~5개의 파티션으로 나뉘어 있다.
$ fdisk -l vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img Disk vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img : 3221 MB, 3221225472 bytes 255 heads, 63 sectors/track, 391 cylinders, total 6291456 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 Disk identifier: 0xdf9ff8a9 Device Boot Start End Blocks Id System vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img1 * 63 155646 77792 e W95 FAT16 (LBA) <- (offset=63 * 512 = 32256 ) vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img2 155648 6291455 3067904 83 Linux <- (offset=155648 * 512 = 79691776 )
1개의 파티션으로 되어있는 경우는 추후 mount할 때 offset 지정을 할 필요 없다.
fdisk -l rpb-console-image-lava-dragonboard-410c-20170803175648-18.rootfs.ext4 Disk rpb-console-image-lava-dragonboard-410c-20170803175648-18.rootfs.ext4: 1.5 GiB, 1556086784 bytes, 3039232 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
이미지 마운트
qq2 디렉토리 위치에서 이미지를 마운트한다.
- 주의: 아래 스크립트의 offset을 지정할 때 위에서 확인한 Start 값 * 512를 계산하여 사용해야 한다.
$ mkdir mnt1 $ mkdir mnt2 $ sudo mount -v -o offset=32256 -t vfat vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img mnt1 $ sudo mount -v -o offset=79691776 -t ext4 vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img mnt2 $ ls mnt2 bin boot dev EFI etc home lib lost+found media mnt opt proc run sbin sys tmp usr var $ cd .. $ sudo umount mnt1 $ sudo umount mnt2
만일 1개의 파티션만을 사용하는 이미지인 경우 다음과 같이 사용한다.
$ mkdir mnt1 $ sudo mount -v -o loop rpb-console-image-lava-dragonboard-410c-20170803175648-18.rootfs.ext4 mnt1 $ ls mnt2 bin boot dev etc home lib lost+found media mnt opt proc run sbin sys tmp usr var $ cd .. $ sudo umount mnt2
2) 압축된 루트 파일로 준비하는 과정
만일 다운로드한 루트 파일 시스템이 파티션을 가진 이미지가 아니라서 마운트가 안되는 경우가 있다. 그런 경우는 루트파일 시스템을 그냥 압축만 해 놓은 상태이다. 이러한 파일은 1개의 파티션을 만들고 그 안에 압축된 디렉토리와 파일들을 풀어 넣어야 한다.
1개의 파티션 이미지 생성
1M * 4000개 = 약 4G의 용량을 가진 1개의 파티션을 가진 이미지를 만든다.
$ dd if=/dev/zero of=rootfs.ext4 bs=1M count=4000 $ mkfs.ext4 -F rootfs.ext4
마운트 및 루트 파일 시스템 구성
생성한 파티션 이미지를 마운트한 후 루트 파일을 풀어 이미지를 완성한다.
$ sudo mount rootfs.ext4 mnt1 -o loop $ cd mnt1 $ sudo rm -rf lost+found $ sudo tar xf ../linaro-stretch-developer-20180416-89.tar.gz $ ls binary $ sudo mv binary/* . $ sudo rm binary -rf $ cd .. $ sudo umount mnt1
3. 게스트 OS(Linaro 커널) 빌드
커널을 빌드하는데 툴체인 및 관련 툴들이 사전에 설치되어 있어야 한다.
- sudo apt-get install 명령을 사용하여 갖가지 툴들의 설치가 필요하다. (생략)
커널 빌드 환경 변수 준비
다음 항목들을 ~/.bashrc 파일 등에 기록해두자. 주의: 리나로 툴체인이 아래 PATH 지정한 디렉토리 위치에 설치되어 있어야 한다.
- export PATH=/opt/gcc-linaro-5.3-2016.02-x86_64_aarch64-linux-gnu/bin:$PATH
- export ARCH=arm64
- export CROSS_COMPILE=aarch64-linux-gnu-
툴체인 확인
export PATH가 적용된 후에는 아래 명령을 입력 후 탭(tab) 키를 누르면 관련 파일들이 출력되는 것을 확인할 수 있다.
$ aarch64-linux-gnu- aarch64-linux-gnu-addr2line aarch64-linux-gnu-gcov-tool aarch64-linux-gnu-ar aarch64-linux-gnu-gdb aarch64-linux-gnu-as aarch64-linux-gnu-gfortran aarch64-linux-gnu-c++ aarch64-linux-gnu-gprof aarch64-linux-gnu-c++filt aarch64-linux-gnu-ld aarch64-linux-gnu-cpp aarch64-linux-gnu-ld.bfd aarch64-linux-gnu-elfedit aarch64-linux-gnu-nm aarch64-linux-gnu-g++ aarch64-linux-gnu-objcopy aarch64-linux-gnu-gcc aarch64-linux-gnu-objdump aarch64-linux-gnu-gcc-5.3.1 aarch64-linux-gnu-ranlib aarch64-linux-gnu-gcc-ar aarch64-linux-gnu-readelf aarch64-linux-gnu-gcc-nm aarch64-linux-gnu-size aarch64-linux-gnu-gcc-ranlib aarch64-linux-gnu-strings aarch64-linux-gnu-gcov aarch64-linux-gnu-strip
툴체인 버전 확인
설치된 툴체인의 PATH가 잘 지정되었는지 확인해본다.
$ aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-gcc (Linaro GCC 5.3-2016.02) 5.3.1 20160113
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
빌드
다운 받은 게스트 OS용 커널이 있는 디렉토리에서 커널을 빌드해본다. 처음 빌드(make all) 시 PC 성능에 따라 수분~ 수십분이 소요된다.
- lsk_defconfig를 사용하여 커널 디렉토리에 .config를 생성한다.
- 처음 커널을 빌드하는 경우다음과 같은 유틸리티들이 먼저 설치되어 있어야 한다.
- $ sudo apt-get install bison build-essential curl flex git pkg-config zlib1g-dev libglib2.0-dev zlib1g-dev libpixman-1-dev
$ cd lsk-4.9 $ make lsk_defconfig $ make menuconfig $ make -j 8
Guest OS에서 커널과 관련하여 개발이 필요한 경우 include 헤더들이 필요하다.
$ make -j 8 deb-pkg
4. QEMU에서 게스트 OS 실행 준비
QEMU를 사용하여 게스트 OS를 실행하려면 관련된 커멘드 옵션들이 상당히 다양하고 길다. 따라서 쉽게 가동하기 위해 스크립트를 만들어 사용하기로 한다.
게스트 OS 가동 스크립트 준비
qq2 디렉토리에서 다음 run.sh 스크립트를 준비하여 게스트 OS를 편하게 실행할 수 있도록 한다.
버추얼 머신(-M virt) 환경에서는 virtio-net-device 밖에 사용할 수 없다. 그러나 다른 머신 모드를 사용하면 다른 이더넷 드라이버를 에뮬레이션 할 수 있다. 예를 들어 인텔 이더넷 드라이버를 사용하려면 네트웍 부분을 다음과 같이 변경할 수 있다. (다음 두 줄에서 id를 다르게 설정해야 한다)
- -net nic,id=mynet0,model=e1000 \
- -net tap,id=mynet1,ifname=tap0,br=virbr0,script=no,downscript=no \
QEMU의 guest OS에서 사용할 네트워크 디바이스의 설정이 끝났으면 다음은 호스트와의 연동에 필요한 항목을 선택해야 한다. 외부 인터넷과 연동하려면 host의 tap 인터페이스와 연동이 필수이다. 그렇지 않고 내부에 host와의 tcp/udp 연동만 할 경우에는 user 모드 드라이버를 선택할 수 있다. 다만 user 모드 드라이버를 사용하는 경우 ping등이 지원되지 않는다.
- -device virtio-net-device,netdev=mynet2 \
- -netdev user,id=mynet2,net=20.0.2.0/24,dhcpstart=20.0.2.5 \
아래의 run.sh 스크립트를 준비한다.
- 주의:
- 다음 스크립트의 -dtb ${DTB_FILE} \ 행을 삭제하는 경우 virt 머신에 내장된(embed) 디폴트 디바이스 트리가 자동으로 적용된다.
- 만일 1개의 파티션을 갖는 이미지를 사용하는 경우 root=/dev/vda로 변경하여 사용한다.
$ cd qq2 $ vi run.sh WORK_DIR=${HOME}"/workspace" KERNEL_DIR=${WORK_DIR}"/lsk-4.9" KERNEL_IMG=${KERNEL_DIR}"/arch/arm64/boot/Image" ROOTFS="vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img" DTB_FILE="virt.dtb" if [ 1 == 0 ]; then qemu-system-aarch64 -smp 2 -m 1024 -cpu cortex-a57 -nographic \ -machine virt,dumpdtb=virt_original.dtb \ -kernel ${KERNEL_IMG} \ -append 'root=/dev/vda2 rw rootwait mem=1024M console=ttyAMA0,38400n8' \ -device virtio-net-device,netdev=mynet1 \ -netdev tap,id=mynet1,ifname=tap0,br=virbr0,script=no,downscript=no \ -device virtio-blk-device,drive=disk \ -drive if=none,id=disk,file=${ROOTFS},format=raw else qemu-system-aarch64 -smp 2 -m 1024 -cpu cortex-a57 -nographic \ -machine virt \ -kernel ${KERNEL_IMG} \ -dtb ${DTB_FILE} \ <- 디바이스 트리를 별도로 작성하지 않을 사람은 삭제 -append 'root=/dev/vda2 rw rootwait mem=1024M console=ttyAMA0,38400n8' \ -device virtio-net-device,netdev=mynet1 \ -netdev tap,id=mynet1,ifname=tap0,br=virbr0,script=no,downscript=no \ -device virtio-blk-device,drive=disk \ -drive if=none,id=disk,file=${ROOTFS},format=raw fi reset $ chmod 755 ./run.sh
게스트 OS 중단 스크립트 준비
실행된 게스트 OS의 실행을 강제로 종료시키기 위해 다음 fin.sh 스크립트를 준비한다.
$ vi fin.sh ps -ef | grep 'qemu-system-aarch64' -m 1 | awk '{print $2}' | xargs kill -9 $ chmod 755 ./fin.sh
디바이스 트리 사용(option)
virt 머신(-M virt 옵션)을 사용하는 구성에서는 임베드된 디바이스 트리를 사용하는데 이를 외부에서 지정하는 방법을 알아보자.
- 참고: Get Cache Info in Linux on ARMv8 64-bit Platform | Zhiyi’s Blog
run.sh 스크립트 내부의 if 조건을 1 == 1로 변경하여 실행시키는 경우 virt 머신 내부에 포함된 virt_original.dtb가 자동으로 생성된다.
- 한 번 생성한 후에는 if 조건을 원래대로 돌려 놓는다.
$ cat virt.dts /dts-v1/; / { interrupt-parent = <0x8001>; #size-cells = <0x2>; #address-cells = <0x2>; compatible = "linux,dummy-virt"; platform@c000000 { interrupt-parent = <0x8001>; ranges = <0x0 0x0 0xc000000 0x2000000>; #address-cells = <0x1>; #size-cells = <0x1>; compatible = "qemu,platform", "simple-bus"; }; fw-cfg@9020000 { dma-coherent; reg = <0x0 0x9020000 0x0 0x18>; compatible = "qemu,fw-cfg-mmio"; }; virtio_mmio@a000000 { dma-coherent; interrupts = <0x0 0x10 0x1>; reg = <0x0 0xa000000 0x0 0x200>; compatible = "virtio,mmio"; }; (virtio_mmio 노드 반복 생략) gpio-keys { #address-cells = <0x1>; #size-cells = <0x0>; compatible = "gpio-keys"; poweroff { gpios = <0x8003 0x3 0x0>; linux,code = <0x74>; label = "GPIO Key Poweroff"; }; }; pl061@9030000 { phandle = <0x8003>; clock-names = "apb_pclk"; clocks = <0x8000>; interrupts = <0x0 0x7 0x4>; gpio-controller; #gpio-cells = <0x2>; compatible = "arm,pl061", "arm,primecell"; reg = <0x0 0x9030000 0x0 0x1000>; }; pcie@10000000 { interrupt-map-mask = <0x1800 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 0x8001 0x0 0x0 0x0 0x3 0x4 0x0 0x0 0x0 0x2 0x8001 0x0 0x0 0x0 0x4 0x4 0x0 0x0 0x0 0x3 0x8001 0x0 0x0 0x0 0x5 0x4 0x0 0x0 0x0 0x4 0x8001 0x0 0x0 0x0 0x6 0x4 0x800 0x0 0x0 0x1 0x8001 0x0 0x0 0x0 0x4 0x4 0x800 0x0 0x0 0x2 0x8001 0x0 0x0 0x0 0x5 0x4 0x800 0x0 0x0 0x3 0x8001 0x0 0x0 0x0 0x6 0x4 0x800 0x0 0x0 0x4 0x8001 0x0 0x0 0x0 0x3 0x4 0x1000 0x0 0x0 0x1 0x8001 0x0 0x0 0x0 0x5 0x4 0x1000 0x0 0x0 0x2 0x8001 0x0 0x0 0x0 0x6 0x4 0x1000 0x0 0x0 0x3 0x8001 0x0 0x0 0x0 0x3 0x4 0x1000 0x0 0x0 0x4 0x8001 0x0 0x0 0x0 0x4 0x4 0x1800 0x0 0x0 0x1 0x8001 0x0 0x0 0x0 0x6 0x4 0x1800 0x0 0x0 0x2 0x8001 0x0 0x0 0x0 0x3 0x4 0x1800 0x0 0x0 0x3 0x8001 0x0 0x0 0x0 0x4 0x4 0x1800 0x0 0x0 0x4 0x8001 0x0 0x0 0x0 0x5 0x4>; #interrupt-cells = <0x1>; ranges = <0x1000000 0x0 0x0 0x0 0x3eff0000 0x0 0x10000 0x2000000 0x0 0x10000000 0x0 0x10000000 0x0 0x2eff0000 0x3000000 0x80 0x0 0x80 0x0 0x80 0x0>; reg = <0x0 0x3f000000 0x0 0x1000000>; msi-parent = <0x8002>; dma-coherent; bus-range = <0x0 0xf>; #size-cells = <0x2>; #address-cells = <0x3>; device_type = "pci"; compatible = "pci-host-ecam-generic"; }; pl031@9010000 { clock-names = "apb_pclk"; clocks = <0x8000>; interrupts = <0x0 0x2 0x4>; reg = <0x0 0x9010000 0x0 0x1000>; compatible = "arm,pl031", "arm,primecell"; }; pl011@9000000 { clock-names = "uartclk", "apb_pclk"; clocks = <0x8000 0x8000>; interrupts = <0x0 0x1 0x4>; reg = <0x0 0x9000000 0x0 0x1000>; compatible = "arm,pl011", "arm,primecell"; }; pmu { interrupts = <0x1 0x7 0x304>; compatible = "arm,armv8-pmuv3"; }; intc { phandle = <0x8001>; reg = <0x0 0x8000000 0x0 0x10000 0x0 0x8010000 0x0 0x10000>; compatible = "arm,cortex-a15-gic"; ranges; #size-cells = <0x2>; #address-cells = <0x2>; interrupt-controller; #interrupt-cells = <0x3>; v2m { phandle = <0x8002>; reg = <0x0 0x8020000 0x0 0x1000>; msi-controller; compatible = "arm,gic-v2m-frame"; }; }; flash@0 { bank-width = <0x4>; reg = <0x0 0x0 0x0 0x4000000 0x0 0x4000000 0x0 0x4000000>; compatible = "cfi-flash"; }; psci { migrate = <0xc4000005>; cpu_on = <0xc4000003>; cpu_off = <0x84000002>; cpu_suspend = <0xc4000001>; method = "hvc"; compatible = "arm,psci-0.2", "arm,psci"; }; cpus { #size-cells = <0x0>; #address-cells = <0x1>; cpu@0 { reg = <0x0>; enable-method = "psci"; compatible = "arm,cortex-a57"; device_type = "cpu"; }; cpu@1 { reg = <0x1>; enable-method = "psci"; compatible = "arm,cortex-a57"; device_type = "cpu"; }; }; timer { interrupts = <0x1 0xd 0x304 0x1 0xe 0x304 0x1 0xb 0x304 0x1 0xa 0x304>; always-on; compatible = "arm,armv8-timer", "arm,armv7-timer"; }; apb-pclk { phandle = <0x8000>; clock-output-names = "clk24mhz"; clock-frequency = <0x16e3600>; #clock-cells = <0x0>; compatible = "fixed-clock"; }; memory { reg = <0x0 0x40000000 0x0 0x40000000>; device_type = "memory"; }; chosen { bootargs = "root=/dev/vda2 rw rootwait mem=1024M console=ttyAMA0,38400n8"; stdout-path = "/pl011@9000000"; }; };
디바이스 트리에 Custom 노드 추가(Option)
먼저 생성된 virt_default.dtb를 아래의 명령으로 dts 포맷으로 변경한다.
- $ dtc -I dtb -O dts virt_default.dtb -o virt_default.dts
virt_default.dts 파일이 생성되었으면 이를 virt.dts로 복사하고 스크립트의 마지막에 Custom 노드를 추가한다.
- $ cp virt_default.dts virt.dts
다음은 virt.dts에 gpio 노드를 추가한 모습을 보여준다.
$ vi virt.dts (생략) gpio { compatible = "foo,foo-gpio"; reg = <0x0 0x0 0x0 0x4>; ngpios = <2>; #gpio-cells = <2>; gpio-controllers; interrupt-controller; interrupts = <0 33 0>; }; }
마지막으로 다음 명령을 사용하여 virt.dts를 virt.dtb로 변환한다.
$ dtc -I dts -O dtb virt.dts -o virt.dtb $ ls virt* -la -rw-rw-r-- 1 jake jake 65536 5월 9 15:55 virt_default.dtb -rw-rw-r-- 1 jake jake 8523 5월 9 16:12 virt_default.dts -rw-rw-r-- 1 jake jake 7617 5월 9 16:06 virt.dtb -rw-rw-r-- 1 jake jake 8701 5월 9 16:06 virt.dts
5. QEMU로 게스트 OS(Linaro 커널) 실행
$ ./run.sh normal option. [ 0.000000] Booting Linux on physical CPU 0x0 [ 0.000000] Linux version 4.9.94-gd1480f9-dirty (jake@jake-vb) (gcc version 5.3.1 20160113 (Linaro GCC 5.3-2016.02) ) #7 SMP PREEMPT Wed May 9 13:58:03 KST 2018 [ 0.000000] Boot CPU: AArch64 Processor [411fd070] [ 0.000000] Memory limited to 1024MB ... Last login: Wed May 9 05:21:04 UTC 2018 on tty1 root@genericarmv8:~#
문제점 해결
아래 WARNING 로그에 다음과 같은 메시지들 중 하나라도 나타나면 네트워크가 제대로 동작하지 않을 것이다.
- “Warning: netdev mynet0 has no peer”
- “Warning: vlan 0 is not connected to host network”
- “Warning: vlan 0 with no nics”
또는 위의 메시지 출력도 없고 콘솔 출력이 없는 경우 다음 두 가지가 잘못되었을 가능성이 있다.
- 커널 크로스 컴파일이 잘못된 경우
- QEMU의 스탠다드 콘솔 출력 문제
커널 크로스 컴파일이 잘못된 경우
커널이 위치한 디렉토리에서 아래와 같이 커널이 크로스 컴파일되었는지 확인한다.
$ file vmlinux vmlinux: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, BuildID[sha1]=d85b613e8a8b495e70e79814917c86a88afecd8e, not stripped
QEMU의 스탠다드 콘솔 출력 문제
QEMU가 자신의 환경 설정과 동일하게 설정되지 않은 경우 이므로 QEMU 소스를 받아 빌드하여 사용한다. 이렇게 소스를 직접 빌드하는 경우 configure 유틸리티가 자신의 호스트에 설치된 라이브러리를 분석하여 적합하게 설정한다.
$ git clone git://git.qemu.org/qemu.git qemu $ cd qemu $ sudo apt-get install libsdl-dev-image1.2-dev <- 안되면 libsdl1.2-dev (or libsdl2-dev) $ git submodule update --init dtc $ git submodule update --init pixman <- 또는 (sudo apt-get install libpixman-1-dev) $ ./configure --target-list=aarch64-softmmu --enable-fdt --enable-vhost-net $ make $ sudo make install
잘 동작하는 경우 커널 버전 확인
커널 버전 4.9 또는 4.14가 잘 설치되었음을 알 수 있다.
root@genericarmv8:~# uname -r 4.9.94-gd1480f9
mysqld와 apache2 데몬 서비스 중단 (lamp 패키지 사용 시)
lamp 패키지가 추가되어 있는 이미지에서는 불필요한 데몬 두 개와 관련된 시작 링크들을 삭제한다.
- 구동 절차가 SysVinit을 사용하는 경우이다. (systemd는 다르다. 추후 확인 필요)
$ sudo update-rc.d -f mysqld remove update-rc.d: /etc/init.d/mysqld exists during rc.d purge (continuing) Removing any system startup links for mysqld ... /etc/rc0.d/K45mysqld /etc/rc1.d/K45mysqld /etc/rc5.d/S45mysqld /etc/rc6.d/K45mysqld $ sudo update-rc.d -f apache2 remove update-rc.d: /etc/init.d/apache2 exists during rc.d purge (continuing) Removing any system startup links for apache2 ... /etc/rc0.d/K20apache2 /etc/rc1.d/K20apache2 /etc/rc2.d/S91apache2 /etc/rc3.d/S91apache2 /etc/rc4.d/S91apache2 /etc/rc5.d/S91apache2 /etc/rc6.d/K20apache2
6. 게스트 OS 네트워크 설정
부트 파일 시스템의 구동 방식이 전통적인 방식의 SysVinit 또는 최근 추세인 systemd 둘 중 어느 방법을 사용하는지에 따라 설정방법이 다르다. 또한 Lan Manager 및 dsnresolv의 사용 유무도 네트 워크 설정 방법을 다르게 하는 요인이다. 이 글에서는 위의 예제에서 사용한 2 가지의 루트 파일 시스템 이미지에 대한 설정 방법을 가이드한다.
vexpress64-openembedded_lamp-armv8-gcc-4.9_20150725-725.img.gz 사용 시
SysVinit을 사용한 전통적인 방법으로 루트 파일 시스템으로 구성되어 있다. (Lan Manager, resolvconf 탑재되지 않음)
- 최신 desktop 등에서는 systemd가 사용되지만 embedded 시스템에서는 여전히 SysVinit이 많이 이용되고 있다.
다음 파일을 수정해서 사용할 ip 및 gateway를 지정한다.
$ vi /etc/network/interfaces auto eth0 iface eth0 inet static address 20.0.2.5 netmask 255.255.255.0 gateway 20.0.2.4
resolvconf가 탑재되지 않은 루트 파일 시스템에서의 dns name 서버 등록은 resolv.conf 파일을 수정해야 하는데 다음과 같이 이 파일이 휘발성 메모리를 사용하므로 이를 부트 후에 런타임에서 수정해야 함을 알 수 있다.
$ ls resolv.conf -la lrwxrwxrwx 1 root root 20 May 9 02:08 resolv.conf -> /var/run/resolv.conf
따라서 아래와 같이 디폴트 부트 시퀀스 5레벨에 해당하는 위치에서 동작하는 스크립트를 작성한다.
$ vi /etc/init.d/route_init.sh echo route init... echo "nameserver 168.126.63.1" > /etc/resolv.conf $ chmod 755 /etc/init.d/route_init.sh $ cd /etc/rc5.d $ ln -s ../init.d/route_init.sh S02routeinit.sh
linaro-stretch-developer-20180416-89.tar.gz 사용 시
최신 Trend인 systemd가 사용되었고 Network Manager, resolvconf등도 모두 탑재된 루트 파일 시스템으로 구성되어 있다.
다음과 같이 Guest OS에서는 무선 네트웍을 사용하지 않으니 Network Manager가 이더넷을 관리하지 못하게 managed=false인 상태로 그냥 수정없이 사용한다.
$ cat /etc/NetworkManager/NetworkManager.conf [main] plugins=ifupdown,keyfile [ifupdown] managed=false
다음 파일을 수정해서 사용할 ip 및 gateway를 지정한다. (네트웍 디바이스에 따라 eth0 대신 enp0s1일 수도 있다.)
$ vi /etc/network/interfaces auto eth0 iface eth0 inet static address 20.0.2.5 netmask 255.255.255.0 gateway 20.0.2.4
resolvconf가 탑재되어 알아서 상위 네트웍에서 dns 네임 서버를 찾아 다음과 같이 등록하므로 특별히 추가 설정을 할 필요 없다.
$ cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 8.8.8.8
위의 내용에 nameserver 127.0.0.53이 등록되어 있는 경우 불필요하여 삭제하려면 다음과 같은 명령을 사용한다.
$ systemctl disable systemd-resolved
최종 동작 확인
$ reboot ... Last login: Thu May 10 14:50:58 UTC 2018 on tty1 root@genericarmv8:~#
gcc 컴파일러 동작 확인
$ gcc --version gcc (Linaro GCC 4.9-2015.06) 4.9.4 20150629 (prerelease) Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
인터넷 동작 확인
root@genericarmv8:~# cat /etc/resolv.conf nameserver 168.126.63.1 root@genericarmv8:~# ifconfig eth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:20.0.2.5 Bcast:20.0.2.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 ... root@genericarmv8:~# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default jake-vb.local 0.0.0.0 UG 0 0 0 eth0 20.0.2.0 * 255.255.255.0 U 0 0 0 eth0 root@genericarmv8:~ # ping hello.net PING hello.net (52.216.20.122): 56 data bytes 64 bytes from 52.216.20.122: seq=0 ttl=36 time=199.754 ms 64 bytes from 52.216.20.122: seq=1 ttl=36 time=197.717 ms
참고
- QEMU 에뮬레이션(arm64) | 문c – 현재글
- 어셈블리(head.S) – gdb 디버깅 방법 | 문c
- Linaro Stable Kernel (LSK) | linaro.org
- Welcome to the Linaro releases storage server | linaro.org
- index : linux-linaro-stable.git | linaro.org
- Pre built linaro tool-chain | linaro.org
- Download QEMU | qemu.org
- QEMU Wiki Documentation
- QEMU version 2.12.0 User Documentation | qemu.org