테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

에뮬레이터와 가상 머신

게임 에뮬레이터는 옛 게임기를 통째로 소프트웨어로 흉내 내고, 가상 머신은 지금 쓰는 컴퓨터를 하드웨어의 도움으로 여러 대처럼 나눠 쓴다. 작은 CPU 인터프리터를 직접 돌려 보는 것에서 시작해 메모리 맵·타이밍·JIT, 하이퍼바이저·트랩·2단계 주소 변환, 원격 데스크톱, 언어 가상 머신, 법적 쟁점까지 따라간다.

목차 (14)
  1. 한 문장 답: 무엇을 흉내 내는가
  2. 컴퓨터를 흉내 낸다는 것
  3. 에뮬레이터 만들기 1: CPU 인터프리터
  4. 메모리 맵과 장치
  5. 그래픽·소리·입력
  6. 타이밍과 동기화
  7. 빠르게 하기: 동적 재컴파일(JIT)
  8. 가상 머신이란
  9. VM의 쓰임
  10. 원격 데스크톱: 화면은 보내고 입력은 받는다
  11. 언어 가상 머신: 같은 말의 다른 뜻
  12. 법과 윤리
  13. 흔한 오해
  14. 핵심 자료

한 문장 답: 무엇을 흉내 내는가

에뮬레이터는 "다른 기계"를 소프트웨어로 흉내 낸다. NES의 CPU는 6502 계열이고 PC는 x86-64나 Arm이라 기계어가 서로 통하지 않는다. 그래서 에뮬레이터는 게임의 명령을 하나씩 읽어 그 뜻대로 자기 변수를 바꾸고, 화면·소리·컨트롤러까지 모두 프로그램으로 만들어 낸다. 가상 머신(VM)은 "같은 종류의 기계"를 나눠 쓴다. x86-64 서버 위의 x86-64 게스트 OS는 대부분의 명령을 CPU가 그대로 실행하고, 하드웨어를 직접 건드리는 특권 동작만 하이퍼바이저가 가로채 처리한다. 그래서 VM은 거의 제 속도가 나고, 에뮬레이터는 흉내 내는 기계보다 훨씬 빠른 컴퓨터가 필요하다.

왼쪽은 게임 롬, 에뮬레이터, 호스트 OS, 호스트 CPU가 쌓인 에뮬레이터, 오른쪽은 게스트 OS 둘, 하이퍼바이저, 같은 종류의 CPU가 쌓인 가상 머신에뮬레이터가상 머신게임 롬6502 기계어(다른 명령 집합)에뮬레이터 프로그램명령을 하나씩 읽어 흉내 · CPU·화면·소리 전부호스트 OS(Windows·macOS·Linux)호스트 CPU: x86-64 또는 Arm앱 A게스트 OS Ax86-64 기계어앱 B게스트 OS Bx86-64 기계어하이퍼바이저: 특권 명령만 가로챔같은 x86-64 CPU(VT-x·AMD-V)명령마다 호스트 명령 여러 개 → 느림대부분 CPU가 직접 실행 → 거의 제 속도
에뮬레이터와 가상 머신. 에뮬레이터는 다른 명령 집합의 기계어를 소프트웨어가 해석하고, 가상 머신은 같은 CPU 위에서 하이퍼바이저가 특권 동작만 가로챈다.

경계가 늘 깔끔하지는 않다. QEMU는 혼자 돌면 Arm 보드를 x86 PC에서 흉내 내는 에뮬레이터이고, KVM과 함께 돌면 같은 종류의 CPU를 쓰는 VM이 된다. 비슷한 말로 묶이는 것들을 한 표에 놓으면 다음과 같다.

종류무엇을 흉내 내나어떻게명령 집합예
에뮬레이터다른 기계 전체(CPU·화면·소리·입력)소프트웨어로 명령을 해석하거나 번역달라도 된다Mesen(NES), Dolphin(게임큐브·Wii), QEMU 전체 시스템 모드
가상 머신같은 종류의 컴퓨터 한 대CPU가 게스트 명령을 직접 실행, 특권 동작만 하이퍼바이저가 처리같아야 한다KVM, Hyper-V, VMware ESXi, VirtualBox
컨테이너흉내 내지 않는다 — 격리된 프로세스호스트 커널을 함께 쓰고 namespace·cgroup으로 나눈다같아야 한다Docker, Podman, Kubernetes 파드
언어 VM실재하지 않는 가상의 기계바이트코드를 해석하거나 JIT으로 번역상관없다JVM, .NET CLR, WebAssembly 런타임, CPython
호환 계층다른 OS의 API나 다른 CPU의 앱 하나시스템 콜·API를 바꿔 주거나(Wine) 앱의 기계어를 번역(Rosetta 2)Wine은 같아야, Rosetta 2는 달라도Wine·Proton, Rosetta 2, WSL 1

컴퓨터를 흉내 낸다는 것

게임기도 컴퓨터다. 기계어를 실행하는 CPU, CPU가 주소로 읽고 쓰는 메모리(RAM과 카트리지 ROM), 화면을 그리는 그래픽 칩(NES의 PPU), 소리를 내는 오디오 칩(APU), 컨트롤러 같은 입출력 장치가 있고, 이 모두가 하나의 수정 발진기에서 나온 시계에 맞춰 움직인다. 게임은 이 부품들이 정확히 어떻게 반응하는지에 기대어 만들어졌다.

소프트웨어로 보면 이 모든 것이 결국 숫자다. 레지스터는 변수 몇 개, RAM은 바이트 배열, ROM은 파일에서 읽어 온 읽기 전용 배열, 그래픽 칩은 내부 레지스터와 비디오 메모리 배열이다. 그러니 에뮬레이터의 뼈대는 단순하다. 기계의 상태를 담은 배열 묶음과, 그 상태를 명령 하나씩 바꾸는 루프다. 루프를 한 번 돌 때마다 흐른 시간만큼 그래픽·소리 칩도 진행시키면 한 대의 게임기가 된다.

에뮬레이터는 레지스터, RAM, ROM, PPU, APU, 컨트롤러 상태와 이를 바꾸는 네 단계 루프로 이루어진다상태: 숫자 배열 묶음CPU 레지스터: A · X · Y · PC · SP · PRAM: 2 KiB 바이트 배열카트리지 ROM: 읽기 전용 배열PPU: 화면 상태 · 비디오 메모리APU: 소리 채널 다섯 개의 상태컨트롤러: 눌린 버튼 8비트루프: 명령 하나씩① 메모리[PC]에서 명령 읽기② 어떤 명령인지 해석③ 레지스터·메모리 바꾸기④ 흐른 사이클만큼 장치 진행1초에 수백만 번 되풀이
에뮬레이터의 뼈대. 왼쪽 상태(NES 기준)를 오른쪽 루프가 명령 하나씩 바꾼다. 실제 NES CPU는 초당 약 179만 사이클을 돌린다.

