본문 바로가기

Software/C/C++

[C++] [64비트 윈도우 프로그래밍] ① 32비트 프로그램을 엄호하라

88년은 필자가 한창 8비트 컴퓨터였던 MSX2에 빠져있던 시기였다. 그 당시 교육용 컴퓨터로 16비트 컴퓨터가 채택되어 8비트 컴퓨터의 열렬한 지지자였던 필자의 가슴에 상처(?)가 되었던 기억이 난다.

10여년이 지난 지금 컴퓨터는 발전에 발전을 거듭해 64비트 CPU와 64비트 운영체제가 등장했다. 본 연재에서는 3회에 걸쳐 이제 곧 대중화될 64비트 윈도우에서 프로그래밍을 하기 위한 가이드를 제공한다. 부디 필자의 8비트 시절과 같은 실수를 범하지 말고 좀 더 넓어진 64비트 메트릭스에서 네오를 찾을 수 있기를 바란다.

연재 가이드

운영체제 : 윈도우 XP 64비트 에디션, 윈도우 2003 64비트 에디션
개발도구 : 에디터, 32/64비트 C/C++ 컴파일러
기초지식 : C/C++ 프로그래밍, 윈도우 구조 이해
응용분야 : 64비트 윈도우에서 동작하는 모든 프로그램
얼마 전 미국 시애틀 근교 레드몬드에 위치한 MS 본사에서는 인텔과 AMD 및 MS 개발자들이 주제 발표자로 나와 다양한 64비트 컴퓨팅 관련 주제를 다룬 64비트 개발자 세미나를 개최했다. 세미나에 참석한 MS 개발자들은 “64비트 컴퓨터에서 프로그래밍을 구현하는 것이 그렇게 어려운 일이 아니다”라고 설명했다.

16비트에서 32비트로 바뀔 때에 비해 32비트에서 64비트로 가기 위해 필요한 일의 양은 상당히 적을 것이라고 예상하고 있었다. 하지만 쉽다고 해서 아주 할일이 없다는 이야기는 아니다. 현재 32비트 윈도우에서 사용되는 가장 대중적인 CPU는 인텔 호환의 32비트 CPU(AMD도 인텔 호환이었다)이다. 32비트 프로그램을 제작할 때 기존 개발자는 하나의 소스를 작성하고 단 하나의 바이너리만을 생성하면 되었다.

그러나 64비트 윈도우에서 대중화된 64비트 CPU는 크게 두 가지 종류로 구분된다. IA64과 x64이다. IA64는 인텔에서 만든 아이태니엄을 기반으로 하며 개인 사용자보다는 기업 환경과 고성능 컴퓨팅을 목표로 하고 있다. x64는 AMD64를 기반으로 하고 있으며 32비트의 호환성과 64비트의 높은 성능을 목표로 하고 있다.

현재는 인텔에서 EM64T라는 AMD64 CPU의 클론을 제작할 만큼 x64가 대중적인 선호 측면에서 조금 더 앞서 있는 상황이다. 인텔의 시장 예측의 실패라고 할 수도 있는데, MS는 인텔이 주도한 IA64 시스템 대신 미래에는 <그림 1>과 같이 x64 시스템이 주류가 될 것으로 예상하고 있다.

사용자 삽입 이미지
<그림 1> 64비트 시스템의 현재와 미래 동향

IA64 또는 x64가 인기를 얼마나 더 많이 끄는가는 사실 개발자에게 그렇게 큰 관심사는 아니다. 단지 이렇게 두 부류의 64비트 시스템으로 나누어진 상황은 사용자 입장에서야 CPU의 종류가 다양해진 만큼 선택의 폭이 넓어져서 좋을 수 있지만 개발자 입장에서는 조금은 곤혹스러운 일이다.

왜냐하면 생성해내야 할 바이너리의 수가 두 가지 더 증가하기 때문이다. 기존의 32비트 시절에는 하나의 소스로 하나의 바이너리만 생성하면 되지만 앞으로 다가올 가까운 미래에는 하나의 소스로 32비트 바이너리, IA64 바이너리, x64 바이너리를 모두 생성해야 하기 때문에 개발자들의 부담은 더 커지게 된다.

64비트 CPU 정보 얻어오기
64비트 윈도우에서 내 시스템의 CPU가 인텔의 IA64인지 x64인지를 알고 싶다면 어떻게 해야 할까? GetNativeSystemInfo() 함수를 이용하면 CPU의 종류를 알아 낼 수 있다. 한 가지 주의할 점은 64비트 윈도우에서수행되는 32비트 프로세스는 GetNativeSystemInfo() 함수를 이용해야만 정확한 CPU 종류를 얻어 올수 있고, 64비트 프로세스라면 GetSystemInfo() 함수를 사용해도 무방하다. 64비트 프로세스에서 GetNativeSystemInfo() 함수를 사용하면 내부적으로 GetSystemInfo() 함수를 호출해 결과를 리턴한다.

