32Bit 윈도우즈 에서는 4GB 이상에 물리 메모리가 장착되어 있을 때 사용가능한 물리 메모리가 3.25GB 정도로 표시가 됩니다.
( 단, 이 사용가능한 물리메모리는 PC 하드웨어에 따라서 약간 달라질 수 있습니다. )
다음은 32Bit 윈도우즈 에서 4GB이상에 물리 메모리가 장착되어 있고 PAE 옵션이 활성화되어 있다면,
실제 물리 메모리크기를 읽어오는 방법입니다.
MmGetPhysicalMemoryRanges 함수를 이용해서 윈도우즈에서 표시되는 실제 물리메모리 크기를 읽어오며,
그 후에 윈도우즈에서 인식하지 못 하는 물리 메모리크기를 추가로 얻어온 후 이를 합치게 됩니다.
GetPhysicalMemorySize 함수에 리턴값은 실제 물리메모리의 전체크기이며,
인자로 들어가는 uWindowsReservedSize는 윈도우즈에서 인식하지 못 한 메모리 크기입니다.
해당 소스는 커널레벨에서 작동하는 코드이며, 유저레벨에서 사용할 수 없습니다.
#define MEMORY_SCAN_START_ADDRESS ((ULONG64)4 * 1024 * 1024 * 1024)
#define MEMORY_SCAN_BLOCK_SIZE ((ULONG64)1024 * 1024 * 16)
// 이러한 상수는 적용해서는 안됩니다.
// 3.25GB인 경우도 있고 3.5GB인 경우도 있고 PC마다 다르게 맵핑이 되어 있습니다.
//#define MEMORY_3_25GB_4GB_HARDWARE_MAPPED_IO_SIZE ((ULONG64)1024 * 1024 * 768);
//#define MEMORY_HARDWARE_MAPPED_IO_START_ADDRESS ((ULONG64)3328 * 1024 * 1024);
#define MI_CONVERT_PHYSICAL_TO_PFN(Va) (((ULONG)Va << 3) >> 15)
typedef struct _PHYSICAL_MEMORY_RUN
{
PFN_NUMBER BasePage;
PFN_NUMBER PageCount;
} PHYSICAL_MEMORY_RUN, *PPHYSICAL_MEMORY_RUN;
typedef struct _PHYSICAL_MEMORY_DESCRIPTOR
{
ULONG NumberOfRuns;
PFN_NUMBER NumberOfPages; // NumberOfPages * PAGE_SIZE is physical memory size.
PHYSICAL_MEMORY_RUN Run[1]; // NumberOfRuns is the total entries.
} PHYSICAL_MEMORY_DESCRIPTOR, *PPHYSICAL_MEMORY_DESCRIPTOR;
ULONG64 GetPhysicalMemorySize(ULONG64 *uWindowsReservedSize);
BOOLEAN IsPAEEnable()
{
// CR4 레지스터를 체크하는 방법도 있지만 커널내부에서 체크를 해주는 함수가 지원됩니다.
// 추후에 64비트 포팅을 위해 어셈블리 및 기계어는 사용하지 않는게 좋습니다.
return (ExIsProcessorFeaturePresent(PF_PAE_ENABLED));
}
ULONG64 GetPhysicalMemorySize(ULONG64 *uWindowsReservedSize)
{
PPHYSICAL_MEMORY_DESCRIPTOR MmPhysicalMemoryBlock;
PPHYSICAL_MEMORY_RANGE MmPhysicalMemoryRange;
PHYSICAL_ADDRESS AboveAddress;
SIZE_T MemoryBlockSize;
PFN_NUMBER NumberOfPages;
ULONG64 uPhysicalMemorySize;
ULONG64 uPhysicalMemoryBlockCount;
ULONG NumberOfRuns;
ULONG Run;
uPhysicalMemorySize = 0;
MmPhysicalMemoryRange = MmGetPhysicalMemoryRanges();
if (MmPhysicalMemoryRange == NULL)
{
return 0;
}
NumberOfRuns = 0;
NumberOfPages = 0;
while ((MmPhysicalMemoryRange[NumberOfRuns].BaseAddress.QuadPart != 0) && (MmPhysicalMemoryRange[NumberOfRuns].NumberOfBytes.QuadPart != 0))
{
NumberOfRuns++;
NumberOfPages += (PFN_NUMBER)BYTES_TO_PAGES(MmPhysicalMemoryRange[NumberOfRuns].NumberOfBytes.QuadPart);
}
if (NumberOfRuns == 0)
{
return 0;
}
MemoryBlockSize = sizeof(ULONG) + sizeof(PFN_NUMBER) + sizeof(PHYSICAL_MEMORY_RUN) * NumberOfRuns;
MmPhysicalMemoryBlock = ExAllocatePoolWithTag(NonPagedPool, MemoryBlockSize, 0);
MmPhysicalMemoryBlock->NumberOfRuns = NumberOfRuns;
MmPhysicalMemoryBlock->NumberOfPages = NumberOfPages;
for (Run = 0; Run < NumberOfRuns; Run++)
{
MmPhysicalMemoryBlock->Run[Run].BasePage = (PFN_NUMBER)MI_CONVERT_PHYSICAL_TO_PFN(MmPhysicalMemoryRange[NumberOfRuns].BaseAddress.QuadPart);
MmPhysicalMemoryBlock->Run[Run].PageCount = (PFN_NUMBER)BYTES_TO_PAGES(MmPhysicalMemoryRange[Run].NumberOfBytes.QuadPart);
}
uPhysicalMemorySize = MmPhysicalMemoryBlock->NumberOfPages * PAGE_SIZE;
ExFreePoolWithTag(MmPhysicalMemoryBlock, 0);
DbgPrint("Windows Based Memory Size : uPhysicalMemorySize %I64u\n", uPhysicalMemorySize);
// 4GB 주소부터 메모리를 스캔합니다.
AboveAddress.QuadPart = MEMORY_SCAN_START_ADDRESS;
if (IsPAEEnable() == TRUE)
{
// 반드시 PAE가 활성화되어 있을 경우에만 접근합니다.
// 만약 PAE가 비활성화되어 있을 때 접근하게 되면 KMODE_EXCEPTION_NOT_HANDLED 블루스크린이 발생 할 수 있습니다. ( Windows 2000 계열에서 해당 블루 스크린이 발생하여 PAE를 체크하도록 변경한 것입니다. )
while (TRUE)
{
PVOID pVirtualAddress;
pVirtualAddress = MmMapIoSpace(AboveAddress, MEMORY_SCAN_BLOCK_SIZE, MmNonCached);
if (pVirtualAddress != NULL)
{
PVOID pCheckAddress;
ULONG uOldData;
// 참고...
// MmMapIoSpace 리턴받은 가상주소는 결국에는 물리 메모리주소를 가르키게 됩니다.
// 여기서 중요한 부분은 만약 물리 메모리주소가 실제 하드웨어에 6GB 메모리가 설치되어 있고 7GB주소에 접근하려고 하여도
// 에러가 발생하지 않습니다.
// 이것은 윈도우즈 운영체제에서 관리하는 것이 아니며 CPU에서 발생하는 것입니다.
// 존재하지 않은 메모리에 접근하였다면 일반적으로 읽어온 데이터는 비트가 모두 1을 가집니다.
pCheckAddress = (UCHAR *)pVirtualAddress;
uOldData = *(ULONG *)pCheckAddress;
// xor 연산을 한 후 값을 씁니다.
*(ULONG *)pCheckAddress = *(ULONG *)pCheckAddress ^ 0x55AA55AA;
if (*(ULONG *)pCheckAddress == uOldData)
{
// xor연산을 하여 값을 썼음에도 불구하고 이전 데이터와 동일하다면 이 메모리 주소는 존재하지 않는 메모리주소를 말합니다.
MmUnmapIoSpace(pVirtualAddress, MEMORY_SCAN_BLOCK_SIZE);
break;
}
else
{
// 이 메모리 영역이 존재합니다.
MmUnmapIoSpace(pVirtualAddress, MEMORY_SCAN_BLOCK_SIZE);
}
}
else
{
break;
}
AboveAddress.QuadPart += (ULONG64)((ULONG64)16 * 1024 * 1024);
}
}
if (AboveAddress.QuadPart > MEMORY_SCAN_START_ADDRESS)
{
// 4GB이후에 메모리 영역이 존재합니다.
// 적절한 계산을 통해 메모리 크기를 가져옵니다.
uPhysicalMemorySize = AboveAddress.QuadPart - MEMORY_SCAN_START_ADDRESS + uPhysicalMemorySize;
}
if (uWindowsReservedSize != NULL)
{
*uWindowsReservedSize = (AboveAddress.QuadPart - MEMORY_SCAN_START_ADDRESS);
KdPrint(("PhysicalMemorySize : %I64uMB, ReservedSize : %I64u\n", uPhysicalMemorySize / 1024 / 1024, *uWindowsReservedSize / 1024 / 1024));
}
else
{
KdPrint(("PhysicalMemorySize : %I64uMB\n", uPhysicalMemorySize / 1024 / 1024));
}
return uPhysicalMemorySize;
}