by 꽈배기
가상 메모리를 얼핏 들으면 새로운 공간으로 착각하기 쉽지만, 프로그램 실행에 필요한 일부분만 램에 적재하며 나머지 메모리는 디스크에 저장해두며 필요에 따라 교체하며 사용하는 방식이다. x86 환경에서 가상메모리 주소 대역폭은 0~ 2^32^ -1, x64 환경에서 주소 대역폭은 0~ 2^64^-1 이다.
예전 프로그램들은 하나의 메모리 공간을 전부 할당받아 사용한다는 전제하에 만들었기에 동시성을 보장하기 어려웠다. 멀티태스킹의 필요성이 대두되며 각 프로세스마다 메모리 공간 할당에 대한 효율적인 방안이 요구되었고, 해당 결과로 가상 메모리가 등장하게 된다.
가상메모리의 역할은 다양하게 있는데, 주요 이유로는 보안 안정성, 큰 프로세스 사용 가능, 불연속적 공간을 연속적 메모리 공간으로 가정할 수 있다는 것이다. 서로 다른 프로세스가 같은 메모리를 공유하는 상황을 가정하면 가상 메모리의 역할을 확실히 알 수 있다. 만약 프로세스 A에 할당된 영역을 벗어나 접근하려 한다면 어떻게 될까? 널 에러를 반환하거나 심하다면 다른 프로세스의 메모리를 참조하여 오류를 불러올 것이다. 이를 가상 주소와 물리 메모리 주소를 매핑하는 과정을 거쳐 메모리 가상화를 통해 서로 배타적인 메모리 공간을 사용함으로 안정성을 확보하게 된다.
그렇다면 어떻게 가상메모리는 실제 메모리 공간보다 큰 프로세스를 실행할 수 있을까? 예를 들어 램 용량이 16인데 반해 프로세스의 총합이 20이라면 모든 메모리를 로드하여 실행할 수 없을것이다. 그렇기에 프로세스 실행에 필요한 특정 메모리 부분만 로드하여 필요시 하드 디스크와의 스왑을 통해 더 많은 공간을 사용할 수 있는것이다. 즉 추가적인 공간을 만들어주는것은 아니고, 필요할 때마다 가져와 사용하니 공간을 더 효율적으로 사용할 수 있다는 말이다.
실제 물리 메모리엔 파편화되어 있지만, 가상 메모리에선 연속적으로 취급할 수 있다.
실제 데이터가 존재하는 메모리를 물리메모리라 하며 이곳의 고유 주소를 물리 주소라 한다. CPU가 가상주소 요청을 받는다면 이 논리주소를 물리주소로 변환시켜주어야 하는데, 이 매핑 과정을 다루는 것이 메모리 관리자이다.
프로세스가 필요로하는 데이터 메모리를 가져오도록 결정한다. 빠른 작업을 위해 이를 캐싱하기도 함.
프로세스 조각을 메모리의 어떤 곳에 위치 시킬지 결정하는 정책이다.
가상 메모리를 일정한 크기로 분할하고 그 크기에 프로세스를 파편화 하여 저장하는 기법을 페이징이라 한다. 해당 과정을 통해 비연속적인 물리 메모리 공간을 페이징하여 연속적으로 저장해 사용할 수 있다.
페이지 : 프로세스가 가리키는 논리 주소 공간을 나눈 것.
프레임 : 물리 메모리를 일정한 크기로 나눈 블록으로 페이지와 대응된다.
페이지가 연속적으로 저장되며 프레임과 페이지의 대응을 관리한다.
페이지 테이블을 이루는 요소를 페이지 테이블 엔트리라고 한다. 엔트리는 페이지 정보와 연관되는 프레임 정보를 가지며 여러 비트들에 대한 정보를 포함한다.
CPU가 요청에 따라 메모리에 적재된 데이터를 접근하려면 페이지 테이블 정보의 위치를 봐야 할 것이다. 그렇다면 CPU는 이 페이지 테이블 위치를 어떻게 알 수 있을까? 페이지 테이블 베이스 레지스터 (PTBR) 라는 레지스터가 페이지 테이블에 대한 정보를 가리키고 있다.
일반 4KB 대용량 4MB
32비트 가상 주소에서 페이지 디렉토리/ 테이블 에 10비트 * 2 로 나머지 12 비트가 4kb의 용량을 가지게된다.
논리 주소 - 물리 주소간의 변환을 수행한다.
프로세스의 요청에 따라 특정 메모리에 접근해 변수를 가져오기 위해서는 우선 페이지 테이블이 있는 메모리에 먼저 접근 해야한다. 페이지 테이블 데이터를 가져 온 후 이를 참조해 논리 주소 -> 물리 주소간 변환이 이루어지면 실제 메모리에 접근해 변수를 가져온다.
결국 2번의 메모리 접근이 발생하기에 페이징 성능에 영향을 준다.
가상 주소에서 실제 주소 조회를 위한 캐시로 빠른 데이터 접근과 캐시 지역성을 위해 사용한다. TLB는 프로세스의 요청에 의해 CPU가 처음 메모리를 페이지 테이블에서 가져오고, 최근 사용된 데이터를 캐시에 적재한다. 다음 프로세스의 요청이 있을때 TLB를 우선적으로 탐색해 TLB Hit이 이루어진다면 캐시 메모리로 접근하는 방식이다.
MMU는 이런 페이지 테이블 캐싱을 위해 TLB라고 하는 페이지 테이블만 전용으로 캐싱하는 별도의 캐시 메모리를 두게 된다. (최근에는 TLB가 CPU 내부에 존재한다고 한다? 음…)
가상 메모리의 접근 흐름도를 한번 살펴보자.
캐시 지역성 문제, 적재된 캐시 내부에서 모든 메모리가 사용되진 않는다. 캐시 라인
의문점이 들수도 있을것이다. 분명 페이지 테이블에 대한 정보는 요청된 프로세스에 모두 저장되어 있는데 어떻게 페이지 폴트가 뜨는걸까?
만약 부모 프로세스를 fork 하였을때 메모리를 어떻게 관리할 수 있을까? 기존처럼 별도의 메모리 공간에 부모 프로세스를 복제한다면 메모리 낭비, 프로세스 생성 시간이 오래 걸릴것이다. 이를 위해 copy on write가 있다. 부모 프로세스 / 자식 프로세스가 동일한 페이지와 프레임을 공유하다 둘 중 하나라도 페이지에 쓰기 작업 수행 시 해당 페이지를 별도의 공간으로 복제한다. (프로세스 생성 시간 절약, 메모리 절약)
페이지 테이블 마저 페이지 단위로 나누어서 실제 메모리에 연속되지 않게 배치한다는 것을 의미한다. 페이지 테이블을 구성하는 페이지 시작 위치를 알아야 인덱스에서 원하는 값을 찾아낼 수가 있게 된다. 이를 사용하는 이유는 프로세스의 크기에 따라 페이지 테이블 또한 커질텐데, 이 큰 용량을 모두 메모리에 올리는 건 비효율적이기에 당장 사용될 페이지 테이블 영역만 적재하는 것이다.
그래서 이러한 방식으로 페이지 테이블을 운영하기 위해서는 (페이지 테이블을 페이징한 정보를 담을 추가 페이지 테이블 < 말이 참 어렵다 )이 필요하게 된다.
이럴경우 4MB의 페이지 테이블은 2^20^으로 구성되는데, 4KB 페이지 테이블 2^10^으로 구성될 수 있다는 뜻이다. 64비트 환경에서는 계층적 페이지가 더 증가하게 되며 유효한 페이지 테이블을 탐색하는 방법이 고안되었다.
논리 주소의 페이징에 해시값을 사용한다. 해시값이 동일해 충돌이 발생한다. 이를 위해 Linked list로 구현된다.
기존 페이지 테이블 방식은 프로세스당 페이지 테이블을 가지고 있기에 사용되지 않음에도 페이지의 최대 수 만큼 테이블을 지니고 있어야 한다.
역페이지 테이블은 물리 주소를 논리 주소로 바꿔주는 테이블이다. 모든 프로세스가 페이지 테이블을 참조한다. 이로 인해 페이지 테이블의 entry 수와 물리 메모리 frame 수가 동일하게 적용된다. 모든 프로세스가 역페이지 테이블을 참조하기 때문에, 프로세스 정보 구분을 위한 pid가 존재한다.
기존 테이블 참조 방식과 달리, pid와 페이지 넘버를 비교하며 탐색을 진행하기에 시간 복잡도가 추가로 소모된다.
이러한 문제를 해결하기 위해 등장한 개념이 Inverted page table입니다.
처음부터 모든 페이지를 적재하지 않고 필요한 페이지만 메모리에 적재하는 기법
처음부터 아무런 페이지를 적재하지 않지만, 페이지 요청에 따라 적재하게 되며 시간이 지날수록 페이지 폴트가 줄어드는 방식
페이지를 모두 적재하게되면 메모리가 가득 찰 것이다. 이때 어떤 페이지를 보내야할지 판단해야한다. 페이지 교체 알고리즘은 페이지 폴트가 적을수록 좋다.
CPU가 참조하는 페이지들 중 연속된 페이지를 생략한 페이지열이다.
가장 우선적으로 적재된 페이지를 교체 대상으로 선정
사용 빈도가 가장 낮은 페이지 교체하는 알고리즘
가장 오래 사용되지 않은 페이지 교체하는 알고리즘 최적 페이지와 달리 지금까지 사용되지 않은 페이지는 앞으로도 사용되지 않을것이라 판단하여 이를 기준으로 한다.
페이지 폴트가 자주 발생하는 이유?
프로세스 크기에 맞춰 물리 메모리와 가상 메모리를 자르는 방식이다. 기존 페이징 방법과는 달리, 메모리 크기 단위가 다르기에 항상 시작 메모리 주소가 매핑 테이블에 포함되어있어야 한다.
메모리 접근 권한은 읽기, 쓰기, 실행, 추가등이 존재하는데 크게 읽기, 쓰기, 실행의 권한을 사용한다.
페이징 및 세그멘테이션 기법은 매핑 테이블에 메모리 접근 권한 정보를 가지고 주소 변환이 일어날때 유용한 접근인지 검사한다. 만약 권한이 없는 메모리에 해당 동작들을 수행하면 오류가 발생하게 된다.
페이징에서는 페이지마다 접근 권한이 다르기에 페이지 테이블 행에 접근 권한에 대한 정보를 가지는 권한 비트가 추가 된다. 메모리 관리자는 주소 변환이 일어날 때마다 페이지 테이블의 권한 비트로 판단한다.
하지만 페이지 테이블에 권한 비트가 추가됨에 따라 크기가 커지는데, 인접한 페이지의 경우 비슷한 접근 권한을 가지는 반면 페이지마다 권한 비트를 설정하기에 메모리 낭비가 발생한다.
페이지로 분할된 가상 주소 공간중 서로 연관있는 영역을 세그먼트로 묶어 테이블로 관리하고, 각 세그먼트를 구성하는 페이지를 해당하는 페이지 테이블로 관리한다. 이렇듯 중복되는 권한 비트를 세그멘테이션 테이블로 옮겨 와 테이블의 크기를 줄일 수 있다.
일반 스레드 간의 컨텍스트 스위칭과 프로세스간의 컨텍스트 스위칭을 비교한다면 당연하게도 프로세스간 스위칭이 느리다는것을 알 수 있다. 스레드간 컨텍스트 스위칭은 저장, 호출을 반복하는 반면 프로세스간 컨텍스트 스위칭시 한 단계가 추가로 들어가게 된다.