에뮬레이터 만들기 1: CPU 인터프리터

CPU 흉내의 핵심은 fetch → decode → execute 세 단계다. 프로그램 카운터(PC)가 가리키는 메모리에서 명령 바이트를 읽고(fetch), 그 비트 모양으로 어떤 명령인지 가려내고(decode, 보통 거대한 switch), 명령의 뜻대로 레지스터·메모리·PC를 바꾼다(execute). 실제 CPU가 회로로 하는 일을 코드 몇십 줄이 똑같이 한다.

첫 에뮬레이터로 흔히 고르는 것이 CHIP-8이다. 1970년대 후반 COSMAC VIP 같은 취미용 컴퓨터에서 게임을 쉽게 만들려고 고안한 가상 기계라, 실제 칩이 아니면서도 구조가 진짜 CPU와 같다. 메모리 4 KiB, 8비트 범용 레지스터 16개(V0~VF, VF는 자리올림 플래그 겸용), 주소 레지스터 I, 64 × 32 흑백 화면, 60 Hz로 줄어드는 타이머 두 개, 2바이트짜리 명령 서른몇 개가 전부다. 프로그램은 0x200번지부터 올라간다. 아래 그림은 그 가운데 일곱 가지 명령만 구현한 인터프리터로 작은 프로그램을 실제로 돌린 기록이다.

CHIP-8 프로그램 목록과 레지스터, PC, 메모리 값이 명령마다 바뀌는 단계 그림주소기계어명령2006003LD V0, 32026100LD V1, 02048104ADD V1, V020670FFADD V0, 2552083000SE V0, 020A1204JP 20420CA300LD I, 30020EF155LD [I], V0~V12101210JP 210마지막 줄은 멈춤 표시(자기 자신으로 점프)레지스터메모리V0V1VFIPC30030100000020000실행 전300000202001번째 명령 실행 뒤300000204002번째 명령 실행 뒤330000206003번째 명령 실행 뒤230000208004번째 명령 실행 뒤23000020A005번째 명령 실행 뒤230000204006번째 명령 실행 뒤250000206007번째 명령 실행 뒤150000208008번째 명령 실행 뒤15000020A009번째 명령 실행 뒤1500002040010번째 명령 실행 뒤1600002060011번째 명령 실행 뒤0600002080012번째 명령 실행 뒤06000020C0013번째 명령 실행 뒤06030020E0014번째 명령 실행 뒤0603002100615번째 명령 실행 뒤
1 / 16

프로그램 9개 명령(18바이트)을 0x200에 올렸다. 레지스터는 모두 0이고 PC는 0x200이다. 이제 fetch → decode → execute를 되풀이한다.

작은 CHIP-8 인터프리터로 3 + 2 + … + 1을 계산한다. V0을 하나씩 줄이며 V1에 더하고, 0이 되면 V0·V1을 메모리 0x300·0x301에 적는다. 모든 값은 노트의 인터프리터 함수가 실제로 실행해 얻었다.

몇 가지가 눈에 띈다. ADD V0, 255로 1을 뺀 것은 8비트 레지스터가 256에서 0으로 감기기 때문이다. 에뮬레이터는 이런 넘침까지 원래 기계와 똑같이 해야 한다. SE는 조건이 맞으면 다음 명령을 건너뛰는 식으로 분기하고, 반복문은 JP로 되돌아가 만든다. 실제 CHIP-8 에뮬레이터는 여기에 화면에 스프라이트를 XOR로 그리는 DXYN, 키 입력, 타이머를 더하면 완성이다. NES의 6502는 명령이 150개쯤(공식 명령 56종 × 주소 지정 방식)으로 늘고 플래그 규칙이 까다로울 뿐, 루프 구조는 같다.

원래 기계의 명세를 아는 것이 출발점이다. CHIP-8은 공개 문서가 있지만 상용 게임기는 대개 공개 문서가 없어, 사람들이 실제 기계로 실험해 동작을 알아내고 위키에 정리해 왔다. 남이 만든 바이너리를 읽는 일반적인 방법은 리버스 엔지니어링 노트, 기계어와 컴파일러의 관계는 컴파일러 노트에 있다.

메모리 맵과 장치

CPU 입장에서 세상은 주소뿐이다. 6502는 16비트 주소를 내보내 64 KiB를 가리킬 수 있는데, NES 기판은 그 주소를 보고 어느 부품이 응답할지 정한다. $0000~$07FF는 내부 RAM 2 KiB, $2000~$2007은 PPU 레지스터 8개, $4000~$4017은 APU와 컨트롤러, $8000~$FFFF는 카트리지 ROM이다. 기판이 주소 선 일부를 무시하기 때문에 RAM은 $0800~$1FFF에 세 번 더 비쳐 보이고(거울, mirror), PPU 레지스터 8개는 $3FFF까지 8바이트마다 되풀이된다.

NES CPU 주소 공간: RAM, PPU 레지스터, APU와 입력, 카트리지 RAM, 카트리지 ROM이 위에서 아래로 놓인 그림CPU가 보는 64 KiB 주소(16비트)RAM 2 KiB(+ 거울 3벌)PPU 레지스터 8개(+ 거울)APU·컨트롤러($4000~$4017)·확장카트리지 RAM(세이브)카트리지 ROM 32 KiB(+ 매퍼 레지스터)$0000$2000$4000$6000$8000$FFFF높이 = 주소 크기예: $0801 → RAM[$0001]예: $3456 → PPU 레지스터 $2006색칠 = 메모리 맵 I/O(읽고 쓰면 장치가 반응)
NES CPU가 보는 64 KiB. 높이를 주소 크기에 비례해 그렸다. 아래 예는 거울 구역의 주소가 원래 칸으로 접히는 모습으로, 노트의 주소 분배 함수가 계산했다.

이처럼 장치 레지스터를 메모리 주소처럼 읽고 쓰게 하는 방식을 메모리 맵 I/O라 한다. $2007에 바이트를 쓰면 RAM에 저장되는 것이 아니라 PPU가 비디오 메모리에 그 값을 적고 내부 주소를 하나 올린다. 그래서 에뮬레이터의 메모리 읽기·쓰기 함수는 단순한 배열 접근이 아니라 주소를 보고 장치 함수로 보내는 분배기다. 마이크로컨트롤러가 GPIO·UART 레지스터를 똑같이 주소로 다루는 모습은 임베디드 노트에 있다.

