What is Virtual Memory?
가상 메모리(Virtual Memory)는 소프트웨어에서 바라보는 관점의 메모리로, 동작시 실제 물리적인 메모리의 주소값으로 맵핑(mapping)된다. 프로세서에서 이 역할은 MMU(Memory Management Unit)이 수행한다.
그럼 왜 가상 메모리를 사용하는 것일까?
첫번째 이유는 메모리에 확장성(Extensibility)을 부여해주기 때문이다. 물리적 메모리는 한정적이지만, 가상의 메모리는 그보다 더 큰 공간으로 구성가능하다. 물론 실질적으로 물리적 메모리의 크기보다 더 큰 데이터를 저장할수는 없다(데이터를 압축한다거나 하는 기능은 논외로 하자). 또한 DRAM과 같은 휘발성 메모리 장치와 디스크와 같은 비휘발성 메모리 장치, 그리고 프로세서의 내부 레지스터의 공간까지 하나의 가상공간에 연속적으로 맵핑이 가능하다. 각각의 저장매체를 별도의 구분없이 메모리 주소만 알면 사용이 가능한 것이다.
임베디드기기에서 통상 메모리라고 하면 DRAM과 같은 메인메모리(main memory 주기억장치)를 말하는데, 가상메모리를 사용함으로써 실제 메인 메모리 장치가 아닌 디스크(하드디스크나 플래쉬 메모리)를 부가적인 메모리공간으로 활용할 수 있다. 임베디드가 아닌 PC와 같은 범용 OS에서는 이런 부가적인 저장장치(secondary storage)의 활용이 가상메모리사용의 큰 목적이다.
두번째 이유는 모든 프로그램에 동일한 메모리공간을 제공해줄수 있기 때문이다. 개발자 입장에서는 (대부분의 개발과정에서) 물리적인 메모리가 아닌 OS가 제공하는 메모리공간만 신경쓰면 된다.(믈론 물리적 메모리의 전체 크기나 OS에서 사용하는 메모리 크기와 같은 전반적인 정보는 알고 있어야 한다). 운영체제가 동작하는 환경에서는 여러가지 프로그램(엄밀히 말해 프로세스process)이 동시에 실행이 되기 때문에, 메모리공간에 대한 관리가 필요하다.
프로그램 A와 프로그램 B가 동시에 실행되고 있는 경우, 만약 물리적인 메모리를 사용한다면 프로그램 B는 프로그램 A가 사용한 메모리를 제외한 메모리를 사용해야 한다. 그럴려면 프로그램 B가 현재 사용되고 있는 메모리 현황에 대해 실시간으로 알고 있어야 한다. 혹시라도 다른 프로그램에서 사용하는 메모리를 건드리게 되면, 시스템은 오동작하게 된다. 뒤에서 다시 얘기할 메모리 보호(memory protection)과도 연관된다. 결과적으로 불편하고 비효율적이다. 모든 프로그램(프로세스)에 동일한 메모리공간을 제공해주고, OS에서 알아서 처리하는 것이 효율적이다.
세번째, 메모리 할당과 관리에 효율적이다. 물리적으로 연속되지 않은 메모리라도 가상으로 연속적인 메모리 공간으로 사용가능하다. 여러 프로그램들이 메모리를 사용하다 보면 메모리 단편화(Memory Fragmentation) 문제가 발생한다. 메모리 단편화는 메모리 공간이 조각조각 나뉘는 것을 말한다. 큰 사이즈의 메모리 할당이 필요한 경우 문제가 될 수 있다. MMU는 페이징(Paging)과 세그먼트(Segment), 메모리 풀(Memory Pool)과 같은 기법을 사용하여 메모리 단편화를 해결한다. 페이징은 고정크기로 메모리를 할당하는 것이고, 세그먼트는 가변크기로 할당하는 방식이다. 더 자세한 내용은 인터넷에 많이 나오니 따로들 공부해보시길 바란다.
페이징과 세그먼트 기법은 함께 쓰일수 있다(x86). 소프트웨어 개발을 하다보면, 페이지 폴트page fault라는 용어와 세그멘테이션 폴트segmentation fault라는 오류를 가끔 만날텐데, 이 둘에 대해서는 개념을 알아둘 필요가 있다.
page fault는 디스크를 보조 메모리로 사용하면서 발생하는 정상적인 동작이다. 프로세스로부터 특정한 가상주소의 메모리영역 데이터 - 즉 page를 요청받았을때, 해당 page가 물리적 메모리 공간에 없는 경우에 발생한다. 이 경우 MMU는 디스크(secondary storage)로부터 해당 page를 가져온다. 이는 오류가 아닌 정상적인 예외처리신호(exception signal)다. 지난 시간에 알아본 캐시동작 중 발생하는 Cache Miss와 동일한 개념이다.
하지만 segmentation fault는 에러다. 이는 프로세스가 허용되지 않은 메모리영역에 접근할 때 발생한다. 메모리 보호기능이 동작함으로써, 프로그램을 비정상종료시키는 것이다. segmentation fault는 프로그램을 잘못 짜면 흔하게 발생하는 버그다.
네번째 가상메모리 사용의 잇점은, 메모리 보호기능(Memory Protection)을 제공한다. 이것은 보안(Security)이나 안정성(Stability) 측면에서 매우 중요한 기능이다. 각각의 프로세스는 별도의 메모리공간을 점유하며, 다른 프로세스의 메모리공간을 참조할 수 없다. 윈도우에서 프로그램을 실행할때 비정상적인 메모리 접근이라는 에러창이 뜨는 경우를 간혹 본적이 있을 것이다. 만약 메모리 보호기능이 없다면, 이런 에러를 보기도 전에 먼저 시스템이 멈춰버릴 공산이 크다.
이보다 더 많은 이점이 있을 것이다. 더 많은 이론과 기술이 있을 것이다. 일단 생각나는대로 설명했으니 조금이라도 도움이 되었으면 좋겠다.
아주 가벼운 RTOS(실시간 운영체제)가 사용되거나, 운영체제(OS)를 사용하지 않는 경우, 즉 단순한 임베디드 소프트웨어에서는 가상메모리가 아닌 물리메모리를 직접 사용하기도 한다.
물리메모리에 특정한 오프셋을 더한 일률적인 주소체계를 사용하는 방식이 대표적이다. 통상적으로 부팅 소프트웨어(Bootloader)가 들어있는 ROM memory의 시작 주소값은 0다. 특정 오프셋을 더해 가상메모리 주소값을 산출해내는 예시는 아래와 같다.
Offset(Non-Cache) = 0x8000_0000
Offset(Cache) = 0xA000_0000
Physical ROM Start Address: 0x0000_0000
--> Virtual ROM Start Address(Non-Cache): 0x8000_0000
--> Virtual ROM Start Address(Cache): 0xA000_0000
Physical RAM Start Address: 0x4000_0000
--> Virtual RAM Start Address(Non-Cache): 0xC000_0000
--> Virtual RAM Start Address(Cache): 0xE000_0000
위 예시에서 Cache 영역은 해당 범위의 메모리를 억세스할 때 캐시를 쓰는 것이고, Non-Cache 영역은 캐시를 쓰지 않도록 가상 메모리 공간을 나눠서 설정을 하는 것이다. 이렇게 시스템과 소프트웨어의 설정을 통해 가상메모리 공간을 필요에 맞게 구성해서 사용하는 것이 가능하다는 정도로 이해하시면 되겠다.