임베디드 소프트웨어에서 인터럽트란?
이번 시간에는 인터럽트(Interrupt)에 대해 알아보겠습니다.
개인적으로 임베디드 소프트웨어의 백미는 인터럽트에 있다는 생각입니다.
우선 인터럽트가 무엇인가에 대해 개념적으로 알아보겠습니다.
인터럽트는 작업을 처리하는 하나의 방식입니다. 말 그대로 방해하는 것, 치고 들어오는 녀석입니다.
소프트웨어 개념으로 폴링(Polling)과 반대 개념입니다.
폴링은 똑같은 시간마다 할 일이 있는지 주기적으로 확인해서 일을 처리하는 방식입니다.
인터럽트는 작업이 있으면 바로바로 처리하는 방식이구요.
일견 인터럽트가 더 효율적이고 더 좋아보이지만, 실상 꼭 그렇지는 않습니다. 인터럽트를 쓰려면 인터럽트를 발생시키는 조건이나 상위 작업이 필요합니다. 또한 인터럽트가 발생하면 운영체제는 그동안 하고 있던 일들을 멈추고 인터럽트를 처리해야 합니다(컨텍스트 스위칭 Context Switing 이라 하죠). 그동안 했던 일들을 날려버릴수는 없으니 어딘가에 저장해두었다가, 인터럽트가 끝나면 다시 저장해둔 것을 가져와서 하던 일을 계속해야 합니다. 이것 자체가 오버헤드(Overhead)가 됩니다. 만약 항상 주기적으로 처리가 필요한 일이 있으면 인터럽트를 쓰는 것은 비효율적입니다.
이상이 인터럽트에 대한 개념적인 설명이라면, 실제 임베디드 디바이스에서 있어 인터럽트의 개념은 훨씬 다양합니다. 사실 인터럽트는 프로세서에서 제공하는 익셉션 신호(Exception Signal) 의 일종입니다. Exception은 말 그대로 예외적인 상황을 말합니다. 비주기적으로 발생할 수 있고 시스템에서 꼭 처리해야 하는 상황을 말합니다.
프로세서의 아키텍처마다 Exception은 조금씩 다릅니다.
그리고 인터럽트를 소프트웨어 인터럽트와 하드웨어 인터럽트로 구분하기도 하는데, 여기서는 싸그리 무시하고 인터럽트는 하드웨어 인터럽트로 단정하겠습니다. 더 엄밀하게 한정해서 임베디드 디바이스 프로세서의 외부에서 발생하는 인터럽트만을 다루도록 하겠습니다.
잠깐 설명한대로 인터럽트는 외부장치가 발생시킵니다. 인터럽트가 발생하면 프로세서는 하던 일을 멈추고 인터럽트 신호가 어디서 발생했는지 찾은 다음, 해당 인터럽트 번호에 맞는 코드(ISR-Interrupt Service Routine)을 수행합니다. 보통 ISR 코드는 간단합니다. 말그대로 인터럽트는 시급히 처리가 필요한 일들이므로, ISR은 수행시간이 짧고 기능에 제약이 있습니다. 그래서 많은 작업이 필요한 경우, Top Half/Bottom Half라는 개념을 사용합니다. 인터럽트 발생시 일단 시급한 코드 먼저(Top Half) 실행하고 프로세서에서는 시급하지 않지만 해당 인터럽트 관련해서 꼭 처리가 필요한 부분(Bottom Half)를 나중에 처리하는 거죠.
외부에서 발생하는 다양한 인터럽트 신호를 처리하기 위해 프로세서는 통상 인터럽트 컨트롤러라는 모듈을 내장하고 있습니다. 이 인터럽트 컨트롤러가 어디서 인터럽트가 발생했는지 감지하고, 어떤 인터럽트를 먼저 처리할지도 제어하게 됩니다. 시스템이 복잡하면 인터럽트의 가짓수도 많아집니다. 동시에 여러가지 인터럽트도 발생합니다. 우선순위에 기반한 인터럽트 처리가 필요하겠죠.
프로세서에서 처리가능한 인터럽트 신호의 수는 정해져 있는 있습니다. 통상적으로 디바이스의 종류에 따라 필수적으로 사용되는 인터럽트들이 있습니다. 이런 인터럽트 핀들은 고정이죠. 그밖에 프로세서는 선택적으로 사용가능한 인터럽트 핀들을 제공합니다. 개발자가 설정해서 사용이 가능한 거죠.
초보 개발자가 하드웨어 인터럽트의 개념에 대해 가장 손쉽게 배울수 있는 모듈은 GPIO (Ch4-2. PIO와 pin muxing 참조)입니다. 이미 우리는 GPIO가 사용자가 구성 가능한 입출력 핀이며, 이것을 인터럽트로 사용할수도 있다고 배웠습니다.
다시 말해 GPIO는 세가지 모드로 사용가능합니다. 입력, 출력, 그리고 인터럽트죠. 엄밀히 말해 프로세서 입장에서는 인터럽트도 입력입니다만, 거꾸로 생각해서 프로세서 입장에서 출력 GPIO가 다른 장치에 인터럽트 입력으로 들어갈수도 있는 거죠. 하지만 이는 프로세서 입장에서는 단순한 출력이므로, 편의상 입력, 출력, 인터럽트 이렇게 3가지 모드가 구성가능하다 이렇게 이해해도 무방합니다.
GPIO를 인터럽트로 쓰려면, 프로세서에서 필요로 하는 설정을 하면 됩니다. 보통 소스코드에서 제공되는 API나 레지스터를 직접 설정하는 방법 두가지중 하나를 사용합니다. 인터럽트로 설정을 하고 인터럽트가 실행될 때 수행되는 ISR 함수를 등록해주면 됩니다.
여기서 한가지 더 언급하자면, 인터럽트 조건 설정이 필요합니다. 언제 인터럽트가 발생하는 건지 트리거링(Triggering) 조건을 정해줘야 하는거죠. 해당 핀이 High일때 인터럽트를 발생시킬 것인지, 아니면 Low일때 인터럽트를 발생시킬건지 정해야 하는거죠. 해당 핀을 사용하는 외부장치의 동작 사양을 보고 결정하면 됩니다. 보통 Rising Edge나 Falling Edge로 조건을 많이 설정합니다(Ch3-5. 타이밍 다이어그램 참조).