CS

메모리를 관리하는 전략! 논리/물리 주소와 MMU, 페이징과 세그먼테이션까지!

망고와플 2025. 2. 15. 19:56

메모리 공간은 한정이 되어 있습니다.그렇기에 여러 프로세스를 실행하려면 메모리공간을 효율적으로 쓸 수 있어야 합니다.

CPU가 프로세스를 처리할 때 보는 주소값과 실제 물리 메모리의 주소값은 다릅니다.

이때 프로세스가 보는 메모리 영역을 논리 메모리 영역, 또는 가상 메모리 영역이라고 하고, 실제 데이터가 있는 메모리 영역을 물리 메모리 영역이라고 합니다. 

즉, 모든 프로세스는 자신만의 논리주소 공간을 가지며, 0부터 시작합니다.(하지만 실제로 메모리의 0번 주소에 있는것은 아닙니다.)

그럼 왜 논리주소를 사용할까요? 대표적으로 다음의 이유가 있습니다.

  • 보안 및 안정성
    • 자신의 논리주소만 본다 -> 다른 프로세스의 메모리에 접근불가. - 메모리보호
  • 메모리 관리 효율성
    • 가상 메모리를 통해 메모리를 동적으로 할당 가능
  • 멀티 태스킹 지원
    • 각 프로세스는 독립적인 논리주소공간을 가지기에, 동시에 여러 프로세스를 실행가능
  • 코드 재사용 및 이식성 향상
    • 같은 프로세스를 여러번 실행시, 각 실행되는 객체들이 동일한 논리주소를 사용할 수 있다.
    • 실행위치에 관계없이 코드가 동일한 논리주소에서 실행 - 바이너리를 이식하기 쉽다.

즉, 프로세서는 이러한 이유 때문에 논리주소를 통해 프로그램을 실행시키지만, 실제로는 물리주소가 사용되고 있는것이죠.

제가 처음 이부분을 공부했을땐 이런 의문이 있었습니다.

"어? 그럼 CPU는 논리주소를 통해 프로세스를 돌린다면 해당 위치의 데이터를 어떻게 찾아갈수 있는거지? 누가 어떻게 변환을 시켜주는거야?"

결론부터 말하자면, 논리-물리주소의 변환은 MMU라는 장치에서 수행합니다


그럼 MMU란 뭘까요?

위키백과에 따르면 MMU(memory Management Unit)는  CPU가 메모리에 접근하는 것을 관리하는 컴퓨터 하드웨어 부품입니다.

그럼 현대의 MMU가 어떤식으로 동작하는지 그림으로 한번 보겠습니다.

CPU가 가상 메모리 주소를 MMU에 넘겨주면 MMU는 그 주소를 받아 그에 해당하는 실제 메모리 주소로 바꿉니다.

이때 가상 메모리 주소와 실제 메모리 주소 사이의 변환을 위해 MMU는 변환 참조 버퍼(TLB)라는 고속의 보조기억장치를 참조합니다. (현재 대부분의 하드웨어는 TLB를 가지고 있습니다. 없을경우 계속 테이블을 왔다갔다 해야해서 오버헤드 문제가 있습니다.)

TLB에 원하는 값이 없을 경우, 변환 정보를 페이징 테이블 혹은 세그먼트 테이블로부터 얻어 옵니다. (이때 테이블의 동작은 아키텍쳐와 운영체제에 따라 서로 다릅니다.)


연속 메모리 할당, 그리고.. 단편화

1950~1960년대 초기 컴퓨터에는 MMU(Memory Management Unit)가 없었기 때문에, 운영체제가 직접 메모리를 관리했습니다.
이 당시에는 논리주소와 물리주소가 1:1로 매핑되었으며, 프로세스가 실행되려면 반드시 연속된 메모리 공간이 필요했습니다.

멀티프로세싱을 지원하기 위해 운영체제는 연속 메모리 할당(Contiguous Memory Allocation) 방식을 사용하였습니다.
연속 메모리 할당이란, 각 프로세스를 물리 메모리의 연속된 공간에 배치하는 방식입니다.

