NT의 가상 주소 변환

The Virtual Address Translation with Considering MMU and TLB

 

가상 주소를 변환 하는 것 자체는 매우 간단한 일이다. 가상 주소와 물리 주소의 매핑(Mapping)정보를 이용하여 단순히 해당 물리 주소의 정보를 가져오는 것으로 해결 할 수 있다. 하지만 여기에는 해당 컴퓨터 아키텍처의 성능을 고려 해야 한다. 이에 따라 이러한 매핑 정보들을 캐시하는 TLB (Translation Lookaside Buffer)가 일반적으로 사용된다. 이러한 TLB은 하드웨어적으로 구성되며 각 시스템의 아키텍쳐에 따라 완전히 다른 형태를 이루게 된다. NT 가상 메모리 매니저(VMM, Virtual Memory Manager)는 이종간의 TLB들을 관리하도록 하며 가상 주소와 물리 주소간의 번역을 하드웨어적으로 처리하는 MMU들의 동작을 범용적으로 추상화 하기 위해서 몇 가지 자료구조와 복잡한 알고리즘을 사용한다.

 

정명수 |

필자는 지난 3년간 삼성전자에서 플래시 메모리와 관련된 연구와 임베디드 소프트웨어, 커널 드라이버 등을 개발 했었다. 현재는 조지아 공대(Georgia Institute of Technology) 컴퓨팅 칼리지에 재학 중이다. 글쓰기를 매우 좋아하며 학부시절에는 객체 지향 패러다임을 통하여 해석하는 프로그래밍 언어론에 관심이 있었으나 실무과정을 거치면서 컴퓨터 아키텍처로 관심사가 옮겨졌다. 최근에 관심 있는 분야는 운영체제, 파일시스템, 실시간 스케줄링 등이다.

 

 

가상 주소와 물리 주소간의 번역은 아키텍쳐에 따라 매우 다를 수 있다. 설사 주소 변환 또는 번역 작업이 쉽게 이루어 질 수 있다고 하더라도 이를 수행하는 여러 가지 MMU와 TLB와 같은 하드웨어들을 도와 호환성과 이식성을 가진 운영체제를 만드는 것은 복잡한 데이터 조작과 하드웨어 관리를 필수적으로 수반한다. 이번 컬럼에서는 윈도우 NT가 VMM을 통해서 이러한 작업을 어떻게 수행 하는 지와 더불어 이를 위한 자료구조, 그리고 그들간의 연결 상태들을 알아보도록 하자.

 

만약 독자가 MMU와 TLB동작을 제대로 이해하지 못하고 있다면 필자의 블로그의 Fundamental Note의 OS 카테고리의 Virtual Address 란을 반드시 읽어보기 바란다. 본 컬럼에서는 가상 주소에 대한 기본을 다루는 것이 아니라 윈도우 NT를 중심으로 가상 주소를 운용하는 기법을 이야기 하는 것으로 가상 주소에 대한 기본 운영체제 지식이 수반 되지 않으면 전반적이 이해에 대하여 어려움을 겪을 수도 있다.

 

가상 주소 번역 (Translation of Virtual Address)

윈도우 NT의 각 가상 주소(Virtual Address)는 32 bit로 구성 된다. 이러한 가상 주소는 반드시 바이트 단위의 특정 주소 공간으로 번역 할 수 있는데, 이를 위해서는 크게 두 가지의 시스템 콤포넌트(Component)에 대한 이해가 필요하다. 첫째는 프로세서에 의해 제공되는 메모리 관리 유닛(MMU, Memory Management Unit, 이후 MMU로 언급)이고 다른 하나는 운영체제에 의해 구현된 가상 메모리 매니저(VMM, Virtual Memory Manager)이다. 디바이스 드라이버 개발자가 일반적으로 이해는 물리, 가상 주소간의 번역은 가상 주소에서 물리 주소로의 번역이 일반적이나, 반대로 물리 주소에서 가상 주소로의 번역 또한 가능하다. 예를 들어서 주소간 번역을 맡고 있는 페이지 매핑 테이블의 사이즈가 부족하여 특정 주소 공간의 컨텐츠를 메모리가 아닌 스토리지로 저장해서 공간을 확보해야 하는 경우, 해당 컨텐츠를 스토리지로 내보내고 나서 이를 무효화 처리를 해주어야 하는데 이때 해당 물리 주소가 어떤 가상 주소에 매핑 되어 있는지를 찾아야 적절한 무효화 처리를 해줄 수 있다.

 

