'보호모드'에 해당되는 글 2건

본 문서를 작성하신 "라키시스" 님께 감사드립니다.

코드:
이 문서는 인텔 아키텍쳐의 메모리 어드레싱에 관한 문서 중 페이징에 관련된 내용을
다루는 문서이다. 일전의 세그먼트에 관련된 문서에 연속되는 내용을 가진다.

참조하는 문헌은 Understanding the Linux Kernel 과 Intel Software Developer's
Manual Vol.3 이다.

정리한 사람 : 라키시스 (mailto:lachesis@kldp.org, http://lachesis.pe.kr)
수정 일자 :
    - 2001. 3. 21
    - 2001. 3. 24 : 오타 및 개념 보충, 리눅스의 페이징 추가

차례
Part 1. 페이징의 개요
Part 2. 페이징에 사용되는 프로세서 레지스터들 및 플래그
Part 3. 페이지 디렉토리, 페이지 테이블
Part 4. physical address 를 구하는 법
Part 5. 페이지 테이블 엔트리, 페이지 디렉토리 엔트리의 구조
   삽입 : 인텔의 캐쉬 메모리
Part 7. Translation Lookaside Buffers
Part 8. 리눅스에서의 페이징의 구현
Part 9. 메모리 레이아웃

-------------------------------------------------------------------------------

Part 1. 페이징의 개요

앞서도 얘기했듯이, 리눅스는 세그먼트 기반의 메모리 관리 기법보다, 페이징 기법에
기반한 메모리 관리를 한다. 이제부터 인텔 프로세서들에서 페이징 기법을 어떻게
지원하는지에 대해서 알아 보고, 리눅스에서는 그러한 아키텍쳐상의 구조를 어떻게
활용하는지 알아보도록 한다.

앞의 문서에서도 설명했듯이, 프로그래머가 특정 메모리 셀에 접근하기 위해서
지정하는 Logical address 는 세그멘테이션 유닛을 거쳐서 Linear address 로 변환되고,
이렇게 변환된 Linear address 는 페이징 유닛을 거쳐서 Physical address 로 변환된다.

    Logical                         Linear                     Physical
    address  +-------------------+  address   +-------------+  address
    -------->| Segmentation Unit |----------->| Paging Unit |----------->
             +-------------------+            +-------------+

메모리 접근시에 페이징의 기법이 사용되면, 프로세서는 linear address space 를
직접 메모리 혹은 디스크에 매핑되는 고정된 크기의 '페이지' 라는 단위로 나누어서
바라보게 된다. 주로 인텔 계열에서는 4KB 의 크기를 가진다. (alpha 칩 같은 경우는
페이지 프레임의 크기가 8KB 이다)

여기서 잠시, 페이징 기법의 특성을 보다 강조하기 위해서 세그멘테이션과의 차이점을
짚고 넘어가도록 하자.

페이징 기법은 항상 고정된 크기의 페이지들을 사용한다. 반면, 세그먼트들은 제각각
다른 크기를 가질 수 있다.
그리고, 만약 메모리 어드레싱 기법에 세그멘테이션만 사용된다면, Physical memory 에
존재하는 것으로 나타나는 데이터 구조들은 항상 전체 부분이 실제 메모리에 위치해
있어야 할 것이다. 반면, 페이징 기법도 함께 사용된다면, 데이터 구조들은 일부분은
메모리에, 일부분은 디스크 기록 장치에 있는 식으로 유지될 수도 있다. 왜 그러한지는
앞으로 다룰 페이지 디렉토리나 페이지 테이블 엔트리의 구조를 살펴보면 알 수 있다.

페이징을 사용해서 선형 주소를 물리적 주소로 바꿀 때 요구되는 어드레스 버스와
데이타 버스에서 일어나는 자료 전송 사이클을 최대한 줄이기 위해서 가장 최근에
접근된 페이지 디렉토리(뒤에 설명한다)와 페이지 테이블(역시 뒤에 나온다) 엔트리들은
프로세서 내에 존재하는 Translation Lookaside Buffer(TLB) 라는 프로세서에 내장되어
있는 메모리 디바이스에 캐쉬된다. 이 TLB 에 관한 내용역시 뒷부분에서 다루도록 한다.

-------------------------------------------------------------------------------

Part 2. 페이징에 사용되는 프로세서의 레지스터 및 플래그

이제는 페이징 기법을 적용하기 위해 프로세서가 사용하는 control register 들을
알아 보도록 하자.
페이징은 프로세서의 제어 레지스터(control register : cr0, cr1, cr2, cr3, cr4...)
들에 있는 세가지의 플래그를 통해서 컨트롤된다.

    1. PG(Paging) flag : cr0 레지스터의 31번 비트이다. 이 cr0 레지스터는 80386
       이후의 모든 인텔 프로세서에서 사용 가능한 레지스터이다.

       이 플래그는 프로세서에게 page-translation mechanism 을 사용할 것이라는
       사실을 알려준다. 일반적으로 멀티 태스킹을 지원하는 운영체제들은 부팅시에
       프로세서를 초기화 하면서 이 플래그를 세팅한다.
       이 플래그가 1 로 세팅되어 있으면, 프로세서는 주소 변환 방식에 페이징
       방식도 사용하게 된다.

    2. PSE(Page size Extensions) flag : cr4 레지스터의 4번 비트이다. 이 cr4
       레지스터는 인텔의 Pentium / Pentium Pro 프로세서에서 사용된 것을 처음으로
       인텔의 프로세서들에 사용되기 시작했다.

       이 플래그는 PAE 플래그와 함께 사용되어서 좀 더 큰 페이지 크기의 사용을
       허용한다. 보통 기본적으로 한 페이지으 크기는 4KB 이나, 이 플래그가 세팅
       되면, 크기가 4MB 이거나 2MB 인 페이지를 사용할 수 있게 된다.

    3. PAE(Physical address extension) flag : cr4 레지스터의 5번 비트인데, 이
       플래그는 Pentium Pro 프로세서 이상의 기종에만 사용한다.

       이 플래그는 physical address 에 36비트의 주소를 사용할 수 있도록 해 주는데,
       36 비트의 주소를 사용하면 최대 64GB 까지의 메모리 주소를 지정할 수
       있다. 여담이지만, 리눅스 커널을 컴파일 하면서 high memory support 항목이
       있는데, 2.4 커널에서는 최대 지원 가능한 메모리 크기로 64GB 와 4GB를
       두고 있는데, 그 이유가 바로 이 PAE 플래그를 세팅했을 때 physical address
       를 64 기가까지 지정할 수 있고, PAE 가 클리어되면, 32비트 주소를 Physical
       address 에 사용하므로, 4GB 까지 사용할 수 있기 때문이다.
       즉 이 커널 옵션은 PAE 플래그의 사용 유무를 결정하는 옵션이라고 할 수
       있겠다. :-)

       그리고, 이 플래그는 페이징 기법을 사용할 때에만 활성화 될 수 있다. 이
       플래그를 사용한 36비트 주소 지정방식은 페이지 디렉토리와 페이지 테이블을
       이용하여 구현되었기 때문이다.
       자세한 것은 뒤에서 다루겠다.


-------------------------------------------------------------------------------

Part 3. 페이지 디렉토리, 페이지 테이블

