Wiring Pi - ISR.c (인터럽트 서비스 루틴) 분석
이번에는 인터럽트 서비스 루틴에 대해서 분석해볼 예정이다.
뭐,,, 인터럽트에 대한 내용은 따로 CS 지식 정리 할 때 정리하겠다.
애초에, 인터럽트는 너무 흔한 개념이기도 하고 기본적으로 이게 궁금해서 온 사람은 모를리 없다는 것을 전제로 하여 진행할 것이기 때문!
isr.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <wiringPi.h>
// globalCounter:
// Global variable to count interrupts
// Should be declared volatile to make sure the compiler doesn't cache it.
static volatile int globalCounter [8] ;
/*
* myInterrupt:
*********************************************************************************
*/
void myInterrupt0 (void) { ++globalCounter [0] ; }
void myInterrupt1 (void) { ++globalCounter [1] ; }
void myInterrupt2 (void) { ++globalCounter [2] ; }
void myInterrupt3 (void) { ++globalCounter [3] ; }
void myInterrupt4 (void) { ++globalCounter [4] ; }
void myInterrupt5 (void) { ++globalCounter [5] ; }
void myInterrupt6 (void) { ++globalCounter [6] ; }
void myInterrupt7 (void) { ++globalCounter [7] ; }
/*
*********************************************************************************
* main
*********************************************************************************
*/
int main (void)
{
int gotOne, pin ;
int myCounter [8] ;
for (pin = 0 ; pin < 8 ; ++pin)
globalCounter [pin] = myCounter [pin] = 0 ;
wiringPiSetup () ;
wiringPiISR (0, INT_EDGE_FALLING, &myInterrupt0) ;
wiringPiISR (1, INT_EDGE_FALLING, &myInterrupt1) ;
wiringPiISR (2, INT_EDGE_FALLING, &myInterrupt2) ;
wiringPiISR (3, INT_EDGE_FALLING, &myInterrupt3) ;
wiringPiISR (4, INT_EDGE_FALLING, &myInterrupt4) ;
wiringPiISR (5, INT_EDGE_FALLING, &myInterrupt5) ;
wiringPiISR (6, INT_EDGE_FALLING, &myInterrupt6) ;
wiringPiISR (7, INT_EDGE_FALLING, &myInterrupt7) ;
for (;;)
{
gotOne = 0 ;
printf ("Waiting ... ") ; fflush (stdout) ;
for (;;)
{
for (pin = 0 ; pin < 8 ; ++pin)
{
if (globalCounter [pin] != myCounter [pin])
{
printf (" Int on pin %d: Counter: %5d\n", pin, globalCounter [pin]) ;
myCounter [pin] = globalCounter [pin] ;
++gotOne ;
}
}
if (gotOne != 0)
break ;
}
}
return 0 ;
}
우선 위 코드는 WiringPi에 있는 인터럽트 서비스 루틴 예제이다.
글로벌 카운터로 변화 가능한 volatile array 변수를 설정하고, 인터럽트 발생에 따라 해당 변수 값을 추가시켜준다.
그치만, 이걸 볼려고 온 것은 아닐것이니... 이번에는 저 wiringPiISR에 대해 알아보자!
wiringPiISR(int pin, int mode, void (*function)(void) )
pin 번호, 모드, 그리고 함수 포인터를 받는 형태이다.
함수의 내부는 다음과 같이 구현되어있다.
/*
* wiringPiISR:
* Pi Specific.
* Take the details and create an interrupt handler that will do a call-
* back to the user supplied function.
*********************************************************************************
*/
int wiringPiISR (int pin, int mode, void (*function)(void))
{
const int maxpin = GetMaxPin();
if (pin < 0 || pin > maxpin)
return wiringPiFailure (WPI_FATAL, "wiringPiISR: pin must be 0-%d (%d)\n", maxpin, pin) ;
if (wiringPiMode == WPI_MODE_UNINITIALISED)
return wiringPiFailure (WPI_FATAL, "wiringPiISR: wiringPi has not been initialised. Unable to continue.\n") ;
if (wiringPiDebug) {
printf ("wiringPi: wiringPiISR pin %d, mode %d\n", pin, mode) ;
}
if (isrFunctions [pin]) {
printf ("wiringPi: ISR function alread active, ignoring \n") ;
}
isrFunctions [pin] = function ;
isrMode[pin] = mode;
if(waitForInterruptInit (pin, mode)<0) {
if (wiringPiDebug) {
fprintf (stderr, "wiringPi: waitForInterruptInit failed\n") ;
}
};
if (wiringPiDebug) {
printf ("wiringPi: mutex in\n") ;
}
pthread_mutex_lock (&pinMutex) ;
pinPass = pin ;
if (wiringPiDebug) {
printf("wiringPi: pthread_create before 0x%lX\n", (unsigned long)isrThreads[pin]);
}
if (pthread_create (&isrThreads[pin], NULL, interruptHandler, NULL)==0) {
if (wiringPiDebug) {
printf("wiringPi: pthread_create successed, 0x%lX\n", (unsigned long)isrThreads[pin]);
}
while (pinPass != -1)
delay (1) ;
} else {
if (wiringPiDebug) {
printf("wiringPi: pthread_create failed\n");
}
}
if (wiringPiDebug) {
printf ("wiringPi: mutex out\n") ;
}
pthread_mutex_unlock (&pinMutex) ;
if (wiringPiDebug) {
printf ("wiringPi: wiringPiISR finished\n") ;
}
return 0 ;
}
우선 차근차근 분해해서 분석해보겠다.
1. const int maxpin = GetMaxPin();
라즈베리파이의 종류에 따라 MaxPin을 받아온다. 종류에 따라 27, 63으로 나뉘게 된다.
이후 이 핀을 기준으로, 범위 내에 있다면, 그리고 받아온 pin 번호를 기준으로 해당 번호에 인터럽트를 적용할 수 있는지 등 다양한 조건을 토대로 점검한다.
이 점검 기준에 부합하지 않는다면 오류 메세지와 함께 인터럽트 설정이 취소되게된다.
2. pthread_mutex_lock (&pinMutex); pthread_mutex_unlock (&pinMutex);
찾아보니, pinMutex 정의가 다음과 같다.
static pthread_mutex_t pinMutex ;
그래서 생각을 해 보았는데, 이 wiring Pi에서는 인터럽트를 하나만 제공하는 것 같다.
왜냐?
위 두 lock과 unlock 사이가 다음과 같기 때문.
isrFunctions [pin] = function ; // 인터럽트 관련 함수가 pthread에 저장이 됨
pinPass = pin; // pinPass -> 전역변수
if (pthread_create (&isrThreads[pin], NULL, interruptHandler, NULL)==0) {
while (pinPass != -1)
delay (1) ;
}
여기서 pthread 생성이 성공하게 되면 무한 루프문으로 들어가게 된다.
즉, pinPass값이 pin으로 유지되는 한 탈출할 수 없고, pthread_mutex_unlock으로 넘어갈 수 없어진다.
이때, pinMutex가 잠겨있는 동안 다른 인터럽트가 들어오더라도, 그 인터럽트는 실행될 수 없다!
이것은 mutex의 특성이기므로, 필요하다면 인터넷에 검색해보길 권장한다.
(뮤텍스란 무엇인지 검색하면 관련 정보가 많으며, 필자는 공용 정보를 접근할 수 없도록 하는 잠금으로 이해하고있다)
간단히 설명하자면, 다른 인터럽트가 들어왔을 때 이미 mutex가 잠겨있다면, pthread_mutex_lock에서 대기하게 된다.
그렇지만 이후 인터럽트가 아예 실행되지 않는 것은 아니고, 이 앞선 인터럽트가 실행 되고 난 뒤 해당 인터럽트의 함수가 실행되는 형태이다.
그렇다면, 과연 들어온 함수의 포인터는 어떻게 인터럽트 실행 함수로 넘어가는 것일까? 이 과정을 살펴보자.
우선 pthread_create에서 function이 넘어가면서, 인터럽트 실행 함수가 쓰레드에 들어간다.
즉,
wiringPiISR(5, INT_EDGE_FALLING, &myInterrupt5);
를 호출하면,
isrFunctions[5] = myInterrupt5;
가 된다.
참고로, 다음과 같이 정의되어있다.
static void (*isrFunctions [64])(void) ;
static pthread_t isrThreads[64];
그러나, interruptHandler가 있어서 그 이후가 어떻게 될 지 명확하지 않다!
3. interruptHandler
static void *interruptHandler(void *arg) {
int pin;
(void)piHiPri(55); // 프로세스 우선순위 설정
pin = pinPass;
pinPass = -1; // `pinPass` 초기화 (인터럽트 대기)
for (;;) { // 무한 루프 (인터럽트 대기)
int ret = waitForInterrupt(pin, -1);
if (ret > 0) { // 인터럽트 감지됨
if (isrFunctions[pin]) {
isrFunctions[pin](); // 등록된 핸들러 실행
}
} else if (ret < 0) {
break; // 인터럽트 대기 종료
}
}
waitForInterruptClose (pin);
}
이 인터럽트 핸들러에서
- 적합한 pin에 맞는 함수를 실행시키고
- 실행 완료 시 인터럽트를 닫은 후 (isrFds[pin] close도 해주고, = -1도 해주며, isrFunctions도 NULL로 바꾼다)
- return NULL ; 을 통해 종료한다.
이렇게, 인터럽트 실행부터 종료까지 그 과정을 알아보았다.
그리고 혹시 저 0~7 이라는 pin값을 어떻게 얻는지 궁금한 사람들은 다음 명령어를 실행해보면 나올 것이다. (wiring pi를 다운했다는 가정)
gpio readall
이 명령어를 실행하면, 어떻게 구성되어있는지 도스창에 나올 것이다. 그걸 참고해서 값을 설정하면 된다!!