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

C# - Windows 운영체제의 2MB Large 페이지 크기 할당 방법

다음의 글이 있군요. ^^

What are the page sizes used by Windows on various processors?
; https://devblogs.microsoft.com/oldnewthing/20210510-00/?p=105200

Page 크기는 CPU 종류에 의해 결정된다고 합니다.

Processor Page size Reasonable choices
Normal Large
x86-32 without PAE 4KB 4MB 4KB only
x86-32 with PAE 4KB 2MB 4KB only
x86-64 4KB 2MB 4KB only
SH-4 4KB 1KB, 4KB
MIPS 4KB 1KB, 4KB
PowerPC 4KB 4KB only
Alpha AXP 8KB 8KB, 16KB, 32KB
Alpha AXP 64 8KB 8KB, 16KB, 32KB
Itanium 8KB 4KB, 8KB
ARM (AArch32) 4KB N/A 1KB, 4KB
ARM64 (AArch64) 4KB 2MB 4KB only

위의 표에 보면 Normal 페이지의 크기를 선택할 수 있는 CPU(SH-4, MIPS, Alpha AXP/64, Itanium, ARM)들이 있는데 현재는 윈도우에서 지원하고 있지 않는 CPU들입니다. 또한 그중에서도 MIPS와 ARM(AArch32)의 경우 1KB를 지원하는 방식이 원래의 4KB 페이지를 분할하는 1KB subpages 방식으로 지원했다고 합니다. ARM의 경우 이후 ARM64(AArch64)에 와서는 그런 지원을 없앴습니다.

또한, Large 페이지에 "—" 표시가 있는 것들은 해당 CPU들이 Large Page를 지원하긴 하지만 당시 윈도우에 그 기능을 넣기도 전에 지원이 중단되었다고 합니다. 반면, ARM(AArch32)의 경우 "Lage Page" 자체를 CPU 차원에서 미지원하는 경우였고.

우리가 흔히 아는 x86/x64 프로세서 모두 Normal 4KB, Larget 2MB입니다. (근래의 윈도우 운영체제는 모두 DEP를 위해 PAE가 적용되었습니다.)

현재 현실적으로 사용할 수 있는 윈도우 운영체제가 x86/x64/ARM64를 지원하기 때문에 그냥 모든 페이지가 Normal 4KB, Large 2MB라고 간단하게 기억해 두시면 되겠습니다. 그런데, 여기까지 읽었는데도 혹시 윈도우에서 페이지 크기를 바꾸는 것이 가능할지 궁금한 분이 계실까요? ^^ 처음에도 말했지만, Raymond Chen은 다음의 글에서도 답을 하고 있습니다.

Is there a way to change the minimum size for large pages?
; https://devblogs.microsoft.com/oldnewthing/20160614-00/?p=93665

The large page minimum size isn’t chosen by Windows. It’s chosen by the processor.
...
So if you want to have custom sizes for large pages, you need to start by asking the processor manufacturers.





윈도우 운영체제의 페이지 크기는 다음과 같은 코드로 구할 수 있습니다.

using System.Runtime.InteropServices;

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

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern UInt32 GetLargePageMinimum();

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

        uint largePageSize = GetLargePageMinimum();

        Console.WriteLine($"normal page granularity: 0x{si.dwPageSize:x8}");
        Console.WriteLine($"large page granularity: 0x{largePageSize:x8}");
    }
}

[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;
}

실행해 보면,

normal page granularity: 0x00001000 // == 4KB
large page granularity: 0x00200000 // == 2MB

예상했던 결과를 구할 수 있습니다. 해본 김에, large page 할당도 호출해 볼까요? ^^ 이를 위해 간단하게 다음과 같이 코딩을 할 수 있는데요,

using System.Runtime.InteropServices;

internal class Program
{
    // ...[생략]...

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType,
          MemoryProtection flProtect);

    [Flags()]
    private enum AllocationType : uint
    {
        COMMIT = 0x1000,
        RESERVE = 0x2000,
        LARGE_PAGES = 0x20000000,
    }

    [Flags()]
    public enum MemoryProtection : uint
    {
        EXECUTE_READWRITE = 0x40,
    }

    static void Main(string[] args)
    {
        // ...[생략]...

        uint largePageSize = GetLargePageMinimum();

        var ptr = VirtualAlloc(IntPtr.Zero, largePageSize,
               AllocationType.COMMIT | AllocationType.RESERVE | AllocationType.LARGE_PAGES,
               MemoryProtection.EXECUTE_READWRITE);
        if (ptr == IntPtr.Zero)
        {
            Console.WriteLine($"Error: {Marshal.GetLastWin32Error()}");
            return;
        }
    }

    // ...[생략]...
}

실행해 보면, 1314 오류 코드(A required privilege is not held by the client.)가 나옵니다. 왜냐하면 이에 대해서는 특권이 필요한데요, 관련해서 아래의 문서에 잘 나와 있습니다.