연속 메모리 할당 방식은 크게 고정 분할 방식(Fixed Partitioning)과 가변 분할 방식(Variable Partitioning)으로 나뉩니다.

먼저 고정 분할 방식의 경우 메모리 영역을 일정크기로 분할 한 뒤 각 영역에 프로세스를 할당하는 방식입니다. 하지만 메모리에 올릴 수 있는 프로세스의 수와 프로세스 크기가 제한된다는 단점이 있습니다. 

또한 Green, Blue 프로세스의 크기가 각각 6MB, 4MB이기 때문에 할당된 블록 내 메모리공간이 남아(2MB, 4MB), 사용되지 않는 공간이 발생하는 것내부 단편화라고 합니다.  

다음으로는 가변 분할 방식입니다.

가변 분할 방식은 할당할 프로세스의 크기에 따라 메모리 공간을 분할 하는 방식입니다.

이때 메모리를 할당하는 알고리즘으로는 최초 적합, 최적 적합, 최악 적합등이 있습니다.

하지만  그림의 최소 적합의 그림을 보면 알 수 있듯, 가변 분할 방식은  메모리가 충분히 남아 있음에도 연속된 공간이 부족해  새로운 프로세스를 실행할 수 없는 외부 단편화를 피할수 없었습니다.

이러한 외부 단편화 문제를 해결하기 위한 방법으로 메모리 압축이 있습니다.
하지만 메모리를 이동시키는 과정에서 CPU와 메모리 자원이 소모가 되고 프로세스가 많을 수록 성능이 저하 발생할수 있습니다.


 MMU의 등장과 비연속 메모리 할당(페이징, 세그먼테이션)의 등장! 

1960년대 후반~1970년대 초반부터 본격적으로 MMU가 도입되면서 "연속 메모리 할당"의 한계를 해결할 수 있는 길이 열렸습니다. 운영체제는 기존의 "연속 메모리 할당" 방식에서 "비연속 메모리 할당" 방식으로 전환할 수 있게 되었습니다.

즉, 논리주소와 물리주소를 분리할 수 있게 되었습니다.

외부단편화 해소를 위한 페이징과, 내부 단편화 해소를 위한 세그먼테이션이 있습니다.

페이징(Paging)이란?

페이징프로세스의 논리 메모리(Logical Memory)와 물리 메모리(Physical Memory)를 일정한 크기의 블록으로 나누어 관리하는 메모리 관리 기법입니다.
이때, 논리 메모리는 페이지(Page), 물리 메모리는 프레임(Frame)으로 나누며, 두 블록의 크기는 동일합니다.
페이지 테이블(Page Table)을 사용하여 논리 주소를 물리 주소로 변환하며, 권한 비트를 통해 페이지의 접근 권한과 상태 정보 등을 함께 관리합니다.

각 프로세스의 페이지 테이블은 해당 프로세스의 PCB(Process Control Block)에 저장되며, 프로세스 실행 중 참조됩니다.

페이징 기법은 논리 주소를 페이지 번호와 오프셋으로 나누고, 페이지 테이블을 이용해 물리 메모리의 프레임과 매핑합니다.
이를 통해 외부 단편화 문제를 해결할수 있지만, 내부 단편화 발생 및 페이지 테이블의 오버헤드 증가하는 단점도 있습니다.
(여기서 내부 단편화의 경우 일반적으로 몇 KB의 수준이기에 감수하기도 합니다.)

위의 사진을 보면 페이지를 2^6개만 만들 수 있나? 하는 의문이 드실수도 있다고 생각합니다.
제가 논리주소의 크기를 16비트로 예시를 들었기 떄문이지 실제로는 훨씬 더 많이 만들 수 있습니다.

세그먼테이션이란?

세그멘테이션은 프로세스의 메모리 공간을 논리적 단위인 세그먼트(Segment) 로 나누어 관리하는 메모리 할당 기법입니다.
이 기법에서는 세그먼트 테이블(Segment Table) 을 사용하여 논리 주소를 물리 주소로 변환합니다.