이제 프로세서가 어떻게 linear address 를 physical address 로 바꾸는 작업을
수행하는지를 알아볼 차례이다. 먼저, 그렇게 하는 과정에서 사용되는 자료구조들에
무엇이 있는지 알아보자.

    1. Page directory
   32비트 크기의 페이지 디렉토리 엔트리(PDE)들을 저장하고 있는 배열이다.
   이 배열은 4KB 크기의 메모리 페이지 하나에 담겨 있는데, 배열의 원소의
   갯수는 1024개이다.
   그리고, 뒤에서도 나오겠지만, 리니어 어드레스의 필드 중 페이지 디렉토리에서
   해당 페이지가 담긴 페이지 테이블을 가리키는 페이지 디렉토리 엔트리의
   페이지 디렉토리에서의 인덱스를 가리키는 필드로 상위 10 비트가 사용되는데,
   10 비트이면, 최대 1024개 까지의 원소를 가질 수 있다. (2e10 = 1024)
    2. Page table
   역시 페이지 디렉토리와 마찬가지로 메모리 페이지 하나를 차지하고 있는
   배열로써, 각 원소의 크기는 32비트이다. 1024개 까지의 원소를 가질 수
   있다.
    3. Page
   크기가 각각 4KB, 2MB, 4MB 중 하나인 연속적인 메모리 주소 공간 (address space)
   이다.
    4. Page-directory-pointer table
   크기가 64비트인 원소들 네개로 이루어진 배열인데, 각 원소들은 페이지
   디렉토리를 가리키고 있다.
   이 구조체는 앞서 설명한 PAE 플래그가 세팅되어 있을 때만 사용한다.
   
아래에 앞서 언급한 플래그들과, 페이지 디렉토리 엔트리에 있는(이것은 아직 설명
하지 않았음. 뒤에 나옴) 페이지 사이즈(PS) 플래그들의 값에 따른 하나의 페이지의
크기와 그 페이지를 지정하기 위해 사용한 Physical address 의 크기를 나열한
표를 적어 보았다. 인텔 매뉴얼 3-20 페이지에 있는 표이다.

    PG flag    PAE flag    PSE flag    PS flag    Page    Physical Addr.
    in CR0     in CR4      in CR4      in PDE     Size    Size
    ==========================================================================
    0          Unused      Unused      Unused     -       Paging Disabled
    1          0           0           Unused     4KB     32 bits
    1          0           1           0          4KB     32 bits
    1          0           1           1          4MB     32 bits
    1          1           Unused      0          4KB     36 bits
    1          1           Unused      1          2MB     36 bits


-------------------------------------------------------------------------------

Part 4. Physical address 구하기

자, 그러면 이제, 정말로, 선형 주소를 어떻게 Physical memory 에 매핑하는지를
알아보자. 32비트 크기의 Linear address 는 세 부분으로 나누어진다.
(아직은 어디까지나, 인텔 프로세서가 인식하는 '주소'라는 개념에 대해 이야기하고
있음을 염두에 두라)

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +-------------------+-------------------+-----------------------+
    | Page Directory    | Page Table        | Offset                |
    +-------------------+-------------------+-----------------------+

linear address 가 위와 같은 구조로 해석되는 경우에는 각 페이지의 크기는 4KB 인데,
(이것은 쉽게 유추해 볼 수 있다. 2e12 = 4K) 이때, 각각의 부분이 하는 역할은 다음과
같다. 프로세서는 어떤 linear address 를 Physical address 로 바꾸기 위해서 먼저,
페이지 디렉토리가 메모리 주소 어디에 저장되어 있는지를 찾는다. 이 정보는 CPU
레지스터 cr3 레지스터에 들어 있다. 이 레지스터를 PDBR (Page Directory Base Reg)
이라고 부르기도 한다. 일단 프로세서가 cr3 레지스터로부터 페이지 디렉토리가 들어
있는 메모리의 첫번째 주소를 읽어들이면, 변환해야 할 linear address 의
page directory 부분의 10비트를 이용하여, 그 linear address 의 페이지에 대한
정보가 어느 페이지 테이블에 담겨 있는지를 찾는다.

CR3 컨트롤 레지스터의 구조는 아래에 보인 페이지 디렉토리 엔트리의 구조와 완전히
동일하다. 단, 비트 31-12 의 이름이 Page Directory Base address 이며,
11-5의 비트와 2-0 비트가 예약된 비트로써, 0 으로 채워져 있다는것이 차이점이다.
레지스터의 3,4 번비트는 여전히 PWT, PCD 라는 이름으로 사용되고 있다.

즉, linear address 의 상위 10비트는 페이지 디렉토리라는 구조체 배열의 '인덱스'
라고 생각하면 되겠다. 그러면, 이 페이지 디렉토리라는 구조체 배열의 원소인
페이지 디렉토리 엔트리(? ^^; 말이 좀 이상하다. 원소나 엔트리나, 그말이 그말이다.)
의 구조는 어떻게 생겼는지 그려 보겠다.

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+
    |                                       |     | |P| | |P|P|U|R| |
    | Page Table Base Address               |Avail|G|S|0|A|C|W|/|/|P|
    |                                       |     | | | | |D|T|S|W| |
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+

이러한 페이지 디렉토리 엔트리의 각 필드의 역할은 나중에 페이지 테이블 엔트리의
각 필드의 값과 함께 설명하기로 하고, 우선, 상위 20비트인 페이지 테이블 베이스
주소 라고 명명되어 있는 필드에 대해서만 설명하겠다.
이 필드에는 이 엔트리가 가리키는 페이지 테이블 구조체의 최초 시작 번지의
Physical address 가 담기게 된다. 마찬가지로, CR3 컨트롤 레지스터의 페이지 디렉토리
베이스 어드레스 필드에는 페이지 디렉토리 구조체의 시작 번지가 Physical address 로
담기게 된다.

그러면, 프로세서는 linear address 의 21번부터 12번 비트를 가지고, 해당하는
페이지 테이블 엔트리가 어느것인지 구해내고, 페이지 테이블 엔트리로부터 4KB 의 연속적인
메모리 영역의 시작번지를 구하게 된다. 그리고, 마지막으로 구해진 베이스 어드레스에
Offset 을 더해서 원하는 주소를 액세스할 수 있게 된다.

참, 설명하다가 보니 잠시 소홀했던 것이 있는데, linear address 의 21번 부터 12번 비트는
최상위 10비트(31-22)가 가리키는 페이지 디렉토리 엔트리가 가리키는 페이지 테이블에서
읽어야 할 페이지 테이블 엔트리의 인덱스이다.

그러면, 페이지 테이블 엔트리는 어떠한 구조를 가지고 있는지 그려 보도록 하자.

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+
    |                                       |     | | | | |P|P|U|R| |
    | Page Base Address                     |Avail|G|0|D|A|C|W|/|/|P|
    |                                       |     | | | | |D|T|S|W| |
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+

최초에 나오는 상위 20비트의 부분이 의미하는 바는 페이지 디렉토리 엔트리의
그것과 크게 다를바 없다. 단지, linear address 가 가리키는 페이지가 처음 시작
하는 base 주소의 Physical address 를 가지고 있다는 점만 차이가 난다.

