QEMU 에뮬레이션(arm64)

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 추천

 

사전 준비 작업

다음 항목들이 사전에 준비되어 있어야 한다. 이 항목들의 설치 방법은 설명하지 않는다.

  • 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 부팅 후 네트워크 설정

호스트 OS인 우분투 버전에 따라 NIC 인터페이스 이름이 아래 둘 중 하나로 결정된다.
  • eth0
  • enp0s3 (우분투 15.10 이상부터 적용)
네트워크 설정을 관리하는 방법은 다음 두 가지가 있는데 여기에서는 /etc/network/interface를 사용한다.
  • /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 계열의 패키지를 선택하여 설명한다.

 

예)

 

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
  • 2) 압축된 루트 파일들
    • 루트 파일 시스템을 만들기 위한 압축 파일들만이 제공된다.
      • *.tar.gz
      • *.tar.xz
    • 아래 샘플은 루트 파일들을 압축하여 제공하므로 번거롭더라도 실제로 사용하기 전에 파티션을 가진 이미지 디스크를 만들고, 그 안에 복사하여 준비한다.
      • 18.04월: linaro-stretch-developer-20180416-89.tar.gz
        • 특징: debian 패킹 + gcc v6.3

 

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 옵션)을 사용하는 구성에서는 임베드된 디바이스 트리를 사용하는데 이를 외부에서 지정하는 방법을 알아보자.

 

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

 

참고

 

 

 

댓글 남기기