인터럽트(Interrupt)란 프로그램이 동작하는 도중에 발생하는 비정상적인 사건을 말한다. 사실 여기서 비정상적인 사건이란 표현은 너무 딱딱한 어투의 말처럼 들리기 때문에 ‘기존의 흐름과는 다른 일’ 혹은 ‘의도하지 않은 일’, ‘생각하지 못한 문제’ 정도로 순화해서 생각하면 이해하기 편할듯 싶다. 그러면 이러한 인터럽트가 사용되게 된 이유는 무엇일까? 이 물음을 대답하려면 운영체제에 대한 개념을 빼놓을 수 없다.



컴퓨터의 운영체제는 외부 장치의 동작과 자신의 시스템 동작을 보다 수월하게 조정하기 위해 인터럽트를 사용한다. 예를 들어 어떤 프로그램이 동작하고 있는 상황에서 키보드와 마우스 같은 입력이 들어오게 되면 현재 프로그램의 상태를 저장하고 각각의 입력을 구분해서 처리해야 한다. 만약 여러 입력장치가 동시에 동작한다고 해도 데이터를 손실하지 않고 수행해야 하므로 언제 어떤 장치에서 입력이 들어왔는지 일일이 확인할 수 있어야 한다. 이때 사용되는 개념이 바로 인터럽트이며 컴퓨터의 프로세서는 외부 장치의 인터럽트 요청 회선 (IRQ, Interrupt Request Line)을 통해 각 장치의 상태를 확인한다. IRQ는 데이터 제어선의 일종으로 해당 입력이 발생했을 때만 전송되기 때문에 프로세서가 각 장치의 상태를 직접 점검할 필요가 없게 된다.


IRQ가 들어오게 되면 운영체제에서는 해당 인터럽트에 대해 적절한 처리를 하게 되는데 이때 발생 원인에 따라 적절한 처리 루틴을 수행하는 것이 바로 인터럽트 서비스 루틴(ISR, Interrupt Service Routine)이다. 쉽게 말해 운영체제에서 프로그램을 수행하는 도중 IRQ가 발생하면 현재 상태를 저장하고 ISR로 분기하여 해당 인터럽트를 처리하고 다시 돌아오는 과정을 거치게된다. 이를 그림으로 표현하면 다음과 같다.



처리 과정을 간략히 설명하자면 메인 프로그램이 동작하는 도중에 IRQ가 발생했을 때 운영체제에서는 현재 명령어를 종료하고 모든 레지스터의 값을 스택 영역에 저장한다. 이때 PC(Program Count) 레지스터에는 ISR이 처리할 프로그램의 시작 위치를 저장하고 인터럽트 프로그램에 제어권을 넘겨 실행한다. ISR에서 프로그램 수행이 완료되면 스택 영역에 저장해 두었던 레지스터를 복원하고 메인 프로그램의 위치에서 다시 시작하는 것으로 인터럽트 처리가 완료된다.


위 일련의 과정은 마치 서브 루틴의 호출과도 유사해 보인다. 하지만 큰 차이점 하나가 있는데 바로 현 상태 저장 유무이다. 서브 루틴의 경우 호출된 프로그램과 연관된 기능을 수행하고 그대로 종료되지만, 인터럽트는 호출된 프로그램과 관련이 없을 수 있기 때문에 다시 돌아갈 수 있도록 관련 레지스터들을 모두 저장해놓고 분기해야 된다. 이 때 중요한 역할을 하는 것이 바로 PC 레지스터이며, 이 레지스터가 다음 수행할 명령어들의 주소값을 모두 저장해놓기 때문에 인터럽트가 끝나면 안전하게 다시 원래 수행 위치로 돌아갈 수 있다. 따라서 위와 같은 과정을 통해 메인 프로그램은 인터럽트의 영향을 받지 않고 수행을 재개하게 된다.