카트리지는 단순한 ROM이 아니다. 32 KiB보다 큰 게임은 카트리지 안의 매퍼(뱅크 전환 칩)가 ROM의 어느 조각을 $8000 창에 보여 줄지 바꾸고, 어떤 카트리지는 세이브용 RAM과 전지를 품고 있다. NES 에뮬레이터는 매퍼 종류마다 이 동작을 따로 구현해야 하며, 그 종류가 수백 가지다. 게임 롬이 어떤 칩에 어떻게 담겼는지는 저장장치 › 기억장치의 종류에서 다룬다.

그래픽·소리·입력

그래픽: NES의 PPU는 프레임 버퍼를 통째로 두지 않고, 주사선을 그리는 순간순간 배경 타일과 스프라이트를 골라 픽셀 색을 낸다. 에뮬레이터는 이 과정을 흉내 내 256 × 240 픽셀 배열을 채우고, 한 프레임이 끝나면 그 배열을 GPU 텍스처로 올려 창 크기에 맞게 확대한다. 게임이 화면을 그리는 도중에 스크롤 값을 바꾸는 기법(화면 위쪽은 고정, 아래쪽만 스크롤)이 흔해서, 줄 단위나 점 단위로 그려야 맞는 게임이 많다.

소리: APU는 펄스파 두 개, 삼각파, 노이즈, 샘플 재생 채널 다섯 개를 CPU 클록에 맞춰 돌린다. 에뮬레이터는 사이클마다 채널 출력을 계산해 섞고, 이 초당 약 179만 개의 값을 호스트 사운드 카드가 받는 48 kHz로 리샘플링해 오디오 버퍼에 쌓는다. 그냥 솎아 내면 높은 주파수가 접혀 들어와 듣기 싫은 소리가 나므로 저역 통과 필터를 먼저 거친다. 샘플링·에일리어싱·필터의 원리는 신호 처리 노트에 있다.

입력: 게임은 $4016을 읽을 때마다 컨트롤러 버튼 상태를 한 비트씩 받는다. 에뮬레이터는 호스트의 키보드·게임패드 상태를 8비트 버튼 값으로 만들어 두고, 게임이 $4016을 읽으면 다음 비트를 돌려준다.

그래픽은 PPU 흉내에서 화면 버퍼를 거쳐 호스트 창으로, 소리는 APU 흉내에서 오디오 버퍼를 거쳐 스피커로, 입력은 호스트 입력에서 버튼 상태를 거쳐 게임으로 가는 세 줄 그림그래픽PPU 흉내줄마다 타일·스프라이트화면 버퍼256 × 240 픽셀 배열호스트 창GPU 텍스처로 확대소리APU 흉내채널 5개를 사이클마다오디오 버퍼샘플을 모아 둠스피커48 kHz로 리샘플링입력호스트 입력키보드·게임패드버튼 상태8비트(A·B·방향…)게임이 읽음$4016을 한 비트씩가운데 = 에뮬레이터가 채우는 배열
그래픽·소리·입력의 세 경로. 가운데 칸이 에뮬레이터가 채우는 배열이고, 오른쪽 끝은 호스트 OS의 창·사운드·입력 API로 이어진다.

소리는 화면보다 까다롭다. 화면은 한 프레임쯤 늦거나 한 번 겹쳐 보여도 잘 모르지만, 오디오 버퍼가 비면 바로 "틱" 소리가 난다. 그래서 많은 에뮬레이터가 오디오 버퍼가 차는 속도에 맞춰 에뮬레이션 속도를 미세하게 조절한다.

타이밍과 동기화

부품들은 같은 시계를 나눠 쓴다. NTSC NES는 21.477 MHz 마스터 클록을 12로 나눠 CPU(1.79 MHz), 4로 나눠 PPU를 돌리므로 CPU 1사이클 동안 PPU는 점 3개를 그린다. 한 프레임은 262줄 × 341점이니 CPU로 치면 약 29,780.5사이클이다. 에뮬레이터의 기본 동기화는 이 비율을 지키는 것이다. CPU 명령 하나를 실행하고 걸린 사이클을 세어, 그 세 배만큼 PPU를, 같은 만큼 APU를 진행시킨다.

CPU 사이클 여덟 칸과 PPU 점 스물네 칸을 나란히 그려, 4사이클 명령 하나 동안 PPU가 12점을 그리는 모습과 프레임당 사이클 수 계산명령 하나(LDA $2002) = 4사이클다음 명령 …CPUPPU12345678그사이 PPU는 12점을 그린다CPU 1사이클 = PPU 3점마스터 클록 21.477 MHz ÷ 12 = CPU 1.789773 MHz, ÷ 4 = PPU 5.369318 MHz1프레임 = 262줄 × 341점 = 89,342점(렌더링 중 홀수 프레임은 1점 짧음)→ 프레임당 CPU 29,780.5사이클 · 초당 60.0988프레임
CPU와 PPU의 1:3 비율. 4사이클 명령 하나가 끝나면 PPU를 12점 진행시켜 맞춘다. 아래 수치는 NESdev 위키의 클록 값으로 계산했다.

문제는 명령 하나 단위로도 거칠 때가 있다는 것이다. 게임이 PPU 상태 레지스터를 읽는 바로 그 사이클에 PPU가 어디를 그리고 있었는지에 따라 결과가 달라지고, 일부 게임은 그런 우연한 타이밍에 기대어 동작한다. 그래서 정확도를 높이려면 사이클마다, 심지어 사이클 안의 읽기·쓰기 순서까지 맞추는 사이클 정확(cycle-accurate) 에뮬레이션을 하게 된다. 반대편 끝은 고수준 에뮬레이션(HLE)이다. 하드웨어를 흉내 내는 대신 "이 함수는 결국 이 일을 한다"는 수준에서 결과만 만들어 낸다. 예컨대 BIOS의 파일 읽기 루틴을 명령 단위로 돌리지 않고 호스트 파일을 바로 읽어 준다. 하드웨어를 회로 수준까지 흉내 내는 쪽은 저수준 에뮬레이션(LLE)이라 부른다.

정확도와 속도는 맞바꾸는 관계다. SNES 에뮬레이터 bsnes(뒤에 higan)의 개발자는 2011년 글에서, 1990년대 에뮬레이터는 25 MHz급 PC로도 돌았지만 하드웨어를 충실히 재현하는 에뮬레이터는 3 GHz가 필요하다고 설명했다. 빠른 에뮬레이터에서는 괜찮아 보이던 게임이 몇 시간 뒤 특정 장면에서 멈추는 식의 문제가 정확도에서 갈린다는 것이다. 오래된 8·16비트 기계는 지금 컴퓨터가 충분히 빨라 정확도를 고르고, 최신 기계일수록 HLE와 아래의 JIT로 속도를 확보한다.

