본문으로 건너뛰기
5분 AI.
GITHUB TRENDING SWIFT WEEKLY #3 딥다이브 · 2026-10-23 분석

UTM 딥다이브
— 실행 버튼 뒤에 숨은 작은 컴퓨터의 설계

가상머신 앱

빠른 판단

무엇
QEMU와 macOS 가상화 프레임워크를 네이티브 UI로 관리하는 Apple 기기용 가상머신 앱
이럴 때
Mac·iPhone·iPad에서 지원되는 게스트 운영체제를 실행하고 CPU·메모리·디스크·입력 장치를 설정할 때
Mac·iPhone·iPad에서 다른 운영체제를 실행하는 UTM을 통해, Swift 화면과 시스템 소프트웨어가 만나는 경계를 공부합니다. QEMU와 Apple Virtualization을 감싸는 공통 인터페이스, 비동기 상태 전이, 설정 파일의 호환성, 프로세스 사이의 파일 권한 전달까지 따라갑니다. 저장소 utmapp/UTM · ⭐36,485 · 포크 1,880 · Swift · Apache-2.0 · 2026-10-23 GitHub 기준. 코드 설명은 커밋 1c5e215 기준이며, Swift 파일 241개·약 61,800줄에는 자동 생성 코드도 포함됩니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

운영체제를 실행하는 엔진과, 그 엔진을 다루는 앱을 구별하자

UTM은 Apple 기기에서 가상 컴퓨터를 만들고 관리하는 네이티브 앱입니다. 가상 컴퓨터의 CPU·메모리·디스크·네트워크를 설정하고, Windows나 Linux 같은 운영체제를 부팅한 뒤 화면과 입력 장치를 연결합니다. 실행 엔진으로 QEMU를 사용하며, macOS에서는 Apple의 Virtualization.framework를 사용하는 경로도 제공합니다.

UTM에서 배울 핵심은 가상 CPU를 직접 만드는 법보다, 복잡한 실행 엔진을 사용자가 다룰 수 있는 앱으로 연결하는 법입니다. 버튼 하나를 처리하려면 설정 해석, 파일 접근, 프로세스 시작, 화면 연결, 오류 복구가 함께 맞물려야 합니다.

용어 이해
호스트와 게스트
호스트는 UTM이 설치된 실제 기기와 운영체제입니다. 게스트는 UTM 안에서 실행되는 운영체제입니다. 예를 들어 Apple Silicon Mac에서 ARM64 Linux를 실행한다면, Mac이 호스트이고 Linux가 게스트입니다.
용어 이해
에뮬레이션과 가상화
에뮬레이션은 다른 컴퓨터의 명령과 장치를 소프트웨어로 흉내 냅니다. 가상화는 호스트와 호환되는 CPU 명령을 하드웨어 지원으로 실행하면서 메모리와 장치를 분리합니다. 둘 다 게스트 OS를 실행하지만, 가능한 CPU 조합과 성능 특성이 다릅니다.
한 문장 비유

UTM은 작은 컴퓨터를 빌려 주는 안내 데스크입니다

QEMU와 Apple Virtualization은 실제로 컴퓨터를 가동하는 설비입니다. UTM은 사용자의 주문을 받아 CPU·메모리·디스크를 준비하고, 화면과 키보드를 연결하며, 문제가 생기면 어느 단계가 실패했는지 알려줍니다.

설비가 두 종류여도 안내 데스크에서 쓰는 기본 말은 같습니다. 시작, 정지, 일시 정지, 재개입니다. 이 공통 언어가 Services/UTMVirtualMachine.swift의 프로토콜로 표현됩니다.

사용자가 하는 일과 코드가 하는 일

사용자의 행동저장소에서 이어지는 책임
VM을 만든다Configuration/의 모델에 CPU·메모리·장치 설정을 담고 .utm 번들로 저장합니다.
실행을 누른다UTMData.run(vm:options:startImmediately:)가 macOS 창을 준비하고 VM 시작 요청을 연결합니다.
화면을 보고 입력한다QEMU 경로는 SPICE와 CocoaSpice를 연결하고, Apple 경로는 Virtualization의 화면·장치 API를 사용합니다.
일시 정지한다백엔드별 구현이 실제 엔진을 멈추고 공통 상태를 갱신합니다. 화면은 이 상태를 관찰합니다.

여기서 VM은 가상 머신의 줄임말입니다. 단순한 앱 창보다 수명이 긴 객체입니다. 창을 표시하는 일, 게스트 CPU를 실행하는 일, 디스크에 설정을 저장하는 일은 서로 다른 책임으로 읽어야 합니다.

2왜 주목받는가

관심의 배경을 기능과 설계에서 찾되, 순위의 원인까지 단정하지 않는다

UTM은 2026-10-23 분석 기준 GitHub Trending Swift Weekly #3에 선정됐으며, GitHub 스타는 36,485개입니다. 최신 릴리스 v5.0.8의 날짜는 2026-10-17, 최근 push는 2026-10-20입니다. 릴리스와 관심이 같은 시기에 있었다는 사실만으로 특정 기능이 순위 상승을 일으켰다고 결론 내릴 수는 없습니다.

UTM은 Apple Silicon의 ARM64 환경에서 ARM64 게스트를 실행하거나, x86·PowerPC 같은 다른 아키텍처의 소프트웨어를 보존하는 데 활용할 수 있습니다. README.md는 QEMU 기반 에뮬레이션과 macOS의 하드웨어 가상화를 소개합니다. 이는 기능을 바탕으로 본 활용 가능성이며, 트렌딩 유입 경로에 대한 통계는 아닙니다. UTM 공식 소개에서 제품 정보를 더 볼 수 있습니다.

비슷한 선택지와 비교하면 보이는 장점

선택지주된 접근UTM의 차이와 선택 기준
QEMU 직접 사용명령행에서 머신·가속기·장치·디스크를 조합합니다.UTM은 네이티브 설정 화면과 VM 목록을 제공합니다. QEMU 옵션을 익히려면 생성된 인자와 비교하기 좋습니다.
Apple API 직접 사용개발자가 Virtualization으로 VM 구성과 실행 UI를 직접 구현합니다.UTM은 완성된 앱 흐름을 제공합니다. 여러 CPU의 에뮬레이션이 필요하면 QEMU 경로도 선택할 수 있습니다.
iSHUTM의 README 기준, iOS에서 사용자 모드 x86 에뮬레이션으로 Linux 터미널 환경을 제공합니다.UTM은 CPU·메모리·장치를 갖춘 시스템 전체를 부팅하는 용도입니다. 셸 학습과 OS 부팅 실험의 범위가 다릅니다.
UTM SEJIT 없이 인터프리터로 실행하는 UTM 배포판입니다.일반 UTM과 공유하는 코드가 많지만 성능·아키텍처 구성이 다릅니다. 가벼운 구형 OS 실험에 먼저 맞춰 봅니다.

QEMU의 개념은 공식 시스템 소개에서 더 볼 수 있습니다. iSH의 실행 범위는 UTM의 README.md·Related 항목을 기준으로 요약했으며, iSH 공식 사이트도 참고할 수 있습니다. 속도나 제품 우열을 측정한 비교표는 아닙니다.

비유

QEMU의 명령행은 전원·배선·부품을 직접 연결하는 작업대에 가깝습니다. UTM의 설정 화면은 그 작업대에 붙인 제어판입니다. 제어판을 분석하면, 사람이 고른 값이 기계가 이해하는 옵션으로 바뀌는 과정을 배울 수 있습니다.

