가상메모리라는 것은 메모리 관리 기법 중 하나로, 프로세스 전체가 메모리 내에 올라오지 않더라도 실행이 가능하도록 하는 기법이다.
가상 메모리의 장점은 다음과 같다.
1. 사용자 프로그램이 물리 메모리의 제약에서 벗어날 수 있다
2. 각 프로그램이 더 작은 메모리를 차지하기 때문에 더 많은 프로그램을 동시에 수행이 가능하다. (물론 그냥 가능한 것처럼 보이는 것이다.)
3. 프로그램을 메모리에 올리고 swap하는데 필요한 I/O의 횟수가 줄어든다.
위의 해당 내용만 알아서는 가상 메모리가 어떤 건지 잘 이해가 가지 않는다. 하나하나씩 배경지식을 알아가면서 가상메모리의 개념을 알아보겠다.
먼저 메모리라는 것은 컴퓨터에서 어떤 존재일까?
컴퓨터에서 컴퓨터는 CPU가 일을 한다. 여기서 일이라 함은 연산을 수행하게 된다.
그런데 CPU가 일을 하려면 아무 정보도 없이 할 수는 없다. 1+2를 수행하더라도 1과 2를 어디서 불러와야 한다.
CPU가 값을 참조해야 한다는 뜻이다. 여기서 값은 레지스터라는 저장 공간에 저장되어 있다. 레지스터는 자료를 보관하는 매우 빠른 기억 장소이다. 그렇지만 레지스터는 용량이 매우 작고, 휘발성이라는 특성을 가지고 있다.
따라서, 레지스터 보다는 조금 더 느리지만, 조금 더 큰 용량인 메인 메모리(물리적 메모리라고 한다)를 두어서 해당 내용을 참조한다.
메인메모리가 레지스터보다는 크기가 크지만, 메인메모리와 레지스터는 용량이 작고 휘발성 특성을 가진다. 그래서, 이 둘보다는 더 많은 내용을 저장하는, 또 비휘발성의 특성을 가지는 DISK memory를 사용한다. (DISK memory는 우리가 흔히 하드디스크(HDD)라고 불리는 것이다.)
CPU는 레지스터와 메인메모리까지의 값만 참조할 수 있다. 그래서 보조저장장치(DISK)에 있는 값을 참조하려면 OS의 도움을 받아서 입출력 작업을 실행해야 한다. 프로그램들은 보통 DISK에 저장되어 있다.
그럼 DISK의 프로그램은 어떤 방식으로 해서 CPU가 실행을 하는 것일까?
먼저 DISK에 프로그램은 이진(binary) 실행 파일 형식으로 존재한다. 만약에 개발자가 작성한 소스 프로그램이 있다면 컴파일러와 링커의 작업으로 실행파일이 생성된다.
예를 들어서, Java로 소스코드를 작성했다고 생각해보자. 그럼 컴파일러는 소스 코드를 내 컴퓨터에서 실행할 수 있도록 바이트 코드로 바꾸어서 소스코드를 JVM에서 실행할 수 있도록 도와준다. 그리고, 링커는 컴파일러에 의해서 생성된 파일을 가져와서 실행 파일로 결합한다.
실행 파일을 실행하면 fork 요청으로 새 프로세스를 생성하고, exec 요청으로 로더를 호출한다. 그럼 여기서 로더는 어떤 역할을 하냐면, 로더는 새로 생성된 프로세스의 주소공간을 사용하여서 지정된 실행 파일을 메모리에 올려준다.
즉, 프로그램을 실행하면 디스크에 존재하던 실행파일이 메모리에 올라오고, 그럼 이 내용을 CPU가 참조할 수 있게 되는 것이다. CPU가 그럼 이제 실행 파일을 실행하면 0번부터해서 시작하는 프로세스마다 독자적인 주소공간을 생성하고, CPU는 이 주소를 바라보는데, 이 주소가 바로 논리 주소이다.즉, CPU는 논리주소를 바라보고 있는 것이다. CPU가 일을 하려면 논리 주소가 메모리 상에 올라와 있어야 한다.
그럼 논리주소는 실제 물리적 메모리의 특정 위치와 매핑 되어야 하는데, 이 매핑 작업을 주소 바인딩 이라고 한다.
바인딩을 하는 방법은 물리적 메모리 주소가 결정되는 시기에 따라서 나누어 지는데, 컴파일 타임 바인딩, 로드 타임 바인딩, 실행 시간 바인딩으로 나누어진다.
여기서 실행 시간 바인딩을 사용하면 가상 메모리를 이제 사용할 수 있게 된다. 실행 시간 바인딩을 하기 위해서는 하드웨어적인 지원이 필요한데, CPU가 주소를 참조할 때마다 주소 맵핑 테이블을 이용해서 바인딩을 점검한다.
레지스터는 현재 프로세스의 물리적 메모리의 시작 주소가 저장되어 있다. 그래서 레지스턴는 논리 주소 + 기존 레지스터 값으로 물리적 주소의 위치를 찾는다. (아까 CPU에 올라올 때, 0번부터 프로세스가 주소공간을 생성한다고 했으니까)
앞에서 말한대로, CPU가 프로세스를 실행하려면 메모리에 올라와 있어야 한다. 그런데 너무 많은 프로그램이 메모리에 올라오면 메모리 공간이 부족하게 된다. 그래서 메모리 공간의 확장 영역으로 스왑 영역 이라는 것을 사용한다.
스왑 공간은 외부 저장장치에 존재하지만 물리메모리의 확장 느낌이다. 물리메모리에 공간이 부족하기 때문에 실행중인 프로세스의 주소공간을 일시적으로 메모리에서 디스크로 내려 놓는 것이다.
스왑 영역은 디스크에 존재하지만 파일시스템과는 별도로 존재한다. 파일시스템은 비휘발성이지만 스왑영역은 메모리 공간의 확장으로 사용하기 때문에 프로세스가 수행 중인 동안에만 일시적으로 저장된다. 메모리에서 스왑영역으로의 이동을 swap in, swap out 이라고 한다.
스왑 영역도 외부저장장치에 존재하기 때문에 OS에 의해 IO작업이 일어난다. 하지만 공간효율성보다는 시간효율성을 고려한 저장이 일어나서 일반적으로 파일시스템에 접근하는 것보다 좀 더 빠른 접근이 가능하다.
즉, 프로세스는 메모리에 올라와야하는데, 만약 현재 실행되고 있는 프로세스 말고 다른 프로세스를 실행하려고 한다면 기존 프로세스를 스왑 영역으로 내쫓고 필요한 프로세스가 물리메모리에 올라와야 한다. 이렇게 된다면 빈번하게 스왑영역과 IO가 발생될 것이다.
만약 프로세스의 크기가 물리 메모리의 크기를 벗어난다면 실행조차 불가능하게 된다. 이런 불편함 때문에 가상메모리 개념이 필요하게 되었다.
즉, 필요한 내용만 물리메모리 위에 올려놓고 사용하면 되지 않을까라는 생각을 사람들이 하게 된 것이다. 이렇게 가상메모리의 개념이 등장한다.
가상메모리는 실제의 물리메모리 기능과 개발자의 논리메모리 개념을 분리한다.
따라서 가상메모리를 사용하면 프로세서 전체의 내용을 메모리에 올릴 필요없이 필요한 부분만 메모리에 올려 실행이 가능하다.
그럼 필요한 부분만 어떻게 올려 줄 수 있을까?
여기서 필요한 페이지만 물리메모리에 적재하는 **요구 페이징 기법(Demand Paging)을 사용한다.
이 기법에서는 주소공간이 하나의 단위가 아니라 여러 개의 페이지로 나눠져 있다. 그 중에서 지금 당장 필요한 페이지들만 물리 메모리에 가져와 사용한다.
그럼 또 의문이 생긴다. 필요한 페이지가 물리 메모리에 올라와 있는지 아닌지를 어떻게 알까?
특정 페이지 메모리 존재임을 구분하기 위해 유효(valid), 무효(invalid) 비트를 사용한다.
invalid는 해당 페이지가 메모리에 없음을 의미하고 이를 페이지 폴트(Page fault)가 발생했다고 한다. 이 경우 보조저장장치에 페이지가 있다면 보조저장장치에서 해당 페이지를 가져온다.
물리메모리에 대응되는 페이지 테이블에 valid, invalid 비트로 물리적 메모리에서 페이지의 바인딩 정보를 확인할 수 있다. 앞서서 말한 하드웨어인 MMU의 도움을 받아 실행 타임 바인딩 기법이 적용된다.
여기까지의 내용을 알고 나면, 가상 메모리의 장점이 어떻게 장점이 되는지 알 수 있다.
'CS > TIL' 카테고리의 다른 글
[DevOps] 쿠버네티스 환경구축 (0) | 2023.03.29 |
---|---|
[DevOps] 도커 (0) | 2023.03.21 |
Transaction과 Rollback (0) | 2023.02.14 |
Logging을 이용한 Database의 Recovery (0) | 2023.02.09 |