빠르게 하기: 동적 재컴파일(JIT)

인터프리터는 같은 명령을 백 번 실행하면 백 번 해석한다. 게임큐브·Wii(약 486~729 MHz PowerPC)나 그 이후 기계를 흉내 내려면 이 낭비를 견딜 수 없다. 그래서 동적 재컴파일(dynamic recompilation, JIT)을 쓴다. 게스트 코드를 분기 명령까지의 블록으로 잘라, 처음 만났을 때 그 블록 전체를 호스트 기계어로 번역해 캐시에 넣고, 다음부터는 번역본을 바로 실행한다. 블록끼리 직접 점프하도록 이어 붙이면(블록 체이닝) 디스패치 비용도 줄어든다.

왼쪽 인터프리터는 명령마다 읽기, 해석, 실행을 되풀이하고, 오른쪽 블록 번역은 블록 4개를 한 번씩 번역해 캐시에서 200번 실행한다인터프리터읽기해석(switch)실행명령마다 되풀이해석 403번블록 단위 번역(JIT)게스트 블록(주소)번역 캐시: 실행 횟수200~208호스트 코드 · 1번20A호스트 코드 · 99번204~208호스트 코드 · 99번20C~20E호스트 코드 · 1번번역 4번(명령 11개), 블록 실행 200번반복이 늘어도 번역 횟수는 그대로다
같은 CHIP-8 합 프로그램을 1부터 100까지로 늘려 돌린 기록. 인터프리터는 실행한 명령 수만큼 해석하고, 번역기는 처음 보는 블록만 한 번씩 번역한다. 블록 경계와 횟수는 실행 기록에서 계산했다.

대가도 있다. 번역 자체가 시간을 먹으니 한 번만 도는 코드에는 손해이고, 게임이 자기 코드를 고쳐 쓰면(자기 수정 코드) 낡은 번역본을 버려야 한다. QEMU의 번역기 TCG는 번역한 코드가 있는 페이지를 쓰기 금지로 표시해 두었다가, 쓰기가 일어나면 그 페이지의 번역을 무효로 만든다. 사이클 단위 타이밍도 블록 단위로 뭉개지므로, 정확도가 중요한 부분은 인터프리터로 돌리는 절충을 한다.

같은 기법이 게임 밖에서도 쓰인다. 게임큐브·Wii 에뮬레이터 Dolphin은 PowerPC 코드를 x86-64와 AArch64로 JIT 번역한다. Apple의 Rosetta 2는 x86-64 Mac 앱을 Apple 실리콘(Arm)에서 돌리기 위해 실행 전에 앱을 통째로 번역해 두고(AOT), 앱이 실행 중에 만들어 내는 JIT 코드는 그때그때 번역한다. Apple은 Rosetta를 macOS 27까지 범용으로 제공하고 이후에는 옛 게임용 일부만 남기겠다고 밝혔다. 번역기도 결국 컴파일러라서, 중간 표현·레지스터 할당·최적화 같은 말이 그대로 나온다. 그 기초는 컴파일러 노트에 있다.

가상 머신이란

가상 머신은 질문이 다르다. "다른 기계를 어떻게 흉내 내나"가 아니라 "같은 기계 한 대를 어떻게 여러 OS가 서로 모르게 나눠 쓰나"다. 이 일을 하는 소프트웨어가 하이퍼바이저(VMM, 가상 머신 모니터)다. 하드웨어 바로 위에서 도는 타입 1(베어메탈)은 데이터센터용이고, 보통 OS 위의 앱으로 도는 타입 2(호스트형)는 개인 PC에서 다른 OS를 띄울 때 쓴다. 생각 자체는 1960~70년대 IBM 메인프레임(CP-67, VM/370)에서 나왔다.

타입 1 하이퍼바이저는 하드웨어 바로 위에 VM 셋을 올리고, 타입 2 하이퍼바이저는 호스트 OS 위에서 다른 앱과 나란히 돌며 VM 둘을 올린다타입 1(베어메탈)타입 2(호스트형)VM 1게스트 OSVM 2게스트 OSVM 3게스트 OS하이퍼바이저ESXi · Hyper-V · Xen · KVM하드웨어(서버)VM 1VM 2하이퍼바이저 앱브라우저편집기 …호스트 OSVirtualBox · VMware Workstation · Parallels하드웨어(노트북·PC)KVM은 Linux 커널 자체를 하이퍼바이저로 바꾸므로 보통 타입 1로 분류한다
하이퍼바이저 두 종류. 타입 1은 서버 하드웨어를 직접 나누고, 타입 2는 호스트 OS가 주는 자원을 다시 나눈다.

자주 나오는 질문이 "KVM은 타입 1인가 2인가"다. KVM은 평범한 Linux 위에 올리는 커널 모듈이라 겉모습은 타입 2 같지만, 올리는 순간 호스트 커널 자체가 하드웨어를 직접 나누는 하이퍼바이저가 된다. 그래서 하이퍼바이저 분류에서도 KVM과 FreeBSD의 bhyve를 "호스트 OS를 사실상 타입 1로 바꾸는" 예로 들며 두 종류의 경계가 늘 분명하지는 않다고 적는다. 분류 이름보다 게스트의 특권 동작을 누가 어디서 처리하느냐(아래 트랩 앤 에뮬레이트)를 보는 편이 정확하다.

트랩 앤 에뮬레이트: 게스트 OS의 커널은 자기가 가장 높은 권한이라고 믿지만, 실제로는 한 단계 낮은 권한에서 돌린다. 그러면 페이지 테이블을 바꾸거나 인터럽트를 끄는 특권 명령을 실행하는 순간 CPU가 막고 하이퍼바이저로 트랩된다(운영체제 노트의 시스템 콜·예외와 같은 길이다). 하이퍼바이저는 그 명령이 하려던 일을 그 VM의 가상 상태에 대해 흉내 내고 게스트로 돌려보낸다. 나머지 평범한 명령은 CPU가 그대로 실행한다. 1974년 Popek과 Goldberg는 이것이 통하는 조건을 정리했다. 시스템 상태를 바꾸거나 그 상태에 따라 결과가 달라지는 민감한 명령이 모두 사용자 모드에서 트랩되는 특권 명령이기만 하면 된다.