Large-Page Support
; https://learn.microsoft.com/en-us/windows/win32/memory/large-page-support

Creating a File Mapping Using Large Pages
; https://learn.microsoft.com/en-us/windows/win32/memory/creating-a-file-mapping-using-large-pages

그러니까, 우선 SeLockMemoryPrivilege 특권을 AdjustTokenPrivileges API를 이용해 활성화해야 하므로, 이전에 구현했던 C# 코드를 이용해 다음과 같이 코드를 추가해 줍니다.

using System;
using System.Runtime.InteropServices;

internal class Program
{
    // ...[생략]...

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
        ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool LookupPrivilegeValue(string? lpSystemName, string lpName, out LUID lpLuid);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);
    
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetCurrentProcess();

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseHandle(IntPtr hObject);

    public const string SE_LOCK_MEMORY_PRIVILEGE = "SeLockMemoryPrivilege";
    public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public UInt32 LowPart;
        public Int32 HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct TOKEN_PRIVILEGES
    {
        public UInt32 PrivilegeCount;
        public LUID Luid;
        public UInt32 Attributes;
    }

    // ...[생략]...

    private static uint TOKEN_QUERY = 0x0008;
    private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020;

    static void Main(string[] args)
    {
        // ...[생략]...

        if (SetLargePagePrivileges() == false)
        {
            return;
        }

        var ptr = VirtualAlloc(IntPtr.Zero, largePageSize,
               AllocationType.COMMIT | AllocationType.RESERVE | AllocationType.LARGE_PAGES,
               MemoryProtection.EXECUTE_READWRITE);
        if (ptr == IntPtr.Zero)
        {
            Console.WriteLine($"Error: {Marshal.GetLastWin32Error()}");
            return;
        }
    }

    // ...[생략]...
    // Some remarks on VirtualAlloc and MEM_LARGE_PAGES
    // ; https://devblogs.microsoft.com/oldnewthing/20110128-00/?p=11643
    private static bool SetLargePagePrivileges()
    {
        IntPtr hToken;
        LUID luidSeLockMemoryPrivilege;
        TOKEN_PRIVILEGES tkpPrivileges;

        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
        {
            Console.WriteLine("OpenProcessToken() failed, error = {0} . SeLockMemoryPrivilege is not available", Marshal.GetLastWin32Error());
            return false;
        }

        if (!LookupPrivilegeValue(null, SE_LOCK_MEMORY_PRIVILEGE, out luidSeLockMemoryPrivilege))
        {
            Console.WriteLine("LookupPrivilegeValue() failed, error = {0} .SeLockMemoryPrivilege is not available", Marshal.GetLastWin32Error());
            CloseHandle(hToken);
            return false;
        }

        tkpPrivileges.PrivilegeCount = 1;
        tkpPrivileges.Luid = luidSeLockMemoryPrivilege;
        tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED;

        if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
        {
            Console.WriteLine("AdjustTokenPrivileges() failed, error = {0} .SeLockMemoryPrivilege is not available", Marshal.GetLastWin32Error());
            return false;
        }

        int result = Marshal.GetLastWin32Error();
        if (result != 0)
        {
            Console.WriteLine("AdjustTokenPrivileges() failed, error = {0} .SeLockMemoryPrivilege is not available", result);
            return false;
        }

        CloseHandle(hToken);

        return true;
    }
}

// ...[생략]...

하지만 이렇게 해도 여전히 AdjustTokenPrivileges API 호출 시 GetLastError 값이 1300(ERROR_NOT_ALL_ASSIGNED - Not all privileges or groups referenced are assigned to the caller.)이 나오며 실패하게 됩니다.

왜냐하면, 애당초 사용자에게는 SeLockMemoryPrivilege 특권을 활성화조차 할 수 없는 상태이기 때문인데 이에 대해서는 이미 이전 글에서 설명했습니다.

C# - gpedit.msc의 "User Rights Assignment" 특권을 코드로 설정/해제하는 방법
; https://www.sysnet.pe.kr/2/0/13207

따라서 관리자 권한을 이용해 이전 글에 실은 예제 코드를 실행하거나, gpedit.msc를 이용해 "Local Computer Policy" / "Computer Configuration" / "Windows Settings" / "Security Settigns" / "Local Policies" / "User Rights Assignment" 패널에서 "Lock pages in memory" 특권에 (2MB 페이지 할당을 실행할 사용자의) 계정 또는 그룹을 추가해야 합니다. 또한, 반드시 변경된 사용자 특권이 적용되도록 재로그인 하는 것도 잊지 마시고.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




이에 대해 검색해 보면 다음의 Q&A 글이 나오는데요,

Enable large pages in Windows programmatically
; https://stackoverflow.com/questions/42354504/enable-large-pages-in-windows-programmatically

