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

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  97  98  99  100  101  [102]  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11383정성태12/4/201723402디버깅 기술: 110. 비동기 코드 실행 중 예외로 인한 ASP.NET 프로세스 비정상 종료 현상 [1]
11382정성태12/4/201721943오류 유형: 436. System.Data.SqlClient.SqlException (0x80131904): Connection Timeout Expired 예외 발생 시 "[Pre-Login] initialization=48; handshake=1944;" 값의 의미
11381정성태11/30/201718435.NET Framework: 702. 한글이 포함된 바이트 배열을 나눈 경우 한글이 깨지지 않도록 다시 조합하는 방법(두 번째 이야기)파일 다운로드1
11380정성태11/30/201718468디버깅 기술: 109. windbg - (x64에서의 인자 값 추적을 이용한) Thread.Abort 시 대상이 되는 스레드를 식별하는 방법
11379정성태11/30/201719137오류 유형: 435. System.Web.HttpException - Session state has created a session id, but cannot save it because the response was already flushed by the application.
11378정성태11/29/201720634.NET Framework: 701. 한글이 포함된 바이트 배열을 나눈 경우 한글이 깨지지 않도록 다시 조합하는 방법 [1]파일 다운로드1
11377정성태11/29/201719888.NET Framework: 700. CommonOpenFileDialog 사용 시 사용자가 선택한 파일 목록을 구하는 방법 [3]파일 다운로드1
11376정성태11/28/201724280VS.NET IDE: 123. Visual Studio 편집기의 \r\n (crlf) 개행을 \n으로 폴더 단위로 설정하는 방법
11375정성태11/28/201719077오류 유형: 434. Visual Studio로 ASP.NET 디버깅 중 System.Web.HttpException - Could not load type 오류
11374정성태11/27/201724173사물인터넷: 14. 라즈베리 파이 - (윈도우의 NT 서비스처럼) 부팅 시 시작하는 프로그램 설정 [1]
11373정성태11/27/201723167오류 유형: 433. Raspberry Pi/Windows 다중 플랫폼 지원 컴파일 관련 오류 기록
11372정성태11/25/201726135사물인터넷: 13. 윈도우즈 사용자를 위한 라즈베리 파이 제로 W 모델을 설정하는 방법 [4]
11371정성태11/25/201719830오류 유형: 432. Hyper-V 가상 스위치 생성 시 Failed to connect Ethernet switch port 0x80070002 오류 발생
11370정성태11/25/201719836오류 유형: 431. Hyper-V의 Virtual Switch 생성 시 "External network" 목록에 특정 네트워크 어댑터 항목이 없는 경우
11369정성태11/25/201721810사물인터넷: 12. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 키보드 및 마우스로 쓰는 방법 (절대 좌표, 상대 좌표, 휠) [1]
11368정성태11/25/201727437.NET Framework: 699. UDP 브로드캐스트 주소 255.255.255.255와 192.168.0.255의 차이점과 이를 고려한 C# UDP 서버/클라이언트 예제 [2]파일 다운로드1
11367정성태11/25/201727495개발 환경 구성: 337. 윈도우 운영체제의 route 명령어 사용법
11366정성태11/25/201719135오류 유형: 430. 이벤트 로그 - Cryptographic Services failed while processing the OnIdentity() call in the System Writer Object.
11365정성태11/25/201721380오류 유형: 429. 이벤트 로그 - User Policy could not be updated successfully
11364정성태11/24/201723336사물인터넷: 11. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스로 쓰는 방법 (절대 좌표) [2]
11363정성태11/23/201723379사물인터넷: 10. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스 + 키보드로 쓰는 방법 (두 번째 이야기)
11362정성태11/22/201719748오류 유형: 428. 윈도우 업데이트 KB4048953 - 0x800705b4 [2]
11361정성태11/22/201722563오류 유형: 427. 이벤트 로그 - Filter Manager failed to attach to volume '\Device\HarddiskVolume??' 0xC03A001C
11360정성태11/22/201722415오류 유형: 426. 이벤트 로그 - The kernel power manager has initiated a shutdown transition.
11359정성태11/16/201721921오류 유형: 425. 윈도우 10 Version 1709 (OS Build 16299.64) 업그레이드 시 발생한 문제 2가지
11358정성태11/15/201726705사물인터넷: 9. Visual Studio 2017에서 Raspberry Pi C++ 응용 프로그램 제작 [1]
... 91  92  93  94  95  96  97  98  99  100  101  [102]  103  104  105  ...