이 저장소의 학습 가치는 쉬운 화면과 까다로운 운영체제 제약 사이의 연결이 실제 코드로 드러난다는 데 있습니다. macOS와 iOS가 같은 시작 버튼을 보여줘도, 내부에서는 프로세스 생성과 JIT 권한의 차이를 처리해야 합니다.

README의 범위와 실제 구현을 함께 읽기

README.md는 QEMU 기반 시스템 에뮬레이션과 30종 이상의 프로세서를 소개합니다. 하지만 모든 배포판이 그 구성을 똑같이 포함하지는 않습니다. scripts/build_dependencies.sh의 ARM64 *-tci 분기에는 아래 일곱 타깃이 명시됩니다.

# scripts/build_dependencies.sh — ARM64 TCI 빌드의 target-list
aarch64-softmmu, i386-softmmu, ppc-softmmu, ppc64-softmmu,
riscv64-softmmu, x86_64-softmmu, m68k-softmmu

README의 SE 요약은 ARM·PPC·RISC-V·x86 계열과 32/64비트 변형을 언급하지만, 이 빌드 목록에는 m68k가 추가되고 arm-softmmu와 riscv32-softmmu는 없습니다. Configuration/QEMUConstant.swift의 WITH_QEMU_TCI 아래 isHidden도 같은 일곱 항목을 노출합니다. 배포판별 지원 범위를 판단할 때는 소개 문구보다 빌드 타깃과 UI 필터를 함께 확인해야 합니다.

3기술 스택 전체 지도

프론트엔드는 네이티브 화면, 백엔드는 기기 안의 VM 실행 계층

이 프로젝트의 백엔드는 웹 서버를 뜻하지 않습니다. 같은 앱 안팎에서 VM을 관리하는 서비스 계층입니다. 주 빌드 정의는 UTM.xcodeproj/project.pbxproj이며, 앱 버전은 Build.xcconfig에서 정합니다. Node.js·Rust·Python 서버 프로젝트에서 찾는 package.json·Cargo.toml·pyproject.toml 대신 Xcode 설정과 SwiftPM 잠금 파일을 읽습니다.

프론트엔드: SwiftUI에 플랫폼별 화면 구현을 결합

구성실제 근거읽을 포인트
Swift·SwiftUIPlatform/UTMData.swift, Platform/VMData.swiftObservableObject·@Published로 VM 상태를 화면에 전달합니다.
AppKit·UIKitPlatform/macOS/Display/, Platform/iOS/창·입력·세션 수명처럼 플랫폼마다 다른 책임을 맡습니다.
Metal·CocoaSpiceUTMSpiceIO와 macOS의 VMDisplayQemuMetalWindowController게스트 출력과 실제 기기 화면을 연결합니다. Metal 사용과 게스트 3D 가속은 구분합니다.
터미널·설정 보조SwiftTerm, InAppSettingsKit, IQKeyboardManager직접 작성할 부분과 라이브러리에 맡길 부분의 경계를 살펴봅니다.

UTM.xcodeproj/project.pbxproj의 여러 타깃은 SWIFT_VERSION = 5.0을 사용합니다. 이는 Swift 언어 모드 설정이며, 설치된 컴파일러가 반드시 Swift 5.0이라는 뜻은 아닙니다. 현대적인 any·async/await가 보인다는 이유만으로 Swift 6 엄격 동시성 검사가 활성화됐다고 판단해서도 안 됩니다.

백엔드: 두 엔진과 여러 연결 부품

부품역할버전 또는 고정 지점
QEMUCPU·장치 에뮬레이션과 가상화patches/sources: 10.0.12-utm, UTM 포크입니다.
QEMUKitQEMU 실행·모니터와 앱의 연결SwiftPM 잠금: e8e9e877619d, main 브랜치의 커밋입니다.
CocoaSpiceSPICE 입출력과 화면 연결SwiftPM 잠금: d8d29fc81004, main 브랜치의 커밋입니다.
Apple VirtualizationmacOS의 네이티브 VM 백엔드OS 프레임워크이므로 별도 패키지 버전 대신 가용 OS 버전을 검사합니다.
SPICE화면·오디오·입력 등 통신patches/sources: 서버 0.14.3, 프로토콜 0.14.4, GTK 클라이언트 0.42
네이티브 지원멀티미디어·네트워크·TPMGStreamer 1.19.1, libslirp 4.9.1, swtpm 0.8.99

SwiftPM 잠금 파일에서 직접 읽는 버전

근거 파일은 UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved입니다. 버전 번호가 있는 항목과 브랜치 커밋으로 고정된 항목을 구분해야 같은 의존성을 재현할 수 있습니다.

의존성고정 값연결되는 기능
swift-argument-parser1.2.3utmctl의 하위 명령과 옵션 파싱
swift-log1.5.3서비스 계층의 로깅
ZIPFoundation0.9.20압축 파일 처리
InAppSettingsKit3.4.1앱 설정 화면 지원
IQKeyboardManager6.5.6iOS 키보드 관련 UI 지원
SwiftTermf2f5349b2a1cosy/SwiftTerm의 fix/visionos-build 브랜치 고정
SwiftConnectc193f74b804e원격 연결을 위한 패키지

인프라·빌드: 네이티브 의존성을 준비한 뒤 앱을 만든다

scripts/build_dependencies.sh는 patches/sources의 소스를 사용해 의존성을 빌드하고, 타깃별 sysroot-*에 배치합니다. autoconf·Meson·CMake 계열 결과물을 Xcode에서 사용할 프레임워크로 정리하는 과정입니다. 그다음 scripts/build_utm.sh로 앱 아카이브를 만들고, 패키징 스크립트가 서명·배포 형식을 처리합니다.

# .github/workflows/build.yml — 책임별 흐름 요약
configuration → build-sysroot
                  ├→ build-utm → package-ios / submit-ios
                  └→ build-sysroot-universal → build-universal
                                              → package-mac / submit-mac