위의 답변에 실린 코드를 보면, 특권에 사용자 계정을 추가(LsaAddAccountRights)하고, 그 특권을 허용(AdjustTokenPrivileges)한 다음 2MB 페이지 메모리 할당(VirtualAlloc)까지 하나의 소스 코드에서 하고 있는데요, (저 당시에는 가능했는지 모르지만) 현재는 저렇게 모든 작업을 동시에 수행할 수 없습니다.

이전 글에 언급한 대로, "SeLockMemoryPrivilege" 특권에 사용자를 포함하는 LsaAddAccountRights 작업은 "관리자 권한"으로 수행한 후 반드시 재로그인을 필요로 합니다. 즉, 로그오프한 다음 다시 로그인을 해야 반영되는 것입니다.

일단, 특권에 사용자가 추가됐으면 이후 AdjustTokenPrivileges를 수행해 특권을 허용하는 작업은 "관리자 권한" 없이 가능합니다. 또한, 그렇게 특권 허용을 했으면 Large 페이지 할당도 관리자 권한 없이 가능하고.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/26/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)
13373정성태6/19/20234398오류 유형: 865. 파이썬 - pymssql 설치 관련 오류 정리
13372정성태6/15/20233110개발 환경 구성: 682. SQL Server TLS 통신을 위해 사용되는 키 길이 확인 방법
13371정성태6/15/20233132개발 환경 구성: 681. openssl - 인증서 버전(V1 / V3)
13370정성태6/14/20233295개발 환경 구성: 680. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 - TLS 1.2 지원
13369정성태6/13/20233092개발 환경 구성: 679. PyCharm(을 비롯해 JetBrains에 속한 여타) IDE에서 내부 Window들의 탭이 없어진 경우
13368정성태6/13/20233225개발 환경 구성: 678. openssl로 생성한 인증서를 SQL Server의 암호화 인증서로 설정하는 방법
13367정성태6/10/20233330오류 유형: 864. openssl로 만든 pfx 인증서를 Windows Server 2016 이하에서 등록 시 "The password you entered is incorrect" 오류 발생
13366정성태6/10/20233130.NET Framework: 2128. C# - 윈도우 시스템에서 지원하는 암호화 목록(Cipher Suites) 나열파일 다운로드1
13365정성태6/8/20232895오류 유형: 863. MODIFY FILE encountered operating system error 112(failed to retrieve text for this error. Reason: 15105)
13364정성태6/8/20233677.NET Framework: 2127. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 [1]
13363정성태6/7/20233241스크립트: 49. 파이썬 - "Transformers (신경망 언어모델 라이브러리) 강좌" - 1장 2절 코드 실행 결과
13362정성태6/1/20233164.NET Framework: 2126. C# - 서버 측의 요청 제어 (Microsoft.AspNetCore.RateLimiting)파일 다운로드1
13361정성태5/31/20233638오류 유형: 862. Facebook - ASP.NET/WebClient 사용 시 graph.facebook.com/me 호출에 대해 403 Forbidden 오류
13360정성태5/31/20233036오류 유형: 861. WSL/docker - failed to start shim: start failed: io.containerd.runc.v2: create new shim socket
13359정성태5/19/20233352오류 유형: 860. Docker Desktop - k8s 초기화 무한 반복한다면?
13358정성태5/17/20233660.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제 [1]파일 다운로드1
13357정성태5/16/20233464.NET Framework: 2124. C# - Semantic Kernel의 Planner 사용 예제파일 다운로드1
13356정성태5/15/20233769DDK: 10. Device Driver 테스트 설치 관련 오류 (Code 37, Code 31) 및 인증서 관련 정리
13355정성태5/12/20233685.NET Framework: 2123. C# - Semantic Kernel의 ChatGPT 대화 구현 [1]파일 다운로드1
13354정성태5/12/20233955.NET Framework: 2122. C# - "Use Unicode UTF-8 for worldwide language support" 설정을 한 경우, 한글 입력이 '\0' 문자로 처리
13352정성태5/12/20233569.NET Framework: 2121. C# - Semantic Kernel의 대화 문맥 유지파일 다운로드1
13351정성태5/11/20234070VS.NET IDE: 185. Visual Studio - 원격 Docker container 내에 실행 중인 응용 프로그램에 대한 디버깅 [1]
13350정성태5/11/20233320오류 유형: 859. Windows Date and Time - Unable to continue. You do not have permission to perform this task
13349정성태5/11/20233660.NET Framework: 2120. C# - Semantic Kernel의 Skill과 Function 사용 예제파일 다운로드1
13348정성태5/10/20233570.NET Framework: 2119. C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
13347정성태5/10/20233931.NET Framework: 2118. C# - Semantic Kernel의 Prompt chaining 예제파일 다운로드1
1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...