최근 수정 시각 : 2024-12-02 06:05:17

가상 메모리

zRAM에서 넘어옴
Virtual Memory

1. 개요2. 기능 및 특징3. 기술적 설명
3.1. MMU (Memory Management Unit)3.2. 페이지 (Page)3.3. 페이지 폴트 (Page Fault)
3.3.1. 페이지 교체 알고리즘
4. 기능
4.1. 디스크 스왑4.2. 메모리 압축4.3. 또다른 방법
5. 기타6. 참조

1. 개요

가상 메모리란 프로그램이 혼자 메모리를 사용하는 것처럼 메모리를 가상화한 것을 말한다.

멀티태스킹이 없던 옛날에는 항상 하나의 프로그램이 모든 메모리를 직접 사용한다는 전제하에 만들었다. 이후 멀티태스킹이 도입될 때 문제가 되던 것 중에 하나가 각자의 프로그램이 동시에 작동한다고 했을때 메모리를 어떻게 분배할지에 대한 것이 있었다.[1]

일반적으로 프로그램이 메모리에 데이터를 읽거나 쓸 때 실행 파일에 저장된 특정 위치에 특정 데이터를 저장한다. 이때 만약 같은 프로그램을 2개 실행한다고 하면 두 프로그램이 메모리의 같은 위치에 데이터를 읽고 쓰게 되고, 두 프로그램이 서로 충돌해 오동작하게 된다.[2]

이를 해결하기 위한 여러 방법들 중 하나가 각 프로그램이 별도의 메모리를 혼자 사용하는 것처럼 가상화하는 것이다.

2. 기능 및 특징

가상 메모리의 주요한 기능 및 특징은 다음 몇 가지로 요약할 수 있다.
  • 보안성 및 안정성 : 프로그램은 메모리에 직접 접근하는 것이 아닌 간접 접근하며 기본적으로 프로그램간 메모리가 분리되며 필요한 경우에만 특정 메모리를 서로 공유해 메모리 접근에 대한 오류를 줄여 보안성 및 안정성이 증가한다.
  • 개발 용이성 : 프로그램은 각자 고유한 메모리를 사용하며 보통 어느정도 큰 특정 용량[3]으로 고정되어 있기에 실제메모리 용량과는 무관하게 프로그램을 만들수 있다. [4]
  • 메모리 관리 : 운영 체제는 프로그램이 추상화된 가상 메모리에 대해 별로 사용되지 않는다고 판단되는 메모리의 일부분을 압축하거나 스왑하여 실제 메모리의 공간을 확보하는 등의 관리를 할 수 있다.

3. 기술적 설명

3.1. MMU (Memory Management Unit)

현대의 CPU들은 모두 메모리 관리 유닛(MMU, Memory Management Unit)이라는 것을 내장하고 있다. 이 MMU라는 용어는 반드시 가상 메모리를 구현하는 유닛만을 얘기하는 것은 아니고, 뱅크 스위칭과 같은 방식으로 처리하는 경우에도 MMU라고 부를 수 있다. 이런 목적으로 별도의 칩이 있어서 CPU와 메모리 사이에 회로 연결로 구현되는 경우도 있다. 물론 현재는 CPU에 포함되어 있다.

여담으로 메모리 보호 유닛 (MPU, Memory Protection Unit)라는 MMU보다 한 단계 낮은 것도 존재한다. 주로 피처폰에 사용되는 CPU에 MMU 대신 내장된다.

3.2. 페이지 (Page)

가상 메모리의 주소 공간은 페이지(Page)라는 것으로 일정한 크기로 분할되어 있다. 페이지의 크기는 하드웨어에 의해 결정이 되며 운영체제는 이러한 페이지들을 페이지 테이블 안에 집어 넣어서 관리하고 있다. 프로세스마다 자신만의 페이지 테이블을 가지고 있다. (유저 메모리) 물론 운영체제도 역시 자신만의 페이지 테이블을 가지고 있다. (커널 메모리) 페이지 테이블은 운영체제에서 관리한다.