사용자 삽입 이미지
<표 1> 64비트 윈도우에서 CPU 종류를 얻어올 수 있는 함수

이 밖에도 32비트 윈도우에서는 CPU를 32개까지 지원했지만 64비트 윈도우에서는 최대 64개까지 지원한다. 윈도우에서 내부적으로 CPU 정보를 유지할 때 각 비트별로 한 CPU씩 할당하기 때문에 64비트 윈도우에서는 더 많은 64개의 CPU를 지원할 수 있는 것이다.

주의할 점은 64비트 윈도우에서 실행되는 32비트 프로세스는 뒤에 설명할 WOW64가 CPU에 대한 정보를 32비트로 제한하기 때문에 32개의 CPU만 사용할 수 있다. 예를 들어 64비트 윈도우에 64개의 CPU가 있어도 32비트 프로세스에서 GetProcessAffinityMask() 함수나 SetThreadAffinityMask() 같은 함수를 이용하면 32개의 CPU에 대해서만 값을 얻어오거나 설정할 수 있다.

윈도우 안의 또 하나의 윈도우 WOW64
우리는 아직 32비트의 시대에 살고 있으며 가장 많이 사용하는 워드프로세서, 게임, 인터넷 메신저 모두 32비트 프로그램이다. 한때 스티븐 잡스가 만든 혁신적인 워크스테이션과 운영체제였던 넥스트와 넥스트스텝이 컴퓨터 업계에 큰 충격으로 다가온 적이 있었다.

그러나 그것들이 실패할 수밖에 없었던 가장 큰 이유 중 하나가 실제 소비가가 쓸 수 있는 응용 소프트웨어가 극히 적었다는 점이다. 이와 같이 무언가 새로운 것을 만들 때 이상적인 개념을 실체화하는 것도 중요하지만 이전 것에 대한 호환성을 제공하지 못한다면 결국 실패할 수밖에 없다.

호환성이 기술과 시장에 큰 영향을 끼친다는 것은 아이태니엄과 아이태니엄2로 64비트 CPU의 독자 노선을 걷던 독불장군 인텔이 결국 호환성을 중시한 AMD64 진영의 인기 앞에 승복하고 오히려 EM64T라는 클론 CPU를 만들어야 했다는 것에서도 알 수 있다. 즉, 사용자에게 사용되지 않고 팔리지 않는 기술은 사장되기 쉽다. MS는 이런 사실을 예전부터 너무나 잘 알고 있었다. MS-DOS에서 윈도우로 넘어갈 때도 새로운 기술보다는 이전 버전의 하위 호환성을 선택했고 결국 완전한 개인 사용자용 32비트 운영체제는 윈도우 2000에 와서야 빛을 볼 수 있었다.

이렇듯 호환성을 중시하는 MS는 새로운 64비트 윈도우에서 기존의 32비트 프로그램이 동작하도록 하기 위해 WOW(Windows On Windows)64를 탑재하고 있다. 64비트 윈도우는 대다수의 응용 프로그램이 완전한 64비트로 포팅되기 전까지 WOW를 통해 32비트 응용 프로그램을 사용할 수 있도록 하고 있다.

그러나 모든 경우에 호환성이 우선시되는 것은 아니다. 64비트 윈도우에서는 16비트 프로세스는 더 이상 동작할 수 없으며 커널모드 드라이버는 WOW64와 같은 에뮬레이션 레이어가 존재하지 않아서 반드시 64비트로 포팅해야 한다. 결론적으로 64비트 윈도우에서는 WOW64를 통해 수행되는 32비트 프로세스와 64비트 윈도우 상에서 곧바로 수행되는 64비트 프로세스가 모두 동작할 수 있다.

WOW64의 구성과 동작 방식
WOW64는 커널모드가 아닌 사용자모드에서 동작하며, 커널모드와 기존의 x86 버전의 Ntdll.dll 사이에 위치해서 32비트 응용 프로그램의 API 호출을 중간에서 가로챈 후 64비트 API 호출로 변환한다. WOW64를 구성하는 DLL과 각각의 기능은 <표 2>와 같다.

사용자 삽입 이미지
<표 2> WOW64를 구성하는 DLL

좀 더 자세히 설명하면 32비트 프로세스가 시작할 때 wow64.dll은 32비트 버전의 Ntdll.dll을 로드한다. 이때 Ntdll.dll은 프로세스 실행에 필요한 32비트 DLL들을 로드한다.

