거북이의 쉼터

(2022.05.27) Address Translation (1) 본문

코딩 삽질/OS 요약 정리

(2022.05.27) Address Translation (1)

onlim 2022. 5. 27. 13:43

드디어 면접이 끝나 미뤄놨던 요약 정리를 다시 시작한다. 사실 그렇게 빡세게 해야 할까라고 생각한 것이 중간 평균이 48이었기에 A- 컷은 기말을 정말 조지지 않은 이상 달성했기 때문이다. 그리고 교수님이 수업에 강조했거나, 직접 풀어보라고 언급한 부분이 주로 시험에 나온다는 사실을 알게 되어서 그 부분만 중점적으로 다루면 고득점을 할 수 있겠다고 생각했다. 그래서 중간보다는 요약은 설렁설렁할까 한다. 대신 거의 단순 받아적기 수준이라 분량이 뻥튀기 된다 ㅋㅋ

 

중간 이후에 다룰 첫 주제는 Memory Management이다. 메모리는 하드웨어이니 OS 디자이너가 해야할 첫 단계는 abstraction을 구상하는 것이다. 직접적으로 물리 메모리를 프로세스에게 노출하는 것은 protection에 위배되어 위험하며, 프로그래밍하는 것을 어렵게 하기 때문에 가상 메모리라는 추상화를 설계한다. 가상 메모리는 Protection을 위한 것이기에 안전하며, 편리함과 효율성 또한 제공한다. 가상 메모리 공간의 주소를 물리 메모리 공간의 주소로 번역하기 위해 Memory Management Unit이라는 하드웨어를 사용할 것이다. 

 

MMU는 요청된 가상 주소가 정당할 경우 연동된 물리 주소에 접근한다. 만약 정당하지 않다면 커널에게 페이지 폴트를 일으켜서 프로세스를 죽여버린다. 여기서 질문은 어떻게 해당 주소가 정당한지 아닌지를 판단할까? 바로 페이지 테이블을 참조하는 것이다. 해당 테이블은 OS에 의해 세팅되며 HW(MMU)는 이를 참조만 할 뿐이다. 만약 매핑이 되어있지 않다면, HW가 페이지 폴트를 내는 것이다. 어찌보면 HW는 번역의 mechanism을 제공하고, OS는 번역의 policy을 제공한다고 할 수 있을 것이다. 만약 이런 기반이 정해져있지 않았다면, 커널은 모든 주소가 안전한 주소인지 SW적으로 검사를 할 필요가 있기에 이는 성능 측면에서 심각한 저하를 일으킬 것이다. 

 

주소 번역의 디자인 목적은 다음과 같다.

 

1. Memory Protection

한 어플리케이션이 다른 어플리케이션의 메모리에 간섭하는 것을 방지한다.

 

2. Memory Sharing

어떠한 경우에는 Sharing이 필요하기도 하므로 이를 어떻게 달성할 것인지가 문제이다.

 

3. Sparse Address

모든 가상 주소가 사용되는 것은 아니며, 각 메모리 영역은 크기가 동적으로 변하기도 하기 때문에 이를 어떻게 효율적으로 관리할 수 있는지의 문제이다. 

 

4. Efficiency

번역에는 비용이 들어간다. 얼마나 많은 lookup이 필요한지, 그리고 페이지 테이블이 메모리 내에서 차지하고 있는 크기가 얼마인지가 대표적인 번역의 cost이다. 이를 절감하는 것 또한 목적 중 하나이다. 

 

가상 메모리 매핑에는 두 가지 중요한 요소가 작용하고 있다. 아키텍쳐가 메카니즘을 제공하고, OS는 그 내용을 채워 넣어서 policy를 제공하는 것이다. 위의 목적을 달성하는 것은 모두 매핑 메커니즘을 어떻게 설계하는가에 달린 문제이다. 매핑 메커니즘의 대표적 예시로는 Base and Bound, Segmentation, Paging을 꼽을 수 있다.

1. Base and Bound

