Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 3개 있습니다.)
.NET Framework: 2086. C# - Windows 운영체제의 2MB Large 페이지 크기 할당 방법
; https://www.sysnet.pe.kr/2/0/13208

Windows: 218. 왜 윈도우에서 가상 메모리 공간은 64KB 정렬이 된 걸까요?
; https://www.sysnet.pe.kr/2/0/13209

Windows: 219. 윈도우 x64의 경우 0x00000000`7ffe0000 아래의 주소는 왜 사용하지 않을까요?
; https://www.sysnet.pe.kr/2/0/13210




왜 윈도우에서 가상 메모리 공간은 64KB 정렬이 된 걸까요?

아래와 글이 있군요. ^^

Why is address space allocation granularity 64KB?
; https://devblogs.microsoft.com/oldnewthing/20031008-00/?p=42223

지난 글에 정리한 대로, 가상 메모리를 할당하는 경우 일반 페이지 하나의 크기는 4KB인데요, 그렇게 할당은 해도 시스템은 64KB 선형 주소 공간을 한꺼번에 예약하는 구조입니다. 이 값들을 코드로는 다음과 같이 구할 수 있습니다.

using System.Runtime.InteropServices;

internal class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern void GetSystemInfo(ref SYSTEM_INFO Info);

    static void Main(string[] args)
    {
        SYSTEM_INFO si = new SYSTEM_INFO();
        GetSystemInfo(ref si);

        Console.WriteLine($"allocation granularity: {FormatAddr(si.lpMinimumApplicationAddress)}");
        Console.WriteLine($"page granularity: 0x{si.dwPageSize:x8}");
    }

    private static string FormatAddr(nint address)
    {
        string text = $"{address:x16}";

        return $"0x{text.Substring(0, 8)}`{text.Substring(8)}";
    }
}

[StructLayout(LayoutKind.Sequential)]
internal struct SYSTEM_INFO
{
    internal ushort wProcessorArchitecture;
    internal ushort wReserved;
    internal uint dwPageSize;
    internal IntPtr lpMinimumApplicationAddress;
    internal IntPtr lpMaximumApplicationAddress;
    internal IntPtr dwActiveProcessorMask;
    internal uint dwNumberOfProcessors;
    internal uint dwProcessorType;
    internal uint dwAllocationGranularity;
    internal ushort wProcessorLevel;
    internal ushort wProcessorRevision;
}

실행해 보면,

