Wine 11 딥다이브: NTSYNC는 어떻게 리눅스 게이밍의 근본을 뜯어고쳤나


리눅스에서 윈도우 게임을 돌린다는 것은 오랫동안 ‘해커들의 장난감’ 혹은 ‘엄청난 고통을 수반하는 삽질’ 정도로 여겨졌습니다. 하지만 2018년 Valve가 Proton을 발표한 이후 판도가 바뀌었고, 이번에 릴리즈된 Wine 11 은 단순한 연례 업데이트를 넘어 리눅스 게이밍 아키텍처의 근본적인 패러다임 전환을 보여줍니다.

시니어 엔지니어로서 제가 이번 릴리즈에서 가장 주목하는 부분은 잔버그 수정이나 UI 개선이 아닙니다. 바로 수년간 리눅스 진영을 괴롭혀온 Concurrency(동시성) 및 동기화 문제를 커널 레벨에서 마침내 제대로 해결했다는 점입니다.

동기화의 저주: 왜 그토록 느렸는가?

현대의 AAA 게임들은 극도로 멀티스레딩에 의존합니다. 렌더링, 물리 엔진, 오디오, AI 등 수많은 스레드가 동시에 돌아가며, 이들은 Mutex, Semaphore, Event 같은 Windows NT 동기화 기본 요소(Primitives)들을 통해 쉴 새 없이 상태를 조율합니다.

문제는 리눅스 커널에 이 NT 동기화 모델과 정확히 1:1로 매칭되는 기능이 없었다는 점입니다. 과거 Wine은 이를 해결하기 위해 wineserver라는 별도의 데몬 프로세스를 띄우고, 게임 스레드가 동기화가 필요할 때마다 IPC(Inter-Process Communication)를 통해 RPC 호출을 날렸습니다.

초당 수천, 수만 번의 동기화가 일어나는 게임 환경에서 매번 User-space 프로세스 간의 Context Switch 가 발생한다고 상상해 보십시오. FPS 수치가 높게 나와도 미세한 프레임 스터터링(Stuttering)이나 인풋랙이 발생하는 근본적인 병목이 바로 여기 있었습니다.

Esync와 Fsync: 훌륭하지만 한계가 명확했던 ‘꼼수’

엔지니어들은 바보가 아닙니다. 이 병목을 우회하기 위해 CodeWeavers의 Elizabeth Figura를 비롯한 개발자들은 User-space 기반의 해킹을 고안해냈습니다.

  • Esync (Eventfd Synchronization): 리눅스의 eventfd 시스템 콜을 이용해 wineserver를 거치지 않고 동기화를 처리했습니다. 성능은 올랐지만, 파일 디스크립터(FD) 제한에 쉽게 걸리는 치명적인 단점이 있었습니다.
  • Fsync (Futex Synchronization): 리눅스의 futex를 활용해 성능을 한계까지 끌어올렸습니다. 하지만 이는 메인라인 커널에 없는 Out-of-tree 패치를 요구했고, NtWaitForMultipleObjects의 ‘wait-for-all’ 같은 복잡한 엣지 케이스를 완벽하게 에뮬레이션할 수는 없었습니다.

솔직히 말해서, 저는 fsync를 보며 “이 정도면 충분히 빠르지 않나?”라고 생각했습니다. 하지만 시스템 프로그래밍 관점에서 볼 때, 타겟 OS의 동기화 큐를 User-space에서 어설프게 흉내 내는 것은 결국 기술적 부채(Technical Debt)를 쌓는 일일 뿐입니다.

NTSYNC: 마침내 커널로 내려가다

Wine 11의 핵심인 NTSYNC 는 기존의 우회로를 모두 폐기하고 정공법을 택했습니다. 리눅스 커널 6.14에 아예 Windows NT 동기화 API를 모델링한 새로운 커널 드라이버(/dev/ntsync)를 박아 넣은 것입니다.

개념적으로 이 변화가 얼마나 큰지 아래의 의사 코드(Pseudo-code)를 통해 비교해 보겠습니다.

// 과거 방식 (wineserver RPC 병목)
int wait_for_object(handle_t obj) {
    // 1. User-space 게임 스레드 중단
    // 2. wineserver 프로세스로 Context Switch
    // 3. wineserver에서 상태 평가 후 다시 Context Switch
    return rpc_call_wineserver(WAIT_OP, obj);
}

