가상 메모리 컨트롤이 가지는 의미(이거 왜 하는가) Hepap 생성 왜 하는가 MMF가 제공해 주는 장점이 뭔가를 아는 게 가장 중요하다. 언제 활용하고 어떤 의미인지를 파악하는 게 중요하다.
가상 메모리 컨트롤
CPU나 개발자나 메모리를 바라보는 관점은 동일하다.
둘 다 32bit 시스템에서는 메모리가 4GB 공간이 할당된다고 바라본다.
실제 물리적 메모리는 별도로 존재하고 가상 메모리가 물리 메모리에 매핑되는 구조를 가지고 있다.
이번 시간에 얘기할 부분은 가상 메모리인데 이 가상 메모리를 컨트롤한다는 게 어떤 의미를 지니는지 이해하는 게 중요하다.
가상 메모리의 Commit, Free와 물리 메모리의 관계
가상 메모리는 페이지 단위로 나눠진다.
4GB의 가상 메모리 공간이 있다고 하면 4GB / 페이지 크기를 하면 총 페이지의 개수가 나온다.
윈도우에서는 페이지를 관리할 때 페이지에 특성 정보를 부여한다.
그 특성 정보가 뭐냐면 Commit상태 또는 Free 상태이다. 윈도우에서는 Reserve 상태도 있다.
그래서 페이지가 commit 이거나 free이거나 reserve 이 셋 중에 하나의 상태가 되도록 정의하고 있다.
위 이미지는 commit 상태와 free 상태가 어떤 차이점을 지니고 있는지 보여준다.
초기화시켜서 메모리 공간을 할당받지 않은 상태라고 하면 모든 페이지(내가 할당받지 못한 페이지)는 free 상태이다. free 상태라는 거는 할당을 하지 않았다는 거다 즉 물리 메모리에 연결이 이뤄지지 않았다는 의미다. 프로그래머 입장에서는 그 공간을 할당하지 않았으니 당연히 비워있는 메모리 공간이 된다 반면 commit은 물리적인 메모리 공간이 할당이 된 영역을 의미한다. commit은 메모리 공간 할당이 이뤄진 거다. commit이 되기 위한 함수 호출은 malloc함수이다. 하나의 페이지를 commit 상태로 둔다는 것은 하나의 페이지를 물리 메모리에 매핑시키겠다는 의미다. 매핑된 페이지를 반환하면 그 페이지가 free상태가 되면서 물리 메모리와 연결이 끊어진다.
RESERVE 상태의 필요성
메모리 공간을 할당할 때 배열을 예시로 설명해 보겠다. 배열의 크기를 1로 잡을지 1만을 잡을지 상황이다.
평상시는 100도 안 되는 메모리 사용률을 보이는데 가끔 1만이라는 메모리 용량이 요구되는 상황이 온다.
그래서 배열의 크기를 1만으로 잡는 거는 비효율적으로 보인다.
위 이미지를 예시로 들면 페이지 0 ~ 4가 commit 상태인데 페이지 0과 1만 사용되고 있고 2,3,4는 사용 안되고 있지만 commit 상태여서 물리적인 메모리에 올라가 있다. 그래서 2,3,4는 안 쓰고 있는 상황인데 해당 물리 메모리 영역을 사용하지 못해 비효율적인 상황이다. 가상 메모리에서 순차적으로 데이터를 구성할 수 있다. 배열 같은 경우가 그렇다. 배열은 메모리 공간을 순차적으로 연결해서 하나의 메모리 구조를 구성한다. 그래서 페이지 0 ~ 4가 배열로 인해 하나의 메모리 구조를 구성하게 되면 페이지 0,1은 commit으로 하고 2,3,4는 free로 뒀다가 필요하면 commit을 하는 게 불가능해진다.
현재 문제는
- 연결된 메모리 공간이 필요하다.
- 그렇다고 해서 미리 할당하면 낭비가 심해서 연결된 메모리 공간을 제공해 주면서 낭비도 막고 싶다.
그래서 위와 같은 문제를 막기 위해서 reserve 상태를 둔다.
reserve 라는건 메모리 공간을 예약한다는 의미다.
이 예약을 했다는 것은 지금 물리 메모리를 쓰고 있지 않는다라는 의미다.
위 이미지에서 0 ~ 4를 reserve를 하면 메모리 공간을 할당할 때 0 ~ 4 영역은 할당을 허용하지 않는다.
또한 할당을 허용하지 않을 뿐만 아니라 물리적인 메모리 공간의 할당도 이뤄지지 않았다.
따라서 가상 메모리에 reserve 상태를 둘 수 있기 때문에 순차적인 메모리 공간도 얻을 수 있고 물리 메모리 낭비도 막을 수 있다.
메모리 할당의 시작점과 단위 확인
가상 메모리 상에서 메모리 공간을 할당해야 한다. 기본적으로 모든 메모리 공간은 free 상태이다.
free상태에 있다가 해당 상태를 commit 혹은 reserve 상태로 바꿀 수 있다.
그렇다고 아무 영역이나 commit 하거나 reserve 할 수는 없다.
페이지 단위로 메모리 공간을 관리해서 메모리 공간을 할당할 때는 어디서부터 시작을 해라라는 기준이 있다.
그다음에 할당할 때는 최소단위는 이 정도가 된다는 기준이 있다.
메모리 공간을 할당할 때 할당 기준을 가리켜 Allocation Granularity Boundary라고 한다.
Allocation Granularity Boundary는 메모리 할당의 시작점을 이야기하는 거다. 어디서부터를 얘기하는 거다.
그럼 페이지 단위로 메모리 공간을 할당하니 페이지의 시작 주소를 가지고 할당을 요구하면 되지 않겠냐 라는 얘기가 나올 수 있지만 이렇게 돼버리면 할당되는 크기가 작아져버린다.
- 메모리 할당의 시작점
- Allocation Granularity Boundary 기준
- 페이지 크기의 몇 배수: 지나친 단편화를 막기 위해
- 할당할 메모리 크기
- 최소 1페이지 이상
- 페이지 단위로 메모리 공간을 할당하라는 거다.
VirtualAlloc & VirtualFree
malloc은 commit 상태 free는 free 상태
힙 컨트롤
아래 예시는 홍길동과 최대수가 비디오 대여점에서 비디오를 빌린 거를 예시로든 상황이다.
크기가 일정하지 않을 때 보편적으로 lsit 자료구조로 구현한다.
이 list의 특징은 메모리를 반환할 때 일일이 다 들어가서 지워야 한다는 특징이 있다.
최대수가 탈퇴하면 최대수만 지우는 게 아니라 최대수가 대여한 거를 일일이 다 지워야 한다.
그래서 에러가 발생할 확률이 높다. 높은 이유는 하나의 힙 안에 홍길동을 위한 데이터와 최대수를 위한 데이터가 같이 존재해서 그렇다. 우리가 프로세스를 만들면 운영체제가 기본적으로 제공해 주는 힙을 디폴트 힙이라 한다. 디폴트 힙은 힙의 초기 크기가 정해져 있고 그 크기는 늘어날 수 있다. 윈도우에서는 디폴트 힙 이외에 우리가 힙을 만들 수 있게끔 제공해 준다. 그것이 바로 힙 컨트롤이다. 홍길동을 위한 힙 A를 만들고 그 안에 홍길동들의 데이터로 채운다. 최대수를 위한 힙 B를 만들고 그 안에 최대수를 위한 데이터들로 채워져 있다. 최대수가 탈퇴하면 힙 B만 한방에 삭제하면 된다.
가상 메모리 컨트롤은 메모리를 효율적으로 쓰기 위해 예약이라는 특성이 있다. 동적 힙은 메모리의 효율성보다는 데이터를 분리하겠다는 측면이 강하다 즉 관리의 측면이 강하다. 4GB 메모리 공간이 있는데 데이터, 힙, 스택으로 4GB가 꽉 차는 게 아니라 일부는 데이터 일부는 스택, 일부는 힙(디폴트 힙)으로 구성이 되고 나머지는 내버려 둔다. 그렇기 때문에 내버려둔 영역에 추가적으로 힙을 생성할 수 있다. 힙이라는 메모리 공간은 런타임 시점에 결정된다. 가상 메모리 공간 할당도 런타임에 받는다. 그래서 가상 메모리를 힙으로도 볼 수 있다.
Dynamic Heap의 이점
- 메모리 단편화 해소
- 동적 힙은 메모리를 동적으로 할당하므로 프로그램이 필요한 메모리 크기에 따라 유연하게 조절할 수 있다. 따라서 프로그램이 실행 중에 필요한 만큼의 메모리를 동적으로 할당하고 해제함으로써 메모리 조각화를 최소화할 수 있다.
- 프로그램의 로컬리티가 낮아지는 문제점 발생
- 동기화 문제에서 자유
- 스레드별로 힙을 생성할 수 있다.
- 여기서 얘기하는 동기화는 int a에 동시에 접근하는 동기화 문제가 아니라 메모리 공간 할당에 대한 동기화다. 예를 들어 한 메모리 공간에 동시 할당되는 자체도 문제가 될 수 있다. 그렇기 때문에 운영체제는 하나의 힙이 가상 메모리에 할당이 될 때 다른 스레드의 해당 영역의 접근을 막는다.
- 그래서 스레드 별로 힙을 생성하면 해당 힙은 생성한 스레드만 접근이 가능하므로 동기화 문제에서 자유로워진다.
Dynamic Heap 생성 및 할당
- 힙의 생성 및 소멸
- HeapCreate: 힙의 생성
- HeapDestroy: 힙의 소멸
- 생성된 힙 내의 메모리 할당 및 해제
- HeapAlloc: 힙 내에 메모리 할당
- HeapFree: 힙 내에 메모리 반환
MMF(Memory Mapped File)
mmf란 메모리를 파일에 매핑시키겠다는 의미다.
파일의 일부 메모리 공간을 프로세스의 가상 메모리에 연결을 시킨다.
연결이 되면 프로세스의 가상 메모리에다가 데이터를 쓰면 메모리 공간과 매핑된 파일의 메모리 공간에 데이터를 대신 써주는 효과가 있다. 이를 통해 성능 이점과 프로그래밍 편리성의 이점이 있다. 만약 MMF가 없다면 파일에 있는 데이터를 메인메모리에 다 불러와서 저장한 다음에 거기에 직접 데이터를 쓰고 다시 저장을 해야 한다.
MMF 장점
매핑된 상황에서 메모리에 데이터를 write 하면 그게 그대로 파일에 저장이 된다.
프로그램이 실행하면서 메모리에 write, read를 하기 때문에 실질적인 최신 데이터는 메모리가 가지고 있다. 그래서 메모리 부분은 read 하면 최신의 데이터를 가져온다. 그래서 write를 메모리에 하는데 파일에 바로바로 반영할 필요는 없다. 즉 주기적 혹은 특별한 상황이 발생했을 때 파일에 정보를 반영해도 전혀 문제가 없다. 정리하면 빈번한 I/O가 발생하는 곳은 최근의 데이터를 유지하고 실질적으로 최신의 데이터가 저장된다라는 보장만 받을 수 있는 상황이라면 파일 반영하는 거는 주기적, 상황적으로 해도 괜찮다. 파일에 대한 데이터를 메모리가 캐시 해준다라고 이해하면 된다.
MMF 구현 과정
- 파일을 생성한다.
- 파일 연결 오브젝트를 생성하고 여기에 메모리 매핑을 시킬 파일에 대한 정보를 담아둔다. 이 정보를 가지고 파일과 메모리를 매핑시킨다.
- 생성된 파일 오브젝트를 가지고 가상 메모리에 연결을 해달라는 요청을 날리는 메서드이다.
- 반환되는 건 포인터이다. 이 포인터에 write를 하면 파일에 반영이 된다.
메모리 관리(Virtual Memory , Heap , MMF)
Reserve , Commit 그리고 Free
Reserve는 예약, Commit 은 할당, Free는 할당되지 않았음을 의미한다.
Windows 시스템에서 부여할 수 있도록 정의한 페이지의 상태를 의미하는 것이다.
페이지의 총개수는 다음과 같은 공식으로 계산할 수 있다.
가상 메모리의 크기 / 페이지 하나당 크기 = 페이지의 개수
즉 페이지 개수는 가상 메모리의 크기에 비례하며, 모든 페이지는 Reserved , Commit , 그리고 Free 이 세 가지중 하나의 상태를 지닌다.
COMMIT으로 표시된 부분은 물리 메모리에 할당이 이뤄진 부분들 그리고 물리 메모리에 할당이 이뤄지지 않은 페이지를 가리켜 FREE 상태라 표현한다.
시스템이 물리 메모리 사용의 효율성을 높이기 위해서, 아주 큰 배열을 선언하면 필요한 만큼 메모리 할당량을 조금씩 확장할 것으로 기대하는가? 그렇다면 여러분은 너무 나도 많은 것을 바라는 것이다. 생각보다 시스템은 단순하다. 여러분이 페이지를 COMMIT 상태로 만들어 버리면, 해당 페이지는 물리 메모리에 할당되어 버린다. 그 위치가 RAM이던 하드 디스크이든 간에 말이다. 이러한 상황을 고려해서 등장한 것이 RESERVE 상태이다.
FREE 상태에 있던 페이지 중에서 총 다섯 페이지를 RESERVE 상태로 변경시켰다. RESERVE 상태는 FREE와 COMMIT의 중간 상태이다. 일부 페이지를 RESERVE 상태로 둠으로써 다른 메모리 할당 함수에 의해 해당 번지가 할당되지 못하도록 선언할 수 있다. 그러나 예약만 했을 뿐 할당이 완료된 상태가 아니므로 물리 메모리에 할당되지는 않는다. 말 그래도 예약 상태인 것이다. 때문에 물리 메모리의 소비는 발생하지 않는다.
이것이 끝이 아니다. RESERVE 상태에 있는 메모리 중에서 일부만 COMMIT 상태로 변경하는 것도 가능하다. 따라서 메모리의 사용량이 늘어남에 따라서 점진적으로 COMMIT 상태의 페이지 개수를 증가시킬 수 있다. 정말 필요한 만큼의 페이지만 물리 메모리에 할당하는 것이 가능하다.
그렇다면 프로그램 코드상에서 어떻게 RESERVE 상태의 특성을 활용할 수 있을까? 이것이 이번 장을 통해서 우리가 공부해야 할 과제이다. 좀 전에 배열과 관련해서 이야기를 하였는데, 우리의 목표는 동적으로 물리 메모리의 할당량이 증가하는 배열을 만드는 것이다. 물론 배열 인덱스 연산이나 포인터 연산이 가능하도록 하나의 배열로 구성할 것이다
메모리 할당의 시작점과 단위 확인하기
페이지 단위로 메모리를 할당하기에 앞서 알아야 할 정보가 있다.
메모리를 할당하기 전에 기본적으로 생각해야 하는 것은 다음 두 가지다.
메모리 할당의 시작 주소, 할당할 메모리의 크기
가상 메모리 시스템은 페이지 단위로 관리한다.
때문에 페이지의 중간 위치에서부터 할당을 시작할 수 없으며, 페이지 크기의 배수 단위로 할당을 해야만 한다.
일단 메모리 할당의 시작 주소에 대해서 살펴보자. 페이지 크기가 4K 바이트라면 4K의 배수 값이 할당의 시작 주소가 될 수 있다. 그러나 Windows 시스템에서는 메모리가 지나치게 조각나는 것을 막기 위해서, 그리고 관리의 효율성을 이유로 하여 조금 더 넓은 범위의 값을 할당의 경계로 정의하고 있다. 메모리 할당의 시작 주소가 될 수 있는 기본 단위를 가리켜 "Allocation Granularity Boundary"라 한다.
가상 메모리에도 불구하고 OOM(OutOfMemory)이 발생하는 이유
https://for-development.tistory.com/130
스택과 힙 영역은 가상 메모리에 있는 공간일까
'운영체제 > 뇌를 자극하는 윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
뇌를 자극하는 윈도우즈 시스템 프로그래밍 19장 (2) | 2024.09.22 |
---|---|
뇌를 자극하는 윈도우즈 시스템 프로그래밍 18장 (0) | 2024.09.22 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 16장 (5) | 2024.09.22 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 15장 (0) | 2024.09.21 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 14장 (0) | 2024.09.21 |