서론
가상 메모리란 실행하고자 하는 프로그램의 일부만 메모리에 적재해, 실제 메모리보다 더 큰 프로세스를 실행할 수 있도록 만드는 메모리 관리 기법이다. 그렇다면 이러한 메모리 관리 기법이 왜 필요할까?
새로운 프로세스는 새롭게 메모리에 적재되고, 사용되지 않는 프로세스는 메모리에서 삭제된다. 즉, 메모리 정보는 자주 변경될 수 있다는 의미다. 그렇기 때문에 메모리에 적재된 모든 프로세스와 CPU가 이렇게 실시간으로 바뀌는 정보를 모두 기억하고 있기는 어렵다. 그렇다면 CPU는 어떻게 메모리에 적재된 프로세스의 주소를 인식하고 관리할까?
지금부터 알아보도록 하자.
물리 주소와 논리 주소
CPU와 프로세스는 메모리의 하드웨어 상 실제 주소인 물리 주소가 아니라 논리 주소를 이용한다. 논리 주소는 프로세스마다 부여되는 0번지부터 시작되는 주소 체계를 말한다.
CPU와 프로세스가 사용하는 주소 체계는 물리 주소가 아니라 논리 주소이기 때문에 중복되는 물리 주소의 번지 수는 존재하지 않지만, 중복되는 논리 주소의 번지 수는 얼마든지 존재할 수 있다. 하지만 논리 주소라 할지라도 실제로 정보가 저장되어 있는 하드웨어 상의 메모리와 상호작용하기 위해서는 반드시 논리 주소와 물리 주소간의 변환이 이루어져야 한다. CPU는 논리 주소로 이야기하는데, 메모리가 물리 주소로 이야기한다면 서로 원활하게 통신하기 어렵기 때문이다. 그래서 존재하는 하드웨어가 바로 메모리 관리 장치(MMU, Memory Management Unit)이다. MMU는 CPU와 메모리 사이에 위치하며, CPU가 이해하는 논리 주소를 메모리가 이해하는 물리 주소로 변환하는 역할을 한다.
스와핑
메모리에 적재된 프로세스들 중에는 현재 실행되고 있지 않은 프로세스도 있을 수 있다. 이러한 프로세스들을 임시로 스왑 영역(swap space)이라는 보조기억장치의 일부인 영역으로 쫓아내고, 프로세스를 쫓아낸 자리에 생긴 메모리 상의 빈 공간에 다른 프로세스를 적재하여 실행하는 메모리 관리 방식을 스와핑(swapping)이라고 한다. 현재 실행되지 않는 프로세스가 메모리에서 스왑 영역으로 옮겨지는 것을 스왑 아웃(swap-out), 반대로 스왑 영역에 있는 프로세스가 다시 메모리로 옮겨오는 것을 스왑 인(swap-in)이라고 한다. 스왑 아웃 되었던 프로세스가 다시 스왑 인 될 때는 스왑 아웃되기 전의 물리 주소와는 다른 주소에 적재될 수 있다.
이처럼 OS는 스와핑이라는 기본적인 메모리 관리 기법을 통해 프로세스들을 메모리 내 빈 공간에 적재한다.
모바일 시스템은 일반적으로 표준 스와핑 또는 페이지 스와핑을 지원하지 않는다. 따라서 메모리 압축은 대부분의 모바일 운영체제의 메모리 관리 전략의 핵심 부분이다.
연속 메모리 할당
프로세스에 연속적인 메모리 공간을 할당하는 방식을 연속 메모리 할당이라고 한다.
언뜻 보기에는 당연하고 보편적인 방식이라고 느낄 수 있지만, 연속 메모리 할당은 메모리를 효율적으로 사용하는 방법은 아니다. 외부 단편화라는 문제를 내포하기 때문이다.
위 그림과 같은 상황을 가정해보자. 현재 이 메모리에 새로운 프로세스가 적재될 수 있는 빈 공간은 총 50MB이다. 그러나 크기가 50MB인 프로세스를 적재하는 것은 불가능하다. 메모리에 남아있는 공간이 각각 30MB와 20MB이기 때문이다.
이처럼 프로세스들이 메모리에 연속적으로 할당되는 환경에서는 프로세스의 실행과 종료를 반복하며 사이 사이에 빈 공간이 생긴다. 프로세스 바깥에 생기는 빈 공간들은 분명 빈 공간이 맞지만 그보다 더 큰 프로세스를 적재하기 어려운 상황을 초래하고, 이는 메모리 낭비로 이어진다. 이러한 현상을 외부 단편화(external fragmentation)라고 한다.
가상 메모리
스와핑과 연속 메모리 할당은 2가지 문제를 내포한다.
- 적재와 삭제를 반복하며 프로세스들 사이에 발생하는 외부 단편화
- 물리 메모리보다 큰 프로세스를 실행할 수 없다.
프로세스를 반드시 연속적으로 메모리에 할당해야 한다면 메모리보다 큰 프로그램은 적재할 수 없다.
이러한 문제를 해결하는 운영체제의 메모리 관리 기술이 바로 가상 메모리이다. 가상 메모리(virtual memory)란 실행하고자 하는 프로그램의 일부만 메모리에 적재해, 실제 메모리보다 더 큰 프로세스를 실행할 수 있도록 만드는 메모리 관리 기법이다.
보조기억장치의 일부를 메모리처럼 사용하거나 프로세스의 일부만 메모리에 적재함으로써 메모리를 실제 크기보다 더 크게 보이게 하는 기술이라고 할 수 있다.
(가상 메모리 기법으로 생성된 논리 주소 공간은 가상 주소 공간(virtual address space)이라고도 부른다.)
가상 메모리 장점
- 프로그램이 메모리 크기에 대한 제약을 덜 받을 수 있다.
- 동시에 많은 프로그램을 실행하므로 CPU 이용률과 처리율을 높일 수 있다.
- 필요한 영역만 메모리에 로드해 스와핑 횟수를 줄여서 프로그램 실행 속도를 높일 수 있다.
가상 메모리 관리기법에는 대표적으로 페이징과 세그먼테이션이 있다.
페이징
페이징(paging)은 프로세스의 논리 주소 공간을 페이지(page)라는 일정한 단위로 나누고, 물리 주소 공간을 페이지와 동일한 크기의 프레임(frame)이라는 일정한 단위로 나눈 뒤 페이지를 프레임에 할당하는 가상 메모리 관리 기법이다. 이때, 프로세스를 구성하는 페이지는 물리 메모리 내에 불연속적으로 배치될 수 있다는 점에 유의해야 한다.
이와 같이 메모리를 할당하면 외부 단편화가 발생하지 않는다. 페이지라는 일정한 크기로 잘린 프로세스들을 메모리에 불연속적으로 할당할 수 있다면, 연속 메모리 할당처럼 프로세스 바깥에 빈 공간이 생길 수 없기 때문이다.
페이징 기법에서도 스와핑이 사용될 수 있다. 페이징을 사용하는 시스템에서는 프로세스 전체가 스왑 아웃/스왑 인되는 것이 아니라 페이지 단위로 스왑 아웃/스왑 인된다. 페이징 시스템에서의 스왑 아웃은 페이지 아웃(page out), 페이지 인(page in)이라고 부른다.
페이지 단위로 스왑 아웃/스왑 인된다는 것은 프로세스의 일부는 메모리에 적재되고, 일부는 보조기억장치에 적재되었다는 것을 의미한다. 이를 달리 말하면 프로세스를 실행하기 위해 전체 프로세스가 메모리에 적재될 필요는 없다는 것이다.
CPU 입장에서 바라본 논리 메모리의 크기가 실제 메모리보다 클 수 있으며, 페이징을 통해 물리 메모리보다 큰 크기의 프로세스 실행도 가능해진 것이다.
페이지 테이블
CPU가 프로세스를 이루는 어떤 페이지가 어떤 프레임에 적재되어 있는지를 모두 알고 있기는 어렵다. 그런데 페이지는 물리 메모리 내에 불연속적으로 배치될 수 있는데 CPU는 어떻게 다음으로 실행할 페이지의 위치를 찾을 수 있을까?
이러한 문제를 해결하기 위해 프로세스의 페이지와 실제로 적재된 프레임을 짝지어주는 정보인 페이지 테이블(page table)을 활용한다. 페이지 테이블에는 페이지 번호와 실제로 적재된 프레임 번호가 대응되어 있다. 덕분에 CPU는 페이지 테이블의 페이지 번호만 보고도 적재된 프레임을 찾을 수 있다. 프로세스마다 각자의 페이지 테이블 정보를 가지고 있으므로, CPU가 서로 다른 프로세스를 실행할 때는 각 프로세스의 페이지 테이블을 참조하여 메모리에 접근한다.
페이징 기법을 사용하면 페이지를 물리 메모리에 연속적으로 할당할 필요가 없어서 외부 단편화 문제를 해결할 수 있다. 하지만 프로세스 크기가 페이지 수로 나누어 떨어지는지는 보장하지 않으므로 내부 단편화 문제가 발생할 수 있다.
내부 단편화란?
페이지 크기(예: 4KB)보다 프로그램 데이터가 작으면 남는 공간이 발생한다. (예: 3.5KB만 쓰면 0.5KB는 낭비)
이처럼 남는 공간이 발생하는 문제를 내부 단편화라고 한다.