통상적으로 가상 주소 번역은 MMU 하드웨어에 의해 이루어 지는데 이를 위해서는 VMM은 주소 번역에 필요한 적절한 맵(Map)들과 MMU가 실제 주소를 번역 할 때 사용되는 페이지 테이블(Page Table)들을 관리해주어야 한다. 일반적으로 윈도우 NT에서 VMM이 프로세스의 가상 주소 공간에 대한 정보를 구성하는 것은 프로세스가 컨텍스트 스위칭(Context Switching)을 처음 시도 할 때이다. VMM은 컨텍스트 스위칭이 시작 될 때 해당 프로세스의 적절한 물리 주소 번역에 대한 정보를 담아 두기 위해 페이지 테이블을 구성한다.

 

프로세스가 가상 주소 공간을 접근을 시도할 때 MMU는 가상 주소에서 물리 주소공간으로 번역을 시도한다. 이 때 일반적으로 해당 가상 주소 공간에 대한 매핑 정보를 필요로 하는데 해당 매핑 정보 또한 메모리에 테이블 형태로 존재하기 때문에 특정 프로세스가 가상 주소의 컨텐츠에 접근을 시도할 때 상당한 오버헤드가 존재 할 수 있다. 우선적으로 주소 번역을 위한 매핑 테이블을 메모리에서 한번 읽어드려야 하고 이렇게 읽어온 매핑 정보를 통하여 가상 주소를 물리주소로 번역한 뒤, 다시 메모리를 접근하여 원하는 컨텐츠를 확보 해야 한다. 따라서 하나의 가상 주소 접근을 위해 2번의 메모리 접근이 발생하므로 이를 줄이기 위해 TLB라는 매핑 정보에 대한 캐시가 통상적으로 제시 된다. TLB는 가장 최근에 번역 되어진 주소 관간의 정보들을 프로세스 ID(PID)를 통해서 분류하여 저장 해둔다.(PID를 통해서 각 프로세스마다 다른 주소 공간을 분류 하는 경우도 있고 아닌 경우도 있는데 이는 해당 컴퓨터 아키텍쳐(Architecture)에 따라 다르다) 따라서 MMU가 가상 주소를 물리 주소로 번역을 시도 할 때 TLB를 먼저 확인 하여 TLB에 매핑 정보가 있다면 메모리에 접근하여 해당 매핑 정보를 읽어 드릴 필요 없이 바로 처리 할 수 있게 된다. TLB에 대한 자세한 내용은 뒤에서 한번 더 언급 하도록 하겠다. 다시 돌아가서 MMU는 앞서 언급된 번역 속도를 향상 하기 위해 TLB에 먼저 매핑 정보가 있는지 확인한다. 만약 TLB에서 매핑 정보를 찾지 못하는 경우에만 메모리에 접근 하여 매핑 정보를 가져와 가상 주소에서 물리 주소로의 번역을 시도한다. 여기서 각 번역은 페이지 프레임(Page Frame)으로 이루어져 있음을 상기 해야 한다.

가상 주소에서 물리 주소로 변역을 하고 나서 해당 물리 주소가 현재 메모리에 매핑 되어 있는 경우, 프로세스는 바로 해당 물리 주소의 메모리로의 접근을 허락 한다. 그렇지 못한 경우는 우선적으로 예외 (Exception)을 발생 하고 예외 핸들러(Handler)는 페이지 폴트(Fault)를 발생시킨다. 페이지 폴트가 발생하면 이에 대한 핸들러에게 제어권을 보내어 이를 관리 하는데 윈도우 NT의 경우는 VMM 페이지 폴트 핸들러가 이를 담당한다. VMM 페이지 폴트 핸들러는 적절한 데이터를 시스템 메모리로 가져 온 뒤 매핑 정보를 업데이트하고 예외를 발생한 페이지로 제어권을 반환하는 절차를 일반적으로 거친다. 물론 이러한 예외 처리는 페이지 프로텍션(Protection)에 대한 충돌충 있을 때도 하드웨어에 의해 동일하게 일어 날 수 있다.

 