알아 두어야 할 점은 WOW64에서 사용하는 대부분의 32비트 DLL들은 별다른 수정 없이 설치되어 있지만 64비트 프로세스와 메모리를 공유해야 하는 몇 개의 32비트 DLL들은 WOW64에서도 정상적으로 동작할 수 있도록 조금 수정되었다는 사실이다. 그리고 64비트 Ntdll.dll은 32비트 프로세스에서 로드할 수 있는 유일한 DLL인데, wow64.dll은 64비트 ntdll.dll과 32비트 ntdll.dll 사이에서 썽킹을 수행해서 32비트 프로세스의 실행환경을 만들어 준다.

썽킹을 커널모드가 아닌 사용자모드에서 수행하는 이유는 썽킹 과정의 버그로 인해 BSOD(Blue Screen Of Death)가 발생하는 것을 방지하기 위해서이다. WOW64를 구성하는 모듈간의 관계는 <그림 2>를 살펴보면 쉽게 이해할 수 있다.

사용자 삽입 이미지
<그림 2> WOW64의 아키텍처

그렇다면 WOW64 환경에서 수행되는 32비트 프로세스의 성능은 어떨까? 여러 가지 요인에 의해 수행 성능이 결정될 수 있는데 첫 번째로 어떤 CPU에 의해 수행되느냐가 중요하다. 아이태니엄과 같은 IA64 계열의 CPU는 wow64cpu.dll이 32비트 명령어를 에뮬레이션하기 때문에 아무래도 성능이 낮을 수밖에 없다.

그러나 AMD64나 EM64T같은 x64 계열에서 수행되는 32비트 프로세스는 32비트 명령어를 CPU에서 직접 수행하기 때문에 32비트 윈도우에서 수행하는 것과 거의 동일한 성능으로 수행할 수 있다. WOW64에서 메모리를 다루는 것도 이 두 계열의 CPU에 따라 조금 다르다. IA64 계열은 기본적으로 8K 페이지를 사용하게 때문에 AWE(Address Windowing Extention)를 사용할 수 없고 GetWriteWatch(), ResetWriteWatch(), ReadFileScatter(), WriteFileGather() 함수를 사용할 수 없다.

반면에 x64 계열은 기존의 32비트 시스템과 동일한 4K 페이지를 사용하기 때문에 AWE를 사용할 수 있을 뿐만 아니라 사용자 주소 공간도 기존의 2GB가 아닌 4GB의 가상 주소 공간을 사용할 수 있다. 일반적인 가이드는 WOW64를 이용해 기존의 수많은 32비트 응용 프로그램을 수행할 수 있지만 WOW64에서 수행하려는 프로그램이 높은 성능을 요구하는 서버 응용 프로그램이라면 빠른 시일 내에 완전한 64비트 응용 프로그램으로 제작하길 바란다. MS는 WOW64 상에서 서버 응용 프로그램을 구동하는 것을 권장하지 않는다고 한다.

64비트와 32비트 프로세스/메시지 구분하기
현재 MS에서는 하나의 소스로 32비트 바이너리와 64비트 바이너리를 개발할 것을 권장하고 있다. 이 가이드에 맞추어 프로그램을 개발하다 보면 자신이 32비트 프로세스인지 또는 64비트 프로세스인지 판별해야 할 경우가 생기는데 IsWow64Process() 함수를 사용하면 쉽게 판별할 수 있다. 이 함수는 프로세스 핸들을 인자로 받아들이므로 다른 프로세스의 32비트 프로세스 여부를 판단하는 데 유용하게 사용될 수 있다.

그렇다면 64비트 프로세스에서 현재 받은 윈도우 메시지가 32비트에서 발생한 것인지 64비트에서 발생한 것인지를 구분해야 할 때는 어떻게 해야 할까? 64비트 윈도우에서는 IsWow64Message() 함수를 이용해 현재 쓰레드에서 마지막으로 받은 윈도우 메시지가 32비트에서 발생한 것인지 판별할 수 있다. 예를 들어 윈도우 메시지 안에 포인터 타입을 포함하는 데이터가 있고 그 포인터가 32비트 포인터일 경우 주의해야 하는데 이런 경우 IsWow64Message() 함수를 이용해 판별할 수 있다.

사용자 삽입 이미지
<표3> 32비트/64비트 프로세스와 메시지를 구분하는데 사용되는 함수