프로세스는 각자 독립된 가상 주소를 가진다. 예를 들어 A 프로세스는 0x1000라는 주소를 가질 수도 있고 동시에 B 프로세스는 0x1000라는 주소를 가질수도 있다. 만약 두 프로세스가 각자 0x1000라는 주소에 접근할 경우 CPU는 프로세스 (컨텍스트)마다 가지는 페이지 테이블을 참조한다.

페이지 테이블은 가상 주소와 물리 메모리의 실제 주소를 연결시켜주는 테이블이다. 가령 A 프로세스의 페이지 테이블에서 0x1000 주소에 대응되는 실제 주소는 0x30000 으로 나오고 B 프로세스의 페이지 테이블에서는 0x1000 주소에 대응되는 실제 주소가 0x40000인 것이다. 이렇게 되면 두 프로세스는 동일한 메모리 주소를 참조했지만 실제로는 MMU가 프로세스의 페이지 테이블을 참조해서 실제 물리 메모리 주소를 얻고 그 위치를 참조해서 가져오거나 쓰기를 한 것이다. 따라서 두 프로세스의 주소 공간이 겹치지 않고 독립적으로 실행될 수 있으며 프로그래머는 다른 프로세스의 주소 공간을 생각할 필요가 없어진다.

가상 메모리 공간은 비페이징 공간(Nonpaged Space)과 페이징 공간(Paged Space)으로 나뉘어져 있다. 비페이징 공간은 항상 물리 메모리에 존재하는 공간으로 페이징 파일로 옮겨지지 않는다. 반면 페이징 공간은 운영체제가 필요하지 않을 경우 물리 메모리에서 페이징 파일로 옮길 수도 있는 공간이다. 따라서 비페이징 공간은 페이지 폴트가 절대로 일어나지 않는다.

3.3. 페이지 폴트 (Page Fault)

물리 메모리의 모든 내용은 계속 저장해놓지 않는다. 운영체제는 사용되지 않는 페이지를 디스크에 있는 페이징 파일로 옮기게 된다. [5]

페이지 테이블에는 유효 비트라는게 있는데 이를 통해 페이지가 실제 물리 메모리에 존재하는지 알 수 있다. 프로세스가 가상 주소에 접근하게 되어 MMU가 페이지 테이블을 참조한 뒤 페이지를 확인해보는데 유효하지 않을 경우 페이지 폴트 (Page Fault)라는 트립을 발생시킨다. 페이지 폴트을 감지한 운영체제는 CPU의 동작을 잠시 중단시킨 뒤 해당 페이지를 페이징 파일에서 가져와서 물리 메모리의 비어있는 공간에 적재시키고 페이지 테이블을 최신화시킨 뒤 CPU의 동작을 재개시킨다.

따라서 사용되지 않는 페이지를 페이징 파일로 옮기는 것과 물리 메모리에 없는 페이지를 페이징 파일에서 가져와서 물리 메모리에 적재하는 일은 운영체제가 자동으로 수행한다. 다만 페이지 폴트는 그 과정상 디스크에 접근하기 때문에 파일 입출력이 발생하므로 성능이 하락된다.

페이지 폴트 발생 시 운영체제의 페이지 폴트 핸들러로 이동하므로 한 프로세스를 다른 프로세스로 전환하지 않는 IRQ, 즉 디스패처 레벨 이상에서는 페이지 폴트가 일어나서는 안된다. 페이지 폴트 핸들러가 실행될 수 없기에 곧바로 커널 패닉 직행이다.

따라서 커널에 붙여서 동작하는 드라이버의 경우 비페이징된 공간에서 할당된다. 물론 개발자가 일부를 페이징화할 수 있도록 설정할 수 있다.

3.3.1. 페이지 교체 알고리즘