세그먼트 테이블은 논리 주소의 앞 비트에 세그먼트 번호를 인덱스로 사용(페이징은 위에서 6비트를 페이지 번호로 썻었죠?)하며, 각 세그먼트의 시작 주소(Base Address)크기 제한(Limit)을 저장합니다. 이를 통해, 프로세스가 접근하는 주소가 해당 세그먼트의 유효 범위 내에 있는지 확인하고, 논리 주소를 물리 주소로 변환할 수 있습니다.
즉, 세그멘테이션은 논리적 구조를 반영하여 효율적인 메모리 관리가 가능하지만, 외부 단편화 문제가 있습니다. 또한 페이징에 비해 물리주소로 변화하는 과정이 상대적으로 느리기도 합니다. 또한 세그먼트 크기 변화 시 메모리 재할당 필요해서 성능이 저하될 수도 있습니다.

현재, 대부분의 운영체제가 사용하는 세그먼테이션 - 페이징 혼합 기법

위에서 페이징 기법의 경우, 페이지 테이블의 크기가 크다는 단점이 있었고
세그먼테이션의 경우 외부 단편화 문제가 발생하였습니다.

두개를 혼합하여 이러한 단점을 보완하고자 나온 기법이 세그먼테이션 - 페이징 혼합 기법입니다.

세그먼테이션 - 페이징 혼합 기법은 이 기법은 세그먼트 테이블과 페이지 테이블을 결합하여, 중복되는 테이블 구조를 줄이고 메모리 관리의 효율성을 높일 수 있습니다.

또한, 페이징 기법처럼 일정한 크기의 페이지로 나누어 관리하기 때문에, 세그먼테이션 단독 사용 시 발생할 수 있는 외부 단편화를 줄일 수 있습니다.

위의 그림은 전체적인 세그먼테이션-페이징의 동적주소변환(Dynamic Address Translation, DAT) 방법입니다.
가상 주소(VA) 는 다음과 같은 형태로 구성됩니다.(파란색)

VA = < S (Segment Number, 세그먼트 번호) , P (Page Number, 페이지 번호) , D (Offset, 페이지 내 오프셋) >

보라색 화살표를 따라 가상 주소(VA)물리 주소(PA)로 변환되는 과정을 단계별로 살펴보겠습니다.
먼저, 가상 주소의 첫 번째 요소 S(세그먼트 번호) 를 이용하여 세그먼트 테이블을 조회하면, 해당 세그먼트가 저장된 페이지 테이블의 시작 위치를 알 수 있습니다. 이때 세그먼트 테이블의 권한 비트를 통해 메모리에 대한 권한을 제한합니다.

이후, 찾은 Base Address(페이지 테이블의 위치)에서 P(페이지 번호)에 해당하는 엔트리를 검색하여 해당 페이지의 물리적 프레임을 찾습니다. 이때, 페이지 테이블의 Valid/Invalid 비트를 확인하여 페이지가 유효하지 않다면, 운영체제(OS)는 페이지 폴트 처리 루틴(노란색)을 실행합니다.(이는 아래에서 말하겠습니다.)

최종적으로 페이지 테이블에서 찾은 페이지의 물리적 프레임 주소가상 주소의 D(Offset) 을 결합하여 PA(Physical Address) 를 계산합니다.

(가상메모리에 대해 먼저 적었어야 했는데.. 제가 너무 생각없이 글을 적었네요..ㅠ)


가상 메모리와 요구 페이징(Demand Paging)

운영체제(OS)는 사용자가 프로그램을 실행하면, 디스크(저장 장치)에 있는 프로그램 데이터를 메인 메모리(RAM) 로 로드하여 실행합니다.
하지만, 메모리(RAM)의 용량은 제한적이므로 새로운 프로세스를 실행할 공간이 부족할 수 있습니다.
그렇다면 메모리 공간이 부족하면 새로운 프로세스를 실행할 방법은 없을까요?

이러한 메모리 한계를 극복하기 위해 가상 메모리(Virtual Memory) 개념이 등장했습니다.

가상메모리와 스왑 영역

