20장. 메모리 관리(Virtual Memory, Heap, MMF)

유니야·2023년 1월 25일
  • 가상 메모리 컨트롤이 갖는 의미
  • 힙 생성을 하는 이유
  • MMF가 제공하는 장점

✏️ 가상 메모리 컨트롤(Virtual Memory Control)

CPU와 프로그래머 관점에서의 메모리는 가상 메모리.
4GB의 가상 메모리가 있고 물리 메모리가 별도로 존재하며, 가상 메모리가 물리 메모리에 맵핑되는 형태.

💻 가상 메모리의 Commit, Free와 물리 메모리의 관계

가상 메모리는 페이지 단위로 구성됨.
4GB의 메모리 공간이 있을 경우, 페이지 개수 = 4GB / 페이지 크기

윈도우즈는 페이지(가상 메모리)를 관리할 때 특성을 부여.
COMMIT, FREE, RESERVE => 페이지는 다음 세 상태 중 한 가지 상태를 지닌다

  • FREE

    • 메모리 공간을 초기화하고 할당받지 않았을 경우, 할당받지 않은 메모리의 상태
    • 물리 메모리에 맵핑되지 않음
  • COMMIT

    • malloc 등의 함수 호출을 통해 메모리 공간이 할당된 상태
    • 물리 메모리에 맵핑됨
    • 하나의 페이지를 Commit상태로 둔다는 건, 해당 페이지를 물리 메모리와 연결시키겠다는 것.
    • 맵핑된 페이지를 free 함수를 통해 반환하면 물리 메모리와의 연결이 끊어진다.

일 년 내내 동작하는 프로그램이 있다고 가정.
이 프로그램은 보통 메모리 공간을 많이 차지하지 않지만, 특별한 경우에는 1만 메모리를 요구함.
해당 경우를 위해 처음 메모리를 할당할 때 1만 메모리를 미리 할당해둬야 하지만, 이는 매우 비효율적.

💻 RESERVE 상태의 필요성

5개의 페이지를 Commit 상태로 두었으나 실제로는 2개만 사용하고, 나머지 3개는 나중을 위해 미리 잡아둔다면 비효율적. Commit을 통해 해당 페이지는 물리 메모리와 이미 연결된 상태이기 때문에 남들도 할당받을 수 없음.
페이지 2개만 잡아두고 필요할 때 추가적으로 다른 위치에 페이지를 잡아둔다면?
메모리 공간을 순차적으로 할당해 하나의 메모리 구조로 구성하는 것은 프로그래밍 시 중요한 사항.
추후 메모리 공간을 할당한다는 건 연결된 메모리 구조가 아니기 때문에 순차적인 접근 또한 불가능. 따라서 이는 논외의 대상.

1) 연결된 메모리 공간이 필요
2) 그렇다고 미리 할당해두면 낭비가 심하기 때문에 낭비를 해결하고 싶음
이를 해결하기 위해 Reserve, 페이지의 예약 상태로 두는 개념이 등장.

  • RESERVE
    • 메모리 공간을 예약해두고 다른 누군가 해당 메모리에 할당하려할 때 할당을 허용하지 않음.
    • 물리 메모리 할당도 아직 이뤄지지 않은 상태. 필요할 때 물리 메모리와 연결 가능

💻 메모리 할당의 시작점과 단위 확인

기본적으로 모든 메모리 공간은 Free상태.
가상 메모리 할당을 할 때, 윈도우즈는 메모리는 페이지 단위로 관리하기 때문에 아무 주소부터 Commit이나 Reserve 상태로 바꿀 수 없음.
메모리 할당 시 어디서부터 할당해야하는지 기준과 최소 할당 단위가 존재.

  • 메모리 할당의 시작점
    • Allocation Granularity Boundary 기준
    • 페이지 크기의 몇 배수 => 지나친 단편화를 막기 위해
    • 각 페이지의 시작 주소를 메모리 할당 기준으로 허용한다면, 누구나 어느 위치에서든 메모리 공간을 할당할 수 있게 됨. 따라서 단편화 발생.
    • 홀수 번지마다 할당 가능 ex) A B A B => A A B B