해당 방식은 Base와 Bound 레지스터 2개를 사용해 가상 주소를 물리 주소로 변환한다. 프로세스 입장에서는 가장 주소를 사용해 가상 메모리의 영역에 직접 접근하는 것으로 보인다. 예를 들어 가상 주소 100에 접근하려고 하면 가상 메모리의 100번째 영역에 접근하는 것으로 보일 것이다. 그러나 실제로 구현된 것은 다음과 같다: 접근하려는 가상주소를 가지고 Bound 레지스터와 비교를 해서 만약 접근하려고 하는 주소가 Bound를 초과한다면 Exception을 일으킨다. 만약 제한된 범위 이내라면 Base 레지스터와 더해 물리 메모리 상에서 해당 위치에 있는 메모리에 접근한다. 예를 들어 Base 레지스터의 값이 1000이라면 물리 주소 1100에 접근하는 것이다. 각 프로세스는 고유한 Base, Bound 레지스터 값을 가지도록 하고, 각 영역이 겹치지 않도록 한다면 Protection을 달성할 수 있다. Context Switch가 일어날 경우, CPU에 위치한 Base와 Bound 레지스터 값을 바꿔주도록 한다. 

 

해당 방식은 굉장히 간단하며, 두 개의 레지스터와 덧셈, 비교 연산만을 사용하기 때문에 빠른 방식이다. 단점은 메카니즘 자체가 간단한 만큼 우리가 달성하고자 하는 목표를 충족하지 못한다는 점이다. 프로세스는 본인의 code를 바꾸지 않아야 한다. 그러나 해당 방식으로는 section의 분리가 없고, 사용되는 모든 메모리 공간이 하나의 거대한 chunk로 떨어지기 때문에 code 영역만 권한 설정을 따로 하는 것이 불가능하다. 따라서 프로세스가 실수로 code를 바꾸는 것을 막는 것이 어렵다. 또한 프로세스는 자신의 code/data 영역을 다른 프로세스와 share 할 수 없으며, stack/heap을 필요한 만큼 자라게 하는 것 또한 불가능하다.

2. Segmentation

메모리 영역을 용도에 따라 세분화하여, 각 영역을 Base and Bound 식으로 운영한다면 이전처럼 영역이 구분되지 않아 생기는 문제를 해결할 수 있을 것이다. 이를 위해 개발된 매핑 메커니즘이 Segmentation이다. Segmentation에서는 HW 디자이너들이 구분해놓은 4개의 영역인 code, data, heap, stack 각 영역이 서로 고유한 base and bound 값을 갖도록 한다. 이 값들을 테이블에 기록해 놓은 것이 Segment Table이며, 각 프로세스는 Segment Table을 Physical Memory에 가지고 있다. 해당 테이블에는 각 영역의 permission, 즉 read/write 권한 또한 표기할 수 있기 때문에 Base & Bound에서 할 수 없었던 권한 분리 또한 이룰 수 있다. Sharing 또한 가능하다. 세그먼트 테이블의 엔트리가 같은 Base, 같은 Bound 값을 가진다면 같은 영역을 가리키는 형태가 될 것이기 때문에 메모리 공유가 되는 것이다.

 

Segmentation에서 가상 주소의 상위 비트들은 해당 주소가 어떤 세그먼트에 속하는지를 나타내는데 사용된다. 핀토스 부트 코드에서 mov cs : xxx 같은 코드를 볼 수 있는데, 이는 상위 비트를 해석할 때 code segment에 해당하는 가상 주소를 가지고 연산을 한다는 것을 의미한다. 테이블이 메모리에 위치하기 때문에 Context Switch가 일어나도 테이블 전체가 바뀌는 일은 일어나지 않는다. 다만 CPU에서는 각 프로세스마다 번역에 사용할 테이블의 위치를 나타내는 레지스터가 존재하며, 이를 참조해서 Segment Table을 찾기 때문에 해당 레지스터만 교체된다. 

 

위의 그림에서 직접 번역을 해보도록 하자. 상위 2bit는 세그먼트를 찾는데 사용되며, 하위 12bit는 offset을 나타낸다. 0x0240은 상위 비트가 0b00이므로, Base는 0x4000, Bound는 0x6ff이다. 0x240 < 0x6ff이므로 valid한 가상주소이고, Base와 더해 대응하는 물리주소는 0x4240이 된다. 0x265c는 상위 비트가 0b10이므로, Base는 0x3000, Bound는 0xfff이다. 0x65c < 0xfff이므로, valid하며 대응하는 물리 주소는 0x365c이다. 마지막으로 0x3002는 상위 비트가 0b11인데, 이는 테이블 상 존재하는 Base와 Bound값이 없어 invalid한 주소가 되며, fault가 일어날 것이다. 이런 세그먼트 테이블은 OS가 채워넣고, 이를 사용한 주소 번역은 HW가 한다. (kernel sets, HW interpretes)

 

Segmentation을 활용하면 COW를 활용하여 fork를 효율적으로 할 수 있다. fork가 일어날 때, 각 세그먼트를 새롭게 만드는 것이 아닌, 세그먼트 테이블의 내용만을 복사하여 새롭게 만든다. 해당 방식대로면 두 프로세스는 서로 같은 물리 메모리 공간을 공유하게 된다. 이 때, 부모와 자식 프로세스가 공유하는 세그먼트는 모두 read-only로 만들어놓는다. 이렇게 할 경우 한 프로세스에서 공유된 영역에 write 시도가 있을 때, kernel로 fault가 들어올 것이기에 이에 맞춰 COW 대응을 할 수 있기 때문이다. 해당 write는 read-only 영역에 쓰기를 한 것이므로, seg fault (exception)이 일어나게 되어 trap이 발생하는 것이며, 해당 fault를 핸들링하면서 세그먼트 복사가 일어난 뒤, 반환되게 된다. 이 과정은 프로세스에게는 보이지 않게 진행된다. (transparent) 이 때, 한 가지 문제는 세그먼트가 너무 클 경우이다. 작은 부분에서 write가 일어나더라도 현재 구현상 전체 세그먼트를 복사해야만 해결할 수 있다. 

 

Segmentation을 활용하면 부가적으로 Zero-on-Reference라는 기능을 사용할 수 있게 된다. stack과 heap은 엄밀히 말하면 사용중일 때만 필요한 영역이다. 쓸지 안 쓸지도 모르는 영역인데, 프로세스 시작부터 영역을 할당하는 것은 비효율적일수도 있다는 것이다. Zero-on-Reference는 오직 필요한 순간에 해당 영역을 0으로 초기화하여 (zero out) 할당해주는 것을 의미한다. 엄밀히는 할당할 때 zero out하는 것만 포함하지만, 강의 때는 demand paging과 결합하여 설명하였다고 한다. 물리 영역을 zero out하여 반환하는 이유는 기존 프로세스의 흔적이 남아있을 수 있어 이를 다른 프로세스에 노출하지 않기 위해서이다. 메모리는 크게 파일에 원본 내용이 존재하는 file backed memory와, stack/heap 같이 그 원본이 존재하지 않고 요청시에만 생성되는 anonymous memory로 분류할 수 있는데, Zero-on-Reference는 이러한 Anon 메모리에서 발생한다.

 

예시로, 만약 프로세스가 현재 할당된 stack의 범위를 넘어서 메모리 공간을 사용하려고 할 때의 과정을 살펴보자. 우선 OS 커널로 세그먼테이션 폴트가 발생할 것이다. 커널은, 만약 요청이 정당하다면, stack에 추가할 어느 정도의 메모리를 할당한다. 해당 영역을 zero-out한 뒤에 세그먼트 테이블을 추가된 영역을 포함하도록 정정하고, 프로세스로 반환하면 모든 준비는 끝난다.

 

Segmentation의 장점은 다음과 같다.

1. code/data 세그먼트를 다른 프로세스와 공유할 수 있다. 

2. code 세그먼트가 overwrite되는 것을 방지할 수 있다.

3. 프로세스가 알아차리지 못하게 하면서 stack/heap이 필요한 만큼 자라게 할 수 있다.

4. COW가 필요한지 감지해낼 수 있다. 

 

단점은 다음과 같다.

1. 메모리 관리가 복잡하다.

2. Fragmentation이 발생하여, 새로운 세그먼트를 생성하거나 기존의 세그먼트를 자라게 할때, 메모리를 정리해줄 필요가 생길 수 있다. (물론 Base & Bound에서도 발생)

 

Fragmentation은 CS230을 들었다면 익숙할 것이다. 크게 internal과 external fragmentation으로 분류할 수 있는데, external은 중간중간 남는 공간이 많아 수치상으로는 할당할 수 있는데, contiguity가 없어 할당이 불가능한 경우이며, internal은 할당해준 내부 공간이 너무 많이 남을 때를 지칭하는 경우이다. External Fragmentation에는 해법이 있다. 바로 Compacting이다. 이는 모든 세그먼트를 조금씩 이동시켜 연속된 공간을 만들어내는 것으로 고비용의 해법이다. 그래도 해법이 있는 것에 비해 Segmentation에서 Internal Fragmentation은 해결 자체가 어렵다. 

 

다시 본질로 돌아가자. External Fragmentation이 애초에 발생하는 원인은 각 chunk 별로 사이즈가 고정되어 있지 않기 때문이다. 그럼 고정된 사이즈의 chunk를 사용한다면 external fragmentation을 완전히 제거할 수 있지 않을까? 그래서 개발된 것이 Paging, 또는 Paged Translation이다. 

4. Paged Translation

Paging에서는 메모리 전체를 "페이지"라는 고정된 크기의 영역으로 등분한다. 이는 External Fragmentation을 완전히 제거하는 방법이지만, 이는 거저 오는 혜택이 아니다. 세그먼트 방식과 유사하게 각 프로세스는 메모리에 페이지 테이블을 들고 있고, HW는 페이지 테이블의 시작점과 페이지 테이블의 길이를 레지스터에 저장해두고 페이지 테이블을 사용할 수 있도록 한다. Context Switch 때는 해당 레지스터들이 교체된다. OS는 해당 페이지들의 할당 여부를 bitmap으로 관리하기 때문에 비어있는 페이지를 찾아내는 것은 쉽다. 하나의 Virtual Page와 하나의 Physical Frame의 사이즈는 같아야 한다. 페이지 사이즈가 너무 크면, Internal Fragmentation이 발생한다. 그렇다고 페이지 사이즈가 너무 작으면, 페이지 테이블의 크기가 너무 커진다. 이러한 페이지의 특성을 고려해서 4kB 정도의 크기가 적당하다는 판단이 암묵적으로 이뤄졌고, 이 때문에 각종 표준에서 4kB 사이즈의 페이지가 사용되는 것이다. 

 

페이지 테이블은 구현하는 방식이 여러개인데, 일단 지금은 통짜로 한 개의 페이지 테이블이 메모리에 위치한다고 가정하자. 가상 주소의 상위 비트는 페이지 테이블 내에서 해당하는 엔트리의 오프셋을 나타내는 것이며, 하위 비트는 실제 프레임에서의 오프셋을 나타내는 것이다. 4kB 페이지에서는 12 bit가, 2MB페이지에서는 21 bit가 프레임 오프셋을 나타낸다. 번역 예시를 살펴보면 hex로 하위 3자리를 제외하고 나머지 상위 비트는 VPN (Virtual Page Number)를 나타내는 것을 볼 수 있다. 

 

페이징에 관한 몇 가지 사항들을 다시 살펴보면

1. Context Switch가 일어날 때 save/restore되는 것은 페이지 테이블의 포인터와 사이즈 레지스터이다.

2. 페이지 사이즈가 너무 작으면 페이지 테이블의 크기가 너무 커진다.

3. 페이지 사이즈가 너무 커지면 internal fragmentation이 발생한다.

 

페이징과 세그먼테이션의 테이블 크기를 비교하면 페이지 테이블이 압도적으로 크다. 


페이징으로 어떻게 Copy on Write를 구현하는지 살펴본다. 페이징에서 프로세스간 메모리를 공유하는 방법은 서로 다른 프로세스의 페이지가 같은 프레임으로 매핑되도록 페이지 테이블 엔트리를 세팅하면된다. sharing이 일어나는 도중에 한 프로세스가 종료되거나, 해당 페이지를 더 이상 필요로 하지 않을 경우에도, 다른 프로세스가 여전히 해당 프레임를 요구하는 경우에는 함부로 날려서는 안되기 때문에 OS는 얼마나 많은 프로세스가 단일 프레임을 share하고 있는지를 알고 있을 필요가 있다. 이를 reference count라고 하며, sharing에 반드시 요구된다. 물론 segmentation에서도 sharing이 사용될 경우 도입되어야 할 개념이지만 여기서 소개했다. 

 

fork가 될 때의 과정을 살펴보면 아래와 같다.

1. 부모의 페이지 테이블을 복사해서 자식의 페이지 테이블로 사용한다.

2. 모든 페이지를 read-only로 표기한다. 

3. write 요청시에는 page fault가 일어난다.

4. fault가 난 페이지를 복사한다.

5. 복사된 페이지와 원본 페이지를 모두 writable로 정정한다. 복사가 된 이후에 다른 프로세스가 접근할 때는 page fault가 날 필요가 없다.

6. 실행을 재개한다.

 

만약 sharing이 하나의 부모와 두 개의 자식간에 일어났을 경우에는 복사되어 분리된 페이지는 writable로 표기하되, 공유가 지속되고 있는 페이지는 read-only로 남겨놔야 한다. writable로 복구해주는 순간은 sharing이 일어나고 있지 않을 때이다. 


이제 Sparse Address Space에 대해 살펴본다. 최근 어플리케이션은 큰 가상 공간을 사용하지만, 모두 사용하지는 않는다. 이를 Sparse Address Space라고 한다. 이러한 특징은 페이징이 dynamminc mapping을 쉽게 해줄 수 있게 하지만, 큰 주소 공간으로 인해 요구되는 페이지 테이블의 크기가 비약적으로 커지는 단점이 있다. 실제로는 사용되지 않을 엔트리가 대부분이지만 언젠가는 사용될 수도 있다는 우려 때문에 공간을 잡아먹게 되는 것이다. 예시로 32 bit 주소 공간의 4kB 페이지라면 2^20개의 엔트리, 64bit라면 2^52개의 엔트리를 위한 메모리 공간이 할당되어야 한다. 페이지 테이블에만 이 정도의 DRAM을 낭비하는 것은 심각한 낭비이기에 Sparse Address Space에 맞춰 페이지 테이블이 소모하는 공간을 줄이려는 시도가 있었다.

 

이를 압축하는 방법론으로 간단한 예시를 보자. 만약 0부터 100000까지의 모든 숫자를 저장할 필요가 있다면 가장 적합한 구조는 Array일 것이다. 그러나 이들 중 4개 정도의 숫자만 필요하다면 어떤 자료구조가 필요할까? Linked List, Hash, Tree 등 여러 자료구조가 있겠지만 아키텍쳐 상으로 구현하기 편리한 것은 Tree 구조이다. 페이지 테이블 또한 하나의 거대한 페이지 테이블을 만들어 관리하기보다는, 페이지 테이블을 계층화시켜 Tree로 만든다면 메모리 매핑이 필요하지 않은 영역을 매핑하는 부분을 담당하는 페이지 테이블을 생성하지 않아도 되니 DRAM을 아낄 수 있을 것이다. 이를 위한 두 가지 방식이 제시되어 사용된다.