페이지 폴트는 그 특성상 발생 빈도가 높을수록 성능이 저하되므로 운영체제의 사용 환경에 맞추어 페이지 교체 알고리즘들이 고안되었으며, 어떤 페이지를 우선적으로 처리하느냐에 따라 FIFO, LRU, LFU, NUR, OPT로 나누어진다.
  • FIFO(First In First Out) - 페이지 중에서 가장 먼저 올라온 페이지를 우선적으로 교체한다.
  • LRU(Least Recently Used Algorithm) - 페이지 중에서 가장 오래전에 참조된 페이지를 우선적으로 교체한다.
  • LFU(Least Frequently Used Algorithm) - 페이지 중에서 참조 횟수가 가장 낮은 페이지를 우선적으로 교체한다.
  • NUR(Not Used Recently) - 하드웨어 차원에서 페이지 교체를 수행하여 소프트웨어 차원의 페이지 교체로 발생하는 오버헤드를 줄인다.
  • OPT(Optimal) - 페이지 중에서 앞으로 사용하지 않을(또는 오랫동안 사용하지 않을) 페이지를 교체한다. 페이지 교체 알고리즘 중에서 페이지 폴트가 가장 낮은 편이지만 예측할 수 있는 방법이 아예 없다보니 구현하는게 현실적으로 거의 불가능하여 연구 목적으로만 사용된다.
참고로 Microsoft Windows에서는 멀티프로세서 환경에서 FIFO의 변형 알고리즘을 사용한다고 한다. #

4. 기능

4.1. 디스크 스왑

엄밀히 말하면 가상 메모리 기술과 디스크 스왑은 직접적인 연관이 없다. 하지만 역사적으로 가상 메모리 기술이 등장한 이유는 상술한 관리 편의성 등의 이유가 아니라 당시 메모리가 너무 비쌌기 때문에 보조 기억 장치를 메모리로 쓰려는 이유 중 하나였다. 메모리의 가격이 싸진 현재는 디스크 스왑을 할 필요가 없을 정도로 넉넉한 메모리를 구비하는 것도 불가능한 일이 아니지만, 이러한 역사적인 맥락으로 인해 가상 메모리에 대해 논할 때는 디스크 스왑을 같이 이야기하지 않을 수 없다.

가상 메모리 기술은 실제로 존재하지 않는 메모리 영역을 할당하는 것이 가능하지만, 정말 메모리가 부족하다면 페이지 폴트(Page Fault)가 발생해 애플리케이션이 죽든 시스템이 죽든[6] 해 버린다. 이런 문제를 해결하기 위해서 나온 것이 '디스크 스왑'으로, 이는 메모리의 일부분을 디스크에 저장해 두었다가 그 영역을 읽고 쓰고자 할 때 디스크에서 불러와서 메모리에 적재하는 방식이다. 주메모리(램, 캐시 메모리)의 가격이 워낙 비싸고 보조 저장장치(하드디스크, SSD)는 상대적으로 훨씬 싸기 때문에 등장한 방법.

이러한 디스크 스와핑을 사용하는 경우 전체적인 시스템 성능이 다소 저하된다. 보조메모리가 주메모리보다 느려서 병목현상이 발생하기 때문.[7] 설령 주메모리와 보조메모리의 속력이 같다고 해도 메모리 리스트에서 계속해서 대조해 실제 메모리 위치로 맵핑하기 때문에, 맵핑에 걸리는 시간이 전체적인 오버헤드를 점유해 태생적으로 주메모리보다 속도가 느릴 수 밖에 없다. 이런 문제를 조금이나마 극복하기 위해서, 메모리 스왑을 할때 메모리를 압축해 용량을 감소시키는 ZRAM 같은 기술이 사용된다.대신 필요한 경우 소프트웨어를 유연하게 사용(…이라기보단 억지로라도 구동 가능.)할 수 있다는 것이 장점일 것이다.