// NTSYNC 방식
int wait_for_object(int ntsync_fd, handle_t obj) {
    // Kernel-space로 직접 ioctl 호출
    // 큐 관리와 Atomic 연산이 커널 내부에서 즉시 처리됨
    return ioctl(ntsync_fd, NTSYNC_IOC_WAIT_ANY, &obj);
}

이제 동기화는 있어야 할 곳인 커널 에서 직접 처리됩니다. 불필요한 라운드트립이 사라졌고, 이벤트 시맨틱스도 완벽하게 Windows와 일치하게 되었습니다.

Hacker News의 반응과 벤치마크의 함정

기사에서는 Dirt 3가 110 FPS에서 860 FPS로 678% 향상되었다고 호들갑을 떱니다. 하지만 Hacker News 스레드에서 여러 엔지니어들이 날카롭게 지적했듯, 이 수치는 마케팅적 과장이 섞여 있습니다. 이는 esync/fsync를 모두 끈 Vanilla Wine 과의 비교일 뿐입니다. 이미 fsync를 사용 중이던 게이머라면 평균 프레임에서 한 자릿수 퍼센트(%)의 향상만 체감할 수도 있습니다.

하지만 제가 강조하고 싶은 진짜 가치는 ‘최대 프레임’이 아닙니다. NTSYNC의 진가는 안정성(Consistency)접근성 에 있습니다. 더 이상 커스텀 커널 패치를 찾아 헤맬 필요가 없습니다. 최신 Ubuntu나 Fedora를 설치하면 기본적으로 지원되며, Valve의 SteamOS 업데이트를 통해 모든 Steam Deck 유저가 이 혜택을 무료로 누리게 됩니다.

WoW64 완성: 32비트 의존성과의 작별

NTSYNC에 가려졌지만, 개인적으로 가장 환호했던 부분은 WoW64 (Windows 32-bit on Windows 64-bit) 아키텍처의 완성입니다.

과거 64비트 리눅스에서 32비트 윈도우 앱을 돌리려면 호스트 OS에 온갖 32비트 라이브러리(multilib)를 덕지덕지 설치해야 했습니다. 의존성이 꼬여 시스템이 망가지는 경험, 리눅스 유저라면 한 번쯤 겪어보셨을 겁니다.

Wine 11은 단일 64비트 바이너리 내에서 32비트(심지어 16비트까지!) 호출을 자체적으로 변환합니다. OpenGL 메모리 매핑부터 SCSI 패스스루까지 완벽하게 처리해 냅니다. 이는 정말 미친 수준의 엔지니어링 쾌거입니다.

게임보다 어려운 MS Office 호환성

흥미롭게도 Hacker News 스레드에서는 “왜 게임은 잘 돌아가는데 MS Office는 완벽하게 안 돌아가냐?”는 토론이 벌어졌습니다.

이유는 명확합니다. 게임은 주로 GPU(Vulkan/OpenGL)와 입력 장치에만 통신하며 자기만의 세계에서 놉니다. 반면 MS Office는 Windows OS의 모든 지저분한 구석을 다 파고듭니다. COM, OLE, 레지스트리, 윈도우 익스플로러 통합, 심지어 문서화되지 않은 API까지 말이죠. Wine 프로젝트가 이 모든 것을 역공학(Reverse Engineering)하여 구현하고 있다는 사실 자체가 기적에 가깝습니다.

총평: 프로덕션 레벨의 도약

Wine 11은 단순한 에뮬레이터 업데이트가 아닙니다. NTSYNC의 커널 편입과 WoW64의 완성은 수년간 쌓여온 아키텍처 레벨의 기술적 부채를 마침내 청산했음을 의미합니다.

이 기술이 실사용 가능한가요? Valve가 이미 수백만 대의 Steam Deck에 이 기술을 탑재해 팔고 있다는 사실이 그 답을 대신합니다. 리눅스 데스크탑 생태계는 Wine이라는 거인의 어깨 위에서 또 한 번 도약했습니다. 개발자들의 그 끈질긴 인내심과 집념에 깊은 경의를 표합니다.


References: