메모내용

가상 메모리, 가상주소, 페이징, 세그멘테이션

개요

예를들어 8GB 의 DRAM 을 2개 장착하여 사용하고 있다고 하자. 총 16GB 의 메인메모리 용량이 확보된 것이다. 언리얼 엔진을 켜고 게임을 만들다 보면 이게 16GB 안에 다 들어간다고? 싶을때가 많다. 언리얼 엔진 하나만 해도 메모리 사용량이 엄청난데, 그 와중에 게임도 구동을 해야 한다. 심지어 노래를 켜고 인터넷 검색을 하거나 동영상을 틀어놓고 하기도 한다. 어쩔 땐, 예제 프로젝트를 참고하기 위해 2~3개의 언리얼 엔진 프로젝트를 켜고 작업하기도 한다. 상식적으로 생각했을 땐, 16GB 라는 메모리 용량은 턱없이 부족할 것 같다는 생각이 들것이다. 물론 실제로도 부족하다. 부족한데 어떻게 그 모든 프로그램들을 감당할 수 있는 것일까? 이는 가상메모리 라는 기술 덕분이다. 가상메모리 기술 덕분에, 프로세스마다 할당된 공간은 다른 프로세스가 침범이 불가능 하여 안정성 향상이라는 장점도 얻게 되며, 가상 메모리의 크기가 물리 메모리보다 클 경우, 물리 메모리보다 큰 프로세스를 돌릴 수도 있다. 가상메모리의 크기는 os 의 종류에 따라 달라진다.

32bit(×86) Window
4GB ( Kernel 2GB + Application 2GB )
32bit 운영체제가 접근할 수 있는 2³²개 메모리 주소 및 사이즈 4GB
64bit Window
16TB ( Kernel 8TB + Application 8TB )
64bit 운영체제가 접근할 수 있는 2⁶⁴ 개 메모리 주소 및 사이즈 16EB
하지만 현재 chipset 에서 프로그램이 실제쓰는 가상주소는 48비트 까지만 지원하는 상황

가상메모리

가상메모리의 아이디어는 이렇다 4GB 짜리 프로세스를 메모리에 올리더라도, 한번에 사용되는 공간은 4GB 가 되지 않을텐데, 그러면 사용되고 있는 만큼만 메모리에 나누어서 올리면 되지 않을까?

프로세스가 전체적으로 필요한 용량은 4GB 라고 하더라도, 동시에 4G 가 모두 사용되지는 않는다. 그러므로 지금 당장 사용되는 만큼만 메모리에 적재하고 나머지는 적재하지 말자는 개념으로 시작된것이 가상메모리 이다. 하지만 프로세스는 실행되는 순간부터 모든 정보가 어딘가에는 저장이 되어 있어야 한다. 메모리에 저장을 안할거면 어디다가 할까? 바로 보조 기억장치이다. HDD, SDD 와 같은 저장장치의 도움을 받아 가상메모리를 구현하게 된다. 모든 정보를 일단 보조 기억장치에 적재해둔 뒤, 지금 당장 필요한 부분만 꺼내서 메인메모리에 적재하고 사용되지 않게 되면 다시 보조 기억장치로 적재하는 방식인 것이다. 그렇다면, 어떤 방식으로 보조 기억장치와 메모리 사이에서 정보를 옮기는 것일까? 가상 메모리를 실현하는 방법은 페이징, 세그멘테이션 2가지 기법이 있다.

페이징

먼저 프로세스와 메인메모리를 동일한 크기로 조각을 낸다. 예를 들어, 1byte 로 쪼갠다고 한다면, 하나의 페이지는 하나의 프레임에 저장될 수 있을 것이다. 프로세스의 페이지는 각 프로세스마다 0번부터 시작하여 인덱스가 매겨진다. 반면 메인메모리는 하나이기 때문에 0번 페이지가 0번 프레임에 매칭되는 것은 아니다. 페이지는 메인메모리의 어느 프레임으로 매칭이 될 것이며, 해당 정보는 운영 체제가 보유한 페이지 테이블에 저장이 된다.