SSD가 일반 사용자에게 보급되지 않았던 시기에는 스와핑에 쓸 수 있는 보조기억장치가 HDD밖에 없었으므로 자연스럽게 '디스크 스왑'이라는 용어가 정착했지만, 디스크 형태가 아닌 SSD나 USB 메모리 등의 다른 저장 매체도 얼마든지 스왑에 사용할 수 있다.

주메모리(DDR 1 ~ 3)와 보조메모리( HDD, eMMC, USB 메모리)의 속도 차이가 심하게 나던 시절에는 스왑에 사용된 보조 메모리가 병목 현상을 일으켜 시스템 성능을 심각하게 저하시키는 경우가 많았기 때문에 가상 메모리나 스왑 기능을 아예 끄는 것이 추천되기도 했다. 그러나 현대의 보조메모리는 속도가 비약적으로 발전해( NVMe, UFS) 병목 현상을 일으킬 우려가 적으므로 현재는 대부분의 시스템이 스와핑 기능을 적극 활용하고 있다.

4.2. 메모리 압축

zRAM과 zswap은 Linux에서 제공하는 스왑 기능이며, 램 공간을 압축해서 응용하는 기술이다.

zRAM은 램 영역을 압축해서, 디스크처럼 사용할 수 있는 기능이며 여기에 파일시스템이나 swap등을 적용해서 사용할 수 있다. 즉, (이렇게 쓸 일은 없겠으나) 디스크를 잠시 램에 올려둔다거나, 반대로 램에 올라간 데이터 일부를 압축해서 잠시 ROM에 넣어뒀다가 이후에 끌어다 쓰는 등으로 활용할 수 있다.

zswap은 여전히 기존의 디스크 스왑을 필요로 하지만, 중간에 압축된 램을 이용해 버퍼를 제공하므로, 디스크의 입출력을 줄이는 용도로 사용된다.

zRAM의 경우에는 보조 기억 장치를 사용하지 않기 때문에, 디스크 스왑에 비해 빠르게 동작하고, 플래시 메모리의 수명에 크게 영향을 끼치지 않는다.

하지만, 메모리를 과다하게 사용할 때에는 압축에 따른 오버헤드가 발생하므로 CPU 사용률이 증가하며, 스마트폰과 같은 휴대용 기기에서는 배터리 사용량도 증가한다는 단점이 있다.

리눅스 기반인 안드로이드도 이런 스와핑 기능을 제공한다. 메모리가 부족한 저가형 기기를 위해 Android 4.4부터 zRAM이 적용됐으나[8], 안드로이드 6.0부터는 넥서스 5X 넥서스 6P와 같은 플래그십 기기에 적용[9]되기 시작됐고, 중국 스마트폰 제조사들도 RAM 용량 문제를 해결하기 위해 자사 스마트폰에 가상 메모리 기술을 도입하거나 도입 계획하면서 스마트폰 업계의 트렌드로 자리잡고 있다. # 삼성전자 삼성 갤럭시에서 2021년부터 디바이스 케어의 RAM 메뉴에서 RAM Plus라는 이름으로 해당 기능이 제공되고 있다.

마이크로소프트 Windows 10 1511 버전 부터 메모리 압축 기술을 적용하고 있으며 적용 초기에는 메모리 전체를 압축하려고 시도하며 시스템에 과부하가 걸리는 등의 문제가 발생하기도 했으나 이후 상한선을 제한한 것으로 추정, 해결되었다.
한편 "메모리상의 데이터를 압축/해제 하는 과정에서 CPU 성능을 잡아먹는다"는 이유로 본 기능을 끄는 사례가 종종 있는데 앞에 언급된 초창기 문제로 인해 안좋은 인식이 박힌 탓으로 보인다.
현재는 윈도우가 스스로 판단하기에 메모리 용량이 충분하면 알아서 압축 기능을 사용하지 않기 때문에 기능을 끄기보다 메모리 증설을 우선으로 해보는 것을 권장한다.
설령 메모리가 부족하더라도 바로 디스크 스왑을 사용하는 것 보다는 중간에 메모리 압축 기능이 완충역할을 해주는 편이 작업 속도나 보조저장장치 수명 등에도 더 도움될 것이다.

애플 OS X Mavericks (10.9) iOS 7 부터 적용 중인 것으로 알려져 있다.[10] 이에 더해 iPadOS 16부터는 M1 이후 아이패드 한정으로 직접 설정할 수 있게 됐다.

4.3. 또다른 방법

디스크 스왑은 DRAM의 일부분을 보조 기억 장치에 저장하고, DRAM의 해당 영역을 사용하는 것이다.

그런데, SSD의 성능이 향상되고, 옵테인 메모리같은 고성능 장치가 나오자, 아예 SSD의 일부 영역에 메모리 주소를 부여하고 그냥 메모리처럼 쓰는 방식도 사용되고 있다. 스왑에 대한 오버헤드가 감소한다는 장점은 있으나, DRAM에 비해서는 여전히 느리기 때문에 성능이 크게 향상되는 것은 아니다.

5. 기타

가상 메모리 기법은 디스크를 메모리로 사용하는 디스크 페이징(스왑)과는 직접적으로 연관이 없다. 여기서 가상(Virtual)의 의미는 디스크를 가상의 RAM 처럼 쓴다는 의미가 아니라, 각 프로세스마다 실제 물리 메모리가 아닌 가상의 주소 공간을 보이게 한다는 의미이다. 사실 넓게 보면 메모리의 내용을 디스크에 임시로 저장하는 개념 자체는 별로 특이한 것도 아니고 이론적으로는 가상 메모리를 사용하지 않는 시스템에서도 구현할 수 있다.[11] 이렇게 용어가 혼동되어 쓰이는 이유는 과거 Windows 에서 이러한 디스크 스왑 설정을 "가상 메모리" 라고 번역해 놓은 것을 아직까지 그대로 사용하고 있기 때문이다.

램의 크기가 32~128GB 정도로 충분히 크다고 판단해도 가상 메모리를 사용할 필요가 없는 것이 아니므로, 가상 메모리 기능을 비활성화하는 것은 좋지 않다. 특히 20년 이상된 오래된 최적화 팁이랍시고 가상 메모리를 비활성화하는 방법이 많이 소개됐는데 이는 과거 CPU와 램, 하드 디스크 사이의 읽기 쓰기 속력 차이가 워낙 많이 나서 병목 현상이 나고 SSD는 용량이 너무 작았던 시절에야 살짝 통하던 방법이라 현대 컴퓨터와는 맞지 않고 오히려 오류가 더 증가하는 나쁜 방법이다. 최근에는 SSD의 발달로 CPU와 주기억 장치(램), 보조 기억 장치( NVMe 이상의 SSD UFS)간의 병목 현상이 완화되어 오히려 보조 기억 장치 활용을 늘리는 추세라서 해당 최적화를 함부로 따라해서는 안된다.

Linux와 같은 UNIX-like 운영 체제의 경우 스왑을 해제해도 메모리가 실제로 모두 사용되어야 메모리 부족이 뜨는 것에 비해 Windows의 경우 스왑이 활성화 되어 있는 상태에선 다른 운영 체제들과 같이 malloc()등으로 메모리 할당을 요청했을 때 가상 메모리 공간에서 커밋이라는 이름으로 할당되게 되고 이 공간에 실제로 값이 쓰여지기 시작 할 때 실제 메모리 사용률이 올라가기 시작하는것에 비해 해당 옵션을 끄는 경우 사용하지 않고 단순 할당 요청만 해도 운영 체제가 프로세스를 위해 할당 받은만큼 점유해 버린다.