MMU에 대한 설계는 윈도우 NT의 VMM에 상당히 많은 영향을 미친다. MMU 인터페이스 격인 VMM은 하드웨어 의존성이 매우 강하고, 이 때문에 이종간의 플랫폼에서 호환성 및 이식성이 현저히 떨어진다. 저번 컬럼에서 언급 되었듯이 VMM은 시스템의 물리 메모리를 관리 하기 위해서 비페이징 영역(Non-paged pool)에 페이지 프레임에 대한 데이터베이스를 유지 하고 있다. 이 데이터 베이스는 연속적인 물리 주소 공간의 페이지 프레임들의 집합으로 이루어져 있다. 각각의 물리 페이지 프레임이 순차적으로 구성 되기 때문에 (n개의 물리 RAM이 있다면 페이지 프레임 숫자는 페이지 프레임 0번에서 페이지 프레임 n-1으로 구성 된다고 볼 수 있음) 페이지 프레임을 위한 페이지 프레임 숫자(PFN, Page Frame Number) 데이터 베이스 엔트리(Entry) 계산은 매우 간단 하다. 한번 가상 주소가 물리 주소로 번역 되고 나면 PFN은 PFN 데이터 베이스의 사이즈에 의해 곱해지고, 곱한 후에 결과 주소는 물리 베이스 주소 (Base Address)에 더해진 뒤 PFN 데이터 베이스 할당 되게 된다.

 

32 비트 가상 주소를 위한 자료구조

윈도우 NT의 32비트 가상 주소를 고려 하여 주소 번역에 사용되어야 하는 자료 구조, 또는 하드웨어 사이즈들을 고려 해보도록 하자. 이러한 작업은 실제 파일 시스템 드라이버 개발자에게는 빈번한 일로, 주소 공간에 대한 이해와 함께 이러한 주소 공간을 어떤 식으로 관리하고, 자료구조를 구성하는지를 이해하는데 필수적이다. 페이지 테이블 사이즈가 4096 바이트이기 때문에 페이지 오프셋 계산에 12 비트를 필요로 한다. 해당 12 비트는 LSB(Lease Significant Bit)에 저장 된다. 따라서 MMU는 페이지 테이블 내에 PTE의 페이지 프레임들을 구분하는데 20비트를 가지고(32비트 12 비트) 계산 하게 된다. PTE는 20비트로 구성된 (백만 개의) 순차적 배열 정도로 추상화 할 수 있다. 인텔의 x86 아키텍처를 비롯하여 대부분의 아키텍처에서 PTE에 대한 자료구조들을 미리 정의 해두었다. 인텔 플랫폼(Platform)의 경우 각각의 PTE는 반드시 4바이트로 구성된다. 현재까지 고려한 것을 다시 정리하면 가상 주소 공간을 위한 번역 정보에 필요한 사이즈를 유추 할 수 있다. 다시 말해 백만개 엔트리와 각 엔트리(PTE) 4바이트로 구성 되기 때문에 222 (4MB)형태의 사이즈를 필요로 한다. 각각의 페이지 테이블은 하나의 페이지 사이즈에 저장 되어야 하기 때문에 하나의 프로세스에 대한 페이지 테이블은 1024 페이지 프레임으로 모든 PTE들을 구성 될 수 있다.

 

주소 번역에 필요한 정보를 위한 메모리도 매우 큰 사이즈이기 때문에 이에 대한 성능저하를 피하기 위해 페이지 테이블 역시 페이징 된다. 이를 위해서 x86 프로세스는 이중 레벨의 페이지테이블 엔트리를 정의 해두었다. 각각의 프로세스는 페이지 테이블의 PTE들을 포함하는 페이지 디렉토리(Directory)를 가지고 있다. 이 디렉토리는 한 개의 페이지 사이즈와 동일하기 때문에 1024개의 각각 페이지 테이블을 참조하는 PTE들을 가지고 있다. 일반적으로 프로세스를 위한 가상 주소는 10 비트를 예약 해두는데 이 10비트는 페이지 디렉토리로부터 페이지 테이블을 구분하는데 사용 되며 페이지 내에 오프셋으로 12 비트를 사용한다.

 


<그림 1, 가상 에서 물리 주소로의 번역>

 