다시 한번 정리하는 차원에서 프로세서가 주어진 linear address 를 Physical address
로 바꾸는 과정을 한번 더 적어보겠다.

    1. 먼저, 프로세서는 컨트롤 레짓스터 cr3 의 상위 20비트를 참조하여 지금 사용하고
       있는 페이지 디렉토리가 저장된 곳의 Physical address 를 알아낸다.

    2. 그리고, 주어진 linear address 의 상위 10비트의 인덱스를 이용하여 페이지
       디렉토리에서 하나의 엔트리를 찾는다. (이 엔트리의 Physical address 는,
       cr3 레지스터의 상위 20비트의 값 + linear address 의 상위 10 비트의 값)

    3. 이렇게 페이지 디렉토리 엔트리의 값을 읽어오면, 해당 페이지의 물리적 주소를
       가지고 있는 페이지 테이블의 시작주소를 알 수 있다.

    4. 그렇게 해서 찾은 페이지 테이블에서 주어진 linear address 의 21-12번 비트의 10비트
       를 인덱스로 하여 해당하는 페이지 테이블 엔트리를 읽어온다.

    5. 읽어온 페이지 테이블 엔트리로부터 찾고자 하는 페이지의 시작 번지를
       Physical address 의 주소 형식으로 구할 수 있다.

    6. 그러면, 이제 실제로 액세스하기 원하는 바이트의 주소는 구해진 페이지의
       베이스 어드레스에 주어진 linear address 의 하위 12비트에 있는 offset 값을
       더하면 구할 수 있다.

    여기서 잠시, 어떻게 20비트의 주소를 가지고 physical address 를 구할 수 있겠는가?
    기억하고 있겠지만, 한 페이지는 물리적 메모리에서 연속적인 메모리 공간이다.
    페이지의 크기는 4KB 이다. 4KB 를 표현하기 위해서는 12비트가 필요하다.
    20비트를 가지고 왼쪽으로 12비트 시프트 연산을 하면 언제나 기본 단위가
    4KB 인 메모리 주소가 나온다. 즉, 0x00000 000 , 0x00001 000 과 같은 단위로
    물리적 주소가 나오게 되는 것이다. 오른쪽의 12비트는 오프셋에 할당된다.

이제까지 설명한 것들은 모두, 페이지의 단위가 4KB 이며, Physical address extension 이
사용되지 않는, 즉, Physical address size 가 32비트인 경우에 적용되는 것이다.
그러면, 만약, Physical address extension (PAE) 플래그(CR4 레지스터의 5번 비트)가
0 이지만, cr4 레지스터의 4번비트인 PSE 플래그가 1이고, 페이지 디렉토리 엔트리의
PS 플래그(7번 비트)가 1 인 경우, 즉, 한 페이지의 크기가 4KB 가 아니고, 4MB 인
경우에는 어떻게 매핑을 할까?

먼저, 한 페이지의 크기가 4MB 가 되려면, linear address 의 offset 부분이 12비트가
아니고, 22비트가 되어야 함을 생각할 수 있다.
그렇다. 예상한 바와 같이, 그 경우, 즉,

    PG flag  (bit 31 of cr0) = 1
    PAE flag (bit 05 of cr4) = 0
    PSE flag (bit 04 of cr4) = 1
    PS flag  (bit 07 of PDE) = 1

인 경우에는 프로세서가 바라보는 linear address 의 구조가 아래 그림처럼 달라지게 된다.

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +-------------------+-------------------------------------------+
    | Page Directory    | Offset                                    |
    +-------------------+-------------------------------------------+

그리고, 페이지 디렉토리 엔트리의 구조도 아래의 그림처럼 달라진다.

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +-------------------+-------------------+-----+-+-+-+-+-+-+-+-+-+
    |                   |                   |     | | | | |P|P|U|R| |
    | Page Base Address | Reserved          |Avail|G|P|D|A|C|W|/|/|P|
    |                   |                   |     | |S| | |D|T|S|W| |
    +-------------------+-------------------+-----+-+-+-+-+-+-+-+-+-+

위 그림에서 Reserved 라고 표기된 부분은 사용하지 않고, 0 으로 세팅되어 있다.
그리고 이 경우, 베이스 어드레스가 4KB 페이지를 사용할때와는 달리 10비트인데,
이것은 Physical address 의 상위 10비트의 값으로 사용된다. 그러면 어떤 효과가
있을까? 그렇다. 각각의 페이지는 정확히 4MB 의 배수의 크기를 항상 베이스 주소로
가지게 된다.

역시 이경우에도 linear address 가 주어지면, 프로세서는 PDBR(Page Directory
Base Register)로 쓰이는 cr3 레지스터의 상위 20비트에 저장되어 있는 페이지 디렉토리가
시작하는 Physical address 를 참조하여 페이지 디렉토리를 찾고, (물론, 그 값을
왼쪽으로 12비트 시프트 시킨 주소이다)
주어진 linear address 의 상위 10비트를 인덱스로 하여 해당하는 페이지 디렉토리
엔트리를 찾아서 4MB 페이지의 베이스 주소를 구하여, 거기에 linear address 에서
지정하는 Offset 을 더하여 원하는 메모리 주소에 접근하게 된다.
단지 이 경우에는 4KB 페이지가 쓰이는 경우와는 달리 참조하는 테이블이 하나밖에
없다는 것만 4KB 페이지의 경우와 다를 따름이다.


-------------------------------------------------------------------------------

Part 5. 페이지 테이블 엔트리, 페이지 디렉토리 엔트리의 구조

그러면, 이제, 페이지 테이블 엔트리와 페이지 디렉토리 엔트리들의 각각의
필드의 의미에 대해서 살펴볼 차례이다. 우선 그림부터 그려 보자.
앞에서 나왔던 그림에 몇가지 설명이 더 붙은 그림이다.

1. Page Directory entry (4KB Page Table)

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+
    |                                       |     | |P| | |P|P|U|R| |
    | Page Table Base Address               |Avail|G|S|0|A|C|W|/|/|P|
    |                                       |     | | | | |D|T|S|W| |
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+
                                               |   | | | | | | | | |
    시스템 프로그래머가 임의로 사용가능 -------+   | | | | | | | | |
    Global Page (ignored) -------------------------+ | | | | | | | |
    Page size (0 -> 4KB) ----------------------------+ | | | | | | |
    Reserved (set to 0) -------------------------------+ | | | | | |
    Accessed --------------------------------------------+ | | | | |
    Cache disabled ----------------------------------------+ | | | |
    Write-through -------------------------------------------+ | | |
    User/Supervisor -------------------------------------------+ | |
    Read/Write --------------------------------------------------+ |
    Present -------------------------------------------------------+
   

2. Page Table entry (4KB Page)

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+
    |                                       |     | | | | |P|P|U|R| |
    | Page Base Address                     |Avail|G|0|D|A|C|W|/|/|P|
    |                                       |     | | | | |D|T|S|W| |
    +---------------------------------------+-----+-+-+-+-+-+-+-+-+-+
                                               |   | | | | | | | | |
    시스템 프로그래머가 임의로 사용가능 -------+   | | | | | | | | |
    Global Page -----------------------------------+ | | | | | | | |
    Reserved (set to 0) -----------------------------+ | | | | | | |
    Dirty ---------------------------------------------+ | | | | | |
    Accessed --------------------------------------------+ | | | | |
    Cache disabled ----------------------------------------+ | | | |
    Write-through -------------------------------------------+ | | |
    User/Supervisor -------------------------------------------+ | |
    Read/Write --------------------------------------------------+ |
    Present -------------------------------------------------------+


3. Page Directory Entrry (4MB Page)

     3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
     1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    +-------------------+-------------------+-----+-+-+-+-+-+-+-+-+-+
    |                   |                   |     | | | | |P|P|U|R| |
    | Page Base Address | Reserved          |Avail|G|P|D|A|C|W|/|/|P|
    |                   |                   |     | |S| | |D|T|S|W| |
    +-------------------+-------------------+-----+-+-+-+-+-+-+-+-+-+
                                               |   | | | | | | | | |
    시스템 프로그래머가 임의로 사용가능 -------+   | | | | | | | | |
    Global Page (ignored) -------------------------+ | | | | | | | |
    Page size (1 -> 4MB) ----------------------------+ | | | | | | |
    Dirty ---------------------------------------------+ | | | | | |
    Accessed --------------------------------------------+ | | | | |
    Cache disabled ----------------------------------------+ | | | |
    Write-through -------------------------------------------+ | | |
    User/Supervisor -------------------------------------------+ | |
    Read/Write --------------------------------------------------+ |
    Present -------------------------------------------------------+


