본문 바로가기

MINT64 OS 개발

13장. PIC 컨트롤러와 인터럽트 핸들러 이용해, 인터럽트 처리

Ⅰ. 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 컨트롤러 제어 코드와 핸들러 코드의 통합과 빌드