이 때문에 4GB 메모리가 장착된 시스템에서 A, B 프로그램이 각각 4GB씩 할당받고 실제로는 1GB만 사용하는 상황일 때 스왑이 활성화 되어 있으면 Windows는 2GB 메모리를 사용 중이라고 보고하게 되고 (8GB 커밋) 동작에 아무 문제가 없지만 스왑을 해제하게 되면 A 프로그램이 4GB를 할당받으면 운영 체제가 모든 메모리를 사용중인것으로 리포트하게 되며 B 프로그램은 메모리를 할당받는 것이 불가능해 메모리 부족 오류가 뜨게 된다. (malloc() returns NULL)

자세한 내용은 최적화 프로그램 문서를 참고.

6. 참조



[1] 1990년대 초반까지의 도스 시절에는 한번에 하나의 프로그램만 실행되는 것이 일반적이었고, 간혹 램상주(TSR, Terminate and Stay Resident) 기법으로 장치 드라이버나 특수 프로그램들이 동시에 돌아가는 정도였기 때문에, 메모리를 분할하는 세그멘테이션(segmentation)만으로도 충분했지만, 동시에 돌아가는 프로그램 개수가 많아질 수록 가용한 메모리 크기가 N등분되어 작아지므로 세그멘테이션은 근본적인 해결책이 되지 못한다. 아무튼 가상 메모리가 기본 옵션이 된 현재에도 이 시절의 흔적으로 세그멘테이션은 가상 메모리 개념과 독립적으로 동시에 존재하는 개념이 되었고, 최소한으로 쓰인다. 선형적인 물리 메모리를 가상화하여 비선형적(4KB 페이지 단위로 서로 다른 물리 메모리에 위치하고, 또 메모리 할당/보호 상태에 따라 중간에 사용 불가능한 공간들이 있음)인 가상 메모리가 만들어지고, 이 가상 메모리 공간 위에서 세그멘테이션이 동작한다. [2] 예를 들면 메모장을 두 개 키면, 기본적으로 충돌 나서 실행 중에 멈추거나 오류가 날 것이고, 만약 실행에 문제가 없다고 해도 한 메모장에 글자를 적으면 다른 메모장에도 글자가 표시된다거나 해서 동시에 사용하는 의미가 없어진다. [3] 32비트 윈도우 기준 프로그램별로 4GB의 가상 메모리가 할당되며, 보통 이중 2GB를 프로그램이 사용 가능, 나머지는 다른 용도로 고정되어 있다. 64비트 윈도우 기준 32비트 프로그램은 4GB의 가상 메모리가 할당 되며, 프로그램이 사용 가능한 크기가 32비트 윈도우때보다 더 크다. 64비트 프로그램의 경우 128TB의 가상 메모리가 할당된다. [4] 당연히 실제 메모리가 부족하다면 시스템이 불안정 해지거나 느려지며, 실행이 안되는 경우도 있다. [5] Windows에서는 'pagefile.sys'라는 이름의 페이지 파일을 사용한다. [6] 코드명 0x00000050 [7] 시중에서 흔히 구할 수 있는 DDR4 RAM의 랜덤 액세스 속도는 2022년 기준 최고의 성능을 가진 PCIe 4.0 NVMe 규격 SSD보다 무려 20~30배 이상 빠르다. 그나마 인텔의 옵테인 메모리가 DRAM과의 랜덤 액세스 속도 격차가 3~5배 정도로 적은 편이지만, 이 쪽은 보급에 실패해 일반인이 사용할 일이 거의 없는 물건이다. [8] 출처: # [9] 출처: Nexus 5X, Nexus 6P [10] WWDC 2018 iOS Memory Deep Dive 발표 영상: # [11] 애초에 개발자가 명시적으로 구현한다면 어느 시스템이든 안될 건 없으나(이건 데이터 백업이나 다름 없다) 당연히 그런 경우는 포함하지 않으며 응용 프로그램 개발자가 따로 고려하지 않아도 묵시적으로 처리되는 경우만 얘기하는 것이다.

분류