글 작성자: Sowhat_93

Windows 는 프로세스가 가진 가상 메모리 영역을

관리할때 페이지라고 불리는 단위로 쪼개서 관리한다.

이 page라고 불리는 단위는 x86, x64 아키텍처에서는 4KB이며,

IA-64 아키텍처에서는 8KB이다.

 

Windows OS에서는 이 페이지라고 불리는 작다면 작고 크다면 큰 단위의 상태값이 바뀌며

메모리가 관리될 것이다. 

 

App, 그러니까 프로세스 에서 사용하는 모든 주소는 가상 메모리 주소이다.

가상 메모리 주소가 실제로 물리 메모리 주소에 매핑 되게 해야

우리는 물리 메모리에 실제로 값을 저장 시킬 수 있다.

 

어떠한 페이지가 물리 메모리와 매핑된 상태를 commit 상태라고 한다.

그니까 어떠한 페이지가 commit 되었으면, 그 페이지의 시작 주소로부터 4KB는 전부 액세스 되는거다.

 

어떠한 페이지가 commit 상태라면,

페이지의 가상 메모리 주소와 물리 메모리 주소는 매핑이 완료 되었으며,

이제 해당 해당 페이지의 주소 구간내의 가상 메모리 주소 액세스는 MMU에 의해

매핑된 물리 메모리로의 액세스로 이어진다.

 

commit 상태가 아닌 페이지 영역에 대한 접근은 access violation을 일으킬 것이며,

이는 곧 프로세스 종료이다.

아무튼, 페이지라는 단위로 놓인 가상 메모리 공간을 commit 상태로 어떻게 두느냐.

VirtualAlloc을 사용한다. 

 

허나 우리의 프로그램은 VirtualAlloc을 명시적으로 호출하지 않아도 잘 돌아간다.

이는 운영체제가 알아서 프로그램이 사용할 가상메모리 페이지를 관리해주고 있었기 때문이다.

 

현재 자신이 개발하고 있는 프로그램의 가상 메모리 페이지에 대한 commit을

신경 써가며 개발하는 개발자는 많지 않을 것이다.

 

프로그램을 실행하고, 프로세스가 생성될때, 

OS가 알아서 해당 프로세스가 사용할 디폴트로 설정된 영역만큼 알아서 commit 해주기 때문이다.

거듭 강조하지만 App에서 사용하는 모든 주소는 가상 메모리 주소이며, 사용 중이라면 commit된 페이지

구간 내의 주소값 이라는 것을 의미한다.

 

일반적으로 동적할당이라고 불리는 과정은 우리가 보통은 힙이라고 부르는

페이지들에 대해 commit 상태로 만드는 역할을 수행한다.

우리가 잘 알고있는 malloc은 어떠한 페이지에 대해 그 페이지의 상태를 commit 상태로 만드는 역할을 수행 한다.

우리는 malloc의 리턴으로 나온 포인터값, 즉 해당 가상 주소로 그 물리 메모리 공간에 접근할 수 있게 된다.

그리고 우리는 별 의심없이 그것을 사용하고, 매핑된 물리 메모리 공간에 read,write 할 수 있다.

malloc을 사용하면 nullptr를 보게 되는 경우가 있다.

commit에 실패한 경우이다.

 

malloc을 호출하면 commit 상태가 된다고 했다.

그리고 우리는 더 이상 사용하지 않을 메모리에 대해 free를 호출한다.

그리고 이런식으로 free가 호출되고 해당 페이지 구간 내에 commit 상태인 메모리가 없으면

물리 메모리 페이지에 잡힌 매핑이 완전히 풀리고, 페이지의 상태는 free가 된다.

예를 들어 페이지의 크기가 4KB이고, 4KB 짜리 변수 하나를 malloc으로 할당 받았다.

변수의 크기가 4KB이므로 새로운 페이지가 할당된 것이다.

변수에대해 malloc을 호출하면 페이지는 즉시 free상태가 될것이다.

변수가 2KB짜리였다면? 두개를 다 free시켜야 페이지가 free상태가 된다.

 

commit과 free 두가지의 상태만 가지고도 페이지 관리에는 아무런 문제가 없을듯 하다.

허나 한가지의 상태가 더 있다.

이 상태가 reserve 상태이다.

 

이 reserve 상태는, 다른 reserve를 받지 못하도록 설정하기 위해 존재한다.

메모리 관리 효율을 높이기 위해 존재한다.

당장 commit 시킬것은 아니지만, 연속적인 공간에 대한 사용을 미리 예약해

다음번 가상메모리 할당에 포함되지 않게 하는 것이다.

 