레지스트리 접근하기
앞서 보았듯이 64비트 윈도우에는 32비트 프로세스와 64비트 프로세스가 공존한다. 64비트 윈도우에서는 이 두 종류의 프로세스가 접근하는 두 가지 관점의 레지스트리가 존재하게 된다. 하나는 WOW64를 통해 수행되는 32비트 프로세스가 접근하는 레지스트리이고 다른 하나는 순수한 64비트 프로세스가 접근하는 레지스트리이다.

32비트와 64비트 프로세스 간에 서로 다른 관점을 유지하는 이유는 크게 두 가지가 있다. 첫 번째는 32비트 프로세스와 64비트 프로세스가 사용하는 레지스트리 상태를 분리하고 각각 저장하는 설정 값들을 중복되지 않게 구분하기 위해서이다. 두 번째는 서로 같은 위치의 레지스트리를 사용할 경우 상호간에 관리하는 값들을 훼손할 수 있기 때문에 안전한 수행환경을 제공하기 위해서 32비트 응용 프로그램과 64비트 응용 프로그램 레지스트리를 분리시켰다.

<화면 1>을 보면 32비트 프로세스가 사용하는 Wow6432Node 밑의 Microsoft 키와 64비트 프로세스가 사용하는 Microsoft 키가 나누어져 있음을 알 수 있다.

사용자 삽입 이미지
<화면 1> 64비트 윈도우에 존재하는 두 가지 레지스트리 뷰

너는 너대로 나는 나대로 - 레지스트리 접근경로 변경
WOW64는 32비트 프로세스가 레지스트리를 생성하거나 오픈하려고 할 때 몇 개의 키들에 대해서는 레지스트리 키 경로에 Wow6432Node를 덧붙인다. 이렇게 해서 64비트 프로세스가 접근하는 레지스트리 경로와 32비트 프로세스가 접근하는 레지스트리 경로가 변경된다. 모든 레지스트리 키가 이렇게 접근경로 변경(Redirection)이 되는 것은 아닌데 주로 다음과 같은 키들이 분리되며 이 키들을 제외한 다른 키들은 일반적으로 32비트와 64비트 프로세스가 같은 레지스트리 위치에 접근한다.

◆ HKEY_LOCAL_MACHINE\Software
◆ HKEY_LOCAL_MACHINE\Software\Classes
◆ HKEY_USER\*\Software\Classes
◆ HKEY_USERS\*_Classes
◆ *는 각 사용자의 SID 이다.

레지스트리 키 접근경로 변경은 로컬 시스템과 원격 시스템에 따라 다르게 일어나는데 윈도우 서버 2003 SP1 이전에는 로컬시스템 레지스트리에 접근할 때만 키 경로의 변경이 발생하고 RegConnectRegistry 함수를 사용해 원격 시스템에서 레지스트리에 접근할 때는 키 경로의 변경 없이 64비트 레지스트리에만 접근할 수 있다. 그러나 윈도우 서버 2003 SP1를 포함한 이후 버전의 64비트 윈도우에서는 원격 레지스트리에 접근하는 시스템이 32비트 윈도우라면 32비트 레지스트리를 접근할 수 있고 64비트 시스템이라면 64비트 레지스트리를 접근할 수 있다.

너와 나는 하나 - 레지스트리 복사·공유
다음과 같은 시나리오를 생각해보자. 먼저 깨끗한 상태의 64비트 윈도우를 설치하면 .doc 파일에 대한 기본 응용 프로그램으로 Wordpad.exe가 64비트 레지스트리에 등록되어 있다. 이 정보는 32비트 레지스트리에도 복사가 된다. 만약 이 운영체제에 32비트 버전의 MS 오피스를 설치한다면 .doc 파일에 대한 기본 응용 프로그램으로 32비트 레지스트리에 winword.exe가 등록된다. 그리고 이 정보는 64비트 레지스트리에도 복사가 된다. 이 경우 32비트와 64비트에 상관없이 .doc 파일에 대한 기본 응용 프로그램은 32비트 버전의 winword.exe가 된다.

여기에 64비트 버전의 마이크로소프트 오피스를 설치한다면 어떻게 될까? 64비트 레지스트리에 64비트 버전의 winword.exe 경로가 등록되고 이 값은 32비트 레지스트리에도 복사된다. 최종적으로는 .doc 파일에 대한 기본 응용 프로그램으로 64비트 버전의 winword.exe가 등록된 것이고 32비트나 64비트 상관없이 같은 경로의 winword.exe를 사용하게 된다. 결론적으로 32비트와 64비트 레지스트리 중 어떤 키들은 변경이 발생하면 서로 복사함으로서 32비트와 64비트의 설정을 동기화하고 64비트 윈도우에서 응용 프로그램이 유연하게 동작할 수 있도록 해준다.

