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

비밀번호

댓글 작성자
 




... 106  [107]  108  109  110  111  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11249정성태7/12/201718582오류 유형: 410. LoadLibrary("[...].dll") failed - The specified procedure could not be found.
11248정성태7/12/201725064오류 유형: 409. pip install pefile - 'cp949' codec can't decode byte 0xe2 in position 208687: illegal multibyte sequence
11247정성태7/12/201719412오류 유형: 408. SqlConnection 객체 생성 시 무한 대기 문제파일 다운로드1
11246정성태7/11/201718178VS.NET IDE: 118. Visual Studio - 다중 폴더에 포함된 파일들에 대한 "Copy to Output Directory"를 한 번에 설정하는 방법
11245정성태7/10/201723781개발 환경 구성: 321. Visual Studio Emulator for Android 소개 [2]
11244정성태7/10/201723341오류 유형: 407. Visual Studio에서 ASP.NET Core 실행할 때 dotnet.exe 프로세스의 -532462766 오류 발생 [1]
11243정성태7/10/201720024.NET Framework: 666. dotnet.exe - 윈도우 운영체제에서의 .NET Core 버전 찾기 규칙
11242정성태7/8/201720283제니퍼 .NET: 27. 제니퍼 닷넷 적용 사례 (7) - 노후된 스토리지 장비로 인한 웹 서비스 Hang (멈춤) 현상
11241정성태7/8/201719028오류 유형: 406. Xamarin 빌드 에러 XA5209, APT0000
11240정성태7/7/201721962.NET Framework: 665. ClickOnce를 웹 브라우저를 이용하지 않고 쿼리 문자열을 전달하면서 실행하는 방법 [3]파일 다운로드1
11239정성태7/6/201723460.NET Framework: 664. Protocol Handler - 웹 브라우저에서 데스크톱 응용 프로그램을 실행하는 방법 [5]파일 다운로드1
11238정성태7/6/201720982오류 유형: 405. NT 서비스 시작 시 "Error 1067: The process terminated unexpectedly." 오류 발생 [2]
11237정성태7/5/201722615.NET Framework: 663. C# - PDB 파일 경로를 PE 파일로부터 얻는 방법파일 다운로드1
11236정성태7/4/201725880.NET Framework: 662. C# - VHD/VHDX 가상 디스크를 마운트하지 않고 파일을 복사하는 방법파일 다운로드1
11235정성태6/29/201720038Math: 20. Matlab/Octave로 Gram-Schmidt 정규 직교 집합 구하는 방법
11234정성태6/29/201717345오류 유형: 404. SharePoint 2013 설치 과정에서 "The username is invalid The account must be a valid domain account" 오류 발생
11233정성태6/28/201717281오류 유형: 403. SharePoint Server 2013을 Windows Server 2016에 설치할 때 .NET 4.5 설치 오류 발생
11232정성태6/28/201718239Windows: 144. Windows Server 2016에 Windows Identity Extensions을 설치하는 방법
11231정성태6/28/201718872디버깅 기술: 86. windbg의 mscordacwks DLL 로드 문제 - 세 번째 이야기 [1]
11230정성태6/28/201718048제니퍼 .NET: 26. 제니퍼 닷넷 적용 사례 (6) - 잦은 Recycle 문제
11229정성태6/27/201719291오류 유형: 402. Windows Server Backup 관리 콘솔이 없어진 경우
11228정성태6/26/201716733개발 환경 구성: 320. Visual Basic .NET 프로젝트에서 내장 Manifest 자원을 EXE 파일로부터 제거하는 방법파일 다운로드1
11227정성태6/19/201724512개발 환경 구성: 319. windbg에서 python 스크립트 실행하는 방법 - pykd [6]
11226정성태6/19/201716335오류 유형: 401. Microsoft Edge를 실행했는데 입력 반응이 없는 경우
11225정성태6/19/201715662오류 유형: 400. Outlook - The required file ExSec32.dll cannot be found in your path. Install Microsoft Outlook again.
11224정성태6/13/201718156.NET Framework: 661. Json.NET의 DeserializeObject 수행 시 속성 이름을 동적으로 바꾸는 방법파일 다운로드1
... 106  [107]  108  109  110  111  112  113  114  115  116  117  118  119  120  ...