4-1. Paged Segmentation

프로세스 메모리는 segmented 된다. 그러나 일반적인 세그먼테이션에서 세그먼트 테이블 엔트리가 물리 메모리를 매핑했던 것과 달리 각 엔트리는 페이지 테이블을 가리키는 포인터와 그 페이지 테이블의 길이(해당 세그먼트에 들어있는 페이지 개수), 세그먼트의 권한을 갖는다. 즉, 각 세그먼트를 다시 페이징 형식으로 관리하는 것이다. Sharing과 Protection은 페이지 단계나 세그먼트 단계에서 일어날 수 있다. 

 

가상 주소는 3단으로 분할되어 각각 세그먼트, 페이지 #, 오프셋으로 사용된다. 프로세서는 각 프로세스마다 사용되는 세그먼트 테이블의 위치를 찾을 수 있는 레지스터를 가지고 있으며, 세그먼트 테이블과 페이지 테이블 모두 DRAM에 존재한다. 세그먼트 단계의 번역을 먼저 쓰는 이유는 세그먼트가 더 넓은 영역을 지정할 수 있고, 페이지는 그 안에서의 부분적인 영역을 할 수 있기 때문에 세그먼트가 페이지보다 더 큰 컨셉이기 때문이다. 기존에 세그먼트는 물리 공간 내에서도 연속적이어야 했지만, 이 방식에서는 물리 공간에서는 연속적일 필요가 없다. 세그먼트 테이블과 페이지 테이블에서의 access bit을 살펴보면 세그먼트 테이블 단계에서 지정한 access bit에 페이지 테이블의 access bit가 영향을 받게 된다. 때문에 페이지 테이블의 access bit는 필요하지 않을 수 있다.

 

마지막으로 sharing을 어떻게 할 수 있는지 생각해보자. sharing은 페이지 테이블 엔트리가 동일하기만 해도 같은 프레임을 가리키기 때문에 페이지 테이블 단계에서만 sharing이 일어나도 충분하다. 더 복잡한 작동은 본인 스스로 생각해보자. Context Switch에서는 각 프로세스가 메모리 내에 세그먼트와 페이지 테이블을 가지고 있기 때문에 교체되는 것은 세그먼트 테이블을 가리키는 레지스터만이다. 해당 방식이 페이지 테이블의 크기를 줄이는 것은 페이지 테이블이 가상 공간 전체가 아닌, 오직 세그먼트의 내부에 해당하는 영역만 매핑하도록 생성되기 때문이다.

4-2. Multilevel Paging

해당 방식은 최신 아키텍쳐에서 가장 흔하게 사용되는 방법으로, 여러 단계의 페이지 테이블을 두어 주소 번역을 할 수 있도록 한다. 가상 주소의 VPN에 해당하는 부분을 더 잘개 쪼개, 각 부분을 여러 단계의 페이지 테이블의 오프셋으로 각각 활용한다. 각 단계의 페이지 테이블 엔트리는 다음 단계의 페이지 테이블의 physical address를 가지고 있으며, 마지막 단계의 페이지 테이블은 프레임 넘버를 가지고 있어 번역에 사용한다. 이는 페이지 테이블을 주로 순회하는 주체가 HW(MMU)이기 때문에 HW 입장에서 접근하기 편한 것이 physical address이기 때문이다. 이러한 특성 탓에 OS 커널에서 페이지 테이블을 채워넣을 때는 virtual에서 physical address로 번역해서 페이지 테이블을 채워넣어야 한다. 

 