COM의 경우를 생각해 보면 별도의 프로세스로 수행되는 Out-of-Process COM 등록의 경우 앞서 설명한 것처럼 32비트와 64비트 레지스트리 간에 복사가 일어나서 최종적으로 기록한 값이 동기화되게 된다. 반면에 같은 프로세스 주소 공간 안에서 동작하는 In-Process COM의 경우는 복사가 이루어지지 않고 32비트는 COM은 32비트 레지스트리에 64비트 COM은 64비트 레지스트리에 등록이 된다.

이런 현상은 어쩌면 너무나 당연할 결과인데 32비트와 64비트 간에 IPC나 RPC는 허용되는 반면 32비트 모듈이 64비트 프로세스 주소공간 안에서 동작할 수 없고 64비트 모듈이 32비트 프로세스 주소공간 안에서 동작할 수 없기 때문이다. 다음 키들은 32비트와 64비트 레지스트리에 별도로 존재하지만 변경이 발생할 경우 32비트와 64비트 키 간에 복사를 수행해 내용을 일치 시킨다.

◆ HKEY_LOCAL_MACHINE\Software\Classes 일부
◆ HKEY_LOCAL_MACHINE\Software\Microsoft\COM3
◆ HKEY_LOCAL_MACHINE\Software\Microsoft\Ole
◆ HKEY_LOCAL_MACHINE\Software\Microsoft\EventSystem
◆ HKEY_LOCAL_MACHINE\Software\Microsoft\RPC

다음 키들은 32비트와 64비트 레지스트리가 별도로 존재하지 않고 32비트와 64비트 프로세스가 공유해서 사용한다.

◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ SystemCertificates
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Cryptography\ Services
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Classes\ HCP
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ EnterpriseCertificates
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ MSMQ
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ NetworkCards
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ ProfileList
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ Perflib
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ Print
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ Ports
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows\ CurrentVersion\ Telephony\ Locations
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Policies
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows\ CurrentVersion\ Group Policy
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows\ CurrentVersion\ Policies
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows\ CurrentVersion\ Setup\ OC Manager
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Software\ Microsoft\ Shared Tools\ MSInfo
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows\ CurrentVersion\ Setup
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ CTF\ TIP
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ CTF\ SystemShared
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ Fonts
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ RAS
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Driver Signing
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Non-Driver Signing
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Cryptography\ Calais\ Current
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Cryptography\ Calais\ Readers
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows NT\ CurrentVersion\ Time Zones
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Transaction Server
◆ HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ Windows\ CurrentVersion\ Control Panel\ Cursors\ Schemes

다음 키들은 32비트 레지스트리 키에 변경이 발생하면 64비트 레지스트리로 복사를 하고 32비트 레지스트리 키 값은 삭제된다. 이 키들이 특별하게 동작하는 이유는 주로 사용자가 로그온될 때 사용되는 키여서 32비트와 64비트 레지스트리 양쪽에 값이 기록된다면 하나의 프로그램을 두 번씩 수행하게 될 수도 있기 때문이다. 그래서 64비트 레지스트리에만 값을 유지하는 것이다.

◆ HKEY_LOCAL_MACHINE\ Software\ Microsoft\ Windows\ CurrentVersion\ Run
◆ HKEY_LOCAL_MACHINE\ Software\ Microsoft\ Windows\ CurrentVersion\ RunOnce
◆ HKEY_LOCAL_MACHINE\ Software\ Microsoft\ Windows\ CurrentVersion\ RunOnceEx

32비트와 64비트 레지스트리 넘나들기
64비트 윈도우에서는 특별한 작업을 하지 않는다면 32비트 프로세스는 32비트 레지스트리에 접근하게 되고 64비트 프로세스는 64비트 레지스트리에만 접근할 수 있다. 만일 32비트 프로세스가 64비트 프로세스를 접근하는 것을 원하거나 64비트 프로세스가 32비트 프로세스를 접근하는 것을 원한다면 RegOpenKeyEx / RegCreateKeyEx / RegDeleteKeyEx와 같은 레지스트리 관련 API를 사용할 때 특별한 플래그를 부여하면 양쪽 모두의 레지스트리를 접근할 수 있다. 그 플래그는 <표 4>와 같다.

사용자 삽입 이미지
<표 4> 32비트/64비트 레지스트리를 접근하는 함수에서 사용되는 인자