게스트 커널의 특권 명령이 트랩되어 하이퍼바이저가 흉내 낸 뒤 복귀하는 흐름과, 민감한 명령이 특권 명령의 부분집합인지 비교한 두 벤 다이어그램게스트 커널이특권 명령 실행CPU가 막고트랩 발생하이퍼바이저가가상 상태에 흉내게스트로 복귀다음 명령부터게스트 커널은 실제로는 낮은 권한에서 돌기 때문에 특권 명령이 모두 트랩된다특권 명령민감한 명령민감 ⊆ 특권 → 가상화 가능특권 명령POPF 등옛 x86: 민감하지만 특권 아닌 명령트랩 없이 조용히 다르게 동작한다
위: 트랩 앤 에뮬레이트의 흐름. 아래: 민감한 명령이 특권 명령 안에 다 들어 있으면 가상화할 수 있고, 하나라도 밖에 있으면 하이퍼바이저가 모르는 사이에 게스트가 잘못된 결과를 받는다.

x86의 문제와 바이너리 변환: 옛 x86은 이 조건을 어겼다. 민감하지만 특권이 아닌 명령이 18개 있었는데, 예컨대 POPF는 사용자 모드에서 실행하면 인터럽트 플래그를 바꾸지 못하고 트랩도 없이 조용히 무시된다. 게스트 커널이 인터럽트를 켰다고 믿는데 하이퍼바이저는 알 길이 없다. VMware는 1999년 무렵 게스트 커널 코드를 실행 직전에 훑어 문제 명령을 안전한 코드로 바꿔 넣는 바이너리 변환으로 이를 돌파했고, Xen은 게스트 커널 소스를 고쳐 그런 명령 대신 하이퍼바이저를 직접 부르게 하는 반가상화(paravirtualization)를 택했다. 앞 절의 JIT 번역과 같은 기술이 같은 종류의 CPU 위에서 쓰인 셈이다.

하드웨어 가상화: 2005~2006년 Intel VT-x와 AMD-V가 나오며 CPU에 "게스트 모드"가 생겼다. 게스트 커널은 게스트 모드의 링 0에서 정상 권한으로 돌고, 하이퍼바이저가 미리 정해 둔 사건(특정 레지스터 접근, 입출력, 인터럽트 등)이 일어날 때만 CPU가 VM 탈출(VM exit)로 하이퍼바이저에 넘긴다. 이후 x86 하이퍼바이저는 대부분 이 기능을 쓴다. KVM은 Linux 커널에 이 기능을 다루는 모듈을 넣어 커널 자체를 하이퍼바이저로 만들고, 장치 흉내는 QEMU 같은 사용자 공간 프로그램에 맡긴다.

메모리 가상화: 게스트 OS도 자기 페이지 테이블로 가상 주소를 "물리 주소"로 바꾸지만, 그 물리 주소는 사실 하이퍼바이저가 준 가짜다. 처음에는 하이퍼바이저가 게스트 표와 자기 표를 합친 섀도 페이지 테이블을 몰래 만들어 CPU에 물렸다. 게스트가 표를 고칠 때마다 트랩을 걸어 섀도를 맞춰야 해서 비쌌다. 그래서 CPU가 두 단계를 하드웨어로 걷는 2단계 주소 변환(Intel EPT, AMD NPT)이 들어왔다. 대신 TLB가 빗나가면 걸음 수가 곱으로 는다. 게스트 표 한 단계를 읽을 때마다 그 표가 있는 게스트 물리 주소를 다시 호스트 표 네 단계로 바꿔야 하기 때문이다. 4단계 × 4단계면 최대 24번이다.

게스트 페이지 테이블 네 단계마다 호스트 페이지 테이블 네 단계를 거쳐 모두 24번 메모리를 읽는 2단계 주소 변환 격자호스트 4호스트 3호스트 2호스트 1읽기게스트 4단계 표12345게스트 3단계 표678910게스트 2단계 표1112131415게스트 1단계 표1617181920데이터 주소21222324데이터네이티브표 4번 읽기2단계 변환최대 24번 읽기그래서 TLB와거대 페이지가더 중요해진다색칠 = 게스트 표 항목, 나머지 칸 = 그 위치를 찾는 호스트 표 읽기
2단계 주소 변환에서 TLB가 빗나갔을 때 메모리를 읽는 순서. 칸 번호는 읽는 차례다. 페이지 테이블과 TLB 자체는 운영체제 노트의 가상 메모리 절에 있다.

장치 가상화: 게스트에 디스크·랜카드를 주는 방법은 셋이다. 진짜 있는 장치(인텔 e1000 랜카드 등)의 레지스터를 그대로 흉내 내면 어떤 OS든 기존 드라이버로 돌지만, 레지스터를 건드릴 때마다 VM 탈출이 일어나 느리다. 이건 앞의 게임기 에뮬레이터와 똑같은 장치 흉내다. virtio는 처음부터 가상화용으로 약속한 장치라, 게스트 드라이버가 공유 메모리의 링(virtqueue)에 요청을 모아 두고 한 번만 알린다. 가장 빠른 길은 패스스루로, 진짜 장치(또는 SR-IOV로 쪼갠 가상 기능)를 VM에 직접 넘기고 IOMMU가 그 장치의 DMA가 다른 VM 메모리에 닿지 못하게 막는다. 대신 그 VM은 장치에 묶여 다른 서버로 옮기기 어려워진다.

에뮬레이트 장치, virtio 반가상화, 패스스루 세 방식에서 게스트 드라이버가 실제 랜카드에 닿는 경로에뮬레이트 장치게스트: e1000 드라이버레지스터 접근마다 트랩QEMU가 가짜 랜카드 흉내실제 랜카드어떤 OS든 그냥 돈다 · 느림virtio 반가상화게스트: virtio-net 드라이버공유 링에 모아 한 번 알림호스트 백엔드(vhost)실제 랜카드전용 드라이버 필요 · 빠름패스스루게스트: 진짜 드라이버IOMMU가 DMA 주소 보호하이퍼바이저는 비켜섬실제 장치(SR-IOV VF)거의 제 속도 · 옮기기 어려움
장치 가상화 세 방식. 왼쪽으로 갈수록 호환성이 좋고, 오른쪽으로 갈수록 빠르지만 VM이 하드웨어에 묶인다.