TLB를 포함하여 가상 주소가 번역 되는 과정을 다시 정리 해보자. MMU는 TLB를 우선적으로 확인하여 해당 가상 주소에서 물리 주소로의 매핑 정보가 있는지를 먼저 확인한다. 만약 TLB에 존재 하는 경우 (이 경우를 보통 TLB hit로 부른다) 단순히 해당 물리 주소를 바로 반환 하여 작업을 종료 할 수 있다. 만약 TLB에 존재 하지 않는 경우 ( 이 경우는 TLB miss로 불린다)는 조금 작업이 복잡하다. 우선 프로세스마다 존재하는 페이지 테이블 중 현재 가상 주소의 번역을 요청한 페이지 테이블을 확인하여 엑세스가 필요한 물리 페이지 프레임의 정보를 가지고 있는 PTE의 위치를 찾는다. PTE가 가리키는 물리 주소가 해당 프로세스의 권한으로 접근이 가능하고(Protection check) 현재 메모리에 상주하고 있다면 MMU는 해당 주소의 접근을 허락한다. 다른 경우라면 앞서 언급 되었던 것처럼 페이지 폴트 또는 프로텍션 위반으로 예외를 발생 시킨다. 이러한 예외는 결국 VMM에 의해서 관리 된다.

 

여기서 우리는 한가지 개념을 더 이해 해야 한다. 그림 1에 나와 있는 프로토타입 페이지 테이블이다. 사실, 하나 이상의 가상 주소가 같은 물리 주소에 매핑 되는 경우가 있기 때문인데, 프로토 타입 페이지 테이블은 이렇게 하나 이상의 프로세스의 가상 주소 공간이 하나의 물리 페이지로 매핑 되는 페이지 프레임들과 페이지 테이블 엔트리들의 정보를 보유하고 있다. 이를 위해서 우리는 공유 메모리와 메모리 맵드(Mapped) 파일의 컨셉을 이해 해야 한다.

 

 

공유 메모리와 메모리 맵드 파일(Memory Mapped File)

우리가 어플리케이션 레벨에서 개발을 할 때에는 메모리 접근에 대해서 그다지 어려움을 느끼지 못한다. 어플리케이션 프로세스는 간단히 malloc 을 호출 하는 것으로 VMM으로부터 가상 주소를 얻을 수 있고 프로세스는 단순히 해당 가상 주소를 통해서 메모리 블록을 접근 할 수 있다. 계속 언급되어 왔듯이 운영 체제는 가상 주소와 물리주소간의 번역과 이때 성능향상을 위해 채용된 하드웨어들을 관리 해야 하는 책임이 있다. 더욱이 운영체제는 시스템에서 동작하는 모든 프로세스 동작 형태를 확인 할 수 있어야 하며 특정 프로세스에 물리 메모리를 할당하는 작업들도 수반 해야 한다. 동시에 대부분의 어플리케이션은 자신에 필요한 만큼의 메모리를 계속 요청 할 수 있고, 특정의 경우는 스토리지와 같은 디바이스로 I/O를 신청 할 수도 있다. 추가적으로 복잡한 어플리케이션의 경우 공유 메모리를 통한 데이터 공유를 시도하기도 한다.

 