이제부터 각각의 필드의 의미를 설명해 보도록 하겠다.

- 31번 비트부터 12번 비트 까지의 Page (table) base address 필드
   
    4킬로짜리 페이지의 페이지 테이블 엔트리에서는 이 필드는 4KB 페이지의 첫번째
    바이트의 물리적 주소(Physical address)를 의미한다. 이 필드는 물리적 주소의
    상위 20비트의 값으로 해석되어진다. 그렇게 함으로써, 각각의 페이지가 4K 단위의
    크기로 메모리에 존재하게 한다.
   
    4킬로 크기의 페이지의 페이지 디렉토리 엔트리에서 이 필드는 페이지 테이블의
    첫번째 바이트의 물리적 주소를 나타낸다. 역시 이 주소도 physical address 의
    상위 20비트의 값인데, 페이지 테이블이 메모리에 4KB 단위로 존재하도록 강제(?)한다.

    4메가 크기의 페이지 디렉토리 엔트리에서 이 필드는 오직 상위 10비트만 사용되
    는데, 나머지 Reserved 라고 표기된 부분은 사용되지 않고 0 으로 되어 있어야만
    한다. 만약 이 부분(21번 비트 - 12번 비트)이 0 이 아닌 상태이고, PSE, PAE
    플래그가 1 로 세트되어 있으면 프로세서는 페이지 폴트를 발생시킨다.

- 0번 비트 : Present Flag
    이 비트는 해당하는 페이지(페이지 테이블 엔트리의 경우)나 페이지 테이블
    (페이지 디렉토리 엔트리의 경우)이 지금 현재 메인 메모리에 존재하는지,
    존재하지 않는지를 나타내는 플래그이다. 만약 이 플래그가 1로 세팅되어 있으면,
    해당하는 페이지는 물리적 메모리(메인 메모리)에 존재하며, 주소 변환은
    계속 진행한다. 만약 이 플래그가 0으로 클리어되어 있으면, 해당하는 페이지는
    실제 메모리 상에 존재하지 않으며, 프로세서가 이러한 페이지에 접근하려고
    시도한다면, 페이지 폴트 예외상황(exception)이 발생하며, 운영체제로 하여금
    해당하는 페이지를 메모리에 적재하거나 하는 동작을 취할 수 있도록 한다.
    이 비트를 세팅하거나 클리어 하는 동작은 프로세서는 수행하지 않고, 운영체제가
    구현하고 있는 demand-paging 등의 알고리즘에서 그 일(0번 비트를 세트하거나
    클리어 하는 동작)을 수행한다.

    이 비트플래그에서 주의할 점은,
    PAE 가 활성화 되었을 때에는 항상 이 비트가 1 로 세트되어 있어야 한다는 점이다.

- 1번 비트 : Read/Write flag
    해당 페이지(페이지 테이블 엔트리에서) 혹은 페이지군(페이지 디렉토리 엔트리)의
    읽기/쓰기 권한을 지정한다. 이 비트가 0 으로 클리어되어 있으면, 해당하는 페이지는
    읽기 전용이며, 1로 세트되어 있으면, 해당 페이지는 읽기/쓰기가 가능하다.

- 2번 비트 : User/Supervisor flag
    이 플래그가 0으로 클리어되어 있으면, 해당하는 페이지(혹은 페이지군 : group
    of pages)는 이 페이지는 프로세서가 Supervisor 의 특권을 가질 때만 접근
    가능하다. 1 이면, 그 반대이다.

- 3번 비트 : Page Level Write-Through flag (PWT)
    해당하는 페이지 테이블이나 페이지의 캐슁 정책을 결정한다.
    0 으로 클리어되어 있으면, write-back 방식의 캐슁 이 사용되며,
    1 로 세트되어 있으면, write-through 방식의 캐슁이 사용된다.
    그러나 만약 cr0 레지스터의 CD(cache disable) 플래그 (bit 30)가 세트되어
    있으면, 이 플래그는 무시된다.

