[System] 윈도우 메모리 구조 - 1

모든 프로세스는 자신만의 주소공간이 존재한다. 특정 프로세스 내에서 스레드가 수행될때 스레드는 프로세스가 소유하고있는 메모리에 대해서만 접근이 가능하다.

A프로세스의 스레드가 0x1234를 접근하고 B프로세스의 스레드가 같은주소 0x1234를 접근할때

우리는 안다. 구분되어 독립적인 공간이라는것을.

이 주소공간이 가상의 주소공간이라는 사실을 기억해야한다.

어플리케이션이 성공적으로 데이터에 접근하기 위해선 접근하고자하는 주소 공간에 물리적 저장소가 할당되어 있거나, 매핑되어 있어야한다.

프로세스 주소 공간의 분할

파티션 x86 32비트 윈도우 x86 32비트 윈도우, 3GB 유저 모드 x86 64비트 윈도우 IA-64 64비트 윈도우
NULL 포인터 할당 0x00000000 ~ 0x0000FFFF 0x00000000 ~ 0x0000FFFF 0x00000000`00000000 ~ 0x00000000`0000FFFF 0x00000000`00000000 ~ 0x00000000`0000FFFF
유저 모드 0x00010000 ~ 0x7FFEFFFF 0x00010000 ~ 0xBFFEFFFF 0x00000000`00010000 ~ 0x00000000`7FFFFFFF 0x00000000`00010000 ~ 0x000006FB`FFFFFFFF
64KB 접근 금지 0x7FFF0000 ~ 0x7FFFFFFF 0xBFFF0000 ~ 0xBFFFFFFF 0x00000000`80000000 ~ 0x00000000`8000FFFF 0x000006FC`00000000 ~ 0x000006FC`0000FFFF
커널 모드 0x80000000 ~ 0xFFFFFFFF 0xC0000000 ~ 0xFFFFFFFF 0x00000000`80000000 ~ 0xFFFFFFFF`FFFFFFFF 0x000006FC`00010000 ~ 0xFFFFFFFF`FFFFFFFF

각각의 분할 공간을 파티션이라고 한다.

WINAPI함수는 널포인터 할당 파티션에 예약조차 허용하지 않는다. 그리고 사용자가 유저영역 이외에 파티션을 접근하려고 하면 접근위반(Access Error)가 발생한다. ㄴ 이를 통해 오히려 코드상의 포인터 사용 실수에 대한 에러를 발견할수도 있겠다.

놀랍게도 가상주소의 거의 절반은 커널이 사용하기때문에, 절반이 채되지않는 주소공간을 유저영역으로 사용이 가능하다.

커널이 그 큰 용량의 절반을 무엇으로 사용하는걸까? -> 시스템은 커널코드, 디바이스 드라이버 코드, 장치 I/O 캐시 버퍼, 논페이지 풀 할당, 프로세스 페이지 테이블 등을 저장하기 위한 용도로 사용한다.

주소 공간 내의 영역

프로세스 생성시 -> 고유 주소 공간이 주어질때, 대부분의 주소 공간은 free되거나 할당되지 않은 상태이다.

사용을 위해선 VirtualAlloc함수를 호출하여 영역을 예약해야한다. ㄴ 예약을 64KB를 사용하는 기준으로, 기본적으로 64KB 할당, 그리고 그 의미는 메모리에서도 할당 단위를 기준으로 경계 상에 위치해야하는걸 의미한다.

그리고 주소 공간에 영역을 예약할때 영역의 크기는 시스템의 페이지 크기의 배수로 설정해야한다. (페이지란 메모리를 관리할때 사용하는 최소 단위) - 보통 4KB를 현재 사용

그러니깐 64KB 미만을 예약할때, 예를들어 22KB를 예약하면 일단 페이지 배수로 자동 변경되어 예약되고, -> 24KB 예약, 그리고 다음 예약시 나머지 공간 64-24KB을 비우고 다음 64KB 경계에 예약하게 될것이다.


라고 알고있었는데, 내부단편화없이 바로 다음 공간이 free이기 때문에 바로 예약이 가능하다는 정보가 있어 다음 상황을 테스트 해보았다.

/// 첫번째
48KB 예약
16KB 내부단편화되는 공간
-- 64kb 경계
48KB 예약

/// 두번째
48KB 예약
48KB 예약

연속으로 virtualalloc 48kb을 때리고 각 시작주소의 차를 얻어본다.

첫번째 케이스 - 빈공간을 건너띄어 내부 단편화가 되는상황 ㄴ 각 시작 주소값의 차이값은 64KB이 나와야함 두번째 케이스 - 바로 붙는다면 48KB가 나오겠지

LPVOID baseOne = VirtualAlloc(NULL, 1024 * 48, MEM_RESERVE, PAGE_READWRITE);
LPVOID baseTwo = VirtualAlloc(NULL, 1024 * 48, MEM_RESERVE, PAGE_READWRITE);

std::cout << (DWORD)baseTwo - (DWORD)baseOne << std::endl;
//결과
65536

결론 ㄴ 64kb 이하 영역을 예약하더라도 64kb 공간을 사용하게 된다. 항상 예약주소의 시작주소는 64KB 경계 상에 존재


실제로 사용하려면?

물리적 저장소를 영역으로 커밋하는 과정이 필요하다.

주소공간의 예약된 영역을 사용하기 위해서는 물리적 저장소를 할당해야한다. 이후 할당된 저장소와 예약된 영역을 매핑해주어야 사용이 가능할것. -> 이를 물리 저장소에 커밋이라고 한다.

커밋하기 위해서는 VirtualAlloc 함수를 한번 더 호출해주어야 한다.

물리적 저장소를 더이상 사용할 필요가 없다면 해제해야하는데 이것을 디커밋 한다고 한다. 이때에는 VirtualFree 함수를 사용한다.