이제까지 앞에서 알아본 메모리 공간 관리는 모두 가변 크기를 사용하는 세그멘테이션 기법이었다.
가변 크기로 할당하게 되면 외부 단편화 문제가 발생하였고, 이러한 문제를 해결하기 위해 페이징 기법이 나오게 되었다.
즉 가상 메모리에서 page를 실제 메모리에서는 page frame이라고 불리는 고정 크기의 공간으로 프로세스를 실행하는 것이다.
위 좌측 그림에서 64바이트의 크기의 가상주소공간의 크기를 16바이트 4개로 쪼개놨다. 여기서 16바이트 공간을 페이지라고 부른다.
위 우측 그림은 실제 메모리이며 128바이트의 메모리를 16 바이트씩 8개로 쪼개놨다. 여기서 16바이트 공간을 페이지 프레임이라고 부른다.
각 페이지들에 대한 실제 메모리 위치를 기억하고 이를 활용해 주소 변환이 가능하다.
페이징을 사용할 때에 모든 프로세스는 페이지 테이블이 존재한다.
가상 주소를 물리 주소로 변환하기 위해 프로세스마다 페이지 테이블을 사용하는 것이다.
프로세서의 가상 주소를 변환하기 위해서는 vpn(virtual page number)과 offset이 있어야 한다.
맨 위 그림은 64바이트 크기의 가상 공간을 16바이트로 나눠 총 4개의 페이지를 사용하였다.
4개의 페이지를 구분하기 위해 상위 2비트인 VPN에 저장하는 것이다.
그리고 하위 4비트는 offset을 통해 각 페이지의 자세한 위치를 알 수 있다.
위 그림에서는 페이지의 크기가 16바이트이므로 4개의 비트가 offset으로 사용이 되었다.
위 그림에서 010101b는 21의 이진수이며 상위 두비트가 01이기 때문에 두번째 페이지의 하위 4비트 0101에 해당하는 5바이트만큼 오프셋 된 위치를 가리킨다.
이 페이지 테이블의 2번째 인덱스에는 실제 메모리의 프레임 넘버가 저장되어 있다. 이 정보를 통해 PFN으로 변환하면 실제 메모리 주소를 얻을 수 있다.
따라서 VPN을 PFN으로 변환해주고 offset은 그대로 사용하는 것이다. 그렇게 되면 실제 메모리 주소는 1110101b가 된다.
가상 주소를 물리 주소로 변환하기 위한 자료 구조이다. (= 배열)
단순화 하면 선형 페이지 테이블이다.
OS는 VPN을 인덱스로 페이지 테이블을 읽고 쓴다.
VPN으로 해당 프로세스의 페이지 테이블 배열을 인덱싱하고 해당 인덱스 페이지 테이블 앤트리(PTE)를 조회한다.
PTE는 각 페이지의 정보
P : 이 페이지가 물리 메모리에 존재하는가? 아니면 디스크에 존재하는가?
R/W : 읽어도 되는가? 써도 되는가? 실행해도 되는가?
U/S : user mode 프로세스가 페이지에 접근할 수 있는가?
A : 한번이라도 참조한적이 있는가
D : page가 수정된 적이 있는가
PFN : 페이지 프레임 넘버
! 그러나 페이징은 너무 느리다.
해당 PTE를 얻기 위해서는 페이지 테이블의 시작 위치를 알아야 한다.
모든 메모리 접근에서 HW는 추가로 메모리에 접근해야 한다.
명령어, 페이지테이블 둘다 읽기엔 속도가 저하된다.
페이징에서는 잦은 메모리 접근과 페이지 테이블을 위한 메모리 낭비가 있기 때문에 이를 보완하기 위해 나온 것이 TLB이다.
TLB는 CPU의 메모리 관리 장치(MMU)의 일부분이다.
TLB에는 VPN과 PFN 정보가 쌍으로 존재하며 이를 사용해 주소 변환을 도와주는 하드웨어는 바로 캐쉬이다.
간단히 설명하여
TLB로 인한 성능 향상을 알아보자
c언어에서 int는 4바이트이기 때문에 배열의 크기는 40바이트이다.
이 배열이 가상 주소 공간 100에서 시작한다고 하였을 때, 페이지 테이블은 다음과 같다.
배열 요소 하나당 4바이트이며 페이지 크기가 16바이트이기 때문에 page 공간에 4개의 배열 요소가 들어갈 수 있다.
TLB 미스가 나면 페이지 테이블에 접근해야 하는데 과연 TLB 미스는 누가 처리를 하는 것일까?
이렇게 OS로 TLB를 관리하게 되면 하드웨어의 변경 없이 페이지 테이블 구현하는 모든 자료 구조를 사용이 가능하다.
미스가 발생해도 하드웨어가 할 일은 별로 없고 OS TLB Miss 핸들러가 작동하여 해결한다.
TLB의 구성이다.
TLB를 사용할 때, Context Switch가 발생한다면 어떻게 될까?
여러 프로세스는 각자의 가상 메모리 공간이 존재하고 page로 쪼개어지며 페이지 테이블의 인덱스가 같은 경우가 있다.
예를 들어 A의 VPN1에 대한 정보가 TLB에 저장 되어 있는데 context switch가 발생하여 B가 실행된다면
B에서도 VPN1에 대한 주소 변환을 하려고 TLB를 확인할 때, VPN1이 값이 같아 TLB에서 주소 변환을 하면 문제가 발생한다.
다음과 같이 A(위), B(아래)의 VPN 10에 대한 주소 변환 정보가 TLB에 저장이 되어 있다.
VPN은 동일하지만 PFN은 동일하지 않다.
하지만 위 그림과 같이 TLB에 저장하면 어떤 프로세스인지 알 수 없다. 딱 봐도 모르겠다.
서로의 주소 공간이 아닌 곳에 잘못 접근할 수 있어 큰 문제가 될 수 있다.
만약 2개의 프로세스가 페이지를 공유한다면?
위와 같이 PFN이 동일하면 메모리의 사용을 줄일 수 있어 메모리 공간을 더 확보 가능
LRU(Least Recently Used)
사용한지 오래된 엔트리 방출
지역성을 이용
모든 프로세스당 한 개씩 페이지 테이블이 필요하다.
32비트 주소 공간에서 4KB 크기의 페이지를 사용하고 페이지 테이블 엔트리로 4바이트를 사용한다면
페이지 테이블은 매우 커서 많은 메모리가 소비 된다.
그럼 만약 페이지의 크기를 늘린다면 페이지 테이블의 크기를 줄일 수 있을 것이다.
위 그림과 같이 하나의 페이지를 4KB가 아닌 16KB로 늘릴 경우
페이지 테이블의 크기는 1MB가 되는 것이다.
기존 테이블 크기보다 1/4로 감소한 것을 볼 수 있다.
그러나 당연히 이렇게 큰 페이지는 내부 단편화가 발생할 수 있다.
다음 그림과 같이 페이지 테이블의 대부분이 미사용중인 것을 볼 수 있다.
페이지 테이블로 인한 메모리 부담을 줄이기 위해 사용되는 기법이다.
세그멘테이션에서 살펴본것과 같이 모든 프로세스는 Code, Heap, Stack 총 3개의 세그먼트를 갖고있고 이에 해당하는 3개의 페이지 테이블을 갖는다.
실행 중인 프로세스에서 각 세그먼트의 베이스 레지스터는 각 세그먼트 페이지 테이블의 시작 물리 주소를 갖게 된다.
문맥 교환 시, 레지스터들은 새로 실행되는 프로세스의 페이지 테이블의 위치값으로 변경된다.
TLB 미스 발생시 하드웨어는 세그먼트 비트를 사용하여 어떤 베이스와 바운드 쌍울 사용할지 결정한다.
하드웨어는 그 레지스터에 들어 있는 물리 주소를 VPN과 다음과 같은 형식으로 조작하여 PTE의 주소를 얻는다.
따라서 바운드 레지스터가 세그먼트의 페이지 테이블의 끝 값을 가지므로 사용하지 않는 페이지 테이블의 공간을 유지할 필요가 없다.
그러나 이 하이브리드 방법은 세그멘테이션의 문제인 외부 단편화가 있다.
또한 세그멘테이션 방법을 사용할 때 생각해야 하는 연속된 메모리 공간을 관리해야 한다.
정말 외부 단편화, 내부 단편화 이리저리 화가난다.
긴 페이지 테이블을 트리 형식으로 변경한 것이다.
위 그림에서 왼쪽에는 기존의 페이지 테이블, 오른쪽에는 멀티 레벨 페이지 테이블이 있다.
왼쪽 페이지 테이블은 1개의 페이지 테이블은 전혀 사용하고 있지 않고 낭비가 되고 있다.
오른쪽은 페이지 디렉토리라는 자료 구조를 사용하여 실제 사용중인 2개의 페이지테이블에만 접근하고 있다.
이렇게 주소 변환에 한 번의 단계가 추가되어 멀티 레벨이라 부르는 것이다.
페이지 디렉토리는 PDE들로 구성되어 있고, 하나의 PDE는 페이지 크기의 페이지 테이블 조각 하나를 담당한다.
그렇다면 멀티 레벨 페이지 테이블의 장단점을 요약해보자
다음과 같이 64비트의 페이지를 갖는 16KB 크기의 주소 공간이 있다.
VPN은 8비트, 오프셋은 6비트가 필요하다.
가상 페이지 0, 1은 코드, 4, 5는 힙, 254, 255는 스택으로 사용된다. 그리고 나머지 페이지들은 미사용이다.
PTE는 4바이트라 가정하였을 때, 선형 페이지 테이블을 사용하면 페이지 테이블 하나의 크기는 2^10 즉, 1KB이다.
이 상황에서 페이지 디렉토리를 생성해보자
VPN으로부터 페이지 디렉토리 인덱스를 추출하고, 페이지 데이블의 각 페이지 위치를 파악하면 된다.
위 예에서 256개의 PTE가 16페이지에 나뉘어져 있다.
페이지 디렉토리는 페이지 테이블의 각 페이지마다 있어야 하므로 총 16개의 항목이 존재해야 한다.
따라서 VPN4개의 비트를 이용해 위 그림과 같이 디렉토리를 구성한다
페이지 디렉토리의 해당 항목이 유효하지 않다면 예외 처리를 발생시킨다.
유효하다면 더 나아가 페이지 테이블 인덱스로 페이지 테이블 내부에서 PTE를 찾는다.
페이지 테이블 크기를 줄이는 방법은 페이지 테이블을 거꾸로 적용하는 것이다.
즉 프로세스마다 페이지 테이블이 하나씩 존재하는 것이 아닌
모든 프로세스가 하나의 페이지 테이블을 사용하는 것이다.
주소 변환 시간에 검색이 필요하다.
페이지 테이블을 아예 디스크에 저장하여 메모리 낭비를 줄이는 방법을 설명하고자 한다
교체 정책의 목적은 캐시 미스를 줄이는 것이다.
캐시 미스 확률을 통해 평균 메모리 접근 시간을 얻을 수 있다.
평균 메모리 접근 시간 = (캐시 히트 수 x 메모리 1회 접근 시간) + (캐시 미스 수 x 디스크 1회 접근 시간)
tracing the optimal policy는 다음과 같이 동작한다.
처음에 아무것도 없으니 3번까지 미스가 발생한다. -> 강제미스
캐시에 0, 1, 2가 존재하는데 3번 페이를 요청하고 따라서 페이지 폴트가 일어나 페이지를 교체해야 한다.
optimal에서는 페이지 폴트가 발생한 시점에서 가장 나중에 사용되는 페이지 2를 교체한다.
그후 미스에서는 0, 3중 랜덤으로 3을 교체하여 5 / 11의 미스율을 보여준다.
모두 알다시피 메모리에 올라온 순서대로 페이지를 내보내는 것이다.
교체가 발생하게 되면 큐의 앞에 있는 페이지가 방출된다.
썩 좋지 않다.
미스 비율이 옵티말보다 높아졌다.
할당된 물리 프레임 수가 커질수록 페이지 적중률이 올라갈 것 같지만 FIFO에서 오히려 떨어지는 현상
메모리 압박시 랜덤한 페이지를 내보낸다.
가끔씩 최적과 비슷한 성능을 보인다.
LRU : 과거를 보고 교체할 페이지를 선택한다. 최근에 접근된 페이지는 곧 또 접근될 것이라는 아이디어 -> 지역성 활용
따라서 가장 과거에 사용한 페이지를 교체하는 것이다.
최소 최근 접근 페이지를 교체한다.
위그림은 지역성이 없으며 총 페이지 수가 100개일 때의 결과이다.
-> 100개의 페이지에 총 10000번 접근
즉 페이지 요청이 랜덤하게 발생할 때의 결과이다.
이를 보면 지역성이 적용되지 않을 시 모든 방법의 결과가 크데 다르지 않다는 것을 볼 수 있다.
위 그림은 총 접근의 80%가 전체 페이지의 20%에 존재할 때이다.
지역성이 고려되어 있고 LRU가 더 나은 결과를 보여주고 있다.
LRU는 접근이 많은 페이지를 유지할 가능성이 크기 때문에 페이지 폴트 비용이 커질수록 성능이 향상한다.
마지막으로 0~49페이지까지 순서대로 50개의 페이지를 참조하는 것을 반복하는 상황이다.
LRU방식은 메모리 접근 정보를 기록해야 한다.
따라서 메모리 크기가 커진다면 페이지 수가 많아져 LRU페이지를 찾는데 소요되는 시간이 너무 커진다.
use bit형식의 하드웨어 지원이 필요하다.
페이지 하나당 use bit가 존재한다.
page가 참조될 때마다 1로 업데이트 된다.
페이지가 참조되면 use bit는 HW에 의해 1이 된다.
-> HW는 그 비트를 초기화 하지 않는다. OS가 필요할 때 초기화 한다.
그러면 이 LRU를 어떻게 구현할까
-> 간단한 접근 방법은 시계 알고리즘이다.
시계 바늘은 use bit가 0인 페이지를 만날 때 까지 이동한다.
1이라면 최근에 사용되었다고 판단하고 0으로 바꾼 뒤 다른 페이지를 찾는다.
0인 페이지를 만나면 해당 페이지를 교체한다.
다음과 같이 LRU보다는 못하지만 과거 정보를 활용하지 않는 다른 방법보다는 낫다.
HW로 구현된 변경 비트(modified bit, dirty bit)
-> 페이지가 변경 되었다면 방출될 때 디스크에 내용을 기록해야 한다.
-> 변경되지 않았다면, 방출 비용은 없다.
갱신되지 않고 사용 되지도 않은 페이지를 우선 방출하고, 없으면 변경되고 사용되지 않는 페이지를 방출한다.
페이지를 언제 메모리에 로딩할 것인가?
여러가지 옵션이 있음
메모리 사용 요구가 감당할 수 없을 만큼 많고, 실행 중인 프로세스가 요구하는 메모리가 가용 물리 메모리 크기를 초과하는 경우