- 4번 비트 : Page level cachae disable flag (PCD)
    해당하는 페이지나 페이지 테이블의 캐슁을 조정한다. 플래그가 1로 세트되어 있으면,
    해당 페이지 혹은 페이지 테이블의 캐슁은 일어나지 않으며, 0으로 클리어되어 있으면,
    해당 페이지 혹은 페이지 테이블은 캐슁 가능하다.
    3번 비트와 마찬가지로, cr0 레지스터의 cd 플래그가 세트되어 있으면, 이 플래그는
    무시된다.

    ----------------------------------------------
    삽입 : 인텔의 캐쉬 메모리

    여기서 잠깐, 리눅스의 하드웨어 캐쉬 컨트롤 정책에 대해서 알아보고 넘어가도록
    하겠다.
    리눅스는 항상 PCD 와 PWD 플래그를 0 으로 클리어한 상태에 둔다. 그래서 모든
    페이지 프레임에 대해서 캐슁이 이루어지고, 항상 write-back 방식의 캐슁이 이루어
    진다.

    잠깐! write-back 과 write-through 는 무엇인가?
    Understanding Linux Kernel 책의 내용을 요약해 보겠다.
    오늘날의 마이크로프로세서들은 기가헤르쯔에 달하는 클럭 주파수를 가지고 출시되고
    있다. 그러나, RAM(DRAM) 칩들은 그 속도를 못따라가고 있다.
    그래서 CPU 와 RAM 간의 속도 미스매치가 발생해서 CPU 수행 성능에는 매우 좋지
    않은 영향을 미치게 된다. 이러한 drawback 을 방지하기 위해서 프로세서들에는
    보다 빠른 SRAM 을 이용한 하드웨어 캐쉬를 CPU 자체에 내장시키게 되었다.
    독자들도 L1 cache, L2 cache 라는 말을 들어 보았을 것이다. 이러한 것들이 바로
    하드웨어 캐쉬인데, 이것들은 CPU 의 페이징 유닛과 메인메모리 사이에 위치해서
    둘 사이의 클럭 미스매치를 보정(?)해 주게 된다.
   
    이들 캐쉬가 동작하는 원리는 'locality principle' 에 기반하고 있는데, 그것은,
    방금 읽은 메모리 주소 근처의 메모리가 바로 다음에, 혹은 매우 가까운 미래에
    읽혀질 확률이 매우 높다는 내용이다. 실제로, 프로그램을 살펴보면,
    인스트럭션 하나를 실행시키고, 다음 인스트럭션을 읽어와서 그것을 실행시킨다.
    그리고, 점프를 하더라도, 같은 루틴, 즉, 방금 수행한 인스트럭션이 저장된
    메모리와 그리 멀지 않은 번지로 점프를 하게 되는 것이 일반적이다.
    그리고, 메모리에서 데이터를 읽는 경우에도 여기저기 흩어져 있는 데이터를
    읽는 경우는 드물고, 읽더라도, 구조체의 필드들, 혹은, 일정한 메모리 영역을
    읽고, 또, 거기에 쓰는 일이 대부분이다. 즉, 인접한 메모리를 계속 액세스한다.
    따라서 'locality principle' 은 상당히 설득력이 있는 '원리' 라고 할 수 있겠다.
    (구체적인 증명은 못하겠다. -_-)

    인텔 프로세서들에서 제공하는 캐쉬들은 '라인(line)' 이라는 단위로 저장된 내용에
    접근을 하게 되어 있다. 이 '라인' 이라는 단위는 CPU 와 캐쉬를 연결해 주는
    캐쉬 버스의 너비가 32바이트(비트가 아님 : 256비트)라는 데에서 기인하는 단위인데,
    CPU 가 메모리에서 단지 1 바이트의 읽기 동작을 수행하더라도, 캐쉬로부터 읽어들이는
    데이타의 양은 32바이트의 크기이다. 즉, 캐쉬로의 액세스는 항상 '라인' 의
    단위로 일어난다. (32바이트의 크기는 펜티엄과 P6 계열의 프로세서들의 캐쉬
    라인의 너비이다. 486에서는 16바이트이다) 메모리칩의 구조를 쪼~끔 아시는 분들은
    이해가 쉬울 것이다.

    캐쉬가 메모리에 매핑되는 방식은 3가지로 나뉘어지는데, direct-mapped 모드,
    fully associative 모드, N-way associative 모드가 그것이다. 각각의 모드에 대해서
    간단히 설명하자면, direct-mapped 모드는 메인 메모리의 한 '라인' 이 항상 캐쉬의
    정확하게 지정된, 같은 위치에 매핑되어 있는 방식이며, fully-associative 모드는,
    메모리의 어떤 '라인'이든지 캐쉬의 어떤 위치에든지 저장될 수 있는 모드, (fully
    associative meaning that any line in memory can be stored at any location in
    the cache.) N-way associative 모드는, 메모리의 어떤 '라인'이든지 캐쉬의 임의의
    N개의 라인에 저장될 수 있는 모드(N-way associative, where any line of main
    memory can be stored in any one of N lines of the cache)이다.
    (associative memory 에 대한 내용은, MANO 의 Computer System Architecture 의
    Chapter 12 를 참조하라.)

    프로세서의 캐슁 유닛은 hardware cache memory 와 cache controller 로 구성되어
    있는데, 캐쉬 메모리는 메모리의 '라인' 들을 실제로 저장하는 파트이고, 컨트롤러는
    캐쉬 메모리의 각 '라인'마다의 '태그'와 해당 캐쉬 라인의 상태를 나타내는 몇가지
    플래그들을 담고 있는 엔트리들을 저장한다. 이 '태그' 에는 캐쉬 컨트롤러가 현재
    해당 라인에 매핑되어 있는 메모리의 위치를 인식할 수 있는 정보들이 저장된다.

    그러면, 이제, physical address 의 실체를 밝힐 때가 왔다.
    physical address 는 보통 세개의 파트로 나누어지는데, msb 쪽의 파트는 캐쉬
    컨트롤 유닛에 저장되는 '태그' 에 해당하는 정보가 담겨 있고, 가운데 끼어 있는
    파트에는 캐쉬 컨트롤러에 담겨있는 엔트리들의 인덱스가 있다. 그리고, lsb 쪽의
    파트에는 라인에서의 offset 이 담겨 있다.
    그러나, 캐슁 유닛은 프로세스들에 대해서 '투명하게'동작한다는 것을 알고 있기
    바란다.
   
    RAM 에 있는 메모리 셀들에 액세스하기 위해서 CPU 는 physical address 로부터
    캐슁유닛의 인덱스를 뽑아 내고, 캐쉬 컨트롤러에서 해당 인덱스가 가리키는 곳의
    '태그' 와 physical address 에 있는 '태그'와 비교해 본다.
    만약 같은 '태그'를 가지고 있으면, 'cache hit' 가 발생한 것이고, 그렇지 않으면
    'cache miss' 가 발생한 것이다.

    캐쉬 히트가 발생했을 때, cpu 는 해당 메모리에 접근하는 액세스의 종류에 따라
    다른 동작을 취하는데, 만약 읽기 동작이 수행되었으면, 프로세서는 메모리에는
    접근을 하지 않고, 해당하는 번지의 내용을 캐쉬 메모리에서 바로 프로세서의
    레지스터로 옮겨준다. 만약, 쓰기 동작이 수행되었으면, 해당하는 페이지의 PCD
    플개그와 PWT 플래그에 따라서 각각 다른 정책이 취해진다.
    이때 취할 수 있는 정책에는 4가지 정도가 있는데, PWT 플래그와 관련된 정책은
    기본적인 두가지 정책이다.
   - Write combining
   - Write through
   - Write back
   - Write protected
    중에서 Write through 정책과 Write back 정책만 설명하겠다.
    (혹시 커널을 MTRR 지원하도록 컴파일하고, X 에서 MTRR 을 사용해 본 사람들은
    Write combining 이란 말이 결코 낯설지 않을 것이다. 이러한 캐슁 방식에 대한
    내용은 다음에 생각나면 한번 공부해서 다루어 보도록 하겠다.)

    먼저, write through 정책은, 읽기 동작에서든, 쓰기 동작에서든 언제나 캐슁이
    일어나는 방식이다. 쓰기를 할 때 컨트롤러는 캐쉬에도 쓰고, 또, 그에 해당하는
    메모리 '라인' 에도 항상 쓰기 동작을 행한다.

    반면, write back 방식은 역시 읽기동작이든, 쓰기 동작이든 캐슁이 발생하는 것은
    동일하나, 쓰기를 할 때 메모리에는 쓰지 않고, 캐쉬에만 쓰기동작을 행한다.
    그리고, 메모리에 쓰기를 하는 경우는 그 캐쉬 라인을 deallocate 해야 할 때
    캐쉬라인에 있는 내용을 메모리에 write 한다.

    따라서, write back 타입의 메모리가 보다 빠른 속도를 제공한다.

    이왕 설명하기 시작한 참에 펜티엄 프로세서에서 사용하는 캐쉬 메모리들에 대해서
    간단하게 설명하고 넘어가겠다.

    기본적으로 펜티엄 프로세서에서는 인스트럭션과 데이터를 저장하기 위한
    프로세서 내부에 '버퍼' 혹은 캐쉬를 구현해 두었다. 이것들은 프로세서의
    처리 속도 향상을 위해 존재하는데, 펜티엄에서 제공하는 것은,

   L1, L2 캐쉬, TLB 버퍼, write buffer 가 있다.

    먼저, 캐쉬에 대해서 설명하면, 방금 얘기했듯이, Level 1 (L1) 캐쉬와 Level 2 (L2)
    캐쉬가 있는데, L1 캐쉬는 프로세서 상에서 Instruction Fetch Unit 과 매우
    가까운 위치에 붙어 있다. L1 캐쉬는 다시 인스트럭션 캐쉬와 데이터 캐쉬로 나뉘어
    진다. (486 에서는 L1 캐쉬는 펜티엄에서처럼 인스트럭션 저장용 캐쉬와 데이터
    저장용 캐쉬와 같은 식으로 나뉘지 않고 통합되어 있다.)
    그리고, L2 캐쉬는 인스트럭션과 데이터 모두를 저장하는 용도로써 하나로 통합되어
    있다.

    펜티엄 및 P6 family 칩에서의 캐쉬, TLB, write buffer 들의 특성을 적어 보겠다.

    Name                         Characteristics
    ---------------------------------------------------------------------------------
    L1 Instruction cache         8 or 16 KB, 4-way associative, 32byte line size
    L1 Data cache                16 KB, 4-way associative, 32byte line size
    L2 Unified cache             128,256,512,1024,2048KB, 4-way associative, 32byte line size
    Instruction TLB(4KB pages)   32entries, 4-way associative(MMX : fully associative)
    Data TLB(4KB pages)          64 entries, 4-way associative(MMX:fully associative)
    Instruction TLB(large pages) 2 entries, fully associative
    Data TLB(large pages)        8 entries, 4-way associative
    write buffer                 12 entries(P6)

    L2 캐쉬의 경우, 펜티엄까지는 프로세서 패키지 외부에 옵션으로 존재했지만,
    펜티엄 프로, 펜티엄 II, III 에서는 프로세서 패키지 내부에 같이 존재한다.

    보다 자세한 내용을 인텔의 매뉴얼을 참조하라.

    이처럼 프로세서 내부에 두개의 캐쉬가 존재하지만, '리눅스는 이러한 하드웨어의
    세부사항을 무시하고, 오직 하나의 캐쉬만 존재하는 것으로 가정하고 프로그래밍
    되어 있다' 고 한다.

    하드웨어의 이러한 캐슁 서킷을 활성화 시키기 위해서는 cr0 컨트롤 레지스터의
    CD 플래그(30번 비트 : cache disable flag) 를 클리어하면 된다.
    그리고, 캐쉬의 메모리와의 동기화 정책을 결정하는 플래그로 cr0 레지스터의
    NW 플래그(29번 비트 : Not Write-through flag) 가 있다.

    이들 플래그 외에도 앞서 설명했듯이, 페이지 디렉토리 엔트리와 페이지 테이블
    엔트리의 PCD/PWT 플래그가 각각의 페이지 혹은 페이지의 그룹별로 캐쉬의
    en/disable 상태와 write-back/through 정책을 결정할 수 있도록 되어 있다.
    Understanding Linux Kernel 책에서는 이처럼 인텔 프로세서가 페이지별로
    다른 캐쉬정책을 펼 수 있도록 구현된 것을 '재미있다' 고 표현했다 :-)
    : Another interesting feature of the Pentium cache is that....
   
    이러한 일반적인 목적의 캐쉬 외에도 메모리 번지의 번역(?)을 좀 더 빨리 하기
    위한 TLB 라는 특수한 목적의 캐쉬도 존재하는데, TLB 에 대해서는 뒤에서 다루겠다.

    ---------------------------