# 캐시를 구분하는 주요 입력
플랫폼 + CPU 아키텍처
scripts/build_dependencies.sh + scripts/fixup.sh + patches/**
Universal 구성: scripts/pack_dependencies.sh도 포함
캐시 저장 키: 위 입력에 커밋 SHA도 포함

CI의 macOS 러너 기본 이미지는 macos-26이며, 저장소 설정에 따라 자체 macOS 러너를 선택합니다. actions/checkout@v3, actions/cache/restore@v4, actions/upload-artifact@v4가 실제 워크플로에 등장합니다. 앱 매트릭스에는 macOS ARM64·x86_64와 iOS·visionOS의 일반·SE·Remote 구성이 있습니다.

화면 코드만으로 완성되는 앱처럼 보여도, 재현 가능한 빌드에는 Swift 패키지와 네이티브 의존성의 고정 지점이 모두 필요합니다. Build.xcconfig는 제품 버전 5.0.8, 빌드 번호 129를 선언합니다.

4아키텍처 심화 분석

화면에서 엔진까지, 그리고 상태가 다시 화면으로 돌아오기까지

전체 구조: 제어와 화면 출력을 나눠서 보기

사용자: VM 선택 / 설정 / 실행 | v +------------------------------------------------------------+ | Platform/ : SwiftUI + AppKit/UIKit + VMData / UTMData | +-----------------------------+------------------------------+ | 공통 제어 요청 v +------------------------------------------------------------+ | Services/UTMVirtualMachine.swift : 시작 / 정지 / 상태 | +-----------------------------+------------------------------+ | | v v UTMQemuVirtualMachine UTMAppleVirtualMachine | | macOS 전용 v v QEMUKit + UTMQemuSystem Virtualization.framework | | +----------+-----------+ v | | VZVirtualMachine v v macOS: XPC Helper iOS: 앱 안의 pthread → QEMU | v QEMULauncher --> QEMU (HVF 또는 TCG) QEMU 화면/입력: QEMU <--> SPICE <--> CocoaSpice <--> 화면 설정 파일: .utm/config.plist + .utm/Data/ 호스트별 정보: UTMRegistryEntry 원격 접속: Remote/ 경로에서 별도로 연결

이 그림의 위쪽은 VM을 제어하는 흐름입니다. 아래의 SPICE 연결은 QEMU의 화면·입력을 옮기는 흐름입니다. QMP는 QEMU에 정지·재개 같은 명령을 보내는 관리 프로토콜이므로, SPICE와 역할을 바꿔 이해하면 안 됩니다. Apple 백엔드는 QEMU나 SPICE를 거치지 않고 OS의 Virtualization API로 실행됩니다.

설계 패턴 1: 공통 프로토콜과 화면용 어댑터

Services/UTMVirtualMachine.swift는 두 엔진이 공통으로 제공할 동작을 선언합니다. Configuration과 Capabilities는 연관 타입입니다. 각 구현은 자신의 설정 타입과 기능 집합을 유지하면서, 시작·정지 같은 공통 요청을 받을 수 있습니다.

// Services/UTMVirtualMachine.swift — 선언 일부 발췌
protocol UTMVirtualMachine: AnyObject, Identifiable {
    associatedtype Capabilities: UTMVirtualMachineCapabilities
    associatedtype Configuration: UTMConfiguration

    var config: Configuration { get }
    var state: UTMVirtualMachineState { get }
    func start(options: UTMVirtualMachineStartOptions) async throws
    func stop(usingMethod method: UTMVirtualMachineStopMethod) async throws
}

이를 전략 패턴 관점에서 읽으면, 시작이라는 요청은 유지하고 실행 전략을 교체하는 구조입니다. 다만 모든 분기가 사라지지는 않습니다. Platform/macOS/UTMDataExtension.swift의 run(vm:options:startImmediately:)는 Apple VM인지 QEMU VM인지에 따라 적절한 창 컨트롤러를 만듭니다. 엔진별 화면 기능까지 완전히 같지는 않기 때문입니다.

Platform/VMData.swift는 @MainActor의 ObservableObject입니다. 내부의 wrapped: (any UTMVirtualMachine)?에 실제 VM을 담고, 화면에 필요한 상태를 @Published로 노출합니다. load(from:)는 설정 타입을 보고 구체적인 VM 구현을 고릅니다.

subscribeToChildren()는 하위 객체의 변경을 관찰하며, onStateChange 콜백에서 Task { @MainActor in ... }을 통해 상태와 스크린샷을 화면 모델에 반영합니다. 이 부분이 엔진의 알림을 SwiftUI가 관찰할 값으로 바꾸는 어댑터 역할입니다.

비유

프로토콜은 모든 차량이 따라야 할 운전 조작법이고, 백엔드는 서로 다른 엔진입니다. VMData는 엔진에서 올라오는 신호를 운전자가 읽을 수 있는 계기판 값으로 바꾸는 장치에 해당합니다.

설계 패턴 2: 상태 머신과 실패 시 정리

시작 버튼을 두 번 눌렀을 때 두 번 부팅하면 안 됩니다. UTMVirtualMachineState는 stopped·starting·started·pausing· paused·resuming·saving·restoring·stopping을 구분합니다. 전환 중인 상태가 있기 때문에 UI도 진행 중인 작업을 표현할 수 있습니다.

일반적인 시작·일시 정지·재개 경로 stopped --> starting --> started --> pausing --> paused | ^ | | 실패 +------ resuming <------+ v stopped 정지 요청의 의미는 별도 구분 request = 게스트에 종료 요청 / force = 강제 전원 종료 / kill = 프로세스 종료 주의: 모든 상태에서 모든 화살표를 사용할 수 있는 것은 아니다.

Services/UTMQemuVirtualMachine.swift의 start(options:)는 먼저 state == .stopped를 검사하고, 다른 상태이면 invalidVmState 오류를 던집니다. 이어 상태를 .starting으로 바꾼 뒤 _start(options:)를 기다립니다.

// start(options:)의 핵심 흐름을 줄인 설명용 코드
guard state == .stopped else {
    throw UTMQemuVirtualMachineError.invalidVmState
}
state = .starting
do {
    // 실제 코드는 startTask를 저장하고 defer로 해제한다.
    try await _start(options: options)
    state = .started
} catch {
    await registryEntry.setIsSuspended(false)
    await qemuVM.kill()
    state = .stopped
    throw error
}

_start(options:)에는 자원 캐시 준비, TPM 시작, 인자 생성, SPICE 서비스 시작, QEMU 시작이 이어집니다. 중간마다 Task.checkCancellation()을 호출합니다. async를 붙여도 작업 취소와 자원 정리가 자동으로 끝나지는 않습니다. 작업이 취소 신호를 확인하고 이미 만든 자원을 정리해야 한다는 사례입니다.

비동기 기능을 설계할 때는 성공 결과만큼, 중간 상태와 실패 후 되돌아갈 상태를 구체적으로 정해야 합니다. 여기서는 시작 실패 시 실행 엔진을 종료하고 공통 상태를 .stopped로 복구합니다. 이것을 모든 파일 변경까지 되돌리는 데이터베이스 트랜잭션으로 해석해서는 안 됩니다.

용어 이해
상태 머신
객체가 가질 수 있는 상태와 허용되는 이동을 정해 놓은 모델입니다. 이미 실행 중일 때 시작할 수 있는지, 저장 중일 때 디스크를 바꿀 수 있는지를 명시하는 데 도움이 됩니다. 상태 이름을 나열하는 것에 더해, 각 동작의 진입 조건과 실패 경로를 읽어야 합니다.

설계 패턴 3: 저장 형식이 되는 설정 모델

Configuration/UTMQemuConfiguration.swift와 Configuration/UTMAppleConfiguration.swift는 서로 다른 설정 트리를 가집니다. 공통 인터페이스인 UTMConfiguration은 Codable을 사용합니다. 화면의 설정은 잠깐 쓰는 폼 값에 그치지 않고, 다음 실행과 버전에서도 읽어야 할 저장 형식입니다.

Configuration/UTMConfiguration.swift의 load(from:)는 config.plist에서 먼저 UTMConfigurationStub을 읽고 Backend로 분기합니다. QEMU이면 QEMU 설정을, Apple이면 macOS에서 Apple 설정을 디코딩합니다. Backend 키가 없거나 값이 Unknown인 예전 형식은 Configuration/Legacy/ 모델을 통해 마이그레이션을 시도합니다.

// Configuration/UTMConfiguration.swift — 실제 상수
static var oldestVersion: Int { 4 }
static var currentVersion: Int { 4 }
static var dataDirectoryName: String { "Data" }

// 저장 단계 요약: save(to:)
prepareSave(for:) → 번들·Data 디렉터리 준비 → saveData(to:)
→ 참조하지 않는 Data 파일 정리 → XML PropertyListEncoder → config.plist

여기서 설정 형식 버전 4는 제품 버전 5.0.8과 별개입니다. QEMU 설정의 디코더는 허용 범위보다 낮거나 높은 버전을 오류로 처리합니다. 버전 검사를 읽으면 “새 앱에서 저장한 파일을 이전 앱이 읽을 수 있는가”라는 호환성 문제를 구체적으로 이해할 수 있습니다.

registryEntry에는 호스트별·단기적인 정보가 따로 있습니다. 프로토콜의 설명은 화면 크기와 이동식 드라이브 북마크 등을 예로 듭니다. 휴대할 VM 설정과 특정 Mac의 접근 권한을 구분하는 설계이며, .utm을 옮겼다고 원래 호스트의 권한까지 자동 이전되지는 않습니다.

다른 프로젝트에 가져갈 설계
저장 형식을 바꾸는 일은 UI 필드를 추가하는 일보다 넓다

새 필드의 기본값, 이전 파일에서 키가 없을 때의 처리, 미래 버전의 거부 방식까지 함께 생각해 보세요. 문서 편집기·게임 저장 파일·로컬 앱 설정에도 같은 문제가 생깁니다.

설계 패턴 4: 인자 빌더가 파일 자원까지 운반한다

Configuration/UTMQemuConfiguration+Arguments.swift는 설정을 QEMU 인자로 번역합니다. allArguments는 자동 생성 인자와 사용자 인자를 합칩니다. generatedArguments는 CPU·장치·네트워크·디스크 등의 인자 묶음을 순서대로 조합합니다.

Configuration/QEMUArgumentBuilder.swift는 Swift의 @resultBuilder입니다. buildExpression이 문자열·정수·URL을 조각으로 바꾸고, buildEither·buildOptional·buildArray가 조건문과 반복문을 지원합니다. buildBlock은 조각을 병합하고 buildFinalResult는 최종 인자 배열을 만듭니다.

// Configuration/QEMUArgument.swift — 값이 함께 다니는 구조
struct QEMUArgument: Hashable, Identifiable, Codable {
    var string: String
    var fileUrls: [URL]?
    // 나머지 선언 생략
}

// UTMQemuVirtualMachine._start(options:) — 실제 추출식
let arguments = allArguments.map({ $0.string })
let resources = allArguments.compactMap({ $0.fileUrls }).flatMap({ $0 })

문자열과 URL 목록을 함께 유지하는 이유가 중요합니다. 디스크 경로를 명령행 문자열에 넣어도, 별도의 샌드박스 프로세스가 그 파일을 읽을 권한을 얻는 것은 아닙니다. URL 정보를 보존해야 시작 직전에 자원 접근 절차를 수행할 수 있습니다.

QEMUArgumentFragment.merge(_:)는 기존 문자열에 URL 조각을 붙일 때 URL의 쉼표를 ,,로 이스케이프합니다. QEMU의 쉼표 구분 옵션 문법과 파일 이름이 충돌하지 않도록 처리하는 것입니다. 이는 HTML 이스케이프나 셸 인용과 다른 규칙입니다. 어떤 문법의 경계를 넘는지 먼저 구분해야 합니다.

설정의 디스크 URL | v QEMUArgument { string, fileUrls } | | v v 실행 인자 배열 접근해야 할 자원 목록 | v UTMQemuSystem.startQemuWithCompletion: → 북마크 전달 → Helper에서 URL 해석·접근 → QEMULauncher 시작 → QEMU가 파일 사용

Services/UTMQemuSystem.m의 startQemuWithCompletion:은 자원마다 북마크를 준비해 전달합니다. Services/UTMProcess.m의 accessDataWithBookmark:securityScoped:completion:은 XPC 연결이 있으면 Helper에 요청하고, 없으면 현재 프로세스에서 처리합니다.

QEMUHelper/QEMUHelper.m의 같은 접근 메서드는 북마크를 URL로 해석하고 필요하면 갱신합니다. startQemuTask:standardOutput:standardError:libraryPath:argv:completion:는 NSTask로 QEMULauncher를 시작합니다. 권한 전달과 프로세스 수명이 얽히는 지점이므로 “파일 경로가 맞으면 열린다”는 가정으로 읽으면 놓치는 부분이 많습니다.

플랫폼 경계에서 알아둘 예외

Documentation/Architecture.md는 iOS에서 QEMU를 pthread로 실행하고 한 프로세스에서 재실행이 제약된다고 설명합니다. VM 실행 경로의 UTMProcess.startDylibThread:completion:에서도 dlopen과 pthread_create를 확인할 수 있습니다. macOS의 XPC 실행 구조를 iOS에 그대로 적용하면 안 되는 이유입니다.

문서와 코드의 범위 차이
iOS에서 별도 프로세스를 전혀 쓰지 않는다는 일반화는 피한다

현재 코드는 iOS 26 이상에서 qemu-img 같은 도구를 ExtensionKit 기반 Helper에서 실행합니다. 근거는 Services/UTMHelperProcess.swift와 iOSHelper/iOSHelper.swift입니다. VM의 CPU 실행 경로와 디스크 도구의 실행 경로를 구분해야 합니다.

UTMHelperProcess.isSupported는 visionOS에서는 false를 반환합니다. 새 OS 분기가 있다는 사실을 모든 지원 기기에서 기능이 가능하다는 뜻으로 읽어서는 안 됩니다.

5디렉토리 구조 해부

파일 수보다 책임과 읽는 순서를 먼저 파악하자
UTM/ +-- Configuration/ 설정 모델, 저장·복원, QEMU 인자 생성 | +-- Legacy/ 이전 설정 형식의 읽기·마이그레이션 | +-- QEMUArgument*.swift 인자와 URL 자원을 묶는 빌더 | +-- QEMUConstantGenerated.swift 생성된 장치·CPU 상수 +-- Services/ VM 수명, 실행 엔진, 스냅샷, 파일 처리 +-- Platform/ 공통 SwiftUI 화면과 화면용 데이터 | +-- macOS/ AppKit 창·디스플레이·macOS 통합 | +-- iOS/ iOS·공유 모바일 세션/입력 처리 | +-- visionOS/ 공간 UI 등 전용 구성 | +-- *.lproj/ 번역 리소스 +-- QEMUHelper/ macOS XPC 서비스와 자원 접근 +-- QEMULauncher/ QEMU를 별도 프로세스에서 시작 +-- QEMURenderServer/ 별도 렌더링 프로세스 구성 +-- iOSHelper/ iOS 26 이상 QEMU 도구용 확장 +-- Remote/ 원격 서버·클라이언트·메시지 +-- Scripting/ AppleScript 정의와 구현 +-- utmctl/ ScriptingBridge를 사용하는 CLI +-- Intents/ 시스템 단축어·App Intents 연결 +-- scripts/ 의존성 빌드·생성·서명·패키징 +-- patches/ 외부 소스 버전 목록과 패치 +-- Documentation/ 아키텍처·개발·그래픽 설명 +-- UTM.xcodeproj/ 타깃·스킴·SwiftPM 잠금 +-- .github/workflows/ CI 빌드와 배포 작업 +-- Build.xcconfig 앱 버전·서명 설정 연결 +-- README.md / LICENSE 소개와 라이선스

예를 들어 RAM 입력값이 실제 QEMU 옵션으로 바뀌는 과정을 보려면 Platform/에서 화면을 찾고, Configuration/UTMQemuConfigurationSystem.swift의 memorySize를 거쳐, Configuration/UTMQemuConfiguration+Arguments.swift의 architectureArguments를 읽습니다. 엔진 시작 실패를 이해하려면 Services/로 이동합니다.

실제 코드 규모와 집계 해석

분류파일 수줄 수
Swift24161,757
Objective-C347,042
C·Objective-C 헤더401,654
C4485
셸 스크립트82,247
Python1391

커밋 1c5e215의 파일을 확장자별로 나누고 줄바꿈 문자를 센 값입니다. 빈 줄·주석·라이선스 머리말을 포함하며, .git은 제외했습니다. SwiftPM이 외부에서 가져오는 QEMUKit·CocoaSpice 소스나 빌드 시 내려받는 QEMU 전체 코드는 포함하지 않습니다.

Swift에는 Configuration/QEMUConstantGenerated.swift 10,088줄과 Scripting/UTMScripting.swift 323줄이 포함됩니다. 이 두 생성 파일을 빼면 Swift는 51,346줄입니다. 큰 숫자 때문에 모든 줄을 순서대로 읽으려 하지 말고, 기능 하나를 관통하는 경로부터 선택하세요.

읽는 순서 제안

첫 탐색은 여섯 파일로 충분합니다

  1. Services/UTMVirtualMachine.swift: 공통 동작과 상태 이름을 적습니다.
  2. Platform/VMData.swift: 실제 VM과 화면 상태가 연결되는 지점을 찾습니다.
  3. Services/UTMQemuVirtualMachine.swift: 시작 성공·실패 경로를 따라갑니다.
  4. Configuration/UTMConfiguration.swift: 파일을 읽고 쓰는 순서를 확인합니다.
  5. Configuration/UTMQemuConfiguration+Arguments.swift: 설정을 인자로 바꾸는 규칙을 봅니다.
  6. Services/UTMProcess.m: 같은 요청이 플랫폼별로 갈라지는 이유를 확인합니다.

생성 파일의 상수를 하나씩 외우는 것보다 scripts/const-gen.py가 필요한 이유를 이해하는 편이 좋습니다. 장치와 CPU 목록이 많을수록 수작업보다 생성 규칙을 유지하는 편이 일관성을 지키기 쉽습니다.

6학습 포인트 (기술별)

문법을 외우는 대신, 기능 하나가 여러 경계를 통과하는 모습을 따라가자

SwiftUI·Combine: 화면의 원본 데이터 찾기

Platform/UTMData.swift는 VM 목록·선택·작업 중 표시 등을 관리하고, Platform/VMData.swift는 개별 VM의 화면 상태를 감쌉니다. 화면에 보이는 값이 엔진에서 온 것인지, 앱이 잠깐 관리하는 값인지 구분해 보세요. 같은 “실행 중” 표시라도 그 원본은 서비스의 상태이고, 화면 모델은 이를 전달받아 표시합니다.

기술별 실습 아이디어

subscribeToChildren()에서 onStateChange와 objectWillChange를 표시하고, 엔진 상태 변경에서 화면 갱신까지 이어지는 연결을 세 화살표로 그려 보세요. 정답은 “SwiftUI가 알아서 한다”보다 구체적이어야 합니다.

Swift 동시성: 기다림과 실행 위치를 구분하기

Services/UTMAppleVirtualMachine.swift의 _start(options:guestProvisioning:)는 withCheckedThrowingContinuation으로 콜백 API를 async 흐름에 연결합니다. 실제 apple.start 호출은 vmQueue.async 안에 있습니다. 기다림을 표현하는 장치와, API를 어느 큐에서 호출하는지를 나누어 읽어야 합니다.

// Apple 백엔드의 일반 시작 분기 — 제어 구조를 줄인 예시
try await withCheckedThrowingContinuation {
    (continuation: CheckedContinuation<Void, Error>) in
    vmQueue.async {
        // 실제 코드는 apple 존재 여부와 OS별 시작 옵션도 검사한다.
        apple.start { result in
            continuation.resume(with: result)
        }
    }
}

continuation은 기다리는 작업에 결과를 한 번 전달해야 합니다. 전달하지 않으면 대기가 끝나지 않고, 두 번 전달하면 계약을 어기게 됩니다. 실제 코드의 guard let apple 실패 경로가 오류를 전달한 뒤 return하는 이유도 이 관점에서 읽어 보세요. async만 보고 “백그라운드 스레드에서 실행된다”고 해석하지 않는 습관이 중요합니다.

Codable·버전 호환성: 데이터가 오래 사는 앱 설계

UTMQemuConfiguration.init(from:)는 단순히 필드를 채우지 않습니다. 백엔드와 형식 버전을 검증하고 하위 모델을 디코딩합니다. UTMQemuConfigurationSystem.init(from:)는 알 수 없는 CPU 값에 대해 invalidConfigurationValue를 잡고 기본 CPU로 되돌리는 처리도 포함합니다.

실습으로 “없는 키”, “알 수 없는 CPU”, “미래 형식 버전”의 세 입력을 가정하고, 각각 기본값 적용·복구·거부 중 어떤 처리가 나오는지 표로 정리하세요. 모든 디코딩 실패를 같은 오류로 취급하지 않는 법을 배울 수 있습니다.

DSL·타입 설계: 문자열에 잃어버린 의미를 되찾기

QEMUArgumentBuilder의 입력은 모두 문자열로 취급되지 않습니다. URL은 fileUrls를 남기고, 숫자는 문자열로 바뀌며, 최종 인자 경계는 isFinal로 표현됩니다. “문자열을 합친다”는 작업 안에도 자료의 의미와 문법 경계를 보존하는 설계가 있습니다.

비유

주소를 종이에 적는 것과, 출입증을 준비하는 것은 다릅니다. string은 목적지 주소이고 fileUrls는 출입증을 준비할 때 필요한 원본 정보입니다. 주소만 남기면 나중에 접근 권한을 챙길 근거를 잃어버립니다.

경계를 넘기는 데이터에는 값만 담을지, 권한·수명·해석 규칙까지 함께 담을지 판단해야 합니다. 이 질문은 파일 업로드, 데이터베이스 연결, 외부 도구 실행에도 그대로 적용할 수 있습니다.

스냅샷: 실제 상태와 관리용 메타데이터 분리

Services/UTMSnapshotService.swift는 VM 상태와 별도로 스냅샷 목록·제목·계보를 다룹니다. Services/UTMSnapshotBackend.swift는 실제 상태를 저장하고 복원하는 공통 계약을 정의합니다. create(suspendIdentifier:)·restore(_:)·delete(identifier:)가 대표적입니다.

UTMSnapshotBackendEntry.isComplete는 해당 스냅샷이 필요한 모든 이미지를 포함하는지 나타냅니다. 디스크를 추가하기 전의 스냅샷을 복원하면 여러 디스크의 시점이 어긋날 수 있다는 문제를 코드 주석이 설명합니다. 목록에 보이는 항목과 실제 복원 가능한 데이터가 항상 같지는 않다는 점을 배워 보세요.

실습으로 createSnapshot(title:on:)와 restoreSnapshot(_:on:)에서 백엔드 호출과 UTMSnapshotManifest 갱신을 다른 색으로 분류해 보세요. 메타데이터와 실제 디스크 상태가 어긋났을 때 어느 쪽을 조사해야 할지 설명하는 것이 목표입니다.

기능의 이름과 지원 조건도 나누어 봐야 합니다. 이 상위 서비스의 isSupported(for:)는 Apple VM에 대해 macOS 27 이상을 검사합니다. Apple Silicon에서 Apple VM의 중단 상태를 저장·복원하는 경로의 macOS 14 조건과는 다른 범위입니다. 관련 코드가 존재한다는 사실만으로 모든 지원 OS에서 같은 스냅샷 관리 기능을 쓸 수 있다고 결론 내리지 마세요.

CLI·앱 자동화: 같은 기능을 여러 입구로 제공

utmctl/UTMCtl.swift는 ArgumentParser로 명령을 정의하고 ScriptingBridge의 SBApplication으로 UTM 앱에 연결합니다. 별도 HTTP 서버에 요청하는 CLI라고 생각하면 실제 구조를 놓칩니다. Scripting/UTM.sdef가 자동화 인터페이스의 어휘를 정의합니다.

# 이미 설치된 앱을 사용하는 환경에서의 읽기 전용 명령 예시
utmctl list
utmctl status "학습용 Linux"
utmctl snapshot list "학습용 Linux" --json

위 명령은 사용 예시이며 소스 읽기 실습에 실행이 필요하지는 않습니다. 코드에서는 UTMAPICommand.run()과 virtualMachine(forIdentifier:in:)를 따라가며, 이름이나 UUID가 어떤 앱 객체로 연결되는지 확인하세요.

7하드웨어 / 시스템 요구사항

앱이 실행되는 조건, 게스트가 쓸 자원, 개발 도구 조건을 따로 계산하자
대상소스에서 확인되는 기준의미
macOS 앱주 앱 배포 대상 12.0프로젝트 설정의 최소값입니다. 모든 기능이 macOS 12에서 지원되지는 않습니다.
iOS 앱주 앱 배포 대상 15.0일반 UTM과 SE의 실행·배포 조건을 함께 살펴야 합니다.
visionOS 앱주 앱 배포 대상 1.0전용 UI와 별도의 기능 가용성 조건이 있습니다.
iOS 도구 Helper확장 타깃 26.0, 실행 코드도 iOS 26 검사기존 앱 최소 버전과 새로운 보조 기능의 최소 버전은 다릅니다.
Mac CPUCI에 arm64·x86_64호스트와 게스트 CPU가 맞는지에 따라 가상화 가능 여부가 갈립니다.
개발 환경개발 문서는 최신 Xcode, CI 기본 이미지는 macos-26앱의 최소 OS 버전이 개발 도구의 최소 요구사항은 아닙니다.

근거는 UTM.xcodeproj/project.pbxproj, scripts/build_dependencies.sh, .github/workflows/build.yml, Documentation/MacDevelopment.md와 Documentation/iOSDevelopment.md입니다. OS 버전 숫자 하나로 전체 기능의 요구사항을 설명하기 어려운 프로젝트입니다.

CPU: ARM64 Linux와 x86 Windows의 비용은 다르다

Configuration/QEMUConstant.swift의 hasHypervisorSupport는 먼저 기기의 기능을 검사하고, 호스트가 ARM64이면 .aarch64, x86_64이면 .x86_64만 허용합니다. 인자 생성의 machineArguments는 조건에 따라 -accel hvf 또는 -accel tcg를 만듭니다.

호스트와 다른 CPU를 실행할 수 있다는 사실은, 같은 CPU를 가상화할 때와 비슷한 속도가 나온다는 뜻이 아닙니다. 학습용 Linux를 고를 때도 호스트와 같은 아키텍처의 이미지를 먼저 고려하면 부팅·실습 대기 시간을 줄이기 쉽습니다. 이 문서에는 기기별 속도 측정이나 벤치마크 결과를 포함하지 않습니다.

메모리와 저장 공간: 기본값은 권장 사양이 아니다

UTMQemuConfigurationSystem의 구조체 초기값은 memorySize = 512MiB, cpuCount = 0, jitCacheSize = 0입니다. CPU 수 0은 직접 지정하지 않고 자동 선택한다는 의미입니다. 실제 emulatedCpuCount는 호스트의 성능 코어와 게스트 아키텍처를 고려하고, 조합에 따라 단일 코어를 선택하기도 합니다. 메모리 512MiB는 모델의 기본값일 뿐 Windows나 데스크톱 Linux의 권장 메모리라는 뜻이 아닙니다. 마법사나 게스트별 설정은 이 값을 바꿀 수 있습니다.

# 용량을 계획할 때의 항목 — 공식 최소 사양 수치가 아니다
필요한 호스트 RAM ≈ 호스트 OS·앱 사용량 + 게스트 RAM + 엔진·장치·코드 캐시
디스크 여유     = 게스트 이미지 + 설치 매체 + 스냅샷 + 임시·변환 공간

machineArguments는 TCG를 사용할 때 지정된 JIT 캐시가 없으면 memorySize / 4로 tb-size 값을 만듭니다. 이 계산식이 곧 실제 상주 메모리 총량은 아닙니다. iOS에서는 메모리 한도도 제약이므로 기기 전체 RAM을 게스트에 모두 배정하지 않는 것이 좋습니다.

그래픽·권한·빌드 의존성의 조건

소스에는 Vulkan·DirectX 드라이버를 선택하는 경로가 있습니다. vulkanDriver·directXDriver는 OS 버전·렌더러 선택·Rosetta 가용성을 검사합니다. 이 선택 코드만으로 모든 게스트·드라이버·게임 조합의 호환성을 보장할 수는 없습니다. 제품의 그래픽 기능 안내는 UTM 공식 사이트에서 볼 수 있습니다.

오래된 설명을 적용하기 전에
Metal 화면 표시와 게스트 GPU 가속은 서로 다른 층이다

Documentation/Graphics.md는 가속이 없는 픽셀 버퍼도 CocoaSpice의 Metal 렌더러로 표시하는 구조를 설명합니다. 한편 이 문서는 MoltenVK를 미사용으로 적지만, 현재 빌드에는 build_moltenvk() 호출과 관련 소스 고정 값이 있습니다. 문서의 과거 상태와 현재 빌드 구성을 구별하고, 실제 사용 조건은 드라이버 선택 코드에서 다시 확인해야 합니다.

배포본을 사용하는 것과 개발 빌드를 만드는 조건도 다릅니다. 개발 문서는 미리 빌드된 Sysroot-* 아티팩트를 받는 경로를 안내합니다. 저장소만 열어도 QEMU·SPICE가 자동으로 준비되는 구조는 아닙니다. 서명 여부에 따라 USB·브리지 네트워크 등 일부 기능이 달라질 수 있다고 macOS 개발 문서가 설명합니다.

일반 iOS UTM의 JIT 조건은 OS·설치 방식에 따라 달라집니다. SE는 JIT 우회 없이 인터프리터로 실행하는 경로입니다. 설치 방법은 공식 iOS 설치 문서에서 해당 기기와 버전에 맞춰 확인하세요.

8직접 해볼 수 있는 실습 과제

설치·게스트 부팅 없이도 완성할 수 있는 다섯 가지 결과물

처음부터 UTM 전체를 빌드하면 서명과 네이티브 의존성 준비가 학습을 가릴 수 있습니다. 아래 과제는 소스를 읽고 작은 모델로 재구성하는 방식입니다. 실습용 구현이 필요하면 원본 저장소와 분리한 작은 프로젝트에서 작성하고, 실제 디스크 이미지나 Helper 프로세스에 연결하지 않아도 됩니다.

과제 1 · 입문 · 20~30분

RAM 512MiB가 실행 인자가 되는 길 찾기

할 일: UTMQemuConfigurationSystem.memorySize를 출발점으로, architectureArguments·allArguments·_start(options:)를 순서대로 읽습니다. -m 옵션과 실제 문자열 배열이 만들어지는 위치를 찾아 연결도를 그립니다.

결과물: 파일 경로·변수·함수 이름이 적힌 네 단계 표. 완료 기준: “512는 호스트 RAM의 최소 사양인가?”에 아니라고 답하고, 모델 기본값·게스트 할당량·실제 프로세스 사용량의 차이를 설명할 수 있어야 합니다.

과제 2 · 입문 · 30~45분

시작 버튼을 연속으로 누르면 무엇이 막는가

할 일: QEMU VM의 start·pause·resume·stop을 읽고, 진입 가능한 상태와 성공 후 상태를 정리합니다. stop(usingMethod: .request)가 곧바로 .stopped를 대입하는지 확인하세요.

결과물: “현재 상태 / 요청 / 허용 여부 / 후속 처리” 표. 완료 기준: .starting 중 재시작 요청과 시작 실패를 구분하고, 게스트에 종료를 요청하는 일과 프로세스를 종료하는 일의 차이를 설명합니다.

과제 3 · 중급 · 45~60분

파일 이름의 쉼표를 보존하는 인자 빌더 추적

할 일: 가상의 URL /VMs/lab,disk.qcow2를 생각하고, QEMUArgumentFragment(urlFragment:)와 merge(_:)를 종이 위에서 따라갑니다. file= 문자열 뒤에 URL을 붙일 때 string과 fileUrls가 어떻게 변하는지 적습니다.

결과물: 입력 조각·병합 결과·자원 목록을 나란히 둔 표. 완료 기준: 병합 문자열의 file=/VMs/lab,,disk.qcow2와 원래 URL의 lab,disk.qcow2를 구분합니다. 셸 인용을 더하는 과제가 아니라 QEMU 옵션 문법을 추적하는 과제입니다.

과제 4 · 중급 · 60~90분

오래된 설정 파일을 읽는 최소 모델 설계

할 일: UTMConfigurationStub과 버전 검사를 참고해, 이름·메모리·형식 버전만 갖는 작은 Codable 모델을 별도 학습 예제로 설계합니다. UTM 원본의 형식을 바꾸지 말고, 새 키가 없을 때의 기본값과 미래 버전의 오류 정책을 직접 정합니다.

결과물: 버전 1·버전 2·알 수 없는 버전의 예시 데이터와 예상 결과. 완료 기준: 제품 버전과 데이터 형식 버전을 분리하고, 잘못된 모든 입력을 조용히 기본값으로 바꾸면 왜 문제가 생기는지 설명합니다.

과제 5 · 고급 · 2~3시간

실제 OS를 부팅하지 않는 모형 VM 설계

할 일: UTM 프로토콜의 개념을 참고해 별도 예제에 시작·정지·상태만 있는 작은 인터페이스를 만듭니다. “항상 성공하는 엔진”과 “시작 중 오류를 내는 엔진”을 설계하고, 하나의 화면 모델에서 둘을 다루도록 구성합니다. UTM의 전체 프로토콜을 그대로 구현하려고 범위를 넓힐 필요는 없습니다.

결과물: 공통 인터페이스·두 구현·상태 전이 표·오류 시나리오 세 개. 완료 기준: 중복 시작, 시작 실패, 취소가 화면에 어떻게 전달되는지 설명하고, 아직 만들지 않은 자원을 정리하려 하거나 이미 정리한 자원을 두 번 정리하는 경로가 없는지 검토합니다.

실습의 완료 기준은 VM이 한 번 켜졌다는 사실보다, 값과 상태가 어느 경로로 전달되는지 설명할 수 있는가입니다. 소스 탐색을 도울 때는 저장소 루트에서 아래처럼 읽기 전용 검색을 사용할 수 있습니다.

# 소스 탐색 예시: 프로그램을 빌드하거나 실행하지 않는다.
rg -n 'memorySize|architectureArguments' Configuration
rg -n 'func start|state =|invalidVmState' Services/UTMQemuVirtualMachine.swift
rg -n 'fileUrls|func merge' Configuration/QEMUArgument.swift
rg -n 'withCheckedThrowingContinuation|vmQueue.async' Services/UTMAppleVirtualMachine.swift

9관련 기술 심화 학습 로드맵

주 3~5시간을 가정한 여섯 주 과정 — 범위를 좁히고 결과물을 남기자
주차학습 주제·읽을 소스남길 결과물
1주차호스트·게스트·CPU 아키텍처. README.md, Documentation/Architecture.md, Configuration/QEMUConstant.swift에뮬레이션과 가상화 비교표, 호스트·게스트 CPU 조합 세 개
2주차Swift 프로토콜·연관 타입·any. Services/UTMVirtualMachine.swift, Platform/VMData.swift공통 기능과 엔진별 분기가 남는 기능의 목록
3주차SwiftUI·Combine·동시성. subscribeToChildren(), 두 VM의 start 구현상태 전달 그림, continuation 성공·오류 경로 표
4주차Codable·파일 포맷·마이그레이션. Configuration/UTMConfiguration.swift, Configuration/Legacy/작은 버전형 설정 모델, 이전 데이터 세 가지의 처리 정책
5주차QEMU 옵션·DSL·샌드박스·XPC. Configuration/QEMUArgumentBuilder.swift, Services/UTMProcess.m, QEMUHelper/QEMUHelper.m문자열·URL·북마크·프로세스 사이의 전달 지도
6주차스냅샷과 배포. Services/UTMSnapshotService.swift, patches/sources, .github/workflows/build.yml상태와 메타데이터의 구분표, 의존성부터 패키징까지의 빌드 지도

첫 주에는 운영체제 이론 전체를 끝내려 하지 않아도 됩니다. “게스트의 명령을 누가 실행하는가”와 “장치를 누가 흉내 내는가”에 답할 수 있으면 충분합니다. 둘째 주부터는 타입·상태·파일이라는 눈에 보이는 단위로 나누어 공부합니다.

학습 순서의 이유
QEMU 내부보다 앱과 엔진의 경계를 먼저 이해한다

UTM에서 제어와 자원 전달 경로를 익힌 뒤 QMP·TCG·VirtIO로 확장하면, 새 개념이 어느 위치에 들어가는지 알기 쉽습니다. 처음부터 QEMU의 모든 CPU 구현을 읽는 것보다 구체적인 질문을 유지할 수 있습니다.

여섯 주 뒤에 선택할 세 갈래

어느 갈래를 택하더라도 “내가 읽은 함수의 입력·출력·실패 조건은 무엇인가”라는 질문을 유지하세요. 한 번에 이해할 파일 수를 줄이고, 관찰한 근거를 경로와 함수명으로 남기는 방식이 긴 저장소를 읽는 데 도움이 됩니다.

10핵심 키워드 사전

용어를 뜻과 함께, UTM에서 만나는 위치로 기억하자
용어쉬운 설명
가상 머신·VMCPU·메모리·디스크·장치를 갖춘 가상의 컴퓨터. UTM이 만들고 실행하는 대상입니다.
호스트·게스트호스트는 실제 기기와 OS, 게스트는 가상 컴퓨터 안에서 실행되는 OS입니다.
아키텍처CPU가 이해하는 명령 체계. ARM64와 x86_64가 대표적이며 실행 방식 선택에 영향을 줍니다.
에뮬레이션다른 컴퓨터의 CPU·장치를 소프트웨어로 흉내 내는 방식. 넓은 호환성과 실행 비용을 함께 고려합니다.
가상화·HVF하드웨어 지원으로 호환 CPU의 게스트를 실행하는 방식. QEMU의 hvf는 Apple Hypervisor 프레임워크를 이용하는 가속기입니다.
VirtualizationApple이 제공하는 VM 구성·실행 API. UTM의 macOS 전용 백엔드가 사용합니다.
QEMU다양한 CPU와 장치 모델을 제공하는 에뮬레이션·가상화 엔진. UTM은 별도 포크를 사용합니다.
TCG·JITTCG는 QEMU의 명령 변환 구성 요소이고, JIT는 실행 중 필요한 기계어를 만드는 방식입니다.
TCI·UTM SEJIT 대신 인터프리터를 사용하는 실행 경로와 이를 활용하는 배포판. 설치 조건과 성능의 교환 관계가 있습니다.
QMPQEMU를 프로그램으로 제어하는 관리 프로토콜. 정지·재개·조회 같은 명령을 다룹니다.
SPICE화면·입력·오디오 등 VM 입출력을 전달하는 기술. CPU 명령 실행을 담당하는 엔진과는 역할이 다릅니다.
CocoaSpiceUTM이 Apple 플랫폼에서 SPICE 입출력과 화면 표시를 연결할 때 사용하는 패키지입니다.
VirtIO가상 머신에 맞게 설계된 장치 인터페이스. 게스트 드라이버와 호스트 구현이 함께 맞아야 합니다.
MetalApple의 그래픽 API. VM 화면을 Metal로 표시한다는 사실만으로 게스트의 3D 가속을 보장하지는 않습니다.
프로토콜·연관 타입공통 동작과 구현별 타입 관계를 표현하는 Swift 문법. UTM은 VM마다 설정·기능 타입을 연결합니다.
어댑터한쪽의 데이터·호출 방식을 다른 쪽에 맞게 바꾸는 연결부. VMData가 엔진 상태를 화면에 전달하는 역할로 볼 수 있습니다.
상태 머신가능한 상태와 이동 규칙을 정한 모델. 시작 중 다시 시작하기 같은 잘못된 동작을 막는 기준이 됩니다.
MainActorUI 관련 상태 접근을 조율하는 Swift의 전역 액터. VMData·UTMData의 선언과 콜백 전달에서 만납니다.
continuation콜백으로 받은 결과를 기다리는 비동기 작업에 전달하는 연결 장치. 결과 전달 횟수와 오류 경로가 중요합니다.
Codable·PLISTCodable은 Swift 값의 저장·복원을 위한 계약이고, PLIST는 UTM 설정에 쓰이는 파일 형식입니다.
마이그레이션예전 저장 형식을 현재 모델로 옮기는 처리. UTM의 Legacy 설정 읽기에서 확인할 수 있습니다.
result builder·DSL선언식 문법을 특정 결과로 조립하는 장치와 전용 표현 방식. QEMU 인자 생성을 읽으면 감을 잡기 좋습니다.
샌드박스·entitlement앱이 접근할 수 있는 범위를 제한하는 구조와 특별한 기능을 허용하는 선언입니다.
보안 범위 북마크샌드박스에서 파일 접근을 다시 확보할 때 사용하는 URL 관련 데이터. 경로 문자열과 구분해야 합니다.
XPC·Helper프로세스끼리 통신하는 방식과 별도 작업을 맡는 보조 프로세스. macOS의 QEMU 실행 경로에서 만납니다.
스냅샷·manifest스냅샷은 복원할 시점의 상태이고, manifest는 제목·식별자·계보 등을 기록한 관리용 목록입니다.
sysroot·크로스 컴파일대상 환경용 헤더·라이브러리 묶음과, 현재 환경에서 다른 대상용 프로그램을 빌드하는 과정입니다.
코드 서명·공증배포 앱의 서명과 Apple의 배포 검증 절차. 컴파일 성공과 별도의 단계로 CI에 등장합니다.

11참고 링크

제품 정보와 소스 근거를 구분해서 다시 찾아보기

공식 저장소·문서

핵심 소스와 관련 기술

온라인 기술 문서는 계속 바뀔 수 있습니다. 특히 QEMU의 master 문서는 UTM이 고정한 10.0.12-utm과 버전이 다를 수 있으므로 개념을 익히는 데 사용하고, 특정 옵션의 가용성은 해당 포크·버전과 함께 확인하세요.

현재 상태 (2026-10-23 확인)

항목확인 내용
저장소·설명utmapp/UTM · iOS와 macOS용 가상 머신. README는 visionOS도 명시합니다.
선정GitHub Trending Swift Weekly #3 · 2026-10-23 분석 기준
스타·포크스타 36,485 · 포크 1,880 · 2026-10-23 GitHub 기준
생성일2019-04-12
주 언어Swift
최신 릴리스v5.0.8 · 2026-10-17
최근 push2026-10-20
분석 기준 커밋1c5e215 · 2026-10-20 · project: bumped version
앱 설정 버전MARKETING_VERSION = 5.0.8 · CURRENT_PROJECT_VERSION = 129
설정 파일 버전최소·현재 형식 모두 4 · 제품 버전과 별도 관리
코드 규모추적 파일 734개. Swift 61,757줄, Objective-C 7,042줄, 헤더 1,654줄, C 485줄, 셸 2,247줄, Python 391줄.
규모 집계 범위빈 줄·주석·생성 파일 포함. 외부 패키지·다운로드되는 엔진 소스 제외. Swift의 주요 생성 파일 두 개는 합계 10,411줄.
라이선스UTM 자체는 Apache-2.0. README는 별도로 (L)GPL 구성 요소와 재배포 시 확인 필요성을 명시합니다.
사용 전에 알아둘 한계
이 글의 근거 범위와 실제 사용 조건

설명은 커밋 1c5e215의 소스·설정·문서를 기준으로 합니다. 기기별 부팅, 실행 성능, 특정 게임 호환성을 실측한 결과는 아닙니다. 배포판·CPU·OS·서명·게스트 드라이버에 따라 동작 조건이 달라집니다.

트렌딩 순위와 GitHub 수치는 분석일의 상태입니다. 최근 릴리스가 관심을 유발했다는 인과관계는 확인되지 않았습니다. README나 아키텍처·그래픽 문서의 요약이 구현보다 넓거나 오래된 부분은 본문에서 별도로 구분했습니다.

Apache-2.0이라는 저장소 표기를 모든 번들 구성 요소에 일괄 적용해서는 안 됩니다. 엔진·멀티미디어 라이브러리·추가 드라이버를 포함해 재배포할 때는 각 구성 요소의 라이선스와 배포 조건을 확인해야 합니다.