QEMU에 윈도 11 설치하기

제일 먼저 알아야 할 것은 QEMU 윈도에 갇힌 마우스 커서는 Ctrl+Alt+G로 빠져 나올 수 있다. 그리고 버추얼박스에 윈도 11 설치하기 방법으로는 윈도 11을 설치할 수 없었다.

1   QEMU만 사용하기

QEMU만 사용해서 윈도 11을 설치하려고 여러가지를 시도해 봤다. 우선 기본적으로 필요한 슬랙웨어 패키지는 다음과 같다. 순서대로 설치해서 qemu가 usbredir와 libslirp를 컴파일시 포함하도록 해야 된다.

  • usbredir: USB 지원
  • libslirp: 사용자 모드 네트워크 지원
  • qemu: QEMU
    • sbopkg에서 qemu를 찾아서 사용자 옵션을 다음과 같이 지정하고 컴파일한다.
      BRIDGE_HELPER_SETUID=yes SLIRP=yes
  • edk2-ovmf: 시큐어 부트 지원
  • swtpm: TPM 지원

별도로 virtio-win.iso 파일을 여기서 내려 받아야 된다.

cd $HOME

# QEMU 이미지 파일을 만든다.
qemu-img create -f qcow2 .qemu/win11.qcow2 128G

# UEFI 파일을 덮어 쓸 수 있게 복사한다.
[ -d .qemu/ovmf ] || mkdir .qemu/ovmf
cp -a /usr/share/edk2-ovmf-x64/OVMF_CODE.secboot.fd /usr/share/edk2-ovmf-x64/OVMF_VARS.secboot.fd .qemu/ovmf

# 윈도 11 설치 ISO와 virtio-win.iso를 .qemu/iso 디렉토리에 복사한다.

# TPM을 시작한다.
[ -d .qemu/tpm ] || mkdir .qemu/tpm
swtpm socket \
        --tpmstate dir=/tmp/tpm \
        --ctrl type=unixio,path=.qemu/tpm/tpm-sock \
        --tpm2&

# 가상 머신을 시작한다.
qemu-kvm \
        -machine q35 \
        -cpu host \
        -smp 2 \
        -m 16G \
        -chardev socket,id=chrtpm,path=$QEMU/tpm/tpm-sock \
        -tpmdev emulator,id=tpm0,chardev=chrtpm \
        -device tpm-crb,tpmdev=tpm0 \
        -vga virtio \
        -drive file=.qemu/ovmf/OVMF_CODE.secboot.fd,format=raw,if=pflash \
        -drive file=.qemu/ovmf/OVMF_VARS.secboot.fd,format=raw,if=pflash \
        -drive file=.qemu/win11.qcow2,if=virtio \
        -drive file=.qemu/iso/win11-installer.iso,media=cdrom \
        -drive file=.qemu/iso/virtio-win-0.1.240.iso,,media=cdrom

2   Virtual Machine Manager(virt-manager) 사용하기

확실히 GUI라서 쉽긴 쉽다.

2.1   virt-manager를 이용해서 윈도 11 설치하기

QEMU만 사용하기에서 설치한 패키지를 그대로 설치한 후 virt-manager를 설치한다.

  1. 이미지 파일을 $HOME/.qemu 디렉토리 안에 만든다.
  2. 설치 직전에 Customize configuration before install을 선택한다.
  3. Overview ⇒ Firmware: UEFI x86_64: /usr/share/edk2-ovmf-x64/OVMF_CODE.secboot.fd
  4. SATA Disk 1
    • Disk bus: VirtIO
    • Cache mode: none
    • Discard mode: unmap
  5. Add Hardware ⇒ Storage
    • Select or create custom storage: virtio-win.iso
    • Disk type: CDROM device
  6. NIC ⇒ Device model: virtio (bridge를 원하면 브리지 네트워킹 설정하기 참조)
  7. Tablet: 여기서는 이 기기를 지우면 성능이 좋아진다고 하지만 마우스는 상대 좌표를 쓰기 때문에 Synergy 같은 툴로 마우스가 화면 밖으로 나갔다 들어 오면 가상 머신 내의 좌표가 어긋나 버린다. 태블릿 기기는 절대 좌표를 쓰기 때문에 그런 문제가 없다. 이 기기는 그대로 유지한다.
  8. TPM ⇒ Version: 2.0
  9. Begin Installation
  10. 가상 머신이 부팅되면서 윈도 설치화면이 나온다.
  11. Where do you want to install Windows? 여기서 디스크가 보이지 않는다.
    1. Load driver ⇒ Browse ⇒ E:\viostor\w11\amd64 (가상 디스크 드라이버)
    2. Load driver ⇒ Browse ⇒ E:\NetKVM\w11\amd64 (가상 네트워크 드라이버)
  12. 설치가 다 끝나면 E 드라이브에 있는 virtio-win-guest-tools를 설치하고 가상 머신을 끈다.
  13. virtio-win.iso가 들어 있는 CDROM 기기를 제거한다.
  14. win11-installer.iso가 들어 있는 CDROM에서 CD를 제거한다.
  15. 지금까지 만든 이미지를 베이스 이미지로 보존하고 새로운 오버레이 이미지를 생성한다.
    su
    cd .qemu
    mv win11.qcow2 win11-fresh.qcow2
    qemu-img create -f qcow2 -b win11-fresh.qcow2 -F qcow2 win11.qcow2
  16. 이제 가상 머신을 부팅하고 사용하면 된다.

2.2   NAT 네트워킹 설정하기

기본 사용자 모드 네크워크인 NAT은 자체 dnsmasq를 사용하기 때문에 dnsmasq 서버에서는 바로 사용할 수 없다. 다은의 dnsmasq 설정 파일이 필요하다.