- 5번 비트 : Accessed flag
    1로 세팅되어 있을 때 해당하는 페이지나 페이지 테이블이 액세스되었다는 것을
    나타낸다. 즉, 해당 페이지 혹은 페이지테이블로부터 읽기 동작이 수행되었거나
    혹은 페이지테이블이나 페이지로 쓰기 작업이 수행되었음을 뜻한다.
    이 비트는 페이지나 페이지 테이블이 물리적 메모리로 최초로 적재되어서 초기화
    될 때는 항상 0 으로 되어 있다가 모종의 접근이 발생하면, 1로 세팅된다.
    이 비트는 "스티키(sticky)" 류의 플래그라서, 한번 세팅되면, 프로세서는 다시
    이 비트를 클리어하지 않는다. 운영체제 등의 소프트웨어가 클리어 해 주어야 한다.
    6번비트와 함께 운영체제의 메모리 관리루틴에서 사용할 수 있다.

- 6번 비트 : Dirty flag
    이 비트는 해당하는 페이지에 쓰기 동작이 수행되었을 때 1로 세트된다는 점과,
    페이지 디렉토리의 엔트리들에는 존재하지 않는 플래그라는 것만 제외하고는
    5번 비트와 완전히 같다.

- 7번 비트 : Page Size flag
    페이지의 크기를 결정한다. 이 플래그는 페이지 디렉토리 엔트리에만 존재하는
    플래그로써, 0 일때 페이지 크기는 4KB 이고, 1일 때 페이지 크기는 4MB 이다.
    그리고, 1 일 때에는 페이지 디렉토리 엔트리는 페이지 테이블을 가리키는 것이
    아니라, 메모리의 페이지를 가리킨다. 위의 내용을 자세히 읽었으면 알 수 있다.

- 8번 비트 : Global(G) flag
    자세한 내용은 인텔의 매뉴얼을 참조하라. -_-;


0번 비트 (Present flag)와 관련해서 중요한 사항을 하나 얘기하지 않았는데,
이 비트가 0으로 클리어되어서, 해당하는 페이지 혹은 페이지 테이블이 메모리내에
존재하지 않는다고, 즉, 하드 디스크와 같은 보조기억장치에 스왑아웃되어 있다는
것을 가리키는 상태에서는, 나머지 31번부터 1번 비트까지의 값들은 운영체제가
임의로 예를 들어서, 해당 페이지가 존재하는 디스크 위치라든지 하는 값을 저장해
두고 사용할 수 있다.

이제, 페이징은 이정도로 해 두면 되겠다.


-------------------------------------------------------------------------------

Part 7. Translation Lookaside Buffers

인텔의 프로세서들은 linear address 를 physical address 로 바꿀 때 소모되는 메모리
접근, 즉, 페이지 디렉토리를 읽는다든지, 페이지 테이블을 읽는 다든지 하는 등의
시간이 많이 걸리는 작업을 보다 빠르게 수행하기 위해서 TLB (Translation lookaside
buffer)라는 것을 제공한다. 이제, tlb 의 역할을 알아보도록 하자.

방금 설명했듯이, linear address 해당하는 페이지를 찾는다는 것은 상당히 많은
메모리 접근을 요구하는 작업이다. 최소한, 페이지 디렉토리 엔트리를 찾는 데 1회,
페이지 테이블을 찾는 데 1회, 그리고, 해당하는 페이지에 접근하는 데 1회... 이렇게
3회의 메모리 참조가 필요하다. 그런데, 메모리 참조는 잘 알듯이 매우 시간 소모적인
작업이다. 그래서 방금 설명했듯이 프로세서 내부의 빠르게 접근할 수 있는 메모리에
최근에 사용된 페이지 디렉토리의 엔트리나 페이지 테이블의 엔트리를 저장해 둠으로써
시간 소모적인 메모리 접근을 줄이도록 한다. 일종의 캐쉬라고 보면 되겠다.

인텔의 펜티엄급 이상의 프로세서는 TLB 를 데이타용과 인스트럭션 용을 따로 유지한다.
또한, P6 급 이상의 프로세서에서는 페이지 크기가 4메가인지, 4킬로인지에 따라서
TLB 를 따로 할당해 사용한다.

사용자 애플리케이션 프로그램들은 TLB 에 직접 접근할 수 없다. 즉, 특권 레벨이 0 보다
큰 프로세스들은 TLB 버퍼의 내용을 갱신할 수 없다. 단지 특권레벨 0 인 프로그램, 즉,
운영체제와 같은 것들만 TLB 버퍼의 내용을 갱신하도록 할 수 있는데, 이러한 TLB 의
내용의 갱신은 원칙적으로, 페이지 디렉토리나 페이지 테이블이 바뀌었을 경우에
운영체제가 책임지고 갱신을 해 주어야 하는데, 하드웨어적으로 CR3 레지스터에
무슨 내용이든지 '쓰기' 동작이 발생하면, TLB 는 갱신되게 되어 있다.

