Ⅰ. PIC 컨트롤러 제어
1. PIC 컨트롤러 초기화
- 마스터 PIC 컨트롤러와 슬레이브 PIC 컨트롤러의 초기화 코드
// I/O 포트 정의
#define PIC_MASTER_PORT1 0X20
#define PIC_MASTER_PORT2 0X21
#define PIC_SLAVE_PORT1 0XA0
#define PIC_SLAVE_PORT2 0XA1
// IDT 테이블에서 인터럽트 벡터가 시작되는 위치, 0x20
#define PIC_IRQSTARTVECTOR 0x20
void kInitializePIC( void ){
// 마스터 PIC 컨트롤러를 초기화
// ICW1(포트 0x20), IC4 비트(비트 0) = 1
kOutPortByte( PIC_MASTER_PORT1, 0x11 );
// ICW2(포트 0x21), 인터럽트 벡터(0x20)
kOutPortByte( PIC_MASTER_PORT2, PIC_IRQSTARTVECTOR );
// ICW3(포트 0x21), 슬레이브 PIC 컨트롤러가 연결 위치(비트로 표현)
// 마스터 PIC 컨트롤러의 2번 핀에 연결되어 있으므로, 0x04(비트 2)로 설정
kOutPortByte( PIC_MASTER_PORT2, 0x04 );
// ICW4(포트 0x21), uPM 비트(비트 0) = 1
kOutPortByte( PIC_MASTER_PORT2, 0x01 );
// 슬레이브 PIC 컨트롤러를 초기화
// ICW1(포트 0xA0), IC4 비트(비트 0) = 1
kOutPortByte( PIC_SLAVE_PORT1, 0x11 );
// ICW2(포트 0xA1), 인터럽트 벡터(0x20 + 8)
kOutPortByte( PIC_SLAVE_PORT2, PIC_IRQSTARTVECTOR + 8 );
// ICW3(포트 0Xa1), 마스터 PIC 컨트롤러에 연결된 위치(정수로 표현)
// 마스터 PIC 컨트롤러의 2번 핀에 연결되어 있으므로 0x02로 설정
kOutPortByte( PIC_SLAVE_PORT2, 0x02 );
// ICW4(포트 0xA1), uPM 비트(비트 0) = 1
kOutPortByte( PIC_SLAVE_PORT2, 0x01 );
}
- OCW1 커맨드를 이용하여, 인터럽트를 선택하는 코드
void kMaskPICInterrupt( WORD wIRQBitmask ){
// 마스터 PIC 컨트롤러에 IMR 설정
// OCW1(포트 0x21), IRQ 0 ~ IRQ 7
kOutPortByte( PIC_MASTER_PORT2, ( BYTE ) wIRQBitmask );
// 슬레이브 PIC 컨트롤러에 IMR 설정
// OCW1(포트 0xA1), IRQ 8 ~ IRQ 15
kOutPortByte( PIC_SLAVE_PORT2, ( BYTE ) ( wIRQBitmask >> 8 ));
}
- OCW2 커맨드를 이용하여, EOI를 전송하는 코드
void kSendEOIToPIC( int iIRQNumber )
{
// 마스터 PIC 컨트롤러에 EOI 전송
// OCW2(vhxm 0x20), EOI 비트(비트 5) = 1
kOutPortByte( PIC_MASTER_PORT1, 0x20 );
// 슬레이브 PIC 컨트롤러의 인터럽트인 경우, 슬레이브 PIC 컨트롤러에게도 EOI 전송
if( iIRQNumber >= 8 ){
//OCW2(포트 0xA0), EOI 비트(비트 5) = 1
kOutPortByte( PIC_SLAVE_PORT1, 0x20 );
}
}
Ⅱ. 인터럽트, 예외 핸들러, 콘텍스트
1. 콘텍스트 저장과 복원
- 콘텍스트를 저장하고 복원하는 코드
ISRKeyboard:
; 콘텍스트 저장
; RBP 레지스터부터 GS 세그먼트 셀렉터까지 모드 스택에 삽입
push rbp
mov rbp, rsp
push rax
push rbx
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
mov ax, ds ; DS 세그먼트 셀렉터와 ES 세그먼트 셀렉터는 스택에 직접
push rax ; 삽입할 수 없으므로, RAX 레지스터에 저장한 후 스택에 삽입
mov ax, ES
push rax
push fs
push gs
; 세그먼트 셀렉터 교체
mov ax, 0x10 ; AX 레지스터에 커널 데이터 세그먼트 디스크립터 저장
mov ds, ax ; DS 세그먼트 셀렉터부터 FS 세그먼트 셀렉터까지 모두
mov es, ax ; 커널 데이터 세그먼트로 교체
mov gs, ax
mov fs, ax
; C로 작성된 핸들러 함수를 호출
call kKeyboardHandler
; 콘텍스트 복원
; GS 세그먼트 셀렉터부터 RBP 레지스터까지 모두 스택에서 꺼내 복원
pop gs
pop fs
pop rax
mov es, ax ; ES 세그먼트 셀럭터와 DS 세그먼트 셀렉터는 스택에서 직접
pop rax ; 꺼내 복원할 수 없으므로, RAX 레지스터에 저장한 뒤에 복원
mov ds, ax
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rbx
pop rax
pop rbp
; 스택에 에러 코드가 포함되어 있다면 제거
add rsp, 8
; 프로세서 저장한 콘텍스트를 복원하여, 실행 중인 코드로 복귀
iretq
2. 인터럽트와 예외 핸들러 업그레이드
- 매크로를 활용해 작성한 ISR 함수
; 콘텍스트를 저장하고, 셀렉터를 교체하는 매크로
%macro KSAVECONTEXT 0 ; 파라미터를 전달받지 않는 KSAVECONTEXT 매크로 정의
; RBP 레지스터부터 GS 세그먼트 셀렉터까지 모드 스택에 삽입
push rbp
mov rbp, rsp
push rax
push rbx
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
mov ax, ds ; DS 세그먼트 셀렉터와 ES 세그먼트 셀렉터는 스택에 직접
push rax ; 삽입할 수 없으므로, RAX 레지스터에 저장한 후 스택에 삽입
mov ax, ES
push rax
push fs
push gs
; 세그먼트 셀렉터 교체
mov ax, 0x10 ; AX 레지스터에 커널 데이터 세그먼트 디스크립터 저장
mov ds, ax ; DS 세그먼트 셀렉터부터 FS 세그먼트 셀렉터까지 모두
mov es, ax ; 커널 데이터 세그먼트로 교체
mov gs, ax
mov fs, ax
%endmacro
; 콘텍스트를 복원하는 매크로
%macro KLOADCONTEXT 0 ; 파라미터를 전달받지 않는 KLOADCONTEXT 매크로 정의
; GS 세그먼트 셀렉터부터 RBP 레지스터까지 모두 스택에서 꺼내 복원
pop gs
pop fs
pop rax
mov es, ax ; ES 세그먼트 셀럭터와 DS 세그먼트 셀렉터는 스택에서 직접
pop rax ; 꺼내 복원할 수 없으므로, RAX 레지스터에 저장한 뒤에 복원
mov ds, ax
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rbx
pop rax
pop rbp
%endmacro
; #32, 타이머 ISR
kISRTimer:
KSAVECONTEXT ; 콘텍스트를 저장한 뒤, 셀렉터를 커널 데이터 디스크립터로 교체
; 핸들러에 인터럽트 번호를 삽입하고 핸들러 호출
mov rdi, 32
call kCommonInterruptHandler
KLOADCONTEXT ; 콘텍스트를 복원
iretq ; 인터럽트 처리를 완료하고 이전에 수행하던 코드로 복원
; #33, 키보드 ISR
kISRKeyboard:
KSAVECONTEXT ; 콘텍스트를 저장한 뒤, 셀렉터를 커널 데이터 디스크립터로 교체
; 핸들러에 인터럽트 번호를 삽입하고 핸들러 호출
mov rdi, 33
call kKeyboardHandler
KLOADCONTEXT ; 콘텍스트를 복원
iretq ; 인터럽트 처리를 완료하고 이전에 수행하던 코드로 복원
``` 생략 ```
- 인터럽트 핸들러 코드
void kCommonInterruptHandler( int iVectorNumber ){
char vcBuffer[] = "[INT: , ]";
static int g_iCommonInterruptCount = 0;
//===============================================================
// 인터럽트가 발생했음을 알리려고 메시지를 출력하는 부분
// 인터럽트 벡터를 화면 오른쪽 위에 2자리 정수로 출력
vcBuffer[ 5 ] = '0' + iVectorNumber / 10;
vcBuffer[ 6 ] = '0' + iVectorNumber % 10;
// 발생한 횟수 출력
vcBuffer[ 8 ] = '0' + g_iCommonInterruptCount;
g_iCommonInterruptCount = ( g_iCommonInterruptCount + 1 ) % 10;
kPrintString( 70, 0, vcBuffer );
//===============================================================
// EOI 전송
kSendEOIToPIC( iVectorNumber - 32);
}
void kKeyboardHandler( int iVectorNumber )
{
char vcBuffer[] = "[INT: , ]";
static int g_iKeyboardInterruptCount = 0;
//===============================================================
// 인터럽트가 발생했음을 알리려고 메시지를 출력하는 부분
// 인터럽트 벡터를 화면 오른쪽 위에 2자리 정수로 출력
vcBuffer[ 5 ] = '0' + iVectorNumber / 10;
vcBuffer[ 6 ] = '0' + iVectorNumber % 10;
// 발생한 횟수 출력
vcBuffer[ 8 ] = '0' + g_iKeyboardInterruptCount ;
g_iKeyboardInterruptCount = ( g_iKeyboardInterruptCount + 1 ) % 10;
kPrintString( 0, 0, vcBuffer );
//===============================================================
// EOI 전송
kSendEOIToPIC( iVectorNumber - 32);
}
3. IDT 테이블 수정
- 인터럽트와 예외 핸들러를 추가했으므로, IDT 테이블에 등록하는 핸들러 함수도 변경해야 한다.
- 이전 코드의 kDummyHandler 부분을 추가한 핸들러로 대체한다.
- IDT 테이블의 핸들러를 새로운 핸들러로 대체하는 코드
void kInitializeIDTTables( void )
{
``` 생략 ```
//=====================================================================
// 예외 ISR 등록
//=====================================================================
kSetIDTEntry( &( pstEntry[ 0 ] ), kISRDivideError, 0x08, IDT_FLAGS_IST1,
IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT );
kSetIDTEntry( &( pstEntry[ 1 ] ), kISRDebug, 0x08, IDT_FLAGS_IST1,
IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT );
``` 생략 ```
//=====================================================================
// 인터럽트 ISR 등록
//=====================================================================
kSetIDTEntry( &( pstEntry[ 32 ] ), kISRTimer, 0x08, IDT_FLAGS_IST1,
IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT );
kSetIDTEntry( &( pstEntry[ 33 ] ), kISRKeyboard, 0x08, IDT_FLAGS_IST1,
IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT );
``` 생략 ```
// 48번 이하의 핸들러는 기타 핸들러로 등록
for( i = 48 ; i < IDT_MAXENTRYCOUNT ; i++ )
{
kSetIDTEntry( &( pstEntry[ i ] ), kISRETCInterrupt, 0x08, IDT_FLAGS_IST1,
IDT_FLAGS_KERNEL, IDT_TYPE_INTERRUPT );
}
}
4. 인터럽트 활성화와 비활성화
- 프로세서의 RFLAGS 레지스터에는 인터럽트 발생 가능 여부를 설정하는 IF 비트가 있으며, 해당 비트가 1로 설정되었을 때만 인터럽트가 발생한다.
- x86 프로세서는 인터럽트를 활성화하고 싶으면 STI 명령어를, 비활성화하고 싶으면 CLI 명령어를 사용한다. (But, 프로세서의 상태가 어떤가에 대한 명령어는 별도로 지원되지 않는다.)
- RFLAGS 레지스터를 스택에 저장하는 PUSHF 명령어를 명령하므로, 이를 통해 간접적으로 프로세서의 상태를 확인할 수 있다.
- 인터럽트 발생 가능 여부를 제어하고 RFLAGS 레지스터의 값을 읽는 어셈블리어 함수
; 인터럽트를 활성화
; PARAM: 없음
kEnableInterrupt:
sti ; 인터럽트를 활성화
ret
; 인터럽트를 비활성화
; PARAM: 없음
kDisableIntterrupt:
cli ; 인터럽트를 비활성화
ret
; RFLAGS 레지스터를 읽어서 되돌려줌
; PARAM: 없음
kReadRFLAGS:
pushfq ; RFLAGS 레지스터를 스택에 저장
pop rax ; 스택에 저장된 RFLAGS 레지스터를 RAX 레지스터에 저장하여
; 함수의 반환 값으로 설정
ret
// C 함수 선언
void kEnableInterrupt( void );
void kDisableIntterrupt( void );
QWORD kReadRFLAGS( void );
Ⅲ. PIC 컨트롤러 제어 코드와 핸들러 코드의 통합과 빌드
'MINT64 OS 개발' 카테고리의 다른 글
15장. 콘솔 셸 (0) | 2023.07.21 |
---|---|
14장. 키보드 디바이스 드라이버 업그레이드 (0) | 2023.07.21 |
12장. GDT, IDT 테이블, TSS 세그먼트를 추가해 인터럽트에 대비하자 (0) | 2023.07.21 |
11장. 키보드 디바이스 드라이버 추가 (0) | 2023.07.20 |
10장. 64비트 모드로 전환 (0) | 2023.07.20 |