저번 포스트에서 메모리에 대해 간단한 지식만 다뤘다면 오늘은 메모리와 관련해 조금 더 심화된 내용을 다뤄보려고 한다
가상 메모리는 저번에 간단하게 다뤘는데 오늘 좀 더 깊이 다뤄보자
가상 메모리는 프로그램이 실제 물리 메모리(RAM)보다 더 많은 메모리를 사용할 수 있게 해주는 기법이다
이를 통해 프로그램이 필요한 메모리를 동적으로 할당받고, 제한된 RAM보다 더 많은 메모리를 할당받아 실행할 수 있다
가상 메모리는 프로그램을 실행시키는데 최소한의 메모리가 필요한가에 대한 접근 방식으로, 실행에 필요한 일부분만 메모리에 로드하고 나머지는 디스크에 두고 필요할 때마다 교체하면서 쓰는 방식으로 구현된다
가상 메모리는 페이지라는 고정된 크기의 블록으로 나누어 관리된다. 물리적 메모리도 같은 크기의 페이지 프레임으로 나뉜다
페이징은 페이지 단위로 가상 메모리와 물리 메모리를 연결하는 기법으로, 필요한 페이지만 물리 메모리에 로드된다
페이지가 필요한 시점에만 물리 메모리로 옮겨지는 지연 로딩 기법을 사용하여, 전체 메모리 공간을 효율적으로 사용한다
CPU에서 논리 주소를 MMU에 보내면 변환을 거쳐 물리 주소에 저장된 데이터를 가져오는데(매핑) 이 변환 과정은 Page Table
을 통해 이루어진다
가상 주소가 v = (p, d)라면
p : 가상 메모리 페이지(페이지 번호)
d : p 안에서 참조하는 위치(오프셋)
페이지 테이블에 가상 주소가 포함된 페이지 번호가 있는지 확인하고 페이지 번호가 있으면 이 페이지가 매핑된 물리 주소를 알아내고 오프셋을 더하면 실제 물리 주소가 된다
과정
위에서 물리 주소를 얻어올려면 페이지 테이블에 접근하고 물리 메모리를 확인하는 2번의 과정이 필요하다
이 과정이 CPU 입장에서는 상당한 시간이라 이를 줄이고자 등장한게 TLB이다
TLB는 일종의 페이지 정보 캐시라고 하며 최근에 CPU가 요청한 가상 주소에 대한 물리 주소 정보를 저장하고 있어 요청한 주소가 이미 TLB에 있는 경우 메모리에 접근할 필요가 없어 훨씬 빠르다
프로세스의 모든 데이터를 메모리로 적재하지 않고, 실행 중 필요한 시점에만 메모리에 로드하는 기법을 요구 페이징이라고 한다
가상 메모리 시스템에서 많이 사용되며 한 번도 접근되지 않은 페이지는 물리 메모리에 적재되지 않는다
프로그램이 요청한 페이지가 물리 메모리에 없을 때 발생한다. 페이지 폴트가 일어나면 운영체제가 해당 페이지를 디스크에서 RAM으로 불러온다
페이지 폴트가 빈번하게 발생하면 디스크 접근이 많아지므로 성능이 저하되는데 이걸 막으려면 자주 쓰일 페이지를 미리 예측해 로드해놓아야 한다(가능?)
페이지 폴트가 일어나면 요청된 페이지를 디스크에서 메모리로 가져오는데 이때 메모리 공간이 부족할 수 있다
그러면 메모리에 있는 기존 페이지 중 하나를 디스크로 내리고 새로운 페이지를 메모리로 올리는데 이것을 페이지 교체라고 한다
이때 어떤 페이지를 교체할 지를 결정하는 알고리즘이 필요하며 좋은 알고리즘은 최대한 페이지 폴트가 적게 일어나는 알고리즘을 선택해야 한다
메모리에 로드된 페이지 중 가장 먼저 들어온(오래된) 페이지를 교체한다
더 이상 참조되지 않을 것 같은 페이지를 교체한다. 가장 오래전에 참조된 페이지를 교체
참조 횟수가 가장 적은 페이지를 교체한다. 카운트 시점에 따라 두 가지 방식이 있음
최적 페이지 교체 알고리즘인데 앞으로 오랫동안 사용하지 않을 페이지를 교체하는 것임
사실 구현이 불가해서 연구 목적
개발자는 개발하면서 메모리의 할당과 해제를 반복한다. C++ 개발자라면 메모리를 직접 할당하고 해제하며 C# 개발자는 메모리를 할당하고 안 쓰이는 메모리는 GC가 알아서 메모리를 해제한다
계속되는 할당과 해제 과정에서 메모리는 점점 할당된 공간과 비어있는 공간으로 나뉘게 되며 할당 시 비어있는 공간을 계속 찾아 할당할 수 있는 크기만큼 메모리가 있다면 할당을 하게 된다
그러다가 메모리를 할당해야 할 상황에 메모리를 봤더니 메모리 전체적으로 보면 할당할 수 있는 충분한 공간이 있는데 연속적이고 할당 가능한 메모리 공간은 없을 수 있다
메모리가 다음과 같이 있을 때 [][.][][.][], 1번째와 3번째 공간에 이미 메모리가 할당되어 있다. 근데 메모리 두 칸을 할당해야 할 때 메모리에 남은 공간은 3칸인데 연속적인 2칸이 없다
이러한 현상을 메모리 단편화라고 하며 이는 프로그램의 성능을 저하시키고 메모리를 확보하기 어렵게 만든다
메모리 단편화는 두 가지로 구분이 가능하다
외부 단편화가 일반적인 단편화의 개념과 비슷하다
잦은 메모리 할당과 해제로 크기가 다양한 메모리 조각들이 메모리 전체적으로 흩어져 있다. 그래서 연속적인 큰 메모리를 할당해야 할 때 할당이 불가능하다(위의 예시와 비슷)
내부 단편화는 할당된 메모리 블록 중 실제로 사용하지 않는 공간이 생겨 메모리 낭비가 발생하는 경우다
메모리를 할당할 때 넉넉잡아 프로세스가 요구하는 메모리보다 큰 메모리를 할당했을 때 발생
메모리를 고정된 크기로 할당할 때 자주 발생한다
할당중인 메모리 블록을 한 곳으로 몰아 연속된 빈 공간을 확보하는 방법
가상 메모리에서 다루던 페이징 기법이다
메모리를 고정된 크기로 나누고 각 페이지를 독립적으로 관리한다. 프로세스가 필요로 하는 페이지만 할당하고 페이지를 가상 메모리 주소에 매핑하여 사용한다
페이징 기법을 사용하면 연속적이지 않은 공간도 활용할 수 있어 외부 단편화를 해결할 수 있지만 페이지 크기에 따라 내부 단편화가 발생할 수 있음
메모리를 같은 크기의 페이지로 관리했던 페이징 기법과 달리 세그멘테이션 기법에서는 가상 메모리를 서로 크기가 다른 논리적 단위의 세그먼트로 분할한 후 메모리를 할당하여 실제 메모리 주소로 변환한다
각 세그멘테이션은 연속적인 공간에 저장되는데 메모리에 로드될 때 빈 공간에 할당된다. 매핑을 위해 세그멘트 테이블이 필요함
프로세스가 필요한 메모리만큼 할당을 하기에 내부 단편화를 해결할 수 있지만 외부 단편화를 해결하지는 못한다
개발자가 필요한 메모리 공간의 개수, 크기를 지정하여 미리 할당받아 놓고 필요할 때마다 사용하고 반납하는 기법이다
메모리 풀 없이 할당과 해제를 반복하면 단편화가 일어날 수 있지만 미리 공간을 할당하기에 외부 단편화가 일어나지 않는다
그리고 필요한 크기만큼 할당을 해놓기 때문에 내부 단편화 또한 일어나지 않는다
하지만 메모리 단편화로 인한 메모리 낭비보다 메모리 풀을 만들어 놓고 쓰지를 않는다면 그게 더 낭비다
그러므로 메모리 할당과 해제가 잦을 때 사용해야 한다