VM의 쓰임

  • 서버 통합과 클라우드: 사용률이 낮은 서버 여러 대를 VM으로 한 대에 모으면 하드웨어·전기·공간이 준다. 클라우드의 가상 서버 한 대가 바로 이런 VM 하나이며, 고객은 하드웨어를 보지 않고 VM 크기만 고른다. 자세한 것은 클라우드 노트에 있다.
  • 스냅숏과 마이그레이션: VM의 상태는 결국 메모리 내용과 디스크 파일이라 통째로 저장하고 되돌릴 수 있다. 켜진 VM을 다른 서버로 옮기는 라이브 마이그레이션은 VM이 도는 동안 메모리를 복사하고, 그사이 바뀐 페이지만 다시 보내기를 되풀이하다가, 남은 양이 작아지면 아주 잠깐 멈춰 마지막 조각을 옮긴다.
  • 격리와 보안 샌드박스: 게스트는 자기만의 커널을 가지므로 하나가 뚫려도 다른 VM으로 번지기 어렵다. 그래서 악성코드를 일부러 실행해 행동을 관찰하는 동적 분석을 VM에서 하고, 스냅숏으로 깨끗한 상태로 되돌린다(리버스 엔지니어링 노트). 반대로 악성코드가 VM 안인지 알아채고 얌전히 구는 일도 있어, 분석 환경은 VM 흔적을 숨기려 애쓴다.
  • 개발과 테스트: 다른 OS·다른 버전에서 프로그램을 시험하거나, 망가져도 되는 실습 환경을 만들 때 쓴다.
  • 메모리 오버커밋과 같은 페이지 합치기: VM들은 받은 메모리를 다 쓰지 않는 때가 많아, 호스트는 실제 RAM보다 많은 메모리를 VM들에 나눠 주기도 한다(오버커밋). 같은 OS를 띄운 VM끼리는 내용이 똑같은 페이지가 많은데, Linux의 KSM(Kernel Samepage Merging)은 원래 KVM을 위해 만든 기능으로 이런 페이지를 찾아 쓰기 금지된 한 장으로 합치고, 누가 고치려 하면 그때 따로 복사한다(copy-on-write). 다만 모든 VM이 한꺼번에 메모리를 채우면 호스트가 스왑에 빠지므로 여유를 두고 쓴다.
8 GiB VM을 사전 복사로 옮길 때 라운드마다 보내는 양이 줄고, 마지막에 잠깐 멈춰 45 ms 만에 끝나는 막대그래프메모리 8 GiB · 링크 1100 MiB/s · 초당 200 MiB가 바뀐다고 가정1라운드(VM 실행 중)8,192 MiB · 7.45 s2라운드(VM 실행 중)1,489 MiB · 1.35 s3라운드(VM 실행 중)271 MiB · 0.25 s정지 후 마지막49 MiB · 45 ms모두 10,002 MiB를 보내지만 멈춘 시간은 45 ms뿐이다
8 GiB VM의 사전 복사 마이그레이션. 링크 속도와 페이지가 바뀌는 속도는 설명을 위해 정한 값이며, 수치는 노트의 마이그레이션 함수가 계산했다. 바뀌는 속도가 링크보다 빠르면 영원히 끝나지 않으므로, 실제 구현은 라운드 수에 한도를 두거나 VM을 잠시 느리게 만든다.

컨테이너와의 차이: 컨테이너는 VM이 아니다. 하드웨어도 커널도 흉내 내지 않고, 호스트 커널 하나를 모든 컨테이너가 함께 쓰며 namespace·cgroup으로 보이는 것과 쓸 수 있는 양만 나눈다. 그래서 훨씬 가볍고 빨리 뜨지만, 커널이 공유되므로 격리는 VM보다 얇다. 두 장점을 섞으려고 컨테이너마다 아주 작은 VM을 붙이는 방식(Firecracker 마이크로VM, Kata Containers)도 쓰인다. 자세한 구조는 컨테이너 노트에 있다.

원격 데스크톱: 화면은 보내고 입력은 받는다

원격 화면 조작은 흉내가 아니라 중계다. 프로그램은 원격 컴퓨터에서 진짜로 돌고, 내 컴퓨터는 그 화면을 받아 보여 주며 내 마우스·키보드 입력을 돌려보낼 뿐이다. 원격 컴퓨터는 ① OS의 캡처 API로 화면을 받고 ② 이전 프레임과 달라진 곳을 찾아 ③ 압축해 보낸다. 내 컴퓨터는 ④ 풀어서 그리고 ⑤ 입력 이벤트를 보내며, 원격 컴퓨터는 ⑥ 그 입력을 OS에 넣는다. 앱은 진짜 입력을 받은 줄 알고 화면을 다시 그리고, 고리가 다시 돈다.

원격 데스크톱에서 화면 캡처, 바뀐 곳 찾기, 인코딩, 디코딩, 입력 전송, 입력 주입이 고리로 도는 단계 그림원격 컴퓨터네트워크내 컴퓨터① 화면 캡처② 바뀐 곳 찾기③ 압축·인코딩④ 디코딩·그리기⑤ 마우스·키보드⑥ 입력 주입앱이 다시 그림원격 컴퓨터: 화면을 찍어 보내고, 받은 입력을 넣는다내 컴퓨터: 받은 화면을 그리고, 내 입력을 보낸다60 fps라면 화면이 16.7 ms마다 한 장씩 건너간다1920 × 1080, 120 px 타일화면을 16 × 9 = 144칸으로 나눈다커서 이동 · 편집기 한 줄 · 시계→ 9칸(6.25%)만 다시 보낸다Windows: Desktop Duplication APImacOS: ScreenCaptureKitLinux(Wayland): PipeWire 포털모니터 없는 서버: 가상 디스플레이픽셀 그대로: 1920 × 1080 × 4 B × 60 = 3.98 Gbps영상 코덱(H.264·HEVC·AV1): 약 10 Mbps← 약 1/398GPU의 하드웨어 디코더가 압축을 풀어 텍스처로 올린다창 크기에 맞춰 늘이거나 줄여 그린다커서는 내 쪽에서 바로 그려 지연을 숨기기도 한다mousemove x=1000 y=540mousedown leftkeydown KeyA이벤트 하나가 수십 바이트 · 좌표는 원격 화면 기준원격 OS의 입력 API로 넣는다(Windows의 SendInput 등)앱은 진짜 키보드·마우스 입력으로 받는다로그인 화면처럼 보호된 화면은 서비스 권한이 따로 필요하다10841038입력 전송 10 · 원격 앱이 그림 8 · 캡처 2 · 인코딩 4네트워크 10 · 디코딩 3 · 화면 표시 8 (ms, 왼쪽부터)합계 약 45 ms — 가정한 값이며 거리·코덱·화면 주사율에 따라 크게 달라진다
1 / 8

원격 데스크톱은 이 고리를 계속 돈다. 원격 컴퓨터는 화면을 보내고 내 컴퓨터는 입력을 보낸다. 게임 스트리밍·VDI·화상 회의의 화면 공유도 같은 고리다.