가상메모리는 프로세스의 일부만 메모리에 로드하고 나머지는 디스크(저장 장치)에 둔 상태로 프로세스를 실행하는 방식입니다.
이 방식은 비연속적 메모리 할당 기법과 결합되어, 메모리를 보다 효율적으로 활용할 수 있도록 합니다.

사용자는 프로세스 전체가 메모리에 로드 된 것처럼 보이나, 실제로는 필요한 부분만 메모리에 존재합니다. 다시 말해, 물리적 메모리보다 더 큰 메모리 공간을 사용할 수 있는 것처럼 동작하기 때문에 가상메모리라고 불립니다.

즉, 가상 메모리는 RAM의 물리적 한계를 넘어 더 많은 프로세스를 실행할 수 있도록 돕는 핵심 기술입니다.

이때 스왑 영역이란 디스크(SSD/HDD)의 일부를 마치 RAM처럼 사용하는 공간입니다.
Swap - out : 물리메모리가 부족할 때, 사용하지 않는 프로세스의 일부를 스왑 영역으로 옮겨 메모리를 확보
Swap - in : 필요하면 다시 불러옴

요구 페이징(Demand Paging)과 페이지 폴트(Page Fault)

요구 페이징이란 프로세스에 필요한 페이지만 메모리에 로드하는 방식입니다.
페이지를 모두 로드하지 않고 초기에 필요한 영역만 로드한 후 다른 영역은 요청이 올때 메모리에 로드합니다.

페이지 폴트는 프로그램을 실행하다 물리메모리에 필요한 페이지가 없을 경우를 말합니다.
페이지 폴트 발생 시, OS는 참조하려는 page의 주소값이 유효하지 않은지 메모리에 로드되지 않았는지를 판단하고, Swap-in을 통해 해당 페이지를 디스크에서 물리메모리로 올립니다.

이때 swap-in 과정에서 물리 메모리에 빈 프레임이 없을 경우 페이지 교체 알고리즘을 실행합니다. 

페이지 교체 알고리즘이란?
swap-in으로 새로운 페이지를 물리메모리에 로드하려고 할 때 물리 메모리가 꽉 차있는 상황이라면 어떤 페이지를 swap-out할 지를 정해야합니다. 이때 대상 페이지를 정하는 알고리즘을 페이지 교체 알고리즘이라고 합니다.
FIFO (First-In, First-Out), Optimal (OPT), LRU (Least Recently Used), Clock (Second-Chance) 알고리즘 등이 있습니다.


쓰레싱

가상 메모리는 다중 프로그래밍을 가능하게 하여 CPU 이용률을 높일 수 있습니다.
그러나, 실행 중인 프로그램의 개수가 지나치게 많아지면 CPU 이용률이 오히려 떨어지는 현상이 발생할 수 있습니다. 이를 쓰레싱(Thrashing)이라고 합니다.

쓰레싱은 시스템이 페이지 폴트로 인해 지나치게 많은 시간을 Swap 작업에 소비하여, 실질적인 CPU 이용률이 급격히 저하되는 현상입니다. 즉, 너무 많은 프로그램을 실행하면, 물리 메모리에 필요한 페이지를 충분히 유지할 수 없어 Swap-In과 Swap-Out이 반복적으로 발생하게 됩니다.

이러한 스레싱을 예방하기 위해 working set을 설정하는 방법이 있습니다. 지역성을 기반으로 자주 사용하는 페이지를 물리메모리에 고정해버리는 방법입니다.

즉, 가상 메모리를 효과적으로 활용하려면, 적절한 다중 프로그래밍과 워크셋 관리를 해주어야 합니다.


마무리하며

게임 프로그래밍을 하며 사실 놓치기 쉬운 부분이라고 생각해 공부하며 정리를 해보았습니다.
메모리를 관리하는 기법에 대해 깊게 파고 들어보니.. 과거 프로그래머들의 고충에 대해서도 한번 생각해보기도 했어서 좋았던 것 같습니다!

사실 적으면서 후반에 갈수록 힘이 빠졌는데.. 앞으로는 글 하나에 다 적기보단.. 조금씩 쪼개서 적는게 나은것 같습니다.

다음에도 재밌는 내용에 대해 정리해보겠습니다.!