Ⅰ. GDT 테이블 교환과 TSS 세그먼트 디스크립터 추가
1. GDT 테이블 생성과 TSS 세그먼트 디스크립터 추가
- 현재까지 사용한 코드&데이터 세그먼트 디스크립터는 보호 모드 커널 엔트리 포인트영역에서 어셈블리어로 작성한 것이다.
- 멀티 코어를 활성화하게 되면, 코어 별로 TSS 세그먼트가 필요하므로, 이를 대비하여 1MB 이상의 공간에 GDT 테이블을 생성한다.
- C코드로 GDT 테이블을 새로 작성한다.
- 세그먼트 디스크립터 & GDT 테이블 & TSS 세그먼트를 위한 자료구조와 매크로
// 조합에 사용할 기본 매크로
#define GDT_TYPE_CODE 0x0A
#define GDT_TYPE_DATA 0x02
#define GDT_TYPE_TSS 0x09
#define GDT_FLAGS_LOWER_S 0x10
#define GDT_FLAGS_LOWER_DPL0 0x00
#define GDT_FLAGS_LOWER_DPL1 0x20
#define GDT_FLAGS_LOWER_DPL2 0x40
#define GDT_FLAGS_LOWER_DPL3 0x60
#define GDT_FLAGS_LOWER_P 0x80
#define GDT_FLAGS_UPPER_L 0x20
#define GDT_FLAGS_UPPER_DB 0x40
#define GDT_FLAGS_UPPER_G 0x80
// 실제로 사용할 매크로
// Lower Flags는 Code/Data/TSS, DPL0, Present로 설정
#define GDT_FLAGS_LOWER_KERNELCODE ( GDT_TYPE_CODE | GDT_FLAGS_LOWER_S | \
GDT_FLAGS_LOWER_DPL0 | GDT_FLAGS_LOWER_P )
#define GDT_FLAGS_LOWER_KERNELDATA ( GDT_TYPE_DATA | GDT_FLAGS_LOWER_S | \
GDT_FLAGS_LOWER_DPL0 | GDT_FLAGS_LOWER_P )
#define GDT_FLAGS_LOWER_TSS ( GDT_FLAGS_LOWER_DPL0 | GDT_FLAGS_LOWER_P )
#define GDT_FLAGS_LOWER_USERCODE ( GDT_TYPE_CODE | GDT_FLAGS_LOWER_S | \
GDT_FLAGS_LOWER_DPL3 | GDT_FLAGS_LOWER_P )
#define GDT_FLAGS_LOWER_USERDATA ( GDT_TYPE_DATA | GDT_FLAGS_LOWER_S | \
GDT_FLAGS_LOWER_DPL3 | GDT_FLAGS_LOWER_P )
// Upper Flags는 Granulaty로 설정하고 코드 및 데이터는 64비트 추가
#define GDT_FLAGS_UPPER_CODE ( GDT_FLAGS_UPPER_G | GDT_FLAGS_UPPER_L )
#define GDT_FLAGS_UPPER_DATA ( GDT_FLAGS_UPPER_G | GDT_FLAGS_UPPER_L )
#define GDT_FLAGS_UPPER_TSS ( GDT_FLAGS_UPPER_G )
// 1바이트로 정렬
#pragma pack( push, 1 )
// GDTR 및 IDTR 구조체
typedef struct kGDTRStruct
{
WORD wLimit;
QWORD qwBaseAddress;
// 16바이트 어드레스 정렬을 위해 추가
WORD wPading;
DWORD dwPading;
} GDTR, IDTR;
// 8바이트 크기의 GDT 엔트리 구조
typedef struct kGDTEntry8Struct
{
WORD wLowerLimit;
WORD wLowerBaseAddress;
BYTE bUpperBaseAddress1;
// 4비트 Type, 1비트 S, 2비트 DPL, 1비트 P
BYTE bTypeAndLowerFlag;
// 4비트 Segment Limit, 1비트 AVL, L, D/B, G
BYTE bUpperLimitAndUpperFlag;
BYTE bUpperBaseAddress2;
} GDTENTRY8;
// 16바이트 크기의 GDT 엔트리 구조
typedef struct kGDTEntry16Struct
{
WORD wLowerLimit;
WORD wLowerBaseAddress;
BYTE bMiddleBaseAddress1;
// 4비트 Type, 1비트 0, 2비트 DPL, 1비트 P
BYTE bTypeAndLowerFlag;
// 4비트 Segment Limit, 1비트 AVL, 0, 0, G
BYTE bUpperLimitAndUpperFlag;
BYTE bMiddleBaseAddress2;
DWORD dwUpperBaseAddress;
DWORD dwReserved;
} GDTENTRY16;
// TSS Data 구조체
typedef struct kTSSDataStruct
{
DWORD dwReserved1;
QWORD qwRsp[ 3 ];
QWORD qwReserved2;
QWORD qwIST[ 7 ];
QWORD qwReserved3;
WORD wReserved;
WORD wIOMapBaseAddress;
} TSSSEGMENT;
#pragma pack ( pop )
- 세그먼트 디스크립터를 생성하는 함수 코드와 사용 예
- 디스크립터를 생성하는 함수는 파라미터로 넘어온 각 필드의 값을 세그먼트 디스크립터의 구조에 맞추어 삽입해주는 역할을 수행한다.
- GDTR 자료구조의 시작 어드레스를 0x142000으로 설정하고, 그 이후 어드레스부터 각 자료구조를 위치시킨 이유
- 0x100000(1MB) 영역부터 264KB를 페이지 테이블로 사용하고 있기 때문이다.
// GDT 테이블을 초기화 void kInitializeGDTTableAndTSS( void ) { GDTR* pstGDTR; GDTENTRY8* pstEntry; TSSSEGMENT* pstTSS; int i; // GDTR 설정 pstGDTR = ( GDTR* ) 0x142000; pstEntry = ( GDTENTRY8* ) ( 0x142000 + sizeof( GDTR ) ); pstGDTR->wLimit = ( sizeof( GDTENTRY8 ) * 3 ) + ( sizeof( GDTENTRY16 ) * 1 ) - 1; pstGDTR->qwBaseAddress = ( QWORD ) pstEntry; // TSS 영역 설정 pstTSS = ( TSSSEGMENT* ) ( ( QWORD ) pstEntry + GDT_TABLESIZE ); // NULL, 64비트 Code/Data, TSS를 위해 총 4개의 세그먼트를 생성한다. kSetGDTEntry8( &( pstEntry[ 0 ] ), 0, 0, 0, 0, 0 ); kSetGDTEntry8( &( pstEntry[ 1 ] ), 0, 0xFFFFF, GDT_FLAGS_UPPER_CODE, GDT_FLAGS_LOWER_KERNELCODE, GDT_TYPE_CODE ); kSetGDTEntry8( &( pstEntry[ 2 ] ), 0, 0xFFFFF, GDT_FLAGS_UPPER_DATA, GDT_FLAGS_LOWER_KERNELDATA, GDT_TYPE_DATA ); kSetGDTEntry16( ( GDTENTRY16* ) &( pstEntry[ 3 ] ), ( QWORD ) pstTSS, sizeof( TSSSEGMENT ) - 1, GDT_FLAGS_UPPER_TSS, GDT_FLAGS_LOWER_TSS, GDT_TYPE_TSS ); // TSS 초기화 GDT 이하 영역을 사용함 kInitializeTSSSegment( pstTSS ); } // 8바이트 크기의 GDT 엔트리에 값을 설정 // 코드와 데이터 세그먼트 디스크립터 void kSetGDTEntry8( GDTENTRY8* pstEntry, DWORD dwBaseAddress, DWORD dwLimit, BYTE bUpperFlags, BYTE bLowerFlags, BYTE bType ) { pstEntry->wLowerLimit = dwLimit & 0xFFFF; pstEntry->wLowerBaseAddress = dwBaseAddress & 0xFFFF; pstEntry->bUpperBaseAddress1 = ( dwBaseAddress >> 16 ) & 0xFF; pstEntry->bTypeAndLowerFlag = bLowerFlags | bType; pstEntry->bUpperLimitAndUpperFlag = ( ( dwLimit >> 16 ) & 0xFF ) | bUpperFlags; pstEntry->bUpperBaseAddress2 = ( dwBaseAddress >> 24 ) & 0xFF; } // 16바이트 크기의 GDT 엔트리에 값을 설정 // TSS 세그먼트 디스크립터를 설정하는데 사용 void kSetGDTEntry16( GDTENTRY16* pstEntry, QWORD qwBaseAddress, DWORD dwLimit, BYTE bUpperFlags, BYTE bLowerFlags, BYTE bType ) { pstEntry->wLowerLimit = dwLimit & 0xFFFF; pstEntry->wLowerBaseAddress = qwBaseAddress & 0xFFFF; pstEntry->bMiddleBaseAddress1 = ( qwBaseAddress >> 16 ) & 0xFF; pstEntry->bTypeAndLowerFlag = bLowerFlags | bType; pstEntry->bUpperLimitAndUpperFlag = ( ( dwLimit >> 16 ) & 0xFF ) | bUpperFlags; pstEntry->bMiddleBaseAddress2 = ( qwBaseAddress >> 24 ) & 0xFF; pstEntry->dwUpperBaseAddress = qwBaseAddress >> 32; pstEntry->dwReserved = 0; }
2. TSS 세그먼트 초기화
- TSS 세그먼트는 스택에 대한 필드와 I/O 맵에 대한 필드로 구성되어 있다.
- mint64 OS는 I/O 맵을 사용하지 않으며, IST방식을 사용하므로 이에 맞춰 TSS 세그먼트 필드를 설정한다.
- I/O 맵을 사용하지 않게 설정하는 방법
- I/O 맵 기준 주소를 TSS 세그먼트 디스크립터에서 설정한 Limit필드 값보다 크게 설정하면 된다.
- TSS 세그먼트 디스크립터에서는 TSSSEGMENT 자료구조의 크기만큼 Limit를 설정했으므로 그보다 큰 0xFFFF를 설정한다.
- IDT 게이트 디스크립터의 IST 필드를 0이 아닌 값으로 설정하고, TSS 세그먼트의 해당 IST영역에 핸들러가 사용할 스택 어드레스를 설정한다.
- (단, IST의 스택 어드레스는 반드시 16byte로 정렬된 위치에서 시작해야 하는 것을 알아야 한다.)
- I/O 맵을 사용하지 않고 7 ~ 8MB 영역을 IST 1로 사용하도록, TSS 세그먼트를 초기화하는 코드이다.
void kInitializeTSSSegment( TSSSEGMENT* pstTSS ){
kMemSet( pstTSS, 0, sizeof( TSSDATA ) );
pstTSS -> qwIST[ 0 ] = 0x800000;
pstTSS -> wIOMapBaseAddress = 0xFFFF;
}
3. GDT 테이블 교체와 TSS 세그먼트 로드
- GDT 테이블 교체 방법은 LGDT 명령을 사용하여 GDT 정보를 수정하면 된다.
- 세그먼트 디스크립터의 오프셋은 기존 테이블과 같으므로 변경할 필요가 없다.
- LTR 명령어를 사용하여 GDT 테이블 내의 TSS 세그먼트 인덱스인 0x18을 지정함으로써 TSS세그먼트를 프로세서에 설정할 수 있다.
- LGDT와 LTR 명령어를 수행하는 어셈블리어 함수
; GDTR 레지스터에 GDT 테이블을 설정 ; PARAM: GDT 테이블의 정보를 저장하는 자료구조의 어드레스 kLoadGDTR: lgdt [ rdi ] ; 파라미터 1(GDTR의 어드레스)를 프로세서에 로드하여 ; GDT 테이블을 설정 ret ; TR 레지스터에 TSS 세그먼트 디스크립터 설정 ; PARAM: TSS 세그먼트 디스크립터의 오프셋 kLoadTR: ltr di ; 파라미터 1(TSS 세그먼트 디스크립터의 오프셋)을 프로세서에 ; 설정하여 TSS 세그먼트를 로드 ret
- C함수 선언
void kLoadGDTR( QWORD qwGDTRAddress );
void kLoadTR ( WORD wTSSSegmentOffset );
- DT 테이블을 갱신하고, TSS 세그먼트를 프로세서에 로드하는 코드
void Main ( void )
{
``` 생략 ```
kInitializeGDTTableAndTSS( );
kLoadGDTR( 0x142000 );
kLoadTR ( 0x18 );
``` 생략 ```
}
Ⅱ. IDT 테이블 생성, 인터럽트, 예외 핸들러 등록
1. IDT 테이블 생성
- IDT 게이트 디스크립터로 구성된다.
// 조합에 사용할 기본 매크로
#define IDT_TYPE_INTERRUPT 0x0E
#define IDT_TYPE_TRAP 0x0F
#define IDT_FLAGS_DPL0 0x00
#define IDT_FLAGS_DPL1 0x20
#define IDT_FLAGS_DPL2 0x40
#define IDT_FLAGS_DPL3 0x60
#define IDT_FLAGS_P 0x80
#define IDT_FLAGS_IST0 0
#define IDT_FLAGS_IST1 1
// 실제로 사용할 매크로
#define IDT_FLAGS_KERNEL ( IDT_FLAGS_DPL0 | IDT_FLAGS_P )
#define IDT_FLAGS_USER ( IDT_FLAGS_DPL3 | IDT_FLAGS_P )
// 1바이트로 정렬
#pragma pack( push, 1 )
// IDT 게이트 디스크립터 구조체
typedef struct kIDTEntryStruct
{
WORD wLowerBaseAddress;
WORD wSegmentSelector;
// 3비트 IST, 5비트 0
BYTE bIST;
// 4비트 Type, 1비트 0, 2비트 DPL, 1비트 P
BYTE bTypeAndFlags;
WORD wMiddleBaseAddress;
DWORD dwUpperBaseAddress;
DWORD dwReserved;
} IDTENTRY;
#pragma pack ( pop )
- IDT 게이트 디스크립터를 생성하는 코드
- kSetGDTEntry16( ) 함수와 마찬가지로, 16byte로 구성된 자료구조에 값을 설정한다.
- kSetIDTEntry( ) 함수는 파라미터로 넘겨진 값을 자료구조로 채워주는 역할만 한다.
void kSetIDTEntry( IDTENTRY* pstEntry, void* pvHandler, WORD wSelector, BYTE bIST, BYTE bFlags, BYTE bType ) { pstEntry->wLowerBaseAddress = ( QWORD ) pvHandler & 0xFFFF; pstEntry->wSegmentSelector = wSelector; pstEntry->bIST = bIST & 0x3; pstEntry->bTypeAndFlags = bType | bFlags; pstEntry->wMiddleBaseAddress = ( ( QWORD ) pvHandler >> 16 ) & 0xFFFF; pstEntry->dwUpperBaseAddress = ( QWORD ) pvHandler >> 32; pstEntry->dwReserved = 0; }
- IDT 테이블을 생성하는 코드와 임시 핸들러 코드
- kDummyHandler( ) 함수는 테스트를 위해, 화면에 메시지를 출력하고 정지하게 해서 인터럽트 또는 예외 발생 시 쉽게 확인할 수 있도록 하였다.
// IDT 테이블을 초기화
void kInitializeIDTTables( void )
{
IDTR* pstIDTR;
IDTENTRY* pstEntry;
int i;
// IDTR의 시작 어드레스
pstIDTR = ( IDTR* ) 0x1420A0;
// IDT 테이블의 정보 생성
pstEntry = ( IDTENTRY* ) ( 0x1420A0 + sizeof( IDTR ) );
pstIDTR->qwBaseAddress = ( QWORD ) pstEntry;
pstIDTR->wLimit = 100 * sizeof( IDTENTRY ) - 1;
// 0~99까지 벡터를 모두 DummyHandler로 연결
for( i = 0 ; i < 100 ; i++ )
{
kSetIDTEntry( &( pstEntry[ i ] ), kDummyHandler, 0x08, IDT_FLAGS_IST1,
IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT );
}
}
// 임시 예외 또는 인터럽트 핸들러
void kDummyHandler( void )
{
kPrintString( 0, 0, "====================================================" );
kPrintString( 0, 1, " Dummy Interrupt Handler Execute~!!! " );
kPrintString( 0, 2, " Interrupt or Exception Occur~!!!! " );
kPrintString( 0, 3, "====================================================" );
while( 1 ) ;
}
2. IDT 테이블 로드
- LIDT 명령어를 수행하는 어셈블리어 함수와 C 함수 선언 코드
; IDTR 레지스터에 IDT 테이블을 설정
; PARAM: IDT 테이블의 정보를 저장하는 자료구조의 어드레스
kLoadIDTR:
lidt [ rdi ] ; 파라미터 1(IDTR의 어드레스)를 프로세서에 로드하여, IDT 테이블을 설정
ret
// C 함수 선언
void kLoadIDTR( QWORD qwIDTRAddress );
- 함수를 통해, IDT 테이블을 생성하고 로드하는 코드
void Main ( void )
{
``` 생략 ```
kInitializeIDTTables( );
kLoadIDTR( 0x1420A0 );
``` 생략 ```
}
Ⅲ. IDT, TSS 통합과 빌드
'MINT64 OS 개발' 카테고리의 다른 글
14장. 키보드 디바이스 드라이버 업그레이드 (0) | 2023.07.21 |
---|---|
13장. PIC 컨트롤러와 인터럽트 핸들러 이용해, 인터럽트 처리 (0) | 2023.07.21 |
11장. 키보드 디바이스 드라이버 추가 (0) | 2023.07.20 |
10장. 64비트 모드로 전환 (0) | 2023.07.20 |
9장. 페이징 기능을 활성화하여, 64비트 전환 준비 (0) | 2023.07.20 |