일반적으로 I/O는 파일 시스템의 읽기 쓰기의 시스템 콜을 통해서 이루어진다. 따라서 사용자가 I/O를 요청 하는 경우 시스템 트랩에 의해서 사용자 모드에서 커널 모드로 프로세서가 옮겨가고 스토리지로부터 데이터를 인출 하고 나면 다시 모드를 변경 하게 된다. 읽기의 경우, 파일 시스템은 반드시 데이터를 읽어서 시스템 메모리에 적재한뒤 다시 그것을 사용자 어플리케이션의 버퍼 영역으로 복사 해주어야 한다. 쓰기 요청의 경우는 운영체제가 어플리케이션 버퍼로부터 시스템 메모리로 복사를 하는 작업을 먼저 하게 된다. 시스템 버퍼로부터 데이터를 복사하는 행위는 I/O 요청에 대한 시스템 호출에 대한 오버헤드와 함께 어플리케이션의 실행에 큰 부하를 미칠 수 있다. 더욱이 같은 파일에 대해서 다른 두 프로세스가 접근 하는 경우 정말 필요 없는 부하가 가중 될 수 있다. 이러한 경우 두 어플리케이션 프로세스는 같은 바이트 영역을 접근 하지만 앞서 언급된 바대로 자신이 소유한 버퍼를 가지고 하나의 물리적 주소의 데이터를 가져온다. 하나의 데이터에 대해서 다른 두 버퍼를 사용 하는 오버헤드 문제 이외에 데이터의 코히어런스(Coherence)문제도 존재 한다. 두 프로세스의 이름을 프로세스1과 2로 붙여 주고 문제가 되는 부분을 예로 살펴 보자. 프로세스 1이 자신이 소유한 버퍼를 통해서 물리적 주소의 데이터를 가지고 와서 수정하였지만 아직 다시 해당 물리 주소 공간으로 데이터를 업데이트 하지 않았는데 프로세스 2가 같은 물리 주소 공간에 대해서 읽기를 요청하는 경우 프로세스 1에 의해서 수정된 데이터를 보지 못하고 단지 해당 물리 페이지의 데이터를 그대로 가져와서 사용 하게 된다.

 

이와 달리 만약 각 프로세스가 자신이 가지 버퍼를 물리 주소로 매핑 한다면 상황이 달라진다. VMM은 스와핑(Swapping) 데이터를 스토리지로부터 읽어 이를 가상 메모리로 언제든지 제공 할 수 있다. 어플리케이션은 해당 하는 스토리지 파일의 I/O를 신청 하는 대신 특정 메모리를 할당하여 이를 접근 하도록 한다. 그러면 해당 페이지는 페이지 폴트를 일으킬 것이고 이 페이지 폴트는 운영체제에 의해서 데이터가 교환 되고 정상적인 페이지 접근으로 변경 될 것이다. 따라서 어플리케이션은 이러한 방법으로 물리 메모리를 접근 할 수 있다.

 

이러한 파일과 가상 주소 공간의 매핑 방법은 앞서 언급한 문제들을 해결 하는 것 이외에도 다른 장점도 가지고 있다. 같은 파일에 대한 매핑을 신청하는 모든 어플리케이션이 가상 주소 공간에 물리 페이지를 접근 함으로서 어떤 어플리케이션 프로세스가 해당 파일을 수정 하던 간에 일괄적으로 최신의 데이터를 볼 수 있다는 것이다. 따라서 NT VMM은 이러한 방법의 수단으로 파일 매핑을 지원한다. 매핑된 오브젝트는 이러한 스토리지에 존재하는 파일을 대신하는 수단이 된다. 사용자가 파일을 실행 할 때 NT VMM은 이를 신청한 사용자의 프로세스에 가상 공간에 매핑된 오브젝트를 할당시키고 나서 인스트럭션을 실행하게 된다. 만약 같은 머신의 다른 프로세스가 같은 파일을 실행하게 된다면 아까 할당되었던 매핑된 오브젝트를 그 프로세스의 가상 주소 공간에 할당 해준다. 해당 물리 페이지의 VAD(Virtual Address Descriptor)는 이미 메모리에 상주하고 있기 때문에 사용자 프로세스는 상당히 빠른 시간 안에 자신의 요청한 파일을 볼 수 있다.

파일 매핑은 두 프로세스의 물리 메모리를 공유 하는 방법만을 지칭 하지는 않는다. 가상 주소에 해당하는 물리 페이지 프레임으로부터 독립적으로 VAD 조작이 가능 하기 때문에 VMM은 모든 프로세스에 대해서 해당 프로세스의 VAD를 약간 수정 하는 것만 으로 간단히 공유 메모리를 제공 할 수 있다. 이는 사용자 프로세스는 파일을 기반으로 한 공유 메모리를 할당 하는 것 만으로 공유 메모리 오브젝트를 할당 할 수 있다는 것을 의미하기도 한다. 이러한 공유 기능은 VMM의 기본이 되는 작업으로 VMM이 맵핑된 오브젝트 그리고 공유 오브젝트를 제공하기 위해 사용 하는 자료 구조가 바로 앞서 언급되었던 프로토타입 페이지 테이블이다.

 


<그림 2, 같은 페이지를 프로세스마다 다른 가상 주소 공간에 매핑 시킨 예>

 

 

 

