메모리의 구성 => 하드 디스크, 메인 메모리, 캐쉬 (+ 레지스터)
보통 하드 디스크의 기능을 저장 기능에 초점을 맞춰 배우는데, 현 단원은 메모리 매니지먼트에 관한 것이기 때문에 하드 디스크를 실행 기능의 관점에서 볼 것.
(그러나 메인 메모리의 실행에 대해 설명하려면 하드 디스크의 저장 기능도 필요.)
컴퓨터 디자인 예시로 보는 메모리 계층 구조의 이해
메모리 구조 간 관계 일치
하드 디스크 입장에서는 메인 메모리가 캐쉬. 하드 디스크가 데이터를 CPU에 직접적으로 주는게 아니라 메인 메모리에 먼저 올려둠. 즉 모든 메모리는 직접적으로 CPU에 데이터를 넘겨주는게 아니라, 위층 메모리에 데이터를 블럭 단위로 올려둔다.
메모리 구성 간 역할 동일
메모리(하드 디스크, 메인 메모리, 캐쉬)의 역할은 모두 프로그램 실행을 위해 나보다 계층이 높은 메모리에게 데이터를 밀어 넣는 것.
메모리 계층 구조의 필요성

지역성(Locality)
프로그램 실행에 필요한 전체 메모리가 있고 메모리의 시작과 끝이 있다고 가정할 때, 프로그램은 그 사이의 메모리를 산발적으로 접근하지 않는다. 한 메모리에 접근했다면 그 다음에는 그 주변의 메모리를 접근하게 될 가능성이 크다. 이처럼 프로그램의 메모리 접근 흐름은 지역성을 지닌다.
세상에 존재하는 모든 프로그램은 지역성을 지닌다. 일부러 메모리의 산발적인 접근 흐름을 만들지 않는 이상 지역성은 프로그램의 근본적인 성격이기 때문에 지역성을 무시할 수 없다.
CPU가 캐쉬에 어떤 데이터를 요청할 시, 캐쉬가 해당 데이터를 가지고 있을 확률은 90%가 넘는다. 이는 지역성의 특징 중 하나. 따라서 캐쉬의 용량 또한 중요하다.
좋은 프로그램이라면 지역성이 좋아서 캐쉬를 원활히 사용할 것. 그러나 평균적으로 지역성이 좋은 프로그램과 좋지 않은 프로그램이 캐쉬를 사용하는 정도의 차이는 그렇게 크지 않다.
Temporal Locality (반복 접근)Spatial Locality (주변 접근)void BubbleSort(int srcArr[], int n)
{
int i, j, temp; // Temporal Locality
for (i = 0; i < n; ++i)
{
for (j = i + 1; j < n - 1; ++j)
{
if (srcArr[j] < srcArr[j - 1])
{
temp = srcArr[j - 1];
srcArr[j - 1] = temp; // Spatial Locality
srcArr[j] = temp;
}
}
}
}
캐쉬 힛 = 캐쉬에 요청하는 데이터가 존재
CPU가 캐쉬에 특정 데이터를 요청했을 시 해당 데이터가 캐쉬에 있을 확률이 90% 이상인 건 Spatial Locality의 특성 때문이다.
각 메모리 간 데이터 블럭의 전송 단위가 존재함.
메인 메모리와 하드 디스크 간 전송 단위를 10Mb, 메인 메모리와 L2 캐쉬 간의 전송 단위를 5Mb, L2 캐쉬와 L1 캐쉬 간의 전송 단위를 2Mb, 레지스터는 레지스터의 크기만큼 데이터를 담을 수 있으니까 32비트라고 가정.
레지스터와 L1 캐쉬는 가장 빠르게 데이터를 주고 받고, 메인 메모리와 하드 디스크는 데이터를 가장 느리게 주고 받음.
이러한 속도의 부담을 줄이기 위해 메인 메모리와 하드 디스크의 데이터 전송 단위는 크다.

... 중략 ...
int nTotal = 0;
for (size_t i = 0; i < SIZEOFARR; ++i)
{
for (size_t j = 0; j < SIZEOFARR; ++j)
{
nTotal += arr[i][j];
}
}
... 중략 ...

CPU가 하나의 프로그램을 실행하기 위해 필요한 메모리 공간을 2GB라고 가정.
메인 메모리가 가지고 있는 메모리 공간은 256MB 밖에 없음. 이 때 메인 메모리는 프로그램이 요구하는 2GB를 제공하기 위해 하드 디스크를 확장해서 사용함. 하드 디스크는 메인 메모리처럼 데이터를 저장, 삭제하고 비휘발성으로 데이터를 저장할 수도 있음. 이처럼 메인 메모리 공간이 부족할 때 하드 디스크까지 메인 메모리의 영역을 넓히는 것을 가상 메모리 기법이라고 한다.