allocation granularity: 0x00000000`00010000 // 0x10000 == 64KB
page granularity: 0x00001000 // 0x1000 == 4KB

2003년부터 지금까지 저 값은 64KB, 4KB입니다. (다만 CPU별로 달라질 수 있습니다.) 즉, 1바이트가 필요하다고 해도 4KB가 commit되며, 64KB의 선형 주소 공간이 예약되는 것입니다. 이에 대한 원인은 Alpha AXP RISC 프로세서의 동작 특성에서 기인했다고 합니다. RISC 프로세서의 경우 32bit 정숫값을 로드하는 명령어가 없었고, 대신 2개의 16비트 정수를 로드해 합치는 방식이었다고 합니다.

그래서 할당 단위를 64KB보다 작게 하려면 경우에 따라 16비트로 나뉜 2개의 값을 모두 바꿔야 하는 반면, 64KB 정렬이라고 가정하면 상위 16비트 정수 하나만 바꾸면 되기 때문에 성능상 이점이 있었던 것입니다.

아니, 그런데 왜? 64KB보다 작게 했을 때 2개의 값을 바꿔야만 했던 것일까요? 이게 잘 상상이 안 가는데요, 왜냐하면 Alpha AXP는 그것조차도 "부호 있는 정수" 2개를 32비트로 합치는 연산이었다고 합니다. 일례로, 0x1234abcd 주소를 지정하려면, 상위 0x1234는 상관없지만 하위 0xabcd는 음수이기 때문에 더하는 경우 0x1234abcd가 나올 수 없습니다. 따라서 이럴 때는 다음과 같이,

LDAH t1, 0x1235(zero) // t1 = 0x12350000
LDA  t1, -0x5433(t1) // t1 = t1 - 0x5433 = 0x1234abcd

차라리 상위를 0x1235로 지정하고 하위를 음수로 0x5433을 지정해 빼는 방식으로 처리해 0x1234abcd를 구합니다.

따라서 만약 가상 주소 공간의 정렬 단위가 64KB보다 작게 했다면, 이런 경우 DLL 로딩 시 발생하는 재배치 관련해서도 영향을 주게 됩니다. 즉, 모듈 재배치가 64KB의 하위 절반 값에서 64KB의 상위 절반 값으로 바뀐다면 signed 연산이라는 점을 감안해, 예를 들어 모듈의 기준값이 0x12345000이었다가, 재배치되는 주소가 0x12348000이 된다면 다시 LDAH/LDA 연산을 거쳐야만 주소를 지정할 수 있게 됩니다. 이런 방식은, 컴파일러의 명령어 재정렬 최적화까지 고려하면 문제가 더 복잡해진다고 합니다.

이 외에도 문제가 하나 더 있습니다. 예를 들어, 같은 64KB 영역에 있는 2개의 변수에 대한 주소를 계산할 필요가 있는 경우 컴파일러는 한 번의 LDAH 명령어를 공유하는 최적화를 수행할 수 있다고 합니다. 하지만 마찬가지로 64KB보다 작은 범위로 재배치를 허용한다면, 기존의 LDAH를 공유할 수 있었던 변수들조차도 재배치 이후에는 상위 값이 바뀔 수 있기 때문에 관련 최적화를 더 이상 수행할 수 없게 됩니다.

이런 모든 문제를 64KB 정렬로 강제하면 자연스럽게 해결이 됩니다.

자, 그럼 여기까지 완벽하게 이해하셨다면, 이제 여러분은 왜 2GB 메모리의 최상위 64KB 영역이 접근 불가능한 영역인지 알 수 있습니다.

예를 들어, (2GB의 최상위 64KB에 속한 주소의 한 예로) 0x7FFFABCD 주소를 가리키고 싶다고 가정해 보겠습니다. (이전에 예를 든 0x1234abcd처럼) 0x7fff, 0xabcd 주소를 signed 정수의 덧셈 연산으로 지정하려면 이렇게 해야만 합니다.

LDAH t1, 0x8000(zero) // t1 == 0x80000000
LDA  t1, -0x5433(t1)  // t1 = t1 - 0x5433 = 0x7fffabcd

여기서 문제는, LDAH의 상위에 지정한 0x8000 값이 더 이상 "signed 16 비트"로 지정 가능한 양수의 범위가 아니라는 점입니다. 한 가지 다행인 점은, 32비트 Windows NT였지만 재미있게도 Alpha AXP 프로세스는 64비트였다는 점입니다. 그래서 위의 연산은 64비트 Alpha AXP 프로세서에서 실제로는 다음과 같이 연산을 한 후,

LDAH t1, -0x8000(zero) // t1 == 0xFFFFFFFF`80000000
LDA  t1, -0x5433(t1)   // t1 = t1 - 0x5433 = 0xFFFFFFFF`7FFFFABCD

원래 우리가 원했던 0x7FFFFABCD를 얻기 위해 상위 0xFFFFFFFF를 지울 연산이 한 번 더 필요하게 되었고, 이를 위한 트릭으로 ADDL 명령을 이용할 수도 있었다고 합니다.

ADDL t1, zero, t1 // t1 = t1 + 0
// L 접미사가 붙은 ADD 연산은 operand를 32비트로 취급하고, 연산 결과를 64비트로 부호 확장합니다.
// 따라서 0xFFFFFFFF`7FFFFABCD 값은 32비트로 0x7FFFFABCD로 바뀌고,
// 0과 더한 연산 후의 결괏값을 64비트로 부호 확장해 0x00000000`7FFFABCD로 반환

그러니까, 결국 저 주소 영역을 허용해야 한다면 그 주소로의 재배치 가능성까지도 염두에 둬야 하고, 이로 인해 모든 메모리 주솟값 계산에 대해 ADDL 명령어까지 필요로 하게 됩니다.

Raymond Chen에 의하면, 사실 그 주소로의 재배치가 발생할 가능성이 희박한데도 불구하고 그런 경우까지도 고려해야 하는 명령어까지 넣어버리면 메모리 주소 계산 때마다 발생하는 50%의 성능 손실이 너무 컸다고 합니다. 결국 2GB의 마지막 64KB 영역은 "사용할 수 없는 영역(No man's land)"으로 취급하자는 결정에 이르게 된 것이라고.