프로토타입 페이지 테이블 (Prototype Page Table)

페이지 프레임은 프로토타입 페이지 테이블(PPT)로 기술되는 특별한 구조체에 의해 공유 되도록 설계되어 있다. PPT는 다른 중요 커널 오브젝트나 자료구조와 달리 비페이징 영역 이외에도 페이징 영역 모두에 할당 되어 사용 될 수 있다. VMM이 프로세스를 위해 매핑 정보 또는 공유 오브젝트를 생성할 때는 파일 매핑을 기본으로한 물리 페이지를 기술 하기 위해 프로토타입 페이지 테이블 엔트리(PPTE) 또한 같이 할당한다. 매핑된 오브젝트를 위한 PPT는 같은 오브젝트를 매핑하는 모든 프로세스에 의해 공유 된다. 각각의 PPTE는 실제 메모리를 가리킬 수도 아닐 수도 있다. 다시 말하면 해당 페이지는 페이지 물리 페이지 프레임에 매핑 되어 있거나 아니면 스토리지에 매핑 되어 있을 수 있다는 것이다. 모든 프로세스가 같은 PPT를 사용 하기 때문에 프로세스들은 같은 페이지 프레임과 매핑된 데이터를 볼 수 있다. 페이지 프레임이 PPTE에 할당 되면 처음에는 PPTE는 항상 유효 상태(Valid)로 표시 된다. 인텔 x86 MMU와 MIPS 이종간의 아키텍처라도 MMU는 프로토타입 페이지 테이블을 위해 PPTE와 같은 유사 구조 페이지 테이블을 제공하지만 각각은 동일 하지 않다. NT VMM은 이를 위해서 다음과 같은 형태로 공유 메모리를 이종간 MMU에서 동작 시킬 수 있도록 한다.

 

우선 인텔 x86 의 아키텍처의 MMU는 PTE와 페이지 테이블을 정확히 기술하고 있다. 따라서 VMM은 프로세스가 파일 매핑을 생성할 때는 언제든지 PPT와 PPTE를 상주 메모리에 할당한다. 프로세스가 매핑된 파일 오브젝트의 가상 주소를 접근을 시도할 때 MMU는 해당 가상 주소를 페이지 디렉토리 테이블 오프셋을 가지고 적절한 페이지 테이블로 가상 주소를 번역 한다. 초기 상태에서 해당 PTE에 대한 접근은 페이지 폴트를 일으키게 된다. (초기에 물리 주소의 데이터가 로드 되어 있지 않으므로) 이 페이지 폴트는 VMM 페이지 폴트 핸들러고 뛰게 되고 VMM의 페이지 폴트 핸들러는 해당 가상 주소의 정보를 담고 있는 VAD로 하여금 매핑된 오브젝트를 가리키도록 변경 시키게 한다. 따라서 VMM은 적절한 PPTE를 찾을 수 있게 되는데 이 시점에 PPTE는 유효 상태로 마크 되어 있기 때문에 PFN 데이터 베이스 엔트리와 해당 백포인터를 연결 할 수 있다. 동시에 VMM은 PTE를 유효상태로 변경하고 PTE가 적절한 물리 주소를 가리킬 수 있도록 한다. 이러한 연결 방법은 PPTE와 PTE가 적절한 물리 주소 정보를 담을 수 있도록 하고 PFN 데이터 베이스 엔트리는 이 PPTE의 백포인터를 설정하도록 한다. 이후 메모리 접근이 재시도 되고 MMU은 정상적으로 초기화된 PTE를 찾을 수 있게 되므로 해당 가상 주소를 물리 주소로 번역이 가능해지게 된다.

 

 

페이지 테이블 설계에 있어서의 고려사항

여기서 주의 해야 할 것은 PFN 데이터베이스 엔트리는 PTE를 직접 참조 하기 못한다는 것이다. VMM은 PFN 데이터베이스 엔트리로부터 가상 주소가 공유 메모리 오브젝트로 할당된 정보를 가지는 PTE를 찾을 방법이 없다. VMM이 할 수 있는 최선책은 PFN 데이터베이스 엔트리를 참조하는 PPTE를 찾는 것이다. 이러한 기교는 심각한 결함을 야기 할 수 있다. 예를 들어, 커널 모드 컴포넌트가 VMM이 특정 물리 페이지를 쫓아 내기를 원한다고 하면 일반적으로 VMM에게 해당 PFN 데이터베이스 엔트리를 무효화 시키도록 하여 해결 한다.

 