파일시스템 접근하기
조금 먼저 생각해본 독자라면 앞서 레지스트리를 통해 보았듯이 64비트 윈도우에서 WOW64를 통해 수행되는 32비트 프로세스와 일반적인 64비트 프로세스는 서로 다른 파일시스템 뷰를 가지고 있다는 것을 예상할 수 있다. 64비트 윈도우에서는 두 종류의 프로세스가 서로 다른 파일시스템 뷰를 가지게 되는 것은 서로 참조하는 파일을 분리해서 64비트 프로세스는 64비트 DLL을 사용하고 32비트 프로세스는 32비트 DLL을 사용하게 하기 위해서이다. 그래서 64비트 윈도우를 설치하게 되면 <화면 2>와 같이 시스템 디렉토리가 각각 존재하는 것을 볼 수 있다.



사용자 삽입 이미지
<화면 2> 64비트 윈도우의 시스템 디렉토리

더 풀어서 설명하면 64비트 프로세스의 경우 파일 I/O 함수를 이용해 %systemroot%\System32 디렉토리를 접근하게 될 경우 C:\WINDOWS\system32 디렉토리에 접근하게 되겠지만, 32비트 프로세스가 파일 I/O 함수를 통해 %systemroot%\System32 디렉토리를 접근하게 될 경우 WOW64는 자동으로 C:\WINDOWS\SysWOW64 디렉토리로 접근경로를 변경한다. 그러나 다음 디렉토리들과 그 하위 디렉토리들은 접근경로 변경에서 제외된다.

◆ %windir%\System32\Drivers\Etc
◆ %windir%\System32\spool
◆ %windir%\System32\catroot2

파일시스템의 접근경로 변경은 GetSystemDirectiry()를 사용해 보면 분명하게 나타나는데, 32비트 프로세스에서 이 함수를 사용하면 시스템 디렉토리 밑의 SysWOW64를 얻어오게 되고 64비트 프로세스는 시스템 디렉토리 밑의 System32를 얻어오게 된다. 그렇다면 64비트 프로세스에서 32비트 프로세스가 사용하는 시스템 디렉토리를 얻어오려면 어떻게 해야 할까? 64비트 윈도우에서는 GetSystemWow64Directory() 함수를 이용해 이 답을 구할 수 있다.

또한 64비트 윈도우에서는 프로그램 설치 디렉토리인 %Program Files%에 대해서도 조금은 특별하게 관리하는데 시스템 디렉토리와는 달리 WOW64가 직접 접근경로 변경을 수행하지는 않는다. 대신 32비트 프로세스가 %Program Files% 문자열이 포함된 REG_EXPAND_SZ 형식의 데이터를 레지스트리에 기록하려고 할 때 WOW64는 이 데이터를 가로채서 %ProgramFiles(X86)%으로 바꾸어 레지스트리에 기록하며 마찬가지로 레지스트리를 읽을 때도 데이터를 가로채서 %ProgramFiles(X86)%으로 변경한다.

그러므로 프로그램 설치 디렉토리를 얻어와야 한다면 CSIDL_PROGRAM_FILES를 인자로 사용해서 SHGetSpecialFolderPath() 함수로 %Program Files%의 경로를 얻어오기를 권장한다.

이 함수를 이용하면 32비트 프로세스에서는 C:\Program Files(X86)를 얻어올 것이고 64비트 프로세스에서는 C:\Program Files를 얻어올 것이다. 이와 같이 64비트 윈도우에서 프로그래밍을 할 경우 접근경로 변경이 되는 디렉토리들은 소스 상에 하드코딩하지 말고, 반드시 지정한 종류의 디렉토리를 얻어오는 함수들을 이용해서 프로그램을 작성해야 한다.

이 밖에도 64비트 윈도우에서는 윈도우세션메니저(smss.exe)가 관리하는 KnwonDLL 리스트를 32비트와 64비트 두종류로 관리하고, 32비트 프로세스가 KnownDLLs에 접근할 경우 WOW64는 KnownsDLLs32로 접근경로 변경을 수행해 준다. KnwonDLLs 리스트는 레지스트리의 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Session Manager 밑에서 확인할 수 있다. <표 5>는 64비트 윈도우에서 파일시스템에 접근할 때 유용하게 사용되는 함수 리스트이다.

사용자 삽입 이미지
<표5> 64비트 윈도우 파일시스템에 관련된 유용한 함수

실제 64비트 윈도우에서 동작하는 32비트 응용 프로그램을 개발하다 보면 앞서 설명한 64비트 윈도우에서 제공하는 파일시스템 접근경로 변경 방식이 문제가 될 경우가 있다. 예를 들어 필자가 개발하는 안티바이러스 프로그램의 경우 악성코드가 시스템 디렉토리에 감염되어 치료를 시도하는데 원하는 경로가 아닌 접근경로 변경이 된 SysWOW64 밑을 접근하게 된다면 원하는 파일에 접근을 할 수 없기 때문에 문제가 된다.