즉, 다음과 같은 경우에 TLB 버퍼의 갱신이 경우에 발생한다.

    1. 첫번째로, 명시적으로 mov 인스트럭션을 사용해서 cr3 레지스터에 값을 쓰는 경우
       예를 들자면,
       
       movl %cr3, %eax
       movl %eax, %cr3

       와 같은 코드가 2.2 커널의 __flush_tlb() 함수에 있는데, 방금 보인것처럼,
       그냥 단순히 cr3 에 writing 을 함으로서 하드웨어회로가 자동으로 수행하는 tlb
       의 갱신을 이끌어내었다.
       2.4 커널에서는 약간, 다르지만, 결과적으로 똑같다.
       위의 어셈블리 코드를 inline 어셈블리 코드로 바꾼 것과 같다.
       다음과 같은 코드가 __flush_tlb() 함수에 쓰였다. (2.4 커널)

        #define __flush_tlb()                  \
   do {                        \
      unsigned int tmpreg;               \
                           \
      __asm__ __volatile__(               \
         "movl %%cr3, %0;  # flush TLB \n"      \
         "movl %0, %%cr3;              \n"      \
         : "=r" (tmpreg)               \
         :: "memory");               \
   } while (0)

    2. invlpg 인스트럭션을 사용함으로써, tlb 에 저장되어 있는 페이지 테이블 엔트리를
       갱신하도록 할 수 있다.
       리눅스에서는 flush_tlb_page(addr) 이라는 함수를 이용하는데,
       2.2 커널에서는 다음과 같은 어셈블리 인스트럭션이 쓰였다.

       movl $addr, %eax
       invlpg(%eax)

       그리고, 2.4 커널에서도 마찬가지로 코딩해 놓았다. 물론, 본 함수가 취하는 인수도
       하나 늘어났지만, 이것은 2.4 커널의 메모리 관리 혹은 지정방식이 2.2 와 비교해서
       약간 달라졌기 때문이다.

    3. 태스크 스위칭을 할 때.
       리눅스의 프로세스들은 모두 각각의 페이지 디렉토리를 가진다.
       태스크 스위칭을 할 때, 프로세스의 TSS(Task State Segment) 또한 저장되고,
       로드되는데, 여기에 들어가는 정보 중 하나가 바로 cr3 레지스터의 내용이다.
       즉, 프로세스 스위칭을 할 때에는 함축적(?)으로 cr3 레지스터의 내용이
       갱신되는 것이다. 따라서, TLB 버퍼도 갱신된다.

앞서서 페이지 디렉토리엔트리와 페이지 테이블 엔트리의 구조를 설명할 때 G 플래그가
있었다. 제대로 설명을 하지 않고 넘어간것 같은데, 인스트럭션 중에 invlpg 인스트럭션이
실행되어, 운영체제가 tlb 버퍼의 내용을 갱신하려고 할 때, 이 G 플래그가 세팅된
엔트리는 invlpg 인스트럭션에 의해 tlb 에서 삭제되지 않는다. 즉 invlpg 인스트럭션이
적용되지 않는다.

이제 남은 내용은 Physical Address Extension 기능을 이용한 64기가 까지의 메모리 접근
에 관한 내용인데, 이 내용은 다음에 기회가 있으면, 설명하도록 하겠다. 아마 2.4 커널을
계속 분석하다 보면, 틀림없이 이 내용이 필요할 때가 있을 것이다.


-------------------------------------------------------------------------------

Part 8. 리눅스에서의 페이징의 구현

그러면 이제, 리눅스에서 페이징은 어떻게 구현되고, 지원되는지를 알아볼 차례이다.

리눅스는 인텔의 32비트 아키텍쳐에서 지원하는 2-level 페이징 (Physical address 를
구하는데 두번의 자료구조 iteration 이 필요하다) 이 확장된 3-level 페이징을 구현
하고 있다. 이렇게 한 이유는, 64비트 아키텍쳐로 넘어간다면, 페이징은 기존의
2-level 만 가지고는 처리할 수 없기 때문이다. 그래서, 페이지 디렉토리와 페이지
테이블 사이에 '페이지 미들 디렉토리' 라는 구조를 하나 더 두게 된다. 그리고,
페이지 디렉토리도 명칭을 '페이지 글로벌 디렉토리' 라고 부르게 된다.
구조는 단지, 기존의 페이지 디렉토리, 페이지 테이블, 오프셋 으로 구성된 linear
address 의 구조에 페이지 글로벌 디렉토리, 페이지 미들 디렉토리, 페이지 테이블,
오프셋 으로 하나의 구조가 더 들어간 것 뿐이다. physical address 를 산출하는
과정 또한 페이지 미들 디렉토리의 삽입에 따른 단계가 하나 더 있을 뿐, 동일하다.

그러나, 리눅스를 인텔 32비트 아키텍쳐에서 컴파일 할 때에는, 페이지 미들 디렉토리의
크기가 0 이 된다. 즉, 페이지 미들 디렉토리의 존재는 무시된다.... 고는 하지만, 페이지
미들 디렉토리의 구조체는 유지를 하고, 참조도 한다. 단지, 그 영향이 마치 없는 것처럼
작동하도록 프로그래밍 되어 있다.

리눅스 커널 자체에서는 이처럼 linear address 를 physical address 로 변환하는 작업은
일어나지 않는다. 단지, 페이지 글로벌 디렉토리 테이블/엔트리, 페이지 테이블/엔트리
등의 자료구조를 유지하면서, 적절할 때에 cr3 레지스터의 내용을 바꿔 줌으로써,
cpu 의 메모리 어드레싱 유닛이 '자동'으로 주소변환을 수행할 수 있도록 해 줄 따름이다.

또한, 프로세스의 구조를 공부할 때 알게 되겠지만, 리눅스의 프로세스들은 자신만의
페이지 글로벌 디렉토리와 페이지 테이블을 가지고 있어서, 프로세스 스위칭이 발생하면,
TSS 에 cr3 레지스터의 내용을 저장하고, 새로이 실행될 프로세스의 TSS 에 있는 cr3
레지스터의 값을 로드하여, 새로이 실행될 프로세스의 페이지 글로벌 디렉토리가
효력을 발하도록 한다.

리눅스에는 여러가지 페이지 테이블의 조작을 도와주는 매크로들이 있는데,
생략하겠다. 자세한 내용은 Understanding the Linux Kernel 책을 참조하라.


-------------------------------------------------------------------------------

Part 9. 메모리 레이아웃

이젠, 리눅스 커널이 메모리에 로드되고, 실행이 시작되고, 프로세스들이 시스템에서
하나씩 실행되는 과정에서, 시스템의 전체적인 메모리 구조가 어떠한지에 대한 내용을
이야기 해 보겠다.

Part 9-1. 커널 코드와 데이터를 위한 페이지 프레임들

리눅스 커널의 코드와 데이터들은 메모리상의 특정 위치에 항상 '상주' 해서 존재하며,
결코 디스크로 스왑되거나 해서는 안된다.
리눅스 커널은 일반적으로 physical address 0x00100000번지로부터 시작되는 메모리
영역에 상주한다. 다시 말하면, 1메가 바이트 이후의 물리적 메모리에 리눅스 커널은
올라가게 되는 것이다. 그리고, 일반적으로, 리눅스 커널의 크기는 1메가 바이트를
넘지 않는 것이 보통이므로, 리눅스 커널은 물리적 메모리의 1메가 - 2메가 의 영역을
차지하고 존재한다.

그런데, 간편하게 0x00000000 번지부터 커널이 존재하는것이 아니라, 복잡(!) 하게
1메가 (0x0010 0000) 의 메모리 영역부터 존재하는 이유는 무엇인가?
그 이유는 하드웨어의 구성상, 여러가지 주변장치나, PC BIOS 등이 그 영역을 사용하기
때문이다.