무효화된 PFN 데이터베이스 엔트리는 나중에 MMU로 하여금 해당 가상 주소를 참조 할 때 페이지 폴트를 유발 시기키기 위해서 PTE를 무효화로 만든다. 문제는 해당 페이지가 매핑된 오브젝트를 가지고 있을 때이다. VMM은 공유 페이지를 보유하고 있는 페이지 프래임을 PTE를 접근할 방법이 없다. 따라서 만약 VMM에게 특정 페이지 프레임을 메모리에서 쫓아내게 하려고 한다면 VMM은 에러를 반환하여 이 작업이 매핑된 오브젝트와 관련되었을 경우 처리 할 수 없음을 표시하도록 한다. 이것은 우리와 같은 시스템 개발자에게 큰 문제가 될 수 있다.

 

섹션과 (Section and View)

윈도우 NT 시스템은 오브젝트를 기반으로 한다. 여기서 오브젝트는 OOP(Object Oriented Paradigm)에서 이야기하는 클래스와는 다른 개념이다. 다시 말해 윈도우 NT는 대부분의 기능 조작들을 오브젝트 현태로 제공한다. 따라서 파일 매핑은 생성되고 접근 될 때 아래와 같은 두 가지를 고려 해주어야 한다.

 

파일 매핑과 공유 메모리 오브젝트를 관리하는 섹션 오브젝트 (Section Object)는 VMM에 의해서 생성된다.

프로세스가 매핑된 파일이나 공유 메모리 오브젝트에 대해서 접근을 원하는 경우, 호출자는 반드시 VMM에게 해당 파일로 뷰(View)를 매핑 시키도록 요구해야 한다. 결과적으로 이 뷰를 통해서 파일을 보고 제한된 범위 내에서 이를 접근 할 수 있도록 한다. 물론 프로세스로 하여금 같은 파일에 대해서 동시에 여러 개의 뷰를 생성하는 것을 허락 하기도 한다. 또 이 반대로 하나의 파일에 대해서 여러 프로세스에 뷰를 다른 뷰를 제공하는 것도 가능하다.

 

섹션 오브젝트는 다른 NT 오브젝트라 가지고 있는 것과 같이 프로텍션에 대한 특성을 가지고 있다. 섹션 오브젝트에 대한 프로텍션 특성을 명세 함으로서 프로세스는 해당 오브젝트와 파일 오브젝에 대한 데이터들을 정의 할 수 있다. 섹션 오브젝트는 크게 아래 두 가지 카테고리로 분리 된다.

 

실행 이미지에 대한 파일 매핑

비 실행 파일에 대한 매핑

 

우리가 VMM이 매핑 파일을 나타내는 섹션 오브젝트를 생성하도록 요구하는 경우, 우리는 어떻게 매핑된 파일을 다룰 것인가를 명세 할 수 있다. 시스템 로더(Loader) 는 파일 매핑을 사용 하여 명세된 파일 매핑이 실행 가능한 이미지를 실행하도록 해준다. 하지만 만약 복사와 같은 작업을 요청한다면 파일 매핑을 비 실행 파일로 매핑 시킨다. VMM은 섹션 오브젝트가 생성 될 때 마다 이것이 실행 가능한 이미지를 다루는지를 항상 정검한다. 만약 우리가 텍스트 파일등을 실행 가능한 이미지로 파일 매핑을 원한다면 VMM은 에러를 반환 하게 될 것이다.

 

실행 이미지 파일 매핑과 비 실행 파일에 대한 매핑 사이의 가장 큰 차이점은 VMM에 의한 매핑 범위가 어떤 식으로 수행 되는 가에 있다. 비 실행 파일 매핑이 프로세스에 수정 될 때 물리 메모리의 컨텐츠가 VMM에 변경 되기 때문에 이 수정에 대한 요청은 같은 파일 매핑을 사용하는 모든 프로세스에 보여 질 수 있다. 이러한 수정 작업은 나중에 스토리지로 데이터가 플러시 될 때 한번에 반영 된다. 하지만 이미지 파일에 대한 매핑이 수정될 때는 이와 다르게 별도의 페이지에 복사된다. 복사된 이후에 별도의 페이지는 페이지 파일에 의해서 가리켜진다. 만약 프로세스가 파일의 매핑을 취소하면 이 수정사항은 실제 스토리지로 반영 되지 않고 사라 질 수 있다.

 