프로세스 A 가 실행되면서, 0번페이지에 있는 정보와 4번 페이지에 있는 정보가 지금 필요하다고 해보자. 그렇다면, 메인메모리의 어느 프레임에 0번 페이지와 4번 페이지의 데이터를 적제하게 될 것이다. 위에서 말했듯이, 페이지의 인덱스와 프레임의 번호는 항상 일치하는 것이 아니다. (오히려 일치하지 않는 상황이 더더욱 많다.) 그렇기 때문에, 어떤 프레임에 어떤 페이지가 적제되어 있는지 정보를 저장할 곳이 필요하다. 이것이 운영체제(OS) 가 보유한 페이지 테이블 이다. 하지만, 이렇게 쪼개져있고 위치도 계속 왔다갔다 하는 상황이라면, CPU 에선 무엇을 기준으로 주소를 인식해야 할까? 프로세스의 시작점을 기준으로 가상주소 를 부여하는 것이다. 예를 들어, 메인메모리의 주소와 동일한 개념으로 보았을 때 페이지 하나에 10개의 주소가 포함되어 있다고 해보자. 그렇다면, 페이지 0 에는 0~9번지가 있으며, 페이지 1에는 10~19 번지가 있고, 페이지 2 에는 20~29 번지가 있다고 할 수 있을 것이다. 이처럼 하나의 프로세스 시작을 0 으로 해서, 주소를 생성해 준다. 이렇게 만든 주소가 가상주소 이다. 실제로 메인메모리에 적재되면, 가상 주소와는 다른 물리 주소 를 보유하게 된다. (실제 메모리 주소) CPU 에선 해당 가상주소를 기준으로 작업을 처리하게 된다. 예를 들어, 가상주소가 5번지인 데이터가 있다면, 메인메모리의 몇 번지에 적재되어 있든간에 5번지의 데이터를 요구하는 것이다. 가상주소를 특정 방법을 통 물리주소로 변환하고, 메인메모리에선 해당 물리 주소의 데이터를 CPU 에 전달하게 된다. 하지만 페이징 기법에는 몇가지 단점이 존재한다.

페이징 기법의 단점

1. 마지막 페이지에서 내부 단편화가 발생

하나의 프로세스를 동일한 크기로 쪼개는 과정을 거치는 페이징은 프로세스의 크기가 페이지 1개 크기의 배수가 아니라면, 프로세스의 마지막 페이지는 페이지의 크기를 전부 채우지 못한다. 예를 들어, 한 페이지가 10byte라고 가정하고 프로세스 A는 91Byte라고 해보자. 마지막 페이지에선 1Byte만 사용하게 되고, 9Byte만큼의 내부 단편화가 발생하게 된다.

2. 페이지 테이블 크기만큼의 메모리를 추가로 소모해야 함

프로세스의 크기가 클수록, 페이지의 크기가 작을수록 페이지 테이블의 크기는 더욱 커질 것이다. 페이지 테이블은 메인 메모리에 항상 적재되어 있기 때문에 페이지 테이블 크기만큼의 메모리 공간을 활용할 수 없게 된다.

3. 메모리에 두 번 접근해야 함

메모리에 접근하는 시간은 CPU를 기준으로 보았을 때 매우 느린 시간이다. 이를 커버하기 위해 캐시라는 저장 장치를 활용할 정도로 CPU입장에선 메모리에 최대한 직접 접근하지 않는 것이 좋다. 하지만, 페이징 기법의 경우엔 페이지 테이블에 한 번 접근하여 물리적 주소를 알아낸 후, 실제 물리적 주소에 접근하는 두 번의 과정을 거쳐야 한다. 이는 상당한 오버헤드로 다가올 수 있다. 하지만, 실제로는 이를 해결하기 위해 TLB라는 버퍼를 사용하고 있다. 물론, 문제를 완전히 해결하는 것은 아니고 CPU의 캐시와 동일하게 최근에 사용된 페이지 테이블의 데이터를 가져와서 빠르게 탐색하는 용도로 사용한다. 캐시 미스 발생시 메모리에서 데이터를 다시 복사하는 것처럼, TLB또한 TLB 미스 발생시 메모리에 다시 접근하여 데이터를 가져오는 방식으로 사용된다.

세그멘테이션

세그멘테이션은 페이징과 유사하지만 다른 기법이다. 세그멘테이션 또한 프로세스를 여러개의 조각으로 쪼개는 것은 똑같다. 하지만, 단순히 동일한 크기로 쪼개는 것이 아니라 논리적 특성을 기준으로 프로세스를 쪼개게 된다. 예를 들어, 우리가 잘 아는 code, data, heap, stack 또한 일종의 세그멘테이션 기법으로 쪼개진 것이다. 데이터들의 용도나 목적을 기준으로 프로세스를 4개의 조각으로 쪼갠 것이다. 이 외에도 지역변수, 전역변수, 함수, 심볼 등을 기준으로 쪼개기도 한다고 한다. 페이징 기법은 페이지와 프레임의 크기가 동일하기 때문에, 페이지를 그대로 메인 메모리의 프레임에 적재하면 됐지만 세그멘테이션의 경우엔 각 세그먼트의 크기가 고정되어 있지 않기 때문에 메인 메모리를 미리 쪼개놓는 것이 불가능하다. 이러한 이유 때문에 세그멘테이션 기법은 그때 그때 각 세그먼트의 크기만큼 메모리 공간을 할당하게 된다. 세그멘테이션 또한 페이징 테이블처럼 세그먼트 테이블이 존재한다. 하지만, 조각의 수가 적은만큼 차지하는 용량이 매우 작아서 메인 메모리가 아닌 CPU의 레지스터에 저장된다고 한다. 이로 인해, 주소를 찾을 때 메인 메모리에 단 1번만 접근해도 된다. 세그먼트 테이블은 base와 Limit을 저장한다. A라는 세그먼트가 크기가 1400이라고 해보자. A를 메인 메모리의 1000번지로부터 1400 크기만큼 할당하였다면, base는 1000이 되고 Limit은 1400이 된다. 세그멘테이션이 언뜻 보면 페이징보다 좋아보이지만 실제론 페이징보다 많이 사용되지 않는다. 이유는 하나의 치명적인 문제점 때문이다.

세그멘테이션 기법은 외부 단편화를 야기한다.

세그멘테이션 기법은 위에서 말했듯이, 필요할 때마다 그 크기만큼 메인 메모리 상에 새로 할당하게 된다. 가변적인 크기로 메모리 상에서 할당과 해제를 반복하다 보면 자연스럽게 외부 단편화가 발생할 수 밖에 없다. 이로 인해 세그멘테이션 기법을 사용시에 메모리가 아주 비효율적으로 사용되는 상황이 잦아, 세그멘테이션 기법을 단독으로 사용하는 것은 선호되지 않는다고 한다.

출처

가상메모리

참조 -> 널널한 개발자 :: 가상메모리

컴퓨터 마다 물리메모리(실제메모리) 크기가 다르다 컴퓨터 라는 기계의 정체성 CPU 에 있으며, 기본적으로 메모리라는 것은 RAM + HDD 부분을 말한다.

VMS ( Virtual Memory Space) 32 bit 기준으로 4GB 짜리 배열이다. RAM 과 HDD 의 영역을 말하는 선형 메모리. char 가 있는 배열인데, char 가 42억 9천만 개 정도 있는 선형 메모리. 이 안에는 stack, heap ... 등이 모두 VMS 에 들어있다.

가상 메모리 주소는 프로세스 마다 가진다. 그리고 가상 메모리 주소는 물리주소 의 어떤 부분을 가리킨다.

OS 가 등장하고 가상메모리 개념이 생기면서 메모리 의존성이 없어지고 높은 추상성 제공 ( 관리적 의미) -> 보안성에서 의미를 지닌다( 접근 제어를 한다) 메모리 관리의 최소단위 : 1byte 이고, 1byte 마다 주소가 붙는데, 주소가 32bit unsgined integer 라면, 42억 9천 정도 되고 이것이 4GB 가 된다. 이 주소의 길이는 OS 가 몇 bit 냐, CPU 가 몇 bit 냐에 대해 의존한다 64bit 운영체제라면 2^64-1 정도의 메모리 사이즈를 가지고 이는 16EB 까지 사용할 수 있다.

32bit 응용 SW 를 사용하는 컴퓨터의 어플리케이션은 4GB 메모리를 사용한다면, 절반으로 나누어서 쓴다. [ 2GB | 2GB ] Vritual Memory 이때 앞쪽의 2GB 는 user 모드에서 사용하고 뒤쪽의 2GB 는 kernel 이 사용하는데, userMode 의 메모리중 앞부분은 OS 가 사용한다. 즉 유저가 사용할 수 있는 메모리는 1.8 gb 도 안된다. [ 0.2 GB (OS 사용 메모리) | 1.8GB (유저 사용 메모리) ] | [ 2GB (Kernel 사용) ] 이를 테스트 할 수 있는 방법은 malloc 함수를 사용해서 2 GB 를 할당 해달라고 하면, 2GB 가 할당이 안된다. 1.7GB 밖에 할당이 안된다. 때문에 뒤쪽 2GB 는 접근이 안된다. 16 진수로 생각하였을때, 0x 00 01 00 00 부터는 접근이 불가능하다. 뒤쪽 Kernel 부분은 운영체제가 직접 관리하는 부분이다.

메모리 관리자 는 커널 부분에 있다. user ------------------------------------------------------ kernel Kernel 부분에 MM (MemoryManager) 가 있으며 MemoryManager 가 관리할 수 있는 실질적인 메모리 는 [RAM + SWap 영역] 이곳의 영역은 OS 가 자동으로 설정하도록 합니다. 동적(런타임, 프로세스가 실행중) 주소 변환? 프로세스가 실행중인 중간에 주소 체계를 변환한다. 물리 메모리를 관리하기 위해서는 Paging 기법, Segmentation 기법을 섞어서 쓴다고 한다. 무슨 말인지는 모르겠지만. 그래도 가상메모리에서의 시작은 0 번이다. 프로세스 3개가 실행중일때 모든 프로세스가 사용할수 있는 메모리 주소의 시작은 0x 00 부터 시작이다. 프로세스 1개당 사용할 수 있는 메모리 는 4GB 라고 OS 가 말해주지만, 실제 사용할 수 있는 메모리도 2GB 채 안되지만, 일단 프로세스 1 번 , "Hello World" 를 출력하는 어플리케이션이 4GB 를 쓸수 있다고 해서, 이를 모두 사용하지는 않는다. 100MB 도 안쓰겠지. 하지만 프로세스 2번 이 만약 Photoshop 같은 무거운 어플리케이션이라고 한다면, 2GB 도 부족할 것이다. 하지만 물리적 메모리 시작은 다 다르다. 같은 위치의 메모리 주소를 사용한다 하더라도 물리적 주소의 어느곳을 사용할 지 모른다. 심지어 Paging, 또는 Swapout 되어버리면 HDD 의 메모리의 주소를 가리킬 수도 있다고 한다.

Ram HDD
0x 00 00 00 00
[ OS | process1 | process2 | process3 | ... ] +
[____________________...]


그래서 가상메모리 상에서 프로세스상 주소가 같다고 하여, 같은 주소를 가진다고 하여 물리적 주소에서의 주소는 절대 다르다. OS 가 MemoryManager 가 가 MappingTable 이라는 자료구조를 사용하고 있다.

스왑되었다는 이야기는 RAM 이 아닌 HDD 의 어떤 부분을 사용하고 있다 라고 할수 있다. 아마 잘 사용되지 않아서, sleep 상태에 빠져서, 하드디스크의 어딘가로 옮겨졌다 라고 볼 수 있다. 보통 이런 매핑테이블 같은경우는 배열, 또는 리스트 로 구현될 수 있는데 보통 배열로 구현되어 있다고 볼 수 있다. 왜냐하면 왔다갔다 따라다녀야 하기 때문에, 이런 매핑테이블을 운영체제가 관리하면 좋은점이 있는데, 만약 프로세스 A 가 잘못된 연산을 해서 죽어버렸다고 하면 프로세스 A 가 사용하고 있던 메모리 세그먼트 1 을 못쓰는것이 아니라 MM 알고 있기 때문에 메모리 테이블에서 A 가 쓰던 곳의 정보를 비워버리고, 신속히 메모리 회수가 가능하다는것이다 자원 낭비가 없다는 장점이 있다. 커널이 건재하다. 어떤 어플리케이션이 바보짓을 해도, 메모리를 못쓰게 되는 공간이 없다. 만약 이런 부분이 없다면, 어떤 프로세스가 죽고, 살고를 반복한다면 쓸 수 있는 메모리가 점점 줄어들며 결국에는 리부팅 밖에 답이 없어진다. 좋은 운영체제 일수록 가상화 정도를 높인다. 이렇게 운영체제의 성능을 극대화 한 부분이 모바일 에서 이다. 보통 모바일 기기 같은경우, 전원을 끄는 경우가 드물다. 그래도 잘 돌아간다. 바꿔말하면 메모리 관리를 정말 잘 하고 있다는 이야기 이고, 자원 회수가 잘 되고 있다는것이다.

페이지

참조 -> 널널한 개발자 :: 가상메모리 페이징 기법
32 bit 기준으로 0부터 시작하는 메모리 주소
0x00000000 ~ 0xffffffff
___________~4095|4096~______________________
[__________________|______________________________]
<----4KB---->
이 부분을 4KB 단위로 끊어 Page 라는 단위로 나눈다

이는 Paging 기법으로 관리된다고 하면 Page 라고 할 수 있는데, Segmentation 기법으로 관리된다고 하면 Segment 라고 한다. 아무튼 일정 단위를 말한다. 핵심은 메모리를 일정 단위로 나누어 단위화 했다는것이 중요하다.

이것은 C 언어의 2차원 배열이라고 생각 될 수 있으며, 아파트를 생각한다면, Page 에 해당하는 것이 아파트의 #동 이며, process 1 이 Page1 을 사용한다면, process 1 의 임의의 변수가 할당받은 메모리 주소는 page 1 의 어딘가. 즉 " 아파트 100 동 101호 에 살고 있다. " 고 볼 수 있다. 이때 Page 1 동이 가리키는 절대적인 기준이 있으며, 그 안에서 변수들이 할당받은 주소를 상대적인 위치를 영어로 distance 또는 offset 이라고 표현을 한다. 즉 절대적인 기준(Page)으로부터 어느정도 띄어져(distance, offset) 있다는 의미이다.

보통 페이지 사이즈와 프레임 사이즈를 동일하게 맞추는것이 일반적




VA : 가상 주소
PA : 물리 주소