VirtualAlloc 을 호출하는 예제를 보도록하자.

	//가상 메모리 주소와 4096 byte의 물리메모리가 매핑된다. (commit) 
	//VirtualAllocd은 해당 가상 메모리 주소를 리턴한다.
	char* pVirtualPage = (char*)VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);

	//가상 메모리 주소와 4096 * 10byte의 물리메모리가 매핑된다. (commit) 
	//VirtualAlloc은 해당 가상 메모리 주소를 리턴한다.
	//허나, 당장 4096 * 10 byte, 즉 10개의 페이지를 모두 사용하지는 않을 것이고,
	//최대 저만큼 사용할 가능성이 있다면?
	char* pVirtualPage2 = (char*)VirtualAlloc(NULL, 4096 * 10, MEM_COMMIT, PAGE_READWRITE);


	//reserve를 사용해 최대로 사용하게 될 큰 메모리 크기에 대해 우선 가상 메모리 페이지들에 대한 예약만 한다.
	//이렇게 되면 가상 메모리 주소는 갖고 있으되, 물리 메모리에 매핑된 상태는 아니다.
	//물리 메모리는 모든 프로세스가 접근한다.
	//한꺼번에 많이 commit시키게 되면 다른 프로세스가 사용할 물리메모리가 한정될 수 밖에 없다.
	
	//이미 reserve된 영역은 프로세스 내에서 추가적으로 VirtualAlloc을 호출해도 반환되지 않는다.
	//따라서 연속적으로 많은 공간을 사용하게 될 가능성이 있을때 RESERVE를 사용하도록 한다.
	//실제로 물리메모리에 commit을 시킬때는 페이지단위로 commit시킨다. 
	
	//4096 * 10, 즉 10페이지의 가상 메모리 공간을 연속적으로 사용할수 있다.
	char* pVirtualPage3 = (char*)VirtualAlloc(NULL, 4096 * 10 , MEM_RESERVE, PAGE_NOACCESS);

	//첫 페이지를 COMMIT상태로 만든다. 페이지가 물리 메모리에 매핑된다.
	VirtualAlloc(pVirtualPage3, 4096, MEM_COMMIT, PAGE_READWRITE);

	//두번째 페이지를 COMMIT상태로 만든다. 페이지가 물리 메모리에 매핑된다.
	VirtualAlloc(pVirtualPage3 + 4096, 4096, MEM_COMMIT, PAGE_READWRITE);

 

reserve 를 사용할때 에는 보통 PAGE_NOACCESS 를,

commit 을 사용할때 에는 보통 PAGE_READWRITE 를 준다.

추가적인 정보는 msdn을 참고한다.

https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

 

VirtualAlloc function (memoryapi.h) - Win32 apps

Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process.

docs.microsoft.com

 

Allocation Granularity Boundary

할당의 시작점이 되는 가상메모리의 크기를 의미한다.

지나치게 적은 페이지단위로 가상메모리 영역에대해 할당하게 되면,

종래엔 큰 메모리 영역에 대한 reserve하기가 어려울 것이다.

그래서 windows os에서 정해둔 virtualalloc 에 대한 최소 할당 단위이다.

이 크기는 반드시 페이지 크기의 배수가 되어야 한다.  

 

	SYSTEM_INFO SysInfo;
	GetSystemInfo(&SysInfo);
	//가상 메모리 페이지의 크기는?
	SysInfo.dwPageSize;

	//가상 메모리 페이지 할당에 대한 최소 단위 크기 
	//반드시 페이지의 배수이다.
	SysInfo.dwAllocationGranularity;

 

사용이 끝난 가상 메모리 공간에 대한 처리는 VirtualFree로 한다.

	//commit된 첫번째 페이지를 reserve로 돌려보자.
	//commit을 원하면 다시 Virtual Alloc을 호출해서 commit상태로 만들 수 있다.
	VirtualFree(pVirtualPage3, 4096, MEM_DECOMMIT);

	//두번째 페이지를 FREE 상태로 만들어 보자.
	//MEM_RELEASE를 전달하면 가상 메모리 페이지 자체가 FREE상태가 된다.
	//그렇다고 해서 MEM_DECOMMIT을 미리 호출해둘 필요는 없다.
	//만약 물리 메모리와 매핑된 상태(COMMIT 상태)였다면, 
	//페이지가 FREE상태가 되면서 해당 물리 메모리 또한 알아서 반환된다.
	VirtualFree(pVirtualPage3 + 4096 , 4096, MEM_RELEASE);

더 자세한정보는 msdn을 참고하도록 하자.

https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree

 

VirtualFree function (memoryapi.h) - Win32 apps

Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling process.

docs.microsoft.com