같은 맥락으로 램 = 물리 메모리, +@ 영역 = 가상 메모리
CPU를 손님, MMU(Memory management unit)을 종업원으로 음식점 주인이라고 가정.
프로그래머 뿐만 아니라 CPU 관점에서도 가상 메모리는 존재한다. 따라서 CPU는 자신이 필요한 메모리가 충분히 있다고 생각하고 동작.
따라서 사용자가 2GB-1번지 메모리를 요구할 경우 CPU도 똑같이 2GB-1번지 메모리를 요구. MMU는 그 메모리 공간에 실제로 데이터가 있는 것처럼 2GB-1번지에서 데이터를 가져다 주는 중개 역할을 함.
메인 메모리가 총 16KB이라고 가정할 경우
상황 1) CPU는 더 큰 메모리 공간이 있다고 생각하고 1KB번지부터 20바이트 메모리 할당을 요청.
MMU는 블럭 단위로 램을 할당하기 때문에 들어온 블럭을 0 ~ 4KB번지에 할당.
해당 블럭은 더 이상 다른 용도로 사용 불가능.
상황 2) CPU가 36KB번지부터 20바이트 메모리 할당을 요청. 메인 메모리는 순차적으로 메모리를 사용하는게 아니라, 메모리 블럭의 용도를 순간마다 결정지어버리기 때문에 두번째 블럭을 36KB ~ 40KB로 할당.
해당 블럭은 더 이상 다른 용도로 사용 불가능.
가상 메모리 관점에서 보면 두번째 블럭은 36KB ~ 40KB. 그러나 실제 물리적인 주소 상으로는 4KB ~ 8KB.
이 때 CPU가 36KB번지부터 20바이트 메모리 할당을 요청을 한 뒤 36KB번지에 있는 데이터를 요청한다면, MMU는 물리 메모리 주소 4KB에서 데이터를 가져와야 함.
즉, MMU의 역할을 다음과 같다. CPU가 가상 메모리 주소에 있는 데이터를 요구한다면 MMU는 해당 주소를 물리 메모리 주소로 바꿔서 데이터를 가져온 뒤, 이를 CPU에 건네준다. MMU는 제한된 메모리 공간을 적절히 활용하기 위해 메인 메모리와 CPU 사이에서 컨트롤하는 역할.

소프트웨어적으로 메모리의 블럭 단위 이동 => 페이지(Page)
물리 메모리 관점에서 메모리 블럭 => 페이지 프레임(Page Frame)
페이지 단위로 데이터를 할당 및 해제하기 때문에 페이지와 페이지 프레임의 크기는 일치한다.

해결법 => 하드 디스크와 램의 관계를 캐쉬 관계로 개선.
프로그램 구현 시 필요한 메모리는 전부 하드 디스크에 있고, Temporal Locality와 Spatial Locality에 의해 필요한 메모리를 블럭 단위로 램에 가져다두고 실행하는 구조. 이는 가상 메모리의 개념.

램이 꽉 찬 상태에서 CPU가 4KB ~ 8KB 메모리 할당을 요구하는 상황.
사용한지 오래 된 메모리 or 사용 빈도가 낮은 메모리 블럭을 하드 디스크에 STORE 한 뒤, 해당 공간에 새롭게 들어온 4KB ~ 8KB 메모리를 할당.
(사용한지 오래 된 메모리의 경우 지역성의 특성에 의해 다시 사용될 확률이 낮음)

이 때 CPU가 다시 8KB ~ 12KB에 할당한 메모리를 쓰겠다고 요청하는 상황.
먼저 사용한지 오래 된 메모리 or 사용 빈도가 낮은 메모리 블럭을 찾아서 하드 디스크로 STORE 한 뒤 해당 자리에 8KB ~ 12KB를 LOAD.
실행 중인 프로그램에 할당된 메모리 공간은 2GB로 크기가 크다. 이 경우 해당 프로그램은 하드 디스크에 파일로 저장됨. (하드 디스크는 파일 시스템 기반으로 데이터를 저장.)
스왑 파일: 프로세스의 가상 메모리 공간 확장을 위해 생성한 파일.
요구하는 데이터를 스왑 파일에서 찾아서 램에 올려두고, 불필요할 시 스왑 파일에 다시 저장.
이처럼 하드 디스크는 저장의 기능만 하는게 아니기 때문에 램만큼 중요도가 높고, 하드 디스크의 용량 또한 중요하다.