메모리 단편화
물리 메모리에서 메모리 공간이 작은 조각으로 나뉘어 사용 가능한 메모리가 충분히 존재함에도 불구하고 사용 불가능한 상태.

  • 할당할 메모리의 크기
    • 최소 1페이지 이상
  • GetSystemInfo(&si) // si : SYSTEM_INFO 구조체의 변수
    • pageSize = si.dwPageSize // 페이지 크기
    • allocGranularity = si.dwAllocationGranularity // 메모리 할당의 시작점을 어떻게 구성할지
    • 페이지 크기가 4KB일 경우 64KB번지 기준으로 최소 4KB 할당 가능.
      그 다음은 128KB번지부터 할당 가능

💻 VirtualAlloc() & VirtualFree()

// 마치 malloc()과 같음. malloc()은 힙 메모리, 아래 함수는 가상 메모리(근데 이는 사실 힙 메모리)
// malloc()은 COMMIT/FREE 상태로만 가능하지만 아래 함수는 RESERVE 상태도 가능함.
LPVOID VirtualAlloc(
    LPVOID lpAddress,       // 할당의 시작 주소.
    SIZE_T dwSize,          // 할당의 크기
    DWORD flAllocationType, // MEM_RESERVE(거의 쓸일 없음.) or MEM_COMMIT
    DWORD flProtect         // PAGE_NOACCESS or PAGE_READWRITE
);

// 반환: 할당이 이뤄진 메모리의 시작번지
// VirtualFree()는 COMMIT 상태를 FREE 상태로 끌어내리기도 하지만,
// RESERVE 상태를 FREE 상태로 끌어내리기도 함.(잘 안씀)
// 또한 COMMIT 상태를 RESERVE상태로 끌어내리기도 함.
// 결국 아래 함수는 할당 받은 가상 메모리 공간을 반환하는데 의의가 큼.
BOOL VirtualFree(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD dwFreeType // MEM_DECOMMIT(COMMIT->RESERVE) or MEM_RELEASE
);

✏️ 힙 컨트롤(Heap Control)

가상 메모리 컨트롤의 경우, 가상 메모리 공간의 일부를 Commit, Free, Reserve 상태로 둘 수 있었음. 해당 가상 메모리를 힙으로 봐도 무방.

💻 디폴트 힙(Default Heap) & 동적 힙(Dynamic Heap)

아래는 비디오 대여 프로그램의 내부 이미지.
들어올 데이터의 크기가 미리 정해져 있지 않을 경우 보통 list 자료 구조를 사용하는데, list는 메모리를 반환할 때 일일히 들어가서 지워야 함. 즉 사용자 최대수를 지우려 한다면 최대수가 대여한 비디오 목록까지 하나하나 지워줘야 하는데, 하나의 힙 안에 두 사용자를 위한 데이터가 함께 있어서 최대수와 관련된 데이터만 선별적으로 삭제해줘야 함. 이는 에러가 발생할 확률이 높음.

  • 디폴트 힙(Default Heap)
    • 프로세스를 만들면 운영체제가 기본적으로 제공하는 힙
    • 초기 크기가 정해져 있음

윈도우즈에서는 사용자가 디폴트 힙 이외에 힙을 만들 수 있게 해주는데, 이를 힙 컨트롤이라고 한다.

  • 동적 힙(Dynamic Heap)
    • 아래 이미지는 각 사용자를 위한 힙을 만들고, 사용자의 데이터로만 힙을 채운 상태. 만약 최대수를 삭제해야 한다면 힙 B만 날리면 되기 때문에 편리함. 이는 동적 힙(Dynamic Heap) 기법.

가상 메모리 컨트롤은 예약을 통해 메모리의 낭비하지 않는 효율성에 초점이 맞춰져 있었다면, 다이나믹 힙은 데이터의 관리에 초점을 맞춘다.

  • 가상 메모리는 힙인지?