Context Switching 시 가장 상위 레벨의 페이지 테이블을 가리키는 레지스터가 교체가 되며, 사용하지 않는 공간의 페이지 테이블을 할당하지 않음으로서 페이지 테이블의 memory overhead를 줄인다. 해당 방식의 장점은 사용되는 엔트리만 할당할 수 있으며, 물리 메모리가 고정된 사이즈의 페이지로 관리되는 점에서 메모리 할당이 간단하고, 페이지 레벨에서 정교한 sharing이 일어날 수 있다는 점이다. 단점은 단일 레벨 테이블에 비교해서 멀티 레벨 페이지 테이블은 번역 시간이 더 소모된다는 점이다. 단일 페이지 테이블에서는 단일 참조로도 찾을 수 있었던 매핑이 멀티 레벨에서는 복수의 메모리(DRAM) 참조를 요구하게 된다. 이를 translation overhead(cost)라고 부른다. 해당 overhead를 줄이기 위해 도입된 것이 바로 TLB이다. 이는 다음 포스팅에서 다룰 것이다.

5. 실제 구현 방식

TLB를 다루기 전에 실제로 인텔 HW가 페이지 테이블을 구현하는 방식을 알아야 한다. x86는 멀티 레벨 페이지 세그먼트 방식을 사용한다. x86에서 세그먼트 테이블에 해당하는 개념은 Global Descriptor Table (GDT)이며, Context Switch시에는 Global Descriptor Table Register (GDTR)을 교체한다. 멀티 레벨도 도입하고 있어 세그먼트마다 2단계의 페이지 테이블을 사용한다. x86-64에서는 세그먼트 방식이 deprecated되고, 멀티 레벨 방식만 사용한다고 하며, 4단계의 페이지 테이블이 사용된다. 

 

상위 단계의 테이블인 page directory와 하위 단계의 페이지 테이블은 각각 가상 주소의 상위 10개의 비트를 사용해 오프셋으로 접근하기 때문에 각 테이블은 1024개의 엔트리를 가진다. 페이지 디렉토리의 엔트리 내의 32 bit는 이런 방식으로 사용되며

 

하위 12bit는 정보를 저장하는데 사용한다. Present는 다음 테이블이 할당이 되었는가를 나타내는 정보이며, P가 0이면 존재하지 않으므로 접근시 fault가 일어난다. Read/Write는 작업 권한을 나타내며, 0이면 read-only, 1이면 write까지 가능하다는 것을 의미한다. User/Supervisor는 누가 해당 페이지에 접근할 수 있는지를 표시한다. 0이면 커널 같은 상위 권한자만, 1이면 유저도 접근 가능하다는 것을 의미한다. Access bit는 접근했다면 1로 설정되는 bit이다. 

 

페이지 테이블 엔트리는 다음과 같으며, 

 

PTE에는 dirty bit라는 것이 있어, 해당 페이지 write를 할 때 HW에 의해 1로 설정되는 bit이다. access bit와 dirty bit는 후에 자세히 다룰 것이다. 이제 다음으로 넘어가서 TLB에 대해 자세히 살펴보도록 하자.


마지막 잡담으로 매핑 메커니즘을 구현할 수 있는 다른 방식에 대해 생각해보자. 지금까지 다룬 모든 메커니즘은 VA에서 PA로 번역하는 방식을 다루었다. 어떠한 측면에서 보았을 때, 이는 f(VA) -> PA라고 생각할 수도 있다. 큰 범위를 가진 영역을 압축해 제한된 범위의 값으로 만드는 방법이 사용될 수 있을 것이다. 한 가지 방식은 Hash이다. 몇몇 연구에서는 이를 사용한 메커니즘을 개발했다고 한다. 또 하나 떠오르는 방식으로 주목받는 것은 ML이다. 뉴럴넷으로 VA를 넣으면 최적의 PA를 번역하도록 하는 방식이 연구되고 있다고 한다. 가능은 하겠지만... 많이 어려울 것 같다.

'코딩 삽질 > OS 요약 정리' 카테고리의 다른 글

(2022.05.27) Caching & Demand Page  (2) 2022.05.27
(2022.05.27) Address Translation (2)  (0) 2022.05.27
(22.04.11 ~ 04.13) Concurrency Bugs  (0) 2022.04.13
(22.04.11) Synchronization  (0) 2022.04.11
(2022.04.10) Scheduling  (0) 2022.04.10
Comments