Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)

왜 윈도우에서 가상 메모리 공간은 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 명령어까지 필요로 하게 됩니다.

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

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




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/5/2023]

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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...
NoWriterDateCnt.TitleFile(s)
13255정성태2/10/20234512오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/20234402Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/20235179Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/20234004오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/20234458스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/20236276오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/20235121오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/20234170오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/20235071VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/20234198개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/20234770.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/20234127VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
13243정성태2/5/20234976디버깅 기술: 190. windbg - Win32 API 호출 시점에 BP 거는 방법 [1]
13242정성태2/4/20234420디버깅 기술: 189. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.UnauthorizedAccessException
13241정성태2/3/20233920디버깅 기술: 188. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.IO.FileNotFoundException
13240정성태2/1/20234076디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/20233738디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235821.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235480.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20235090개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
13235정성태1/29/20234646개발 환경 구성: 662. openssl - 윈도우 환경의 명령행에서 SAN 적용하는 방법
13234정성태1/28/20235729개발 환경 구성: 661. dnSpy를 이용해 소스 코드가 없는 .NET 어셈블리의 코드를 변경하는 방법 [1]
13233정성태1/28/20237057오류 유형: 840. C# - WebClient로 https 호출 시 "The request was aborted: Could not create SSL/TLS secure channel" 예외 발생
13232정성태1/27/20234808스크립트: 43. uwsgi의 --processes와 --threads 옵션
13231정성태1/27/20233736오류 유형: 839. python - TypeError: '...' object is not callable
13230정성태1/26/20234105개발 환경 구성: 660. WSL 2 내부로부터 호스트 측의 네트워크로 UDP 데이터가 1개의 패킷으로만 제한되는 문제
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...