ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 페이징
    프로젝트/운영체제 만들기 2023. 5. 30. 14:43

    글의 참고

    - http://www.brokenthorn.com/Resources/OSDev18.html

    - https://wiki.osdev.org/Setting_Up_Paging

    - https://en.wikipedia.org/wiki/Page_(computer_memory) 

    - https://en.wikipedia.org/wiki/Page_table

    - https://en.wikipedia.org/wiki/Physical_Address_Extension#Page_table_structures

    - https://wiki.osdev.org/Page_Tables

    - https://wiki.osdev.org/Setting_Up_Paging_With_PAE

    - https://wiki.osdev.org/Setting_Up_Paging

    - https://wiki.osdev.org/Identity_Paging

    - https://wiki.osdev.org/Paging

    - http://www.rcollins.org/articles/2mpages/2MPages.html


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다.


    글의 내용

    - Overview

    : 페이지라는 용어는 2가지 의미를 포함하고 있다.

    1" 연속성
    2" 고정 사이즈

    : 예를 들어, 페이지가 4096B 라는 말은 해당 페이지의 4096B가 모두 연속적으로 붙어있다는 의미고, 모든 페이지의 사이즈는 4096B라는 뜻이다. 

     

    : 페이지는 가상 메모리를 지원하는 운영 체제에서 메모리 관리를 위해 사용하는 가장 작은 단위다. 유사하게 물리 메모리에서는 페이지 프레임이라는 용어를 사용하는데 의미는 동일하다. 즉, 가상 메모리에서는 `페이지`라는 단어를 사용하고, 물리 메모리에서는 `페이지 프레임`이라는 단어를 사용한다.

     

    : 메인 메모리와 보조 기억 장치 사이이에서 페이지를 교환하는 작업을 페이징 혹은 스와핑이라고 한다. 예를 들어, 프로그램이 실행될 때, 해당 프로그램의 메모리가 전부 램에 로딩되는게 아니다. 일부는 디스크에 저장되어 있다. 그리고, 실행 도중에 다른 메모리(페이지)가 필요해지면, 해당 내용을 디스크에서 가져온다.

     

    - 개념

    : 컴퓨터는 메모리를 페이지 단위로 나눔으로써 퍼포먼스의 큰 퍼포먼스의 향상을 가져왔다. 이 개념은 책에서 특정 단어를 어떻게 하면 빨리 찾을 수 있나와 비슷한 개념이다. 예를 들어, 책에서 5000번째 단어를 찾아야 한다고 치자. 처음부터 일일히 세어나가면 시간 소비나 엄청날 것이다. 그런데, 하나의 페이지당 256개의 단어가 반드시 들어가야 한다고 치자. 그 이상, 이하도 안된다. 딱, 256개가 들어가야 한다. 그러면 우리는 5000을 256을 나눠서 19번째 페이지에 중간쯤에 우리가 원하는 5000번재 단어가 있다는 것을 쉽게 알 수 가 있다.

     

     

    - 페이지 사이즈 

    : 페이지 테이블 사이즈

    " 제한된 메모리 자원에서 페이지가 사이즈가 작을 수록, 페이지를 관리하는 메타 데이터(페이지 테이블)의 사이즈가 커진다. 페이지 사이즈가 커질 수록 메타 데이터의 사이즈는 줄어든다. 램이 4GB일 경우, 페이지 사이즈를 4MB로 하면 페이지 디렉토리 하나면 된다. 그러나 페이지 사이즈가 4KB면, 페이지 디렉토리와 페이지 테이블이 각각 1개씩 필요하다.  

     

    " 그리고 페이지 사이즈 또한 성능 및 TLB와 관련하여 문제가 있다. 예를 들어, 우리의 시스템이 램 4GB에 페이지 사이즈를 4KB를 사용한다고 전제하자. 이 때, 사용자 애플리케이션이 1MB를 요구했다고 치자. 이러면, 애플리케이션에게 할당해야 하는 페이지는 총 256개가 된다. 

     

    " 해당 애플리케이션에게 1MB를 할당해줬다고 치자. 그리고 이 애플리케이션이 자신에게 할당받은 1MB의 어딘가에 액세스하려고 할 때 마다, 1MB에 해당 256개의 페이지 중 한 개 혹은 그 이상의 페이지들이 TLB에 캐쉬되어 있어야 한다. 그렇지 않을 경우, TBL Miss 과정이 발생하게 되고, 이 부분은 성능에 아주 큰 영향을 미친다. 만약, 페이지 사이즈가 4MB 면 어떨까? 한 번 TBL Miss 발생 후, 1MB를 모두 커버할 양이기 때문에 해당 애플리케이션이 1MB 영역안에 아무데나 접근을 해도 TLB Miss는 발생하지 않는다. 그러나 보다시피 내부 단편화로 3MB 영역이 낭비가 된다. 

     

    : TLB

    " 매번 가상 메모리 주소를 물리 메모리 주소로 변환하기 위해 페이지 테이블을 읽어야 한다. 이건 비용이 꽤 큰 작업이다. 그러므로, `Translation lookaside buffer`라는 캐쉬가 종종 사용된다. 그런데, TLB의 사이즈는 제한적이고 요청된 메모리 주소를 갖는 페이지가 TBL에 없을 경우(`TBL miss`), 페이지 테이블을 수동으로 검색해서 매핑된 메모리 주소에 해당하는 페이지를 다시 TBL에 넣어주어야 한다. 이런 경우, SW적으로는 `페이지 폴트`가 발생을 하게 된다. 그러면 SW 개발자는 페이지 리플레이스 알고리즘을 적용하여 어떤 메모리상에 올라와 있는 페이지중에 어떤 페이지를 스왑아웃 할지를 정해야 한다. 스왑아웃할 페이지가 정해지면, 해당 페이지를 디스크로 내리고 해당 영역에 새로 요청된 페이지를 로딩한다. 그런데, 여기서 SW 개발자에게 직접 TBL에 해당 페이지를 로딩할 수 있는 명령어가 제공되는지 여부는 파악이 아직 안된다. 그냥, 페이지를 메모리에 올리면 무조건 TLB에 로딩이 되는건지 별도의 명령어가 필요한 것인지는 좀 더 알아봐야 한다.

     

     

    : 내부 단편화

    " 프로세스는 직접 페이지의 개수를 요구하지는 않는다. 즉, 특정 사이즈를 요구하면 그 사이즈에 맞는 가장 적합한 페이지 개수를 OS는 줄 뿐이다. 그런데, 페이지 사이즈가 커지면 내부 단편화가 심하게 발생한다. 예를 들어, 페이지 사이즈가 4096B이다. 그런데, A라는 프로세스는 4B만 필요하다. 이러면 4092B는 낭비가 된다. 만약 페이지 사이즈 8192B면? 페이지 사이즈가 커지면 커 질 수록 내부 단편화는 심해진다.

     

    : 디스크 액세스 속도

    : 흔히 기계식 디스크라고도 불리는 로테이션 디스크에 대한 액세스는 비용이 상당히 많이 든다. 왜냐면, 디스크가 파일들을 찾기 위해 기계적으로 헤드를 돌려가면서 찾기 때문에 전기적으로 동작하는 반도체에 비해 속도가 상당히 느리다. 그래서 디스크 액세스는 최대한 줄여야 한다. 그럴려면, 한 번에 요청할 때 많이 읽어와야 한다. 조금씩 많이 읽는 것보다, 한 번에 많이 읽는게 퍼포먼스 향상에 훨씬 좋다.

     

     

     

    - 페이지 테이블

    : 페이지 테이블은 가상 주소 쳬계를 사용하는 운영 체제에서 사용하는 개념이다. 페이지 테이블은 가상 주소를 물리 주소에 맵핑시키는 정보를 가지고 있다. 가상 주소는 프로그램에 의해 사용되고, 물리 주소는 하드웨어에 의해서 사용된다. 다시 정리하면, 가상 주소를 사용하는 주체는 소프트웨어고, 실제 물리 주소를 사용하는 주체는 하드웨어다.

     

    : 가상 주소 체계를 사용하는 운영체제는 프로세스들에게 자신이 엄청나게 큰 메모리를 할당받아서 사용하고 있다는 착각을 만들게 한다. 그런데, 사실 프로세스의 메모리는 램과 디스크에 분배되어 있다. 여기서 프로세스가 램에 있는(디스크가 아닌) 데이터에 대한 액세스를 요청하면, 운영 체제는 프로세스에게 받은 가상 주소를 물리 주소로 변환해서 반환할 책임이 있다. 

     

     

    - 주소 변환 과정

    : CPU 내부에는 MMU가 존재한다. 그리고 MMU 내부에는 TLB라는 캐시가 존재하는데, TLB에는 최근에 사용된 페이지들에 대한 테이블 맵핑 관련 정보들이 저장되어 있다.

     

    : 가상 주소를 물리 주소로 변경 처리 과정에서 제일 앞 단계는 TLB부터 찾는다. 만약, TLB에 맵핑 관련 정보가 있다면, 가상 주소를 물리 주소로 변환한 뒤 반환한다. 이 과정을 `TLB hit` 이라고 한다. 그런데, 만약 TLB에서 맵핑 되는 물리 주소가 없다면(이 걸 `TLB miss`라고 한다), MMU 혹은 OS에서 직접 작성된 `TBL miss 핸들러`는  페이지 테이블에서 해당 주소를 찾게 된다. 이 과정을 `page walk`라고 한다. 이 과정에서 해당 주소가 존재한다면, TLB에 써진다. 

     

    : 그런데 위에서 `TLB miss` 후에 페이지 테이블에서 해당 주소에 대한 맵핑 정보를 찾아서 TLB에 쓰는 과정은 반드시 수행되어야 한다. 다시 정리하면, TLB에 맵핑 정보가 반드시 다시 꼭 써져야 한다는 것이다. 왜냐면, `TLB miss`는 FAULT 익셉션을 발생시키는데, FAULT가 발생하면 FAULT가 처리된 후, 다음 주소가 아닌 FAULT가 발생했던 지점으로 다시 돌아간다. 즉, `TLB miss`를 또 수행하게 된다. 그래서, `TLB miss`가 발생하면, 맵핍 정보를 반드시 TLB에도 작성해서 FAULT가 발생하지 않게 해야한다.

    출처 - https://en.wikipedia.org/wiki/Page_table

    https://en.wikipedia.org/wiki/Page_table

    : 위의 그림을 요약하면 다음고 같다.

    " 가상 주소를 물리 주소로 변환하기 위해 TLB를 가정 먼저 찾음.
      " TLB에 맵핑 정보가 있으면, 물리 주소를 반환.
      " TLB에 맵핑 정보가 없다면, 페이지 테이블을 검색. 이 때, `TLB miss` FAULT가 발생.
         " 페이지 테이블에 맵핑 정보가 있다면, TLB에 해당 정보를 쓴다. 그리고 TLB에서 물리 주소를 반환한다.
         " 페이지 테이블에 맵핑 정보가 없다면, 디스크에서 찾는다. 이 때, Page Fault가 발생.
           " 디스크에서 해당 정보를 찾아서, 페이지 테이블에 쓴다.

    : 페이지 폴트가 발생했을 때에 대해서 좀 더 구체적으로 알아보자.

     

     

    - 페이지 폴트

    : 여기서 페이지 폴트는 페이지 테이블에서 맵핑 정보를 찾지 못해서 발생한다. 맵핑 정보를 찾지 못한 이유는 2가지가 있다.

    1" 전달받은 가상 주소가 유효한 주소가 아니다.
    2" 전달받은 가상 주소가 램에 없다.

    : 첫 번째는 이유는 대개 프로그래밍 에러에 의해 발생된다. 현대의 운영 체제는 이 이유 때문에 페이지 폴트가 발생하면, 해당 폴트를 발생시킨 프로그램에게 세그먼테이션 폴트를 발생시킨다.

     

    : 두 번째 이유는 대개 다른 페이지들 때문에 요청된 페이지가 디스크로 아웃된 경우이다. 이 경우에는, 다시 디스크에서 램으로 로딩시켜야 한다. 

     

    : 사실 램이 꽉 차있지 않다면, 처리는 간단하다. 디스크에 있는 페이지를 램에 쓰고, 페이지 테이블과 TLB를 업데이트 한 후에 페이지 폴트가 발생했던 지점에서 부터 명령어를 다시 실행하면 된다. 그러나 램이 꽉 차 있다면, 문제가 커진다. 이 경우에는 램에 추가 공간을 만들기 위해, 램에 있는 다수의 페이지들이 디스크로 페이지 아웃 되어야 하는 상황이 발생한다.  

     

    : 이러한 이유 말고도 아키텍처에 따라 MMU는 몇 가지 이유로 페이지 폴트를 발생시킨다.

    1" 페이지 테이블이 READ_ONLY 비트가 SET 되어 있는데, 쓰기를 시도한 경우
    2" 페이지 테이블이 NX(no-execute) 비트가 SET 되어 있는데, 실행하려고 한 경우

     

    - 페이지 테이블 엔트리

    : 페이지 테이블은 여러 개의 페이지 테이블 엔트리로 구성되어 있다. 페이지 테이블 엔트리(PTE)는 가상 주소를 어떻게 물리 주소에 대응할지에 대한 맵핑 정보를 가지고 있다. 그리고 추가적으로 중요한 몇 가지 정보들을 더 가지고 있다.

    1" PRESENT 비트 - 해당 페이지가 램에 있는지 디스크에 있는지. 램에 있으면 1, 디스크에 있으면 0.
    2" DIRTY 비트 - 해당 페이지가 수정되었는지. 
    3" ACCESS 비트 - 해당 페이지에 액세스 했는지.

    : 대개 위의 비트들은 `페이지 대체 알고리즘`을 구현할 때, 사용된다.

     

    : PRESENT 비트는 대개 `페이지 인 & 페이지 아웃` 관련해서 사용되는 비트다. 즉, 페이지 폴트 핸들러를 구현할 때, 자주 볼 비트다.

     

    : DIRTY 비트를 통해 퍼포먼스 향상을 기대할 수 있다. 더티 비트는 해당 페이지가 수정될 때만, SET 된다. 만약, 더디 비트가 SET 되지 않았다면 메모리에 있는 페이지를 굳이 디스크에 쓸 필요가 없다. 왜냐면, 내용이 바뀌지 않았기 때문이다. 

     

     

    - 멀티 레벨 페이지 테이블

    : 아래의 그림은 x86 Two-level without PAE or PSE 페이징 과정을 보여준다. 보호 모드로 오면서 선형 주소라는 개념이 생겼는데, 선형 주소에 대한 설명은 GDT를 참고하자.

    출처 - https://en.wikipedia.org/wiki/Page_table#Multilevel_page_tables

    https://en.wikipedia.org/wiki/Page_table#Multilevel_page_tables

    : 페이징이 활성화되면 선형 주소는 실제 물리 주소가 아니다. 이 선형 주소는 페이징을 위한 세 개의 오프셋으로 나뉜다.

    1" 0-11 : 페이지 오프셋
    2" 12 - 21 : 페이지 테이블 오프셋
    3" 22 - 31 : 페이지 디렉토리 오프셋

     

    : 투-레벨 페이지에서는 페이지 디렉토리 물리 주소가 CR3에  저장되어 있다. 32비트 페이징에서는 `페이지 디렉토리의 물리 주소`를 64비트 페이징에서는 `PML4의 물리 주소`를 저장하고 있다.

    The base physical address of the paging-structure hierarchy is contained in control register CR3.
    ...
    The page map level 4 (PML4) — An entry in a PML4 table contains the physical address of the base of a page directory pointer table, access rights, and memory management information. The base physical address of the PML4 is stored in CR3.
    ...
    CR3 — Contains the physical address of the base of the paging-structure hierarchy and two flags (PCD and PWT). Only the most-significant bits (less the lower 12 bits) of the base address are specified; the lower 12 bits of the address are assumed to be 0. The first paging structure must thus be aligned to a page (4-KByte) boundary. The PCD and PWT flags control caching of that paging structure in the processor’s internal data caches (they do not control TLB caching of page-directory information).

    : 페이지의 시작 주소는 반드시 4KB에 정렬되어 있다. 왤까? 배열을 생각해보자. A[10] 라는 배열이 존재한다. 이 때, A == &A[0]은 모두가 아는 사실일 것이다. 즉, 배열의 시작 주소의 배열의 첫 번째 인자의 주소는 항상 같다. 위의 내용에 이 부분이 그대로 적용된다. 페이지의 시작 주소가 정렬되어 있어야 하는건, 페이지 테이블 및 디렉토리의 시작 주소와 첫 번째 엔트리와 주소가 같기 때문이다. 엔트리의 LSB 12비트는 페이지 플래그 값으로 손상되면 안된다. 

     

    : 페이지 디렉토리는 4KB 길이를 갖으며, 시작 주소는 4KB로 정렬된 주소다. 페이지 디렉토리는 총 1024개의 페이지 디렉토리 엔트리를 가지고 있다. 그리고 페이지 디렉토리 엔트리 하나는 하나의 페이지 테이블의 주소를 가지고 있다. 이 페이지 테이블의 사이즈 또한 4KB이며, 각 엔트리는 4바이트를 차지한다. 그래서 총 페이지 테이블 하나 당 1024개의 페이지를 나타낼 수 있다. 그러면, 페이지가 4KB라면 페이지 디렉토리 하나와 1024개의 페이지 테이블만 있으면, 4G를 표현할 수 있다. 

     

    - x86 페이징

    : 위의 그림은 x86의 3가지 페이징 모드에 따른 상태 변화를 나타낸다. `32-bit Paging`, `PAE Paging`, `IA-32e Paging`으로 나뉜다. 세 가지 모드 전부 전제는 CR0.PE(0) = 1 && CR0.PG(31) = 1 라는 것을 명심하자. 즉, 보호 모드가 반드시 전제가 되어 있어야 한다. PAE는 CR4.PAE(5)를 의미하고, LME는 IA32_EFER.LME(8)를 의미한다. PAE는 물리 주소를 4비트 확장시키는 기능을 의미한다. 그리고 LME는 `Long Mode Enable`의 약자로 64비트로 진입하는 것을 의미한다. 당연히, 64비트로 가면 주소의 크기 또한 증가한다.

    If CR0.PG = 1 and CR4.PAE = 0, 32-bit paging is used. 32-bit paging is detailed in Section 4.3. 32-bit paging uses CR0.WP, CR4.PSE, CR4.PGE, CR4.SMEP, and CR4.SMAP as described in Section 4.1.3.
    ... 
    If CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = 0, PAE paging is used. PAE paging is detailed in Section 4.4. PAE paging uses CR0.WP, CR4.PGE, CR4.SMEP, CR4.SMAP, and IA32_EFER.NXE as described in Section 4.1.3.
    ...
    If CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = 1, IA-32e paging is used.1 IA-32e paging is detailed in Section 4.5. IA-32e paging uses CR0.WP, CR4.PGE, CR4.PCIDE, CR4.SMEP, CR4.SMAP, CR4.PKE, and IA32_EFER.NXE as described in Section 4.1.3. IA-32e paging is available only on processors that support the Intel 64 architecture.

     

     

    : x86 OS 개발 시, 꾀나 중요한 내용이 있다. 바로 인텔에서 만드는 CPU 마다 지원하는 페이지가 다르다는 것이다. 롱 모드에서는 1G 페이지 사이즈를 지원한다고 하지만, 실제로는 지원하지 않는 모델들이 존재한다. 그래서 `CPUID 명령어`를 통해서, 내가 현재 사용하고 있는 CPU가 지원하는 페이지 사이즈를 알 수가 있다. `Intel 3A Volume - 4.1.4 Enumeration of Paging Features by CPUID` 를 참고하자.

     

    : 아래는 인텔에서 지원하는 페이징을 간략하게 표로 정리한 것이다.

    : 인텔 페이징 3가지 모드 중에서 32비트 모드는 PD, PT 밖에 없다. 즉, 32비트는 기본이 2-레벨 페이징. 여기에 `32비트 + PAE`가 되면 PDPT가 추가되서 3-레벨 페이징이 된다. 

     

     

    : 참고로, 인텔에서 64비트에서 1GB 페이지 사이즈를 지원하는 것과 32비트에서 4MB 페이지 사이즈를 `HugaPage` 라고 한다. 그러나, 이 모드들은 모델마다 지원 여부가 될 수도 있고, 안 될 수도 있다. 그 여부는 CPUID 명령어를 통해 알 수 있다. 해당 내용은 `Intel 3A Volume - 4.1.4 Enumeration of Paging Features by CPUID` 를 참고하자.

     

     

    - 32비트 페이징

    : 32비트 페이징은 인텔 문서 `4.3 32-BIT PAGING` 을 참고하면 된다.

     

     

    : 4MB 페이지 사용을 위해서는 `CR4.PSE = 1`이 되어야 한다. `CR4.PSE = 1`는 페이지 사이즈를 4MB로 강제하지 않는다. PDE.PS 비트에 따라서 페이지 사이즈는 4MB 혹은 4KB가 될 수 있다.

     

    ...
    CR4.PSE enables 4-MByte pages for 32-bit paging. If CR4.PSE = 0, 32-bit paging can use only 4-KByte pages; if CR4.PSE = 1, 32-bit paging can use both 4-KByte pages and 4-MByte pages.
    ...
    Page Size Extensions (bit 4 of CR4) — Enables 4-MByte pages with 32-bit paging when set; restricts 32-bit paging to pages of 4 KBytes when clear.

     

     



     

     

     

    - 64비트 페이징

    : 페이징 활성화

    " 페이징의 활성하는 생각보다 굉장히 쉽다. 그냥 CR0의 31번째 비트인 PG를 SET 하면 된다.

    출처 - https://en.wikipedia.org/wiki/Control_register

    https://en.wikipedia.org/wiki/Control_register

     

    : 주의점은 페이징이 활성화되는 시점부터 모든 주소는 가상 주소롤 맵핑이 된다. 즉, 그전에 `mov eax, 0x01020300` 에서 `0x01020300`은 물리 주소였지만, 페이징이 활성화되는 시점부터는 물리 주소가 아닌 가상 주소로 인식되서, 아래의 포맷으로 주소를 해석해야 한다.

    출처 - https://cs4118.github.io/www/2023-1/lect/18-x86-paging.html

    https://cs4118.github.io/www/2023-1/lect/18-x86-paging.html

     

    : 그리고 CR3 레지스터에 페이지 디렉토리의 베이스 어드레스를 넣어야 한다. 이 주소는 선형 주소가 아닌, 물리 주소다. 그리고 상위 20비트만 명시한다. 하위 12비트는 0으로 가정한다. 그래서 반드시 페이지 디렉토리의 베이스 어드레스느는 4KB로 정렬되어 있어야 한다.

     

     

    - PSE

    : PSE는 `Page Size Extension`의 약자로 x86 아키텍처에서 전통적으로 사용하던 4KB 페이지 크기를 늘리기 위해 도입된 기능이다.

     

    : PSE 기능은 CR4 레지스터의 4번째 비트인 PSE를 SET 하면 활성화된다. 아래 표는 CR4.PAE, CR4.PSE, PDE.PS가 어떤 관계를 이루고 있는지를 보여준다.

    출처 - http://www.rcollins.org/articles/2mpages/2MPages.html

    http://www.rcollins.org/articles/2mpages/2MPages.html

     

    : 그리고 PDE에도 7번째 비트인 PS는 PDE가 관리하는 페이지들의 페이지 사이즈를 결정한다. 페이지 사이즈가 4MB가 되기 위해서는 CR4.PSE와 PDE.PS가 모두 SET 되어야 한다.

    출처 - https://wiki.osdev.org/Paging

    https://wiki.osdev.org/Paging

     

    - PAE

    : PAE는 `Physical Address Extension`약자로 x86 아키텍처에서 페이징 설정을 할 때 고려해야 하는 중요한 기능이다. 32비트 x86 아키텍처 PAE가 활성화되면 다음과 같은 특징을 갖는다.

    1" 2-레벨 페이지 테이블 계층을 3-레벨 페이지 테이블 계층으로 변경한다.
    2" 페이지 테이블 엔트리 크기를 32비트에서 64비트로 확장한다. 주의할 점은 페이지 테이블의 크기는 여전히 4KB 라는 점이다. 그래서 1024개의 엔트리에서 512개의 엔트리로 감소한다. 
    3" 4GB 보다 더 큰 범위위 물리 주소에 접근이 가능해진다.

    : x86-64는 기본적으로 4-레벨 페이지 테이블 계층을 사용한다. 

     

    : PAE는 1995년 인텔 `펜티엄 프로`에 처음으로 소개되었다. 초기 32비트 x86 아키텍처에서 PAE는 페이지 테이블 엔트리에서 `페이지 프레임 넘버` 필드를 20에서 24로 증가시키고, 페이지 프레임 오프셋은 그대로 12를 유지했다. 그래서 물리적으로 주소 할당이 가능한 주소 범위를 4GB에서 64GB 까지 확장시켰다. PAE의 지원은 결국 물리적으로 주소 라인을 0 - 31 에서 0 - 35로 확장시키게 된다.

    출처 - https://wiki.osdev.org/Paging

    https://wiki.osdev.org/Paging

     

     

    - 페이지 디렉토리와 페이지 테이블의 오프셋이 10인 이유

     

    - 페이지 디렉토리가 필요한 이유

    : 페이지 디렉토리를 사용한다는 말은 투-레벨 페이지 테이블이 원-레벨 페이지 테이블보다 좋은 점이 무엇이냐를 붙는 것과 같다. 먼저 하나 짚고 싶은 점이 있다. 투-레벨 페이지 테이블이든 원-레벨 페이지 테이블이든 간에 결국 4GB를 표현하기 위한 메타 데이터의 양은 동일하다. 즉, 두 개 모두 4MB가 필요하다.

    1" 원-레벨 : 2^20 *  4
    2" 투-레벨 : 2^10 * 2^10 * 4

    : 근데, 투-레벨 페이지는 원-레벨 페이지보다 좀 더 플렉서블하다. 첫 번째로 메타 데이터를 연속적으로 선언하지 않아도 된다. 원-레벨 페이징에서 페이지 테이블은 부팅 시점에 정적으로 만들어지는 테이블이다. 페이징이 활성화되면, 추가적으로 페이지를 추가하기 어렵다. 데이터를 추가하기 위해 페이지 테이블을 없애고 페이징을 껏다가 페이지 테이블을 새로 등록하고 다시 켜고 하는 말도 안되는 일이 발생할 수 있다. 그러나, 투-레벨 페이징은 마스터 테이블로 페이징 디렉토리를 두고, 필요할 때마다 페이지 테이블을 만들어서 페이지 디렉토리 엔트리에 연결만 하면 된다. 

     

    : 두 번째 장점은 동적으로 할당이 가능하다는 점이다. 원-레벨 페이지는 부팅 시점에 한 번에 만들어야 하기 때문에 4MB를 연속적으로 한 군데에 한 번에 할당해야 한다. 그러나, 투-레벨은 페이지 테이블을 필요할 때마다, 특히 프로세스가 생성될 때마다 원하는 위치에 정렬만 맞춰서 할당받으면 된다. 

     

     

    - 아이텐티티 페이징

    : 현재 x86 보호 모드에서 페이징을 활성화 한다는 말은 가상 메모리를 사용한다는 말과 동의어다. 그렇면, 페이징을 활성화하면 그 즉시, EIP가 가리키는 주소들은 가상 주소 포맷으로 해석이 될까? 바로 해석된다. 그게 문제다. 

     

    : 페이징이 활성화된 직후에는 반드시 아이텐티티 페이징 처리가 된 페이지를 실행시켜야 한다. 그렇지 않으면, 크래쉬가 발생한다. 왜 그럴까? 아래의 코드를 보자.

    00000000 <page_enable>:
       0:	55                   	push   %ebp
       1:	89 e5                	mov    %esp,%ebp
       3:	8b 45 08             	mov    0x8(%ebp),%eax
       6:	0f 22 d8             	mov    %eax,%cr3
       9:	0f 20 c0             	mov    %cr0,%eax
       c:	0d 00 00 00 80       	or     $0x80000000,%eax
      11:	0f 22 c0             	mov    %eax,%cr0
      14:	89 ec                	mov    %ebp,%esp
      16:	5d                   	pop    %ebp
      17:	c3                   	ret

    : 위의 코드는 페이징을 활성화하는 코드다. 실제 페이징은 `mov %eax, %cr0`에서 활성화된다. 여기서 문제가 발생한다. 그 다음 명령어인 `mov %ebp, %esp` 명령어 주소를 보면 0x00000000+11이다. 이 시점에 EIP가 가지고 있는 이 주소를 CPU는 가상 주소로 인식해버린다. 그래서 페이지 디렉토리를 찾는다. 그리고 페이지 테이블을 찾는다. 그리고 최종 페이지를 찾아서 실행한다. 그게 `0x00000000 + 11` 주소여야 저 명령어가 실행되는 것이다.

     

    : 그러나 위와 같이 되는 것은 아주 운이 좋은 거다. 대게는 이 주소는 이상한 물리 주소로 맵핑이 되어버린다. 변환된 물리주소에 저 코드가 있을까? 즉, 가상 주소 `0x00000000+11` 와 맵핑되는 물리 주소에도 `mov %ebp, %esp`가 있을까? 그렇치 않다면, 페이지 폴트가 발생한다. 페이지 폴트를 처리하지 못하면, 더블 폴트, 더플 폴트도 처리하지 못하면 트리플 폴트로 시스템 다운이다.

     

    : 페이징을 활성화할 때, 모든 주소들을 가상 주소로 처리해야 할까? 이건 현재 실행중인 코드에 문제를 야기한다. 그러나 현재 실행중인 코드는 가상 주소와 물리 주소를 동일하게 1:1 로 맵핑하여 계속해서 실행할 수 있게 해주어야 한다. 그리고 중요한 건 현재 실행되고 있는 전체 이미지가 아이텐티티 영역안에 들어와야 에러가 발생하지 않는다.

     

    : 아이텐티티 매핑이 정상적으로 되었는지 확인하려면, 0xC080_0000 과 0x0080_0000에 쓴값이 동일해야 한다. 즉, 증명은 아래와 같이 할 수 있다. 예를 들어, 아래의 예시는 [0xC000_0000 : 0xFFFF_FFFF]을 [0x0000_0000 : 0x3FFF_FFFF] 에 아이텐티티 매핑한것이다. 아래의 예시중에서 한쪽에 값을 썻는데, 반대편 주소에도 적용된다면 아이텐티티 매핑이 정상적으로 진행된 것이다. 

    1" 0xC080_0000 == 0x0080_0000
    2" 0xD200_23FE == 0x1200_23FE
    3" 0xFFFF_2000 == 0x3FFF_2000
    ...

     

     

     

    - 메모리 액세스 과정

    : CPU가 메모리에 액세스하는 과정은 가상 주소가 활성화되기전과 후로 나뉜다. 가상 주소를 쓰기전에는 CPU는 MMU를 사용하지 않는다. 대게 RAM에 접근하기 위해서는 메모리 컨트롤러에 직접 요청을 하거나, 주변 장치들과 통신하기 위해 RAM에 직접 데이터를 쓰기 한다.


    https://medium.com/@connorstack/how-does-an-os-enable-virtual-memory-696a8f75f274#.qz5hxhbcs#d5cb

     

    : 이제 가상 주소가 활성화 해보자. 이제부터는 CPU가 램에 액세스 하기 위해서는 반드시 MMU를 통해서 램에 액세스하게 된다.

    출처 -&nbsp;https://medium.com/@connorstack/how-does-an-os-enable-virtual-memory-696a8f75f274#.qz5hxhbcs#d5cb

    https://medium.com/@connorstack/how-does-an-os-enable-virtual-memory-696a8f75f274#.qz5hxhbcs#d5cb

     

     

    - PML4

    : PML4라는 것은 x86-64에서 새로운 페이징을 위한 추가된 테이블이다. 롱 모드에서는 4개의 테이블을 사용해서 가상 주소와 물리 주소를 맵핑한다. 기본적으로는 48비트 가상 주소를 52비트 물리 주소에 맵핑한다.

     

    : 롱 모드에서 페이지 사이즈는 `4KB, 2MB, 1GB` 3개를 지원한다. 특징은 다음과 같다.

    1" 4KB - 4개의 페이지 테이블이 모두 필요하다(P4, P3, P2, P1)
    2" 2MB - 3개의 페이지 테이블이 필요하다(P4, P3, P2)
    3" 1GB - 2개의 페이지 테이블만 필요하다(P4, P3)

    : 위의 이유는 생각해보면 간단하다. 4KB 페이지 사이즈는 하위 12비트가 필요없다. 남는 비트는 2^36(2^48 / 2 ^12) 이다. 롱 모드에서 각 테이블은 512개 이므로, 512를 모두 표현하려면 9비트가 필요하다. 그렇면, 36 / 9 = 4 이므로, 페이지 사이즈가 4KB 라면 총 4개의 페이지 테이블이 필요하다.

     

    : 페이지 사이즈가 2MB 라면 어떨까? 페이지 오프셋이 2^21 이므로, 페이지 넘버를 구하면 2^27(2^48 / 2^21)이 된다. 27비트로는 각 페이지 테이블이 9비트로 표현되니, 3개의 테이블이 필요하다. 이렇게 하다보면 페이지 사이즈가 1GB면 2개의 테이블이 필요하다는 것을 알 수 있게 된다.

     

    : PTE는 4KB를 표현할 수 있다. 이게 512개 합쳐져서 PT는 2MB(4KB*512)를 표현할 수 있다. 이 말은 PDE는 2MB를 표현한다는 의미가 된다. 그렇면, PD는 1GB(2MB*512)를 표현할 수 있다.

    : 각 페이지 사이즈에 따른 페이지 테이블 엔트리들의 비트값들이 어떻게 바뀌는지를 확인해야 한다. 몇 가지 주의점을 알아보고 가자. `Reserved` 영역은 반드시 0이여야 한다. 아래에서 상세하게 페이지 엔트리들에 대해 보겠지만, 인텔에서는 `Reserved`와 `Ignored`라는 속성을 나눠놓았다. `Ignored`는 0이든 1이든 어떤 값이 와도 상관이 없지만, `Reserved`는 반드시 0이 와야 한다. 대개, 이 영역에 대한 에러로 페이지 폴트가 발생을 하는데 이때 에러 코드를 확인해보면 `Reserved` 영역에 1이 있어서 발생한 에러다.

     

    : 위에서 `M`은 최대 물리 주소(MAX-PHY-ADDR)길이다. CPUID 0x80000008을 지원하지 않는 프로세서는 36비트이다. 만약, 0x80000008 CPUID를 지원한다면 해당 명령어를 통해서 MAX-PHY-ADDR을 확인할 수 있다. 그러나, 대개는 52비트다. 그리고 현재 물리 주소의 최대가 52비트이기도 하다. 인텔이 PML5를 내놓을 때도, 가상 주소는 57비트로 확장했지만, 물리 주소는 여전히 52비트를 유지했다. 

     

     

    - 페이지 테이블 주소가 4KB 정렬인 이유

    : 모든 페이지 [디렉토리|포인터] 테이블 엔트리를 보면 [31:12]는 페이지 테이블의 물리 주소를 가리킨다는 것을 확인할 수 있다. 엔트리[11:0]가 플래그로 사용되면서, 자연스럽게 4KB 정렬이 되는 것이다. 

     

    - 페이지 테이블 사이즈가 4KB 인 이유

    : 데이터 주소가 `N`-byte 정렬이라는 것은 그 데이터의 사이즈 또한 `N` 바이트 라는 것을 의미힌다.

     

     

    : PML4E


    : PML4는 간단하다. 이 엔트리에 하위 테이블이 어떤 상태건 상관없이 하나의 엔트리가 무조건 512GB를 관리한다. R/W가 0이면, 512GB가 READ-ONLY가 되버린다. `Reserved` 영역 반드시 0 이어야 한다. 주의하자.

     

     

    : PDPTE

    : 이 엔트리부터 이제 `PS`가 나온다. `PS`의 값에 따라 지원하는 페이지 사이즈가 달라진다.

     

    : PDE



     

    : PTE

     

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    메모리 맵  (0) 2023.06.01
    커널 이미지  (0) 2023.05.31
    PIT [작성중]  (0) 2023.05.29
    [운영체제 만들기] Exception  (0) 2023.05.25
    [운영체제 만들기] 에러 사항  (0) 2023.05.24
Designed by Tistory.