즉, 전체적인 파일시스템을 접근경로 변경이 되지 않는 일반적인 상태로 접근하고 싶을 때는 Wow64EnableWow64FsRedirection() 함수를 사용해서 파일시스템 접근경로 변경을 끄면 된다. <리스트 1>을 참조하면, 32비트 프로세스에서 파일시스템 접근경로 변경을 끄고 notepad.exe를 접근하면 32비트 시스템 디렉토리에 있는 notepad.exe가 아닌 64비트 시스템 디렉토리에 있는 notepad.exe를 접근하게 된다.

 <리스트 1> 파일시스템 접근경로 변경
사용자 삽입 이미지
사용자 삽입 이미지

그러나 접근경로 변경을 끈 상태에서 32비트 응용 프로그램을 동작시키기 위해선 조심해야 할 것이 있는데 참조하게 되는 DLL이 32비트가 아닌 64비트 DLL을 사용하게 될 수도 있다는 것이다. 이 경우 64비트 DLL이 32비트 프로세스에 로드가 되지도 않을 뿐더러 응용 프로그램의 구동에 심각한 문제를 발생시킬 수도 있다.

예를 들어 psapi.dll이나 advapi32.dll 같은 시스템 DLL의 경우 같은 이름으로 32비트와 64비트가 각각의 시스템 디렉토리에 별도로 존재하기 때문에 잘못 참조하게 될 수도 있다. 그러므로 필요한 부분에서만 접근경로 변경을 끄고 다시 켜주는 방법을 사용해야 한다.

WOW64로의 여행 CWowUtil
개발자라면 누구나 간단하고 쉽게 프로그래밍하기를 원한다. 어떤 사람들은 게으른 사람들이 개발을 가장 잘한다고도 하는데, 힘들고 귀찮을 일을 효율적으로 만들고 자동화하려는 노력을 가장 많이 하기 때문이라고 한다. 마찬가지로 필자도 그런 성향이 다분하다고 할 수 있는데 그래서 64비트 윈도우에서 32비트 호환 프로그램을 작성할 때 사용될만한 몇 가지 함수를 모아 다음과 같은 CWowUItil 클래스를 만들어 보았다.

이 클래스는 내부적으로 사용하는 시스템 DLL을 동적으로 로딩해서 시스템 함수들을 사용하기 때문에 64비트 윈도우가 아니어도 예외(Exception)가 발생하지는 않는다. 사족이지만 이런 클래스가 필요한 이유는 윈도우95부터 윈도우XP 및 윈도우2003까지 광범위한 운영체제에서 동작하는 프로그램을 하나의 소스로 작성할 때 편리하기 때문이다.

실제로 많은 패키지 소프트웨어가 시스템 함수들을 사용할 경우에 이와 유사한 클래스들을 작성해 사용한다. <리스트 2>는 클래스의 헤더이며 전체 소스는 이달의 디스켓을 참조하기 바란다.

 <리스트 2> 클래스의 헤더
사용자 삽입 이미지
사용자 삽입 이미지

상부상조해야 하는 32비트와 64비트
앞서 언급했듯이 64비트 윈도우에는 두 가지 종류의 프로세스가 동작할 수 있다. 하나는 32비트 프로세스이고 다른 하나는 64비트 프로세스이다. 64비트 윈도우에서는 WOW64를 통해 32비트 프로세스가 별다른 어려움 없이 동작할 수 있도록 지원한다. 하지만 32비트 프로세스가 64비트 DLL을 사용하거나 64비트 프로세스가 32비트 DLL을 사용하는 것은 몇 가지 이유 때문에 허용되지는 않는다. 자세히 언급하겠지만 64비트 윈도우의 원칙은 <그림 3>과 같이 하나의 프로세스 주소 공간 안에 64비트와 32비트 코드가 공존하는 것을 허용하지 않는다.

사용자 삽입 이미지
<그림 3> 64비트 윈도우의 원칙


그렇다면 왜 하나의 프로세스 주소 공간 안에 32비트와 64비트 코드가 공존하는 것을 허용하지 않았을까? 크게 세가지 이유가 있는데 첫째는 32비트 DLL은 2G를 넘는 주소공간을 처리할 수 없기 때문이다. 예를 들어 64비트 프로세스에 32비트 DLL이 로드되고 이 DLL로 64비트 포인터가 넘어 간다면 32비트 DLL은 심각한 예외(Exception)가 발생할 것이다.