원격 데스크톱 한 바퀴. 왼쪽 세 칸과 ⑥은 원격 컴퓨터, 오른쪽 두 칸은 내 컴퓨터에서 일어난다. 화면 크기·바뀐 영역·코덱 비트레이트·단계별 지연은 설명을 위해 정한 예다.

대부분의 비용은 화면 쪽에 있다. 1080p 60 fps 화면을 픽셀 그대로 보내면 3.98 Gbps라 보통 인터넷으로는 어림없다. 다행히 사무용 화면은 한 프레임에서 바뀌는 곳이 아주 적고, 게임처럼 다 바뀌는 화면은 영상 코덱이 프레임 사이의 비슷함을 이용해 수백 분의 일로 줄인다. 반대로 입력 이벤트는 몇십 바이트라 대역폭보다 지연이 문제다. 화면이 몇십 ms 늦게 따라오면 마우스가 끈적하게 느껴진다.

프로토콜마다 화면을 보내는 방식이 다르다.

  • VNC(RFB): 가장 단순하다. 원격 프레임 버퍼에서 바뀐 픽셀 사각형을 보내고(필요하면 가벼운 압축), 받는 쪽은 그것을 제자리에 붙인다. 창 시스템을 몰라도 되어 어떤 OS에서든 구현할 수 있다(RFC 6143).
  • RDP: Windows의 원격 데스크톱 프로토콜. 화면을 그리는 명령과 캐시한 비트맵을 보내는 "그래픽 원격화"에서 출발해 지금은 영상 코덱 모드도 쓴다. 클립보드·드라이브·프린터·오디오를 가상 채널로 함께 실어 나르는 것이 강점이다.
  • 원격 플레이·게임 스트리밍: Parsec이나 Moonlight(클라이언트)·Sunshine(호스트) 같은 도구는 GPU의 하드웨어 인코더로 화면 전체를 H.264·HEVC·AV1 영상으로 보내고, 버퍼를 최소로 줄여 지연을 낮춘다. 클라우드 게이밍도 같은 원리로, 게임이 데이터센터의 GPU 서버에서 돌 뿐이다.
  • 브라우저 기반: 설치 없이 브라우저에서 원격 화면을 보는 서비스는 대개 WebRTC로 영상과 데이터 채널을 주고받는다. 이 사이트의 P2P 화상 통화도 같은 WebRTC 위에서 카메라 영상을 보낸다.

NAT 넘기: 집이나 회사의 컴퓨터는 대개 공유기 뒤에 있어 밖에서 바로 연결할 수 없다. 그래서 양쪽이 먼저 바깥 서버에 접속해 서로의 공인 주소를 알아낸 뒤 동시에 연결을 시도해 공유기에 구멍을 내거나(홀 펀칭), 그래도 안 되면 중계 서버를 거쳐 데이터를 주고받는다. WebRTC의 STUN·TURN이 이 두 역할이다. 원리는 네트워크 노트에 있다.

VDI와 클라우드 PC: 데이터센터의 VM에 데스크톱 OS를 띄우고 이 원격 화면 프로토콜로 접속하게 하면 가상 데스크톱(VDI)이 된다. 직원은 어떤 기기로든 같은 책상 환경에 들어가고, 데이터는 데이터센터 밖으로 나가지 않는다. 앞 절의 VM(나눠 쓰기·스냅숏·마이그레이션)과 이 절의 원격 화면이 합쳐진 모습이다.

보안: 원격 접속은 그대로 남이 내 컴퓨터를 조작할 수 있는 문이다.

  • RDP·VNC 포트를 인터넷에 직접 열어 두지 않는다. 자동화된 비밀번호 대입과 원격 접속 서비스의 취약점(2019년 BlueKeep 같은 인증 전 취약점)이 늘 노린다. VPN이나 원격 데스크톱 게이트웨이 뒤에 두고, 다중 인증(MFA)과 네트워크 수준 인증을 켜고, 업데이트를 미루지 않는다. 바탕 개념은 보안 › 네트워크 보안과 제로 트러스트에 있다.
  • 원격 지원 사칭 사기를 조심한다. "컴퓨터가 감염됐다", "환불해 주겠다"며 먼저 연락해 원격 지원 프로그램을 설치하게 하는 수법이 흔하다. 정상적인 회사는 먼저 연락해 원격 접속을 요구하지 않는다. 내가 먼저 찾아간 곳이 아니면 원격 접속 권한을 주지 않고, 이미 줬다면 연결을 끊고 은행과 비밀번호부터 정리한다. 사람을 노리는 공격은 보안 › 사람과 운영에서 다룬다.

언어 가상 머신: 같은 말의 다른 뜻

"가상 머신"이라는 말은 전혀 다른 것에도 쓰인다. JVM, .NET의 CLR, WebAssembly, CPython의 바이트코드 해석기는 실제로 존재한 적 없는 가상의 기계를 정의한다. 컴파일러는 소스를 이 가상 기계의 명령(바이트코드)으로 번역하고, 기계마다 설치된 런타임이 그 바이트코드를 해석하거나 JIT으로 진짜 기계어로 바꿔 실행한다. 배포하는 쪽은 x86용·Arm용을 따로 만들 필요 없이 한 벌만 내면 된다.

Java, C#, Rust, Python 소스가 각각 바이트코드로 번역되고, JVM, CLR, WebAssembly 런타임, CPython이 실제 CPU 위에서 실행하는 그림소스가상 명령 집합가상 머신실제 CPUJava·Kotlin.class 바이트코드JVM(HotSpot)C#·F#CIL(.dll).NET CLRRust·C·C++.wasm브라우저·WasmtimePython.pyc 바이트코드CPythonx86Arm기계와 상관없는 한 벌을 배포한다
언어 가상 머신. 가운데 칸(색칠)이 실제 CPU가 아닌 가상 명령 집합이고, 런타임이 각 기계에서 이것을 실행한다.

구조로 보면 앞의 CHIP-8 인터프리터와 놀랍도록 같다. fetch → decode → execute 루프가 돌고, 자주 도는 코드는 JIT으로 번역한다. 다른 점은 흉내 낼 진짜 기계가 없다는 것이다. 그래서 명세를 설계할 자유가 있어, JVM과 WebAssembly는 레지스터 대신 스택 기계로 정의하고, 타입 검사와 메모리 경계 검사를 명세에 넣어 신뢰할 수 없는 코드를 안전하게 돌리게 했다. 스택 기계 해석기의 동작은 컴파일러 노트의 "인터프리터, 바이트코드 VM, JIT" 절에서 볼 수 있다. 정리하면 시스템 VM(하이퍼바이저가 만드는 것)은 있는 기계를 나누고, 언어 VM은 없는 기계를 만든다.

