개요
인터럽트 핸들러는 인터럽트 벡터에 등록되어 있는 각각의 인터럽트에 대한 실행 함수이다. 인터럽트 핸들러는 커널에서 실행되며 인터럽트 스택을 이용하여 처리된다. 커널은 인터럽트가 호출되면 정해진 인터럽트 핸들러를 불러서 인터럽트를 실행하게 된다. 인터럽트 핸들러는 다른 표현으로 Interrupt service routine (ISR)이라고 불리운다.
마이크로프로세서에 인터럽트가 접수되면, 해당 인터럽트 핸들러의 코드의 위치를 찾고 실행에 옮긴다. 이 전에 실행되던 상태가 없어지면 나중에 복귀 되었을 때 문제가 발생하므로 레지스터와 프로그램 카운터를 보관함으로써 CPU의 상태를 보존한다. 인터럽트가 핸들링이 완료되면 이전의 상태로 복귀된다. OS는 어떤 인터럽트 서비스 루틴을 호출할지 알기 위해서 ISR에 대한 Offset을 OS부팅에서 Interrupt descriptor table에 저장해 둔다 (IDT).
마이크로컨트롤러와 같은 간단한 시스템에서는 바로 RETI와 같은 명령어에 의해 바로 복귀된다. 인터럽트 실행 시, 마이크로프로세서의 레지스터 상태를 스택이나 레지스터 블럭에 대피하고, 인터럽트 핸들러의 기능적 처리를 완료한다. 인터럽트 이전으로 되돌리는 RETI 코드가 실행되기 전에 대피 했던 레지스터를 복귀한다. RETI 명령어에 의해 이제는 프로그램 카운터(PC)를 이전의 주소 위치로 바꿈으로써 복귀가 완료된다. 이러한 마이크로프로세서의 대피 및 복귀는 인터럽트 핸들러의 시작과 끝에 기계어 코드를 삽입해야 한다. 따라서 C언어에서 말하는 함수의 구조와 차이가 있다.
Interrupt handler의 구현
Serial_ISR:
PUSH PSW ; 스택에 상태 레지스터 대피
PUSH ACC ; 스택에 A(Accumulator) 레지스터 대피
JNB RI,output ; RI 레지스터를 확인하여 수신 여부 확인하고, 수신 데이터 없으면 수신 루틴 점프
MOV A, SBUF ; 데이터 버퍼를 A레지스터로 가져오고
MOV inchar, A ; 문자를 변수 inchar에 저장. inchar는 특정 RAM 메모리
CLR RI ; 수신 플래그를 지우고
output: ; 출력 루틴 시작
JNB TI, done ; 현재 데이터 전송 중인지를 확인하여, 전송 중일때는 출력 루틴 점프하여 생략
MOV A, outchar ; 데이터 변수 outchar 내용을 A레지스터로 옮기고
MOV SBUF, A ; 시러얼 전송 버퍼에 써서 하드웨어가 보내기 시작
CLR TI ; 전송 인터럽트 플래그 지우고
done: ; 종료 루틴
POP ACC ; A 레지스터 복귀
POP PSW ; 상태(플래그) 레지스터 복귀
RETI ; 인터럽트 핸들러 종료
8051
8051 시리얼 인터럽트 핸들러를 위한 어셈블러 언어 예 처럼, PUSH/POP 명령어에 의해 마이크로프로세서의 레지스터를 대피하여 기존에 진행되던 루틴에 영향을 미치지 않도록 하는 것이 일반적인 방법이다. 8051의 경우 ACC 뿐만 아니라 각 R0~R7까지의 범용 레지스터의 대피 여부도 함께 고민해야 한다. R 레지스터는 뱅크 구조로 여러개의 쌍이 존재하므로 인터럽트 발생 시 다른 뱅크를 선택적으로 바꾸어 대피 문제를 해결할 수 있다.
X86
X86과 같은 경우에는 CPU는 다음 순서로 스택에 값을 푸쉬한다.
FLAGS -> CS -> EIP
X86-64
CPU가 인터럽트를 호출하면 IST (Interrupt service table, OS에 의해서 부팅 시에 지정됨)에 적혀 있는 RSP로 스택을 변경한다. 이 새 스택에 CPU가 다음 순서대로 값을 푸쉬한다.
SS:RSP(오리지널 RSP) -> RFLAGS -> CS -> RIP
이외에 CPU가 해주는 일은 따로 없다. 레지스터를 저장하고, Interrupt의 종류를 알아내어서, Interrupt을 처리해주는 것은 OS의 재량이다.
Interrupt handler의 올바른 코딩 방법
어셈블리에서 C언어의 함수처럼 특정 기능을 구현하는 블럭을 실행할 때, CALL 명령어를 사용하는데 이때 복귀 명령어는 RET이다. RET은 복귀 주소를 CALL이 실행될 때 스택에 대피 시킨 주소를 사용하여 CALL 다음 명령어로 진행한다. 인터럽트는 임의의 명령어 실행 중에 접수되면, 이 명령어가 끝나자마자 바로 특정 코드 블럭으로 점프한 것이므로 RET과 다르게 RETI을 두어 복귀 절차를 진행한다.
인터럽트 핸들러는 일반적인 C언어의 함수 들과는 차이가 있다. 따라서 인터럽트 핸들러의 표현은 C언어의 함수와는 다른 표현이 되어야 하므로 각각의 개발도구에서 제공한다. 인터럽트 핸들러의 코딩 방법은 개발 도구에서 제공된 방법을 숙지하여 표현을 해야 한다. 이것은 C/C++언어의 표준이 아니기 때문에 개발도구의 사용설명을 참고하여 코딩해야 한다.