본문 바로가기
OS

[32bit] 디스크립터( descriptor )와 GDT ( Global Descriptor Table )

by 올리고당 2021. 7. 27.

목차

 


리얼 모드( Real Mode )와 보호 모드( Protected Mode )

CPU가 처음 시작할 때 동작하는 방식을 리얼 모드(16bit 동작)라고 합니다. 여태까지 우리는 리얼 모드로 동작하는 CPU를 사용했습니다. 보호 모드(32 bit 동작)는 리얼 모드의 단점들을 보완하는 방식으로 동작합니다. 먼저 리얼 모드의 단점에 대해 알아보겠습니다.

 

  1.  오프셋의 크기가 65536( 2의 16 제곱 )이 최대이므로, 한 세그먼트가 관리할 수 있는 영역이 매우 작다. ( 64KB )
  2.  접근에 제한이 없기 때문에, 실행 중인 프로그램에서 다른 중요한 프로그램을 악의적으로 변경할 가능성이 있음.
  3.  세그먼트 : 오프셋 방식으로 물리 주소를 표현하기 때문에, 세그먼트 시작 주소의 마지막 자리는 항상 0이다.
  4.  16 비트의 메모리 주소 표현 한계로 인해, 640KB( 0x00000 ~ 0xA0000 )가 메모리의 최대 용량이다. (0xA0000 ~ 0x100000번지는 BIOS나 비디오 메모리로써 사용됨.)

1번과 4번의 문제는 단순히 비트 수를 늘리는 것으로 해결 가능하지만, 2번과 3번 문제는 다른 방식으로 접근해야합니다. 문제를 해결하기 위해 고안한 방식이 디스크립터( descriptor )를 통해 세그먼트를 관리하는 방식입니다.

 


디스크립터( descriptor )

디스크립터는 해당 세그먼트의 시작 주소( Base Address ), 크기( limit ), 종류( Type ), 여러가지 상태( G, D/B, L, AVL, P, DPL, S )를 묘사하고 있습니다.

 

디스크립터

 

Base Address 와 Segment Limit [각주:1]

위에서 말씀드린 대로, 세그먼트의 시작 주소와 크기입니다.

 

Type

첫 번째 비트(11) : 세그먼트가 데이터 세그먼트( 0 )인지 코드 세그먼트( 1 )인지 지정하는 비트.

 

두 번째 비트(10) : 데이터(E) - EXPAND UP( 0 )/DOWN( 1 ), 코드(C) - NONCONFORMING( 0 )/CONFORMING( 1 )

 

EXPAND UP/DOWN으로 쓰일 때, limit을 바꾸면 아래 그림의 파란 화살표 방향으로 세그먼트의 크기가 늘어납니다.

 

 

 

데이터 세그먼트가 스택으로 쓰일 경우, 크기가 동적으로 변하는 스택을 사용하기 위해서는 세그먼트를 expand-down으로 설정해야 합니다. 인텔 CPU에서 스택은 항상 메모리의 낮은 번지 방향으로 쌓입니다. expand-up으로 설정했을 경우, 스택의 시작 번지가 바뀌게 되어 에러가 발생합니다. 따라서, 스택의 크기를 동적으로 변화시킬 수 없습니다.

 

CONFORMING에 대해서는 나중에 자세히 다루도록 하겠습니다.

 

세 번째 비트(9) : 데이터(W) - 읽기 전용( 0 ) or 읽기/쓰기( 1 ), 코드(R) - 실행 전용( 0 ) or 실행/읽기( 1 )

 

네 번째 비트(8, A) : 액세스 비트, 어떤 프로그램이 세그먼트에 접근했을 때, 1로 변경됨( 초기값 0 ). 커널이 메모리 관리를 할 때, 일정 시간이 지나면 이 비트를 0으로 바꿔줍니다.

 

S flag

시스템 세그먼트( 0 )인지 코드 혹은 데이터 세그먼트( 1 )인지 지정하는 비트. 시스템 세그먼트에 대해서는 나중에 다루도록 하겠습니다. 지금은 1로 설정합시다.

 

DPL ( Descriptor Privilege Level )

해당 세그먼트가 커널 레벨인지, 유저 레벨인지 나타냅니다. 보통 0이면 커널 레벨, 3이면 유저 레벨을 의미합니다.

 

P flag

세그먼트가 메모리 상에 존재하는지 나타내는 비트. 페이징 기능과 관련이 있습니다. 지금은 1로 설정합시다.

 