법과 윤리

이 절은 미국 판례를 중심으로 한 개요이며 법률 조언이 아니다. 나라마다, 사안마다 판단이 다르다.

에뮬레이터 자체는 대개 합법으로 본다. 2000년 미국 제9순회항소법원은 Sony v. Connectix 사건에서, Connectix가 PlayStation 에뮬레이터(Virtual Game Station)를 만들며 분석 목적으로 BIOS를 복사·역어셈블한 중간 복제가 공정 이용이라고 판단했다. 같은 해 Sony v. Bleem 사건에서는 PlayStation 에뮬레이터 Bleem!의 광고가 실제 게임 화면을 비교에 쓴 것을 공정 이용으로 봤다(이 사건은 광고 화면에 관한 것이고 에뮬레이션 자체의 적법성을 따진 것은 아니다).

반면 BIOS·펌웨어와 게임 롬은 콘솔·게임 회사의 저작물이다. 에뮬레이터 코드를 독자적으로 짜는 것과, 남의 BIOS나 게임을 복사해 퍼뜨리는 것은 전혀 다른 문제다. 또 많은 나라가 복제 방지 장치(기술적 보호조치)를 푸는 일 자체를 저작권 침해와 별도로 금지한다. 2024년 닌텐도 스위치 에뮬레이터 Yuzu의 개발사는 닌텐도가 이 조항(DMCA의 우회 금지)을 들어 소송을 내자 240만 달러를 지급하고 개발을 접는 데 합의했다. 판결이 아니라 합의라 선례는 아니지만, 암호 키와 보호 우회가 쟁점이 된다는 것을 보여 준다.

에뮬레이터 코드, BIOS와 펌웨어, 게임 롬이 각각 누구의 저작물인지 나눈 세 칸에뮬레이터 코드직접 짠 프로그램독자 구현은 대개 문제없음BIOS·펌웨어콘솔 회사의 저작물복사·배포는 권리자 몫게임 롬게임 회사의 저작물내려받기·공유도 마찬가지복제 방지(기술적 보호조치)를 푸는 일은 저작권 침해와 별개로 금지될 수 있다미국 DMCA 1201조, 한국 저작권법의 기술적 보호조치 조항 등 · 나라마다 다르다
에뮬레이터를 둘러싼 세 가지. 에뮬레이터 코드와 그것이 실행하는 BIOS·게임은 권리자가 다르다.

합법적으로 즐길 길도 넓다. 공개 도메인이나 제작자가 무료로 푼 홈브루 게임, 직접 만든 CHIP-8·NES 테스트 프로그램은 마음껏 돌려도 된다. 게임 회사가 직접 내는 복각판과 구독 서비스의 클래식 게임 모음도 내부적으로는 에뮬레이터다. 에뮬레이터는 단종된 하드웨어 위에서만 돌던 게임을 보존하는 수단이기도 해서, 박물관·도서관의 디지털 보존 작업에도 쓰인다.

흔한 오해

  • "에뮬레이터와 가상 머신은 같은 말이다" — 에뮬레이터는 다른 기계를 명령 하나하나 흉내 내고, VM은 같은 CPU에서 명령 대부분을 그대로 실행한다(맨 위 표). VM이 거의 제 속도를 내는 이유다.
  • "컨테이너는 가벼운 VM이다" — 컨테이너는 커널을 흉내 내지 않고 호스트 커널을 함께 쓰는 프로세스 묶음이다. 가벼운 대신 커널 취약점 하나가 모든 컨테이너에 걸린다(위 컨테이너와의 차이).
  • "VM 안에서 돌리면 무엇이든 안전하다" — 하이퍼바이저와 흉내 낸 장치도 코드라 버그가 있으면 게스트가 호스트로 빠져나올 수 있다. 공유 폴더·클립보드·패스스루 장치는 그만큼 경계를 넓힌다. 하이퍼바이저를 최신으로 두고, 의심스러운 파일을 다룰 VM은 이런 연결을 끈다.
  • "Rosetta 2는 명령을 하나씩 흉내 내는 에뮬레이터다" — 실행 전에 앱을 통째로 Arm 기계어로 번역해 두는 번역기라, 명령마다 해석하는 인터프리터와 다르다(위 동적 재컴파일 절).

핵심 자료

가상 메모리·트랩·시스템 콜의 바탕은 운영체제 노트, VM 위에 쌓은 서비스는 클라우드 노트, 커널을 나눠 쓰는 격리는 컨테이너 노트에 있다.

자료종류요지링크
Cowgod's Chip-8 Technical Reference명세CHIP-8의 메모리·레지스터·명령 전부를 한 쪽에 정리했다. 첫 에뮬레이터를 만들 때 가장 많이 펴 보는 문서.devernay.free.fr
NESdev Wiki커뮤니티 문서NES의 CPU 메모리 맵, PPU·APU 레지스터, 사이클 단위 타이밍을 실험으로 확인해 모은 위키. 이 노트의 NES 수치는 여기서 가져왔다.nesdev.org
Translator Internals (TCG)공식 문서QEMU가 게스트 코드를 번역 블록으로 잘라 호스트 기계어로 바꾸고, 블록을 잇고, 자기 수정 코드를 감지하는 방법.qemu.org
Popek and Goldberg virtualization requirements백과사전1974년 논문의 정리(민감한 명령 ⊆ 특권 명령이면 트랩 앤 에뮬레이트로 가상화 가능)와 x86이 이를 어긴 사례.Wikipedia
Intel 64 and IA-32 Architectures Software Developer Manuals명세3권 C편이 VMX(VT-x) 명령, VM 진입·탈출, EPT를 다룬다. 하드웨어 가상화의 원전.Intel
KVM공식 문서Linux 커널을 하이퍼바이저로 만드는 KVM의 API와 메모리·인터럽트 가상화 문서 모음.kernel.org
Virtual I/O Device (VIRTIO) Version 1.2표준반가상화 장치의 공용 규격. 게스트와 호스트가 공유 메모리의 virtqueue로 요청을 주고받는 방식을 정한다.OASIS
RFC 6143: The Remote Framebuffer Protocol표준VNC가 쓰는 RFB 프로토콜. 화면을 픽셀 사각형으로 보내고 키·마우스 이벤트를 받는 가장 단순한 원격 화면 구조.RFC Editor
WebAssembly Core Specification표준스택 기계로 정의한 가상 명령 집합. 실제 CPU 없이 의미를 정의한 "가상 머신 명세"의 좋은 예.W3C
더 읽을거리