/etc/dnsmasq.d/60-libvirt.conf

# https://wiki.libvirt.org/Libvirtd_and_dnsmasq.html
interface=eth0
# https://bbs.archlinux.org/viewtopic.php?pid=2018654#p2018654
bind-interfaces

2.3   브리지 네트워킹 설정하기

NAT 네트워킹은 dnsmasq를 이용해서 별도의 서브네트워크를 생성하기 때문에 같은 LAN에 있는 머신에서 가상 머신으로의 접속이 불가능하다. 이를 위해서는 가상 머신이 LAN 상의 DHCP 서버에서 IP 주소를 가져 와야 한다. 일단 가상 머신의 NIC Network source를 Bridge device, Device name을 br0로 설정하고 다음의 명령들을 실행한다.

# https://wiki.archlinux.org/title/network_bridge
HOSTDEV=eth0
HOSTIP=192.168.74.1/24
GATEWAY=192.168.74.125
BRIDGEDEV=br0

ip link add name $BRIDGEDEV type bridge
ip link set dev $BRIDGEDEV up
ip address add $HOSTIP dev $BRIDGEDEV
ip route append default via $GATEWAY dev $BRIDGEDEV
ip link set $HOSTDEV master $BRIDGEDEV
ip address del $HOSTIP dev $HOSTDEV

이제 가상 머신이 LAN 상의 기존 DHCP 서버에서 IP 주소를 가져 오게 설정됐다.

2.4   Virtio-FS로 호스트와 디렉토리 공유하기

  1. 가상 머신을 끈다.
  2. Memory ⇒ Enable shared memory를 선택한다. 아래의 virtiofs를 위해 꼭 필요하다.
  3. Add Hardware ⇒ Filesystem
    • Driver: 기본 값인 virtiofs
    • Source path: 공유하고 싶은 호스트의 디렉토리 경로 (e.g., $HOME/winshared)
    • Target path: 게스트에서 사용할 경로, 단 /\를 안 붙이는 게 더 깔끔하다. (e.g., winshared)
  4. 가상 머신을 부팅한다.
  5. WinFsp를 설치한다. 이 프로그램 없이는 아래의 VirtIO-FS Service를 실행할 수 없다.
  6. Services를 관리자 권한으로 실행한다.
  7. VirtIO-FS Servie를 자동으로 실행되게 하고 시작한다.
  8. 위에서 공유한 win11Z: 드라이브로 인식된다.
  9. WinFsp 때문인지 두 개 이상의 디렉토리는 공유할 수 없다. 심볼릭 링크도 인식하지 않기 때문에 bind mount를 써서 해결해야 된다. root 아닌 일반 사용자는 bindfs를 쓰면 된다. 그런데 /etc/rc.d/rc.libvirt를 재실행해야 됐다.
    su -
    /etc/rc.d/rc.libvirt restart
    virsh net-start default

그런데 이렇게 공유 디렉토리를 설정하고 나니 save가 되지 않는다. 뭐 아쉬울 건 없다. 어차피 Samba가 더 빠르기 때문에 Samba로 갈아 타면 된다.

2.5   Samba로 호스트와 디렉토리 공유하기

/etc/samba/smb.conf 파일을 다음과 같이 만든다.

[global]
   workgroup = WORKGROUP
   server string = SERVER
   server role = standalone server
   hosts allow = VM's IP ADDRESS
   log file = /var/log/samba.%m
   max log size = 50
   dns proxy = no

[shared]
   path = /home/user/shared
   read only = no

Samba를 실행한 후 비밀번호를 설정한다. 이때 윈도의 비밀번호를 그대로 사용하면 가상 머신에서 비밀번호를 물어 보지 않는다.

su -
/etc/rc.d/rc.samba start
smbpasswd -a user

가상 머신에서 \\hostname\shared를 드라이브로 매핑하면 된다. 이제 Virtio-FS 설정을 모두 제거하면 save가 가능하다.

2.6   QEMU가 어는 문제

정확히 같은 문제인지는 모르겠지만 Samba로 네트워크 드라이브를 연결하니까 QEMU 상의 윈도 11이 얼어 버리는 문제가 자주 발생한다. 네트워크 드라이브를 지워도 계속 발생한다. 이런 경우 상태를 Save한 다음 다시 시작하니 해동되었다! 금방 다시 얼지만...

QEMU를 커맨드라인에서 직접 실행하니까 얼지 않았다. 아무래도 virt-manager가 사용하는 QXL 드라이버의 문제일 수도 있겠다. 여기도... 해결이 되었나? 스케일했을 때만 생기는 버그 같다.

--- a/hw/display/qxl-render.c
+++ b/hw/display/qxl-render.c
@@ -222,6 +222,7 @@ static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
     uint32_t max_chunks = 32;
     size_t offset = 0;
     size_t bytes;
+    QXLPHYSICAL next_chunk_phys = 0;
 
     for (;;) {
         bytes = MIN(size - offset, chunk->data_size);
@@ -230,8 +231,14 @@ static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
         if (offset == size) {
             return;
         }
-        chunk = qxl_phys2virt(qxl, chunk->next_chunk, group_id,
-                              sizeof(QXLDataChunk) + chunk->data_size);
+        next_chunk_phys = chunk->next_chunk;
+        chunk = qxl_phys2virt(qxl, next_chunk_phys, group_id,
+                              sizeof(QXLDataChunk));  // fist time, only get the next chunk's data size;
+        if (!chunk) {
+            return;
+        }
+        chunk = qxl_phys2virt(qxl, next_chunk_phys, group_id,
+                              sizeof(QXLDataChunk) + chunk->data_size); // second time, check data size and get data;
         if (!chunk) {
             return;
         }

참고문헌

이 칸을 비워 두세요.