D/B flag

세그먼트가 32비트( 1, D )인지 16비트( 0, B )인지 나타내는 비트.

 

G flag

0일 때 세그먼트의 크기는 limit바이트이고, 1일 때 세그먼트의 크기는 limit x 4KB입니다.

 

L flag

IA-32e 모드에서, 코드 세그먼트 내의 명령어가 32비트 모드로 동작( 0 )할지, 64비트 모드로 동작( 1 )할지 지정하는 비트. IA-32e 모드를 사용하지 않을 것이므로, 항상 0으로 설정합시다.

 

AVL

시스템 소프트웨어에 의해 사용될 수 있는지 없는지 나타내는 비트입니다.[각주:2]

 


GDT(Global Descriptor Table)와 세그먼트 셀렉터(segment selector)

GDT는 전역( Global )으로 쓰일 디스크립터를 모아놓은 테이블입니다. GDT는 메모리의 어디든 존재할 수 있습니다만, 그 위치와 크기는 CPU의 GDTR( GDT register )에 등록시켜 주어야 합니다.

 

 

리얼 모드와 다르게, 보호 모드에서는 세그먼트 레지스터가 80비트로 확장되고, 세그먼트 셀렉터( segment selector )레지스터세그먼트 디스크립터( segment descriptor ) 레지스터로 나뉩니다. 세그먼트 셀렉터의 값은 프로그래머가 직접 변경할 수 있지만, 세그먼트 디스크립터의 값은 오직 CPU 내부 동작을 통해서만 변경되고, 프로그래머가 직접 변경할 수 없습니다.

 

세그먼트 레지스터
세그먼트 셀렉터

 

CPU는 세그먼트 셀렉터의 값이 바뀌면, GDTR에 저장되어 있는 GDT의 시작 주소로 이동합니다. 그 후, 세그먼트 셀렉터의 인덱스 값을 이용해 해당 디스크립터를 찾습니다. 찾은 디스크립터의 DPL과 세그먼트 셀렉터의 RPL을 비교해서 서로 동일하다면, 해당 디스크립터를 세그먼트 디스크립터 레지스터에 저장합니다.

 

n번째 디스크립터를 불러오기 위해서는 세그먼트 셀렉터에 8*n(10진수) 값을 넣어주면 됩니다.

  • 셀렉터에 8( 0x08 )을 넣으면, 이진수로 1000이므로, 인덱스 값이 1이 됩니다.
  • 셀렉터에 16( 0x10 )을 넣으면, 이진수로 10000이므로, 인덱스 값이 2가 됩니다.
  • 셀렉터에 24( 0x18 )를 넣으면, 이진수로 11000이므로, 인덱스 값이 3이 됩니다. 

GDT에 있는 하나의 디스크립터의 크기가 8byte이므로, 인덱스 값이 1 증가할 때마다, 하나의 디스크립터를 건너뛸 수 있습니다. 따라서, CPU는 GDT의 시작 주소를 기준으로, 세그먼트 셀렉터가 가리키는 디스크립터를 바로 가져옵니다.

 


디스크립터로 메모리 물리 주소 찾기

보호 모드에서는 세그먼트 : 오프셋 방식이 조금 다르게 작동합니다. 먼저, 오프셋이 세그먼트의 크기보다 작은지 확인합니다. 크기가 작다면, 세그먼트 셀렉터로 찾은 디스크립터의 세그먼트 시작 주소와 오프셋을 더해 메모리의 물리 주소

[각주:3]를 얻을 수 있습니다.


이번 글에서는 보호 모드와 디스크립터, GDT, 세그먼트 셀렉터에 대해 공부하였습니다.

다음 글에서는 CPU를 보호 모드로 동작시키는 방법을 알아보겠습니다.

 

감사합니다.

 


 

  1. 디스크립터에서 시작 주소와 크기를 분할해서 저장하는 이유는 뭘까? 매뉴얼에서 관련된 정보를 찾지 못하였습니다. 이에 대한 정보를 아시는 분은 댓글로 남겨주세요. [본문으로]
  2. 매뉴얼에 자세히 기술이 되어 있지 않아서, 저도 정확히 잘 모르겠습니다. [본문으로]
  3. 페이징 기능을 이용하지 않는다면, 선형 주소( Linear Address )는 물리 주소로 보아도 무방합니다. [본문으로]