둘째는 32비트 DLL은 4K 페이지를 사용하고 x86 스타일의 예외처리(Exception Handling)를 수행하지만 IA64 계열은 8K 페이지를 사용하고 예외처리 방식도 다르기 때문이다. 셋째로 Kernel32.dll, User32.dll, Gdi32.dll 같은 시스템 DLL들은 하나의 프로세스에 32비트 또는 64비트 중의 단 한 종류만 로드되어야 하기 때문이다.

예를 들어 프로세스가 생성되면서 PEB(Process Environment Block)에 User32.dll이 제공하는 함수 포인터 배열이 저장되고 커널모드 윈도우 서브시스템인 Win32k.sys가 사용자모드 APC를 통해 함수 포인터 배열에 저장되어 있는 User32.dll 함수들을 사용하는 상황을 생각해 보자. 만일 64비트 버전의 User32.dll과 32비트 버전의 Use32.dll을 하나의 프로세스 안에서 동시에 로드해 놓고 있다면 어떤 함수를 호출해야 할지 결정할 수 없을 것이다.

대신 IPC(Inter Process Communication)나 RPC는 모두 32비트/64비트에 상관없이 동작하며 뮤텍스, 세마포, 파일핸들, 이벤트 등의 이름을 가진 커널 객체 및 윈도우 핸들(HWND)은 두 종류의 프로세스에서 공유해 사용할 수 있다.

COM의 경우에는 DLL과 같은 형태의 In-Process COM은 32비트와 64비트 프로세스끼리 공유할 수 없지만 실행파일 형태인 Out-Of-Process COM의 경우에는 32비트와 64비트 프로세스가 모두 사용가능하다. 액티브X의 경우는 DLL형식은 같은 종류의 인터넷 익스플로러에서만 사용가능하지만 실행파일 형식은 다른 종류 간에도 사용가능하다.

참고로 64비트 윈도우에는 32비트 인터넷 익스플로러와 64비트 인터넷 익스플로러가 모두 설치되어 있고 기본 웹 브라우저는 64비트 인터넷 익스플로러이다. 공유 메모리도 32비트와 64비트 프로세스 간에 공유 가능한데, 주의할 점은 공유 메모리 안의 데이터 중에 포인터가 있다면 32비트 프로세스와 64비트 프로세스 포인터 사이즈가 다르기 때문에 주의해야 한다.

이 밖에도 CreateProcess(), ShellExecute()와 같은 함수는 32비트와 64비트 프로세스에서 모두 사용하고 실행할 수 있다. 또한 일반적으로 디버거를 구현하는 데 사용되는 함수인 CreateRemoteThread() 함수는 64비트 프로세스에서 32비트 프로세스에 사용할 수 있다.

언젠간 가야하는 길, 64비트 컴퓨팅
현재까지는 고성능 컴퓨팅이나 엔터프라이즈 환경을 제외한다면 64비트 컴퓨팅이 개인 사용자에게 줄 수 있는 것이 무엇인지 와 닿지 않는 게 현실이다. 필자의 생각에는 모든 지형데이터를 메모리에 맵핑시켜놓고 게임을 즐길 수 있는 WOW(World Of Warcraft. 공교롭게도 Windows On Windows와 약자가 같다)의 64비트 버전이 나온다면 중간 중간 지형을 읽느라 뚝뚝 끊기는 화면 없이 와이번을 타고 멋진 경치를 즐길 수 있지 않을까 라고 상상하는 것 외에는 아직은 별다른 사용처를 찾지 못했다.

필자와 비슷한 생각을 아직은 많은 사람들이 하고 있고 수많은 사용자를 갖고 있는 대부분의 프로그램들조차 64비트 버전 발표 계획을 갖고 있지 않다. 아직은 32비트 응용 프로그램으로도 충분히 만족을 느끼기 때문이다.

그러나 그렇다고 해서 64비트 컴퓨팅으로 가야 하지 않아야 된다는 것은 아니다. 세상은 복잡해지고 있고 고객의 절실한 요구이건 기업의 이윤추구를 위한 허풍이건 간에 언젠간 가야 하는 길이 64비트 컴퓨팅이다.

그러므로 좀 더 넓은 시야를 가진 개발자라면 현재 16비트 응용 프로그램에 대한 지원이 종료되었듯이 윈도우의 32비트 프로그램에 대한 엄호는 언젠가 사라질 것을 예측하고 64비트 컴퓨팅을 대비해야 한다. 다음 글에서는 64비트 윈도우에서 달라진 점들을 개발자의 관점에서 살펴보도록 하겠다.@

출처 : http://www.zdnet.co.kr/techupdate/lecture/os/0,39024998,39134610,00.htm