4GB의 메모리 공간이 있다면, 해당 공간이 데이터/힙/스택으로 꽉 차는게 아니라 일부는 데이터 영역, 일부는 (디폴트)힙 영역으로 구성되고 나머지는 내버려둠. 이 영역에 사용자가 추가로 힙을 생성할 수 있음.
이전에 가상 메모리 컨트롤을 할 때는 일부 가상 메모리를 할당받은 다음 할당받은 메모리의 주소를 받아 상태를 지정해서 사용했음. 이는 디폴트 힙도, 다이나믹 힙도 아님. 이를 힙으로 볼지 가상 메모리를 다른 용도로 사용한다고 볼지는 상관 없다.

그러나 힙은 Runtime 때 크기가 할당 및 결정된다는 기준이 있음. 가상 메모리 컨트롤을 할 때 가상 메모리 공간 또한 Runtime 때 할당받기 때문에 해당 가상 메모리 공간을 힙이라고 볼 수 있음.

💻 Dynamic Heap의 이점

  1. 메모리 단편화 해소
    프로그램의 로컬리티가 낮아지는 문제점을 동적 힙을 통해 관련 데이터들을 빈 틈 없이 연속적으로 배치해서 해결할 수 있음.
  2. 메모리 공간 할당 시 동기화 문제에서 자유
    쓰레드 별로 힙을 생성할 수 있음
    OS는 하나의 힙이 가상 메모리 공간에 할당/반환할 때, 다른 쓰레드의 접근을 막는다. 별도의 힙을 구성할 경우 이러한 동기화의 문제에서 자유로워지기 때문에 안정성이 증가한다.
  • Dynamic Heap 생성 및 할당
    • 힙의 생성 및 소멸
      • HeapCreate(): 힙의 생성
      • HeapDestroy(): 힙의 소멸. 힙에 저장된 모든 데이터가 한 번에 날아감.
    • 생성된 힙 내의 메모리 할당 및 해제
      • HeapAlloc(): 힙 내에 메모리 할당
      • HeapFree(): 힙 내에 메모리 반환

✏️ MMF(Memory Mapped File)

메모리를 파일에 맵핑시키는 개념.
파일의 일부 메모리 공간을 프로세스의 가상 메모리에 연결. 그리고 맵핑된 프로세스의 가상 메모리에 데이터를 쓰면 해당 위치만큼 offset된 파일의 위치에 데이터를 쓰는 효과를 얻을 수 있음.

  • MMF의 장점

    • 프로그래밍의 편리성
      파일에 저장되어 있는 데이터를 정렬해야 한다면 파일 데이터를 모두 메인 메모리로 읽어들여 저장한 뒤 정렬, 그리고 다시 파일로 저장해야 하는 불편함이 있음. MMF를 이용한다면 데이터를 읽고 쓰는 과정 없이 프로세스 가상 메모리 안에서 정렬하면 파일에도 반영이 된다.

    • 데이터를 읽고 쓸 때마다 매번 파일에 반영할 필요 없다.
      프로세스는 가상 메모리 안에서 데이터를 읽고 쓰기 때문에 최신 메모리가 가상 메모리에 있음. 주기적으로, 특별한 상황이 발생했을 때만 파일에 해당 정보를 반영해도 문제가 되지 않음. 빈번한 I/O가 발생하는 곳에서는 가장 최근의 데이터를 유지해주고 최신 데이터가 저장된다는 보장만 있다면 주기적, 상황적으로 I/O를 발생시켜도 문제가 없음.
      즉 메모리 공간이 파일에게 캐쉬 역할을 함.

  • MMF 구현 과정
  1. 파일의 생성
HANDLE hFile = CreateFile(...);
  1. 파일 연결 오브젝트 생성
 HANDLE hMapFIle = CreateFileMapping(hFile, ...);

메모리 맵핑을 위해 해당 파일의 정보를 담은 파일 연결 오브젝트를 생성.

  1. 가상 메모리에 파일 연결
TCHAR* pWrite = (TCHAR*)MapViewOfFile(hMapFile, ...);

생성된 연결 오브젝트를 인자로 전달해 가상 메모리에 연결 요청.
연결하면 포인터 pWrite가 반환되는데, 해당 위치부터 반환 타입의 크기만큼 접근 가능.

0개의 댓글