물론, 현재 버전의 Windows는 Intel/AMD/ARM CPU만을 지원하므로 저런 제약이 없어 64KB 정렬을 유지하지 않아도 되었을 것입니다. 단지, 이미 안정화된 커널 코드인데다 64비트의 광활한 메모리 주소 공간으로 인해 64KB 정렬이라는 제약이 더 이상 제약이라고 할 수 없어 그대로 유지하는 것이 아닌가... 하는 개인적인 추측입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 11/20/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 151  152  [153]  154  155  156  157  158  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1227정성태2/3/201229257.NET Framework: 299. 해당 어셈블리가 Debug 빌드인지, Release 빌드인지 알아내는 방법파일 다운로드1
1226정성태1/28/201270170.NET Framework: 298. 홀 펀칭(Hole Punching)을 이용한 Private IP 간 통신 - C# [15]파일 다운로드3
1225정성태1/24/201225770.NET Framework: 297. 특정 EXE 파일의 실행을 Internet Explorer처럼 "Protected Mode"로 실행하는 방법 [1]파일 다운로드1
1224정성태1/21/201237283개발 환경 구성: 139. 아마존 EC2에 새로 추가된 "1년 무료 Windows 서버 인스턴스"가 있다는데, 직접 만들어 볼까요? ^^ [11]
1223정성태1/20/201227298.NET Framework: 296. 괜찮은 문자열 해시함수? - 두 번째 이야기 [1]파일 다운로드1
1222정성태1/18/201235022.NET Framework: 295. 괜찮은 문자열 해시 함수? [4]파일 다운로드1
1221정성태1/17/201224006오류 유형: 147. System.Runtime.InteropServices.COMException (0x80005000)
1220정성태1/15/201224161.NET Framework: 294. Master web.config 파일을 수정하려면?파일 다운로드1
1219정성태1/15/201226567.NET Framework: 293. Microsoft PowerPoint 슬라이드를 HTML 파일로 ".files" 폴더 없이 저장하는 방법 (C# 코드)파일 다운로드1
1218정성태1/15/201239089.NET Framework: 292. RSACryptoServiceProvider의 공개키와 개인키 구분 [1]파일 다운로드2
1217정성태1/14/201241177.NET Framework: 291. .NET에서 WAV, MP3 파일 재생하는 방법 [1]파일 다운로드1
1216정성태1/14/201229890오류 유형: 146. Microsoft Visual C++ 재배포 패키지 - 설치 로그 남기는 방법 [1]
1215정성태1/9/201227443제니퍼 .NET: 20. 제니퍼 닷넷 적용 사례 (3) - '닷넷'이 문제일까? '닷넷 개발자'가 문제일까? [6]
1214정성태1/3/201224295제니퍼 .NET: 19. 제니퍼 닷넷 설치/제거 방법 - IIS
1213정성태12/31/201124222.NET Framework: 290. WCF - 접속된 클라이언트의 IP 주소 알아내는 방법 - 두 번째 이야기
1212정성태12/31/201124334오류 유형: 145. The trust relationship between this workstation and the primary domain failed.
1211정성태12/31/201129131.NET Framework: 289. WindowsFormsHost를 사용하는 XBAP 응용 프로그램파일 다운로드1
1210정성태12/30/201148105.NET Framework: 288. FFmpeg.exe를 이용한 C# 동영상 인코더 예제 [9]파일 다운로드1
1209정성태12/29/201122748개발 환경 구성: 138. BizTalk 2006 설치 방법
1208정성태12/28/201145717.NET Framework: 287. Excel Sheet를 WinForm에서 사용하는 방법 [8]파일 다운로드2
1207정성태12/26/201124995.NET Framework: 286. x86/x64로 구분된 코드를 포함하는 경우, 다중으로 어셈블리를 만들어야 할까요?파일 다운로드1
1206정성태12/25/201126009.NET Framework: 285. Shader 강좌와 함께 배워보는 XNA Framework (3) - 텍스처 매핑 예제파일 다운로드1
1205정성태12/25/201131701.NET Framework: 284. Thread 개체의 Interrupt와 Abort의 차이점파일 다운로드1
1204정성태12/22/201125201.NET Framework: 283. MEF를 ASP.NET에 성능 손실 없이 적용하려면? [7]
1203정성태12/21/201125570제니퍼 .NET: 18. MEF가 적용된 ASP.NET 웹 사이트를 제니퍼 닷넷으로 모니터링 해본 결과! [6]
1202정성태12/21/201125981오류 유형: 144. The database '...' cannot be opened because it is version 661.
... 151  152  [153]  154  155  156  157  158  159  160  161  162  163  164  165  ...