공유 메모리 오브젝트 또는 섹션 오브젝트를 생성하기 위해서는 NT VMM은 NtCreateSection() 을 후출 해야 한다. 이 루틴은 커널 모드 개발자에게 알려져 있지는 않지만 대신에 ZwCreateSection()을 통해서 이를 대신 수행 할 수 있다.

 

ZwCreateSection

NTSTATUS 
  ZwCreateSection(
    OUT PHANDLE  SectionHandle,
    IN ACCESS_MASK  DesiredAccess,
    IN POBJECT_ATTRIBUTES  ObjectAttributes OPTIONAL,
    IN PLARGE_INTEGER  MaximumSize OPTIONAL,
    IN ULONG  SectionPageProtection,
    IN ULONG  AllocationAttributes,
    IN HANDLE  FileHandle OPTIONAL
    ); 

 

이 루틴은 커널 모드에 의해서 공유 메모리 오브젝트나 스토리지를 우한 파일 매핑을 생성하는데 사용 될 수 있다. 파일 시스템 드라이버 개발자가 네트워크 파일 시스템을 개발 하는 경우라 할 지라도 우리는 이를 공유 메모라니 매핑된 파일 오브젝트를 생성하는데 사용 할 수 있다. 때로는 커널 모드 드라이버 개발자가 공유 메모리 데이터를 사용자 공간의 모듈과 공유를 원하거나 커널 모드 드라이버가 네트워크 건너편에 전에 존재하는 데이터를 원하는 경우 우리는 간단한 공유 메모리 오브젝트나 파일 기반의 공유 오브젝트를 얻어 쉽게 이를 조작 할 수 있다. 또한 커널 모드와 사용자 모드의 모듈간에 데이터를 서로 교환 해야 하는 경우에도 이러한 섹션 오브젝트가 응용 될 수 있다.

 

섹션 오브젝트에 대한 다른 Zw 함수들은 DDK 문서를 참조하여 써도 무방하다. 이에는 아래와 같은 함수들이 있다.

 

ZwOpenSection

NTSTATUS 
  ZwOpenSection(
    OUT PHANDLE  SectionHandle,
    IN ACCESS_MASK  DesiredAccess,
    IN POBJECT_ATTRIBUTES  ObjectAttributes
    );

ZwMapViewOfSection

NTSTATUS 
  ZwMapViewOfSection(
    IN HANDLE  SectionHandle,
    IN HANDLE  ProcessHandle,
    IN OUT PVOID  *BaseAddress,
    IN ULONG_PTR  ZeroBits,
    IN SIZE_T  CommitSize,
    IN OUT PLARGE_INTEGER  SectionOffset  OPTIONAL,
    IN OUT PSIZE_T  ViewSize,
    IN SECTION_INHERIT  InheritDisposition,
    IN ULONG  AllocationType,
    IN ULONG  Win32Protect
    );

ZwUnmapViewOfSection

NTSTATUS 
  ZwUnmapViewOfSection(
    IN HANDLE  ProcessHandle,
    IN PVOID  BaseAddress
    );

 

다음 칼럼에는

다음 컬럼에서는 이번 컬럼에 이어서 파일 매핑의 자료구조와 남은 이슈들을 다루고 VMM이 가상 주소를 관리함에 있어서 가장 키 이슈가 되는 페이지 폴트부분을 중점 적으로 다루도록 하겠다. 마지막으로 파일 시스템 드라이버 개발에 있어서 VMM을 위해 필요한 FSD 구현 사항들을 마지막으로 VMM에 대한 컬럼을 마무리 하도록 할 것이다.

 

References

Rejeev Nagar, "Windows NT File System Internals": A Developer Guide, O'Reilly 1998

P. B. Kruchten."The 4+1 View Model of architecture."

David Garlan and Mary Shaw January 1994 "An Introduction to Software Architecture"