간략하게 인터럽트의 개념에 대해서 알아보았는데 그러면 MCU를 제어하기 위해 사용하는 인터럽트도 마찬가지로 운영체제의 인터럽트와 같은 개념일까? 사실 같으면서도 약간 다르다고 볼 수 있다. 앞서 운영체제의 인터럽트는 ‘의도하지 않은 일’을 처리하는 것으로, 예를 들어 사용자의 의도치 않은 입력이 발생하거나 잘못된 명령어를 수행하려고 할 때 운영체제가 알아서 처리해주는 소프트웨어적인 개념으로 생각하면 된다. 반면에 MCU를 제어하기 위해 사용하는 인터럽트는 ‘언제든 발생할 수 있는 일’을 ‘미리 설정된 대응 방법’ 대로 처리하는 것을 의미한다. 여기서 ‘발생할 수 있는 일’이란 미리 설정해둔 입력 정도로 이해하면 되고 ‘대응 방법’은 해당 입력에 대해 어떻게 처리할 것인가에 대한 구체적인 해결 방법 정도로 이해하면 좋을듯 싶다.

 

그런데 두 개의 개념이 별개가 되는 것으로 이해하면 곤란하다. 보다 정확히 말하자면 운영체제는 인터럽트를 모두 포괄하는 개념으로, 예를 들어 컴퓨터의 리셋 버튼 같은 경우에는 MCU의 인터럽트 개념을 내포하고 있다. 위에서는 하드웨어 인터럽트를 좀 더 강조하기 위해 강제로 구분했을 뿐 실상 인터럽트는 운영체제의 탄생과 같이 시작된 개념이기 때문에 여기서는 하드웨어적인 인터럽트의 기본적인 정의만 숙지하고 넘어가도록 하자.

 

다음으로는 우리가 사용할 MCU인 AVR을 제어하기 위해 사용하는 인터럽트에 대해 살펴보자. ATmega128의 인터럽트 벡터 테이블은 다음과 같다.

 

 


위 표는 ATmege128에서 사용할 수 있는 모든 인터럽트의 종류를 나타내며, 보드 종류에 따라 지원하는 인터럽트의 종류와 벡터 주소값이 다르기 때문에 프로그램이 호환되지 않음을 알고있어야 한다.(해당 보드에 맞춰 다시 컴파일을 시켜주면 된다) 여기서 주의 깊게 봐야할 부분들이 몇 가지 있는데 먼저 ATmega128이 8bit 프로세서 임에도 불구하고각 벡터의 주소 크기가 2바이트로 설정되어 있음을 확인할 수 있다. 사실 일반적으로 메모리의 기본 단위는 1바이트인데 ATmega128 에서는 프로그램 메모리의 기본 단위가 1워드(2바이트)이며 주소 또한 1워드 단위로 부여된다. 이는 ATmega128의 모든 명령어 단위가 1워드를 기반으로 하기 때문에 보다 프로그램 처리 효율성을 높이기 위한 측면에서 그러한 것으로 이해하자.

 

다음으로 인터럽트의 주소는 0x0000 ~ 0x0044 까지 할당되어 있음을 확인할 수 있으며 이 주소의 값은 모두 프로그램 메모리의 주소에 해당 된다. 그리고 각각의 벡터 번호는 우선 순위를 나타내는 것으로 만약 인터럽트가 동시에 걸릴 경우 위 테이블의 번호에 따라 가장 우선 순위가 높은 인터럽트 부터 먼저 처리하게 된다. 테이블을 보면 0x0000에 해당하는 리셋 인터럽트가 가장 최우선 순위임을 확인할 수 있다.


그렇다면 인터럽트가 수행되는 도중에 또다른 인터럽트가 걸리게 되면 어떻게 될까? 8051 같은 MCU의 경우에는 중복 인터럽트가 허용되지만 ATmega128에서는 인터럽트 수행중에는 다른 인터럽트의 접근이 금지되어있다. 따라서 수행중인 인터럽트가 완료된 후 우선 순위에 따라 다음 인터럽트를 처리하게 된다. 물론 이를 인터럽트 처리 코드 안에서 허용할 수는 있지만 이렇게 되면 구현하려는 시스템이 복잡할수록 불안정하고 제어하기 힘들어질 수 있으므로 그다지 권장하지 않는 부분이다. 


'Electronic > AVR' 카테고리의 다른 글

소스 레벨 디버깅  (0) 2017.06.14
외부 인터럽트  (0) 2017.06.08
프로테우스 사용하기  (1) 2017.06.02