예를 들자면, 페이지프레임 0 번은, PC 의 BIOS 가 시스템의 하드웨어 컨피규레이션을
저장하기 위해 사용하며, 물리적 주소 0x000a 0000 부터 0x000f ffff 번지는 바이오스에
의해 사용되거나, 혹은 IAS 그래픽 카드들의 메모리를 매핑하기 위해서 사용하기도
한다. 또한 내가 쓰고 있는 IBM 의 ThinkPad 같은 컴퓨터들은 0x0a 페이지 프레임을
0x9f 페이지 프레임으로 매핑시켜서 특수한 용도로 사용하기도 한다.
이처럼 1메가바이트보다 낮은 영역의 메모리는 하드웨어에 의해 예약되어져서 특수한
용도로 사용되는 경우가 많은데, 리눅스는 커널이 물리적으로 연속적이지 않은
메모리에 저장되는 것을 막기 위해서 1메가 하위의 메모리에 적재되지 않고, 1메가
상위의 메모리에 적재되는 것이다.

그러면, 리눅스에서 사용하는 메모리 영역을 도시해 보겠다. 최초의 512개의 페이지,
즉, 2메가까지의 영역이다.

  0x0  0x1                   0x100                                          0x1ff
    +---+----------------+-----+-------------------+-------------+--------+---+
    |   |                |     |                   |             |        |   |
    | 1 |      2         |  3  |         4         |      5      |   6    | 7 |
    |   |                |     |                   |             |        |   |
    +---+----------------+-----+-------------------+-------------+--------+---+
                             _text               _etext        _edata   _end

그림에서 보듯이 최초의 페이지는 하드웨어에 의해 예약되었고,
0x100 번페이지부터 커널에 의해 사용된다. 메모리 맵 아래에 나와있는 심볼들은
커널 소스에서는 찾아보지 못하고, 커널 심볼을 나열한 파일에서 찾아볼 수 있다.
처음은 _text 심볼은 커널 코드의 시작 주소를 가리키는 심볼이고,
_etext 는 커널 코드의 끝번지를 가리키는 심볼이다. 커널 코드가 끝나면, 커널의
정적변수, 정적 자료구조들이 저장된 data 섹션이 시작되는데, 이 섹션은 _edata
심볼의 주소에서 끝난다. 그리고, 나머지 _end 심볼까지는 커널의 동적 데이터가
저장되는 메모리이다.

아래에 내 노트북에서 사용하고 있는 커널 2.4.2의 System.map 에 나와있는 커널 
심볼들 중 일부를 보이겠다.

   defret:~# cat /boot/System.map-2.4.2-rtc | grep _text
   c0100000 A _text
   defret:~# cat /boot/System.map-2.4.2-rtc | grep _etext
   c0206886 A _etext
   defret:~# cat /boot/System.map-2.4.2-rtc | grep _edata
   c028e340 A _edata
   defret:~# cat /boot/System.map-2.4.2-rtc | grep _end 
   c02fd650 A _end

앞에서 설명했듯이 리눅스 커널은 물리적 메모리 주소 0x100000 에서 시작한다.
그래서 System.map 파일에서 _text 심볼을 살펴보니 그 값이 0xc0100000 이다.
그런데, 앞에 c 가 왜 붙어 있는가? 그 이유는 앞으로 설명하겠다.

앞에서 설명한 것은 물리적 메모리의 구조이다. 그리고, System.map 에 나와있는
주소는 linear address 이다. 리눅스가 메모리에 로드되고 메모리를 초기화하면서,
페이지를 매핑하는 과정은 일반적으로 두 단계로 나뉘어진다.

최초에 컴퓨터에 전원을 넣고, 리눅스 로더에 의해서 메모리에 커널이 적재되면,
리눅스는 페이지 디렉토리와 페이지 테이블을 초기화하는 작업을 하는데,
처음에는 프로세서의 모드가 아직 real 모드에서 동작하고 있다. 이처럼 cpu 가
real mode 에서 동작할 때에는 아직 페이징이 활성화 되지 않은 상태인데, 이러한 상태에서
차후의 protected 모드에서 페이징이 활성화 된 상태에서 동작할 때를 고려하여
가장 효율적이고 간편한 메모리 매핑을 위해서 먼저, 커널은 4메가 크기의 메모리 주소
공간을 만들어서 자신을 적재하여 실행을 시작한다.
그리고 나면, 프로세서를 보호모드로 전환시키고 페이징을 활성화하면서, 사용가능한
모든 RAM 을 매핑하여서 페이지 디렉토리와 페이지 테이블들을 초기화 시킨다.

먼저, 최초의 단계에서, 커널은 4메가 크기의 메모리를 real mode 에서와 protected mode
에서 모두 쉽게 어드레싱할 수 있도록 linear address 0x0000 0000 부터 0x003f ffff 까지를
physical address 0x0000 0000 부터 0x003f ffff 까지에 매핑시킨다. 또한, 커널에 있는
PAGE_OFFSET 이라는 상수로부터 PAGE_OFFSET+0x003f ffff 까지의 linear address 를
동일한 physical address 인 0x0000 0000 부터 0x003f ffff 까지 매핑시킨다.
이 PAGE_OFFSET 이라는 상수는 프로텍티드 모드에서 커널이 시작되는 linear address 를
담고 있는데, 일반적으로, 0xc000 0000 으로 설정된다. (실제 커널 코드에 하드코드
되어 있다.) 즉, 이 말은, 리눅스 커널은 실제로는 linear address 3기가바이트 부터
저장되고, 그 하위의 메모리, 즉, PAGE_OFFSET 하위의 메모리들은 프로세스들이 자유로이
쓸 수 있는 영역이라는 뜻이다.

이렇게 최초의 페이지 테이블의 초기화 과정에서 linear address 0x0000 0000 부터
0x003f 0000 까지의 페이지 테이블은 커널 변수 pg0 가 가리키는 곳에 들어 있으며,
커멀이 사용하는 Page (Global) Directory 는 커널 변수 swapper_pg_dir 이 가리키는
주소에 들어 있다.  (/usr/src/linux/arch/i386/kernel/head.S)

최초의 커널 초기화 과정에서, swapper_pg_dir 의 엔트리들 중, 첫번째, 즉 0x000 번
엔트리와 0x300 엔트리는 동일한 physical address 에 매핑되는 것이다.
그리고, 그 엔트리들이 가리키는 페이지 테이블은 pg0 변수에 들어 있다.
그러나, 이 pg0 변수는 커널 초기화의 첫번째 과정이 끝나고, 프로세서가 보호모드로
들어간 이후에는 사용되지 않는다. 더 이상 phsical address 와 linear address 의 주소가
같은 메모리 매핑 구조를 사용할 필요가 없기 때문이다.

이러한 임시적인 페이지 매핑을 하고 나서, 커널을 메모리에 적재시키고 나면, 실질적인
페이지 테이블의 매핑작업이 일어난다. 그리고, swapper_pg_dir 이라는 페이지 디렉토리를
가리키는 변수도 다시 초기화 된다. 그리고 커널은 0xc0000000 번지부터 존재하게 된다.

(설명이 좀 많이 이상하다. -_-; 혹시.. 다음에 생각나면 더 자세하게 설명하겠다.
 하나의 문서로 너무 오래 끄니까 좀... 피곤하고, 의욕도 떨어진다.)
크리에이티브 커먼즈 라이센스
Creative Commons License
Posted by minzkn

트랙백 주소 :: http://blog.minzkn.com/trackback/144

댓글을 달아 주세요