페이징을 사용하면 외부 단편화는 해결할 수 있지만 내부 단편화라는 문제가 발생할 수 있다.
그렇다면 여기서 궁금증이 생긴다. 내부 단편화와 외부 단편화 중, 운영체제 입장에서는 뭐가 더 문제일까?🧐
결론부터 말하자면 운영체제는 연속된 큰 공간 확보가 어려운 외부 단편화보다는, 일부 공간이 남더라도 내부 단편화를 감수하는 페이징 방식을 더 선호한다.
외부 단편화의 경우 메모리가 남아도 연속적인 공간이 없으면 프로그램을 실행할 수 없다는 점이 치명적이다. 물론 메모리 압축(compaction)을 통해 연속된 빈 공간을 만들 수 있지만 이 과정은 메모리 블록을 실제로 이동시켜야 하므로 시간적으로 비효율적이고 실행 중인 프로그램을 일시 중지하고 복사한 뒤 다시 실행하는 등의 추가적인 CPU 자원 낭비도 발생한다.
압축이 항상 가능한 것은 아니다. 재배치가 어셈블 또는 적재 시에 정적으로 행해진다면, 압축은 실행될 수 없다. 압축은 프로세스들의 재배치가 실행 시간에 동적으로 이루어지는 경우에만 가능하다. 주소가 동적으로 재배치할 수 있다면, 재배치 작업은 프로그램과 데이터를 새로운 위치로 옮기고 새 위치를 반영하기 위해 기준 레지스터만 변경하면 완료된다.
압축이 가능하더라도 그 비용을 검토해 보아야 한다. 가장 단순한 압축 알고리즘은 단순히 모든 프로세스를 한쪽 끝으로 이동시켜 모든 가용 공간이 그 반대 방향으로 모이도록 하는 방법이지만 이 방법은 비용이 매우 많이 든다.
따라서 운영체제는 외부 단편화를 없애고, 내부 단편화를 감수하는 것이 더 효율적인 선택이다.
세그먼테이션
세그먼테이션(segmentation)은 프로세스의 메모리 영역을 일정한 크기의 페이지 단위가 아닌 가변적인 크기의 세그먼트 단위로 분할하는 방식이다. 페이징 기법처럼 프로세스를 나누는 단위가 일정하지 않더라도 유의미한 논리적 단위로 분할한다.
한 세그먼드는 코드 영역(의 일부)일 수도 있고, 데이터 영역(의 일부)일 수도 있다.
이 기법은 세그먼테이션 테이블(segment table)을 사용해 세그먼트의 논리 주소를 물리 주소로 매핑한다. 세그먼트 테이블은 세그먼트 번호를 인덱스로 사용하며, 세그먼트별 시작 주소인 base와 세그먼트 길이인 limit를 저장한다.
세그먼테이션 기법은 프로세스의 메모리 영역을 논리적 단위로 나눠 저장하므로 단위별로 데이터를 보호하기 쉽다는 장점이 있다. 하지만 세그먼트의 크기가 일정하지 않기 때문에 프로세스의 할당/해제를 반복하는 과정에서 외부 단편화 문제가 발생할 수 있다.
또한, 메모리에 로드된 스택 세그먼트 영역에서 오버플로가 발생하면 다른 프로세스와 메모리 영역이 겹칠 수 있어 다른 프로세스의 세그먼트나 스택 오버플로가 발생한 세그먼트를 디스크로 스왑 아웃해야 하는 단점이 있다.
요구 페이징
요구 페이징(demand paging)은 프로세스에서 필요한 페이지만 메모리에 로드하는 방식이다.
페이지를 모두 메모리에 로드하지 않고 초기에 필요한 영역만 로드한 후 다른 영역은 요청이 올 때 메모리에 로드한다. 다음 그림과 같이 필요한 페이지를 물리 메모리에 로드하고, 필요하지 않은 페이지는 디스크에 저장한다.
프로그램을 실행하다가 물리 메모리에 필요한 페이지가 없을 때 이를 페이지 폴트(page fault)라고 한다. 페이지 폴트가 발생하면 디스크에서 필요한 페이지를 스왑 인한다. 이때 페이지에 해당하는 메모리 영역이 물리 메모리에 있는지는 페이지 테이블로 파악할 수 있다. 페이지 테이블은 해당 프레임이 존재하면 v(valid) 값을, 프레임이 존재하지 않거나 유효하지 않은 주소 값이면 i(invalid) 값을 반환.
페이지 폴트가 발생했을 때 처리 과정은 다음과 같다.
- 필요한 페이지가 물리 메모리에 있는지 없는지 페이지 테이블에서 확인한다. 페이지 폴트가 발생하면 i를 반환한다.
- i를 반환하면 OS는 참조하려는 페이지의 주소 값이 유효하지 않은지 아니면 메모리에 로드되지 않은 영역인지 판단한다.
- 필요한 페이지가 메모리에 로드되지 않은 영역이라면 디스크에서 해당 영역을 찾는다.
- 디스크에서 해당 페이지 영역을 스왑 인한다. 이때 물리 메모리에 비어 있는 프레임(free frame)이 있으면 페이지를 해당 영역에 바로 로드한다. 만약 비어 있는 프레임이 없으면 페이지 교체 알고리즘(page replace algorithm)을 호출해 기존에 로드된 페이지를 디스크로 스왑 아웃한 후 새로운 페이지를 로드한다.
- 페이지 테이블에서 새로 로드한 페이지의 값을 v로 변경한다.
- 프로세스를 다시 실행한다.
극단적인 경우에는 메모리에 페이지가 하나도 안 올라와 있는 상태에서도 프로세스를 실행시킬 수 있다. 운영체제에서 명령 포인터(instruction pointer)의 값을 프로세스의 첫 명령으로 설정하는 순간, 이 명령이 메모리에 존재하지 않는 페이지에 있으므로, 페이지 폴트를 발생시킨다. 페이지가 적재되고 나면 프로세스는 수행을 계속하는데 프로세스가 사용하는 모든 페이지가 메모리에 올라올 때까지 필요할 때마다 페이지 폴트가 발생한다. 일단 필요한 모든 페이지가 적재되고 나면 더이상 폴트가 발생하지 않는다. 이것이 순수 요구 페이징(pure demand paging)이다. 즉, 어떤 페이지가 필요해지기 전에는 결코 그 페이지를 메모리로 적재하지 않는 방법이다.
스레싱
스레싱(thrashing)은 동시에 일정 수 이상의 프로그램을 실행했을 때 오히려 CPU 이용률이 떨어지는 상황을 말한다.
즉, 메모리의 페이지 폴트율이 높은 것을 의미한다. 어떤 프로세스가 실제 실행보다 페이징에 더 많은 시간을 사용하고 있으면 스레싱이 발생했다고 한다. 메모리에 너무 많은 프로세스가 동시에 올라가게 되면 스와핑이 너무 많이 일어나서 발생한다.
페이지 폴트가 일어나면 CPU 이용률이 낮아지고, CPU 이용률이 낮아지면 운영체제는 'CPU가 한가한가?'라고 생각하며 가용성을 더 높이기 위해 더 많은 프로세스를 올리는 악순환이 반복되며 스레싱이 발생하는 것이다.
스레싱을 예방하려면 워킹 세트(working set)를 설정하는 방법이 있다. 워킹 세트는 지역성을 기반으로 자주 사용하는 페이지를 저장해 두는 것을 의미한다. 워킹 세트를 바탕으로 자주 사용하는 페이지를 물리 메모리의 프레임에 고정하면 페이지 폴트가 빈번하게 발생하는 현상을 방지할 수 있다.
틀린 부분이 있다면 댓글로 알려주세요! 감사합니다.😊
[Reference]
'Development > 운영체제' 카테고리의 다른 글
페이지 교체 알고리즘 (0) | 2025.01.16 |
---|