메뉴 건너뛰기

NEOS White Paper

주의 : 본 기능은 NEOS V5에만 적용됩니다.

 

1. 인터럽트 핸들러는 최대한 가볍게

아마 대부분의 엔지니어 분들은 당연하게 받아들이고 넘어갈 얘길 텐데요, 인터럽트 핸들러는 최대한 가볍게 빠른 시간 안에 작업을 마치고 빠져나가는 것이 중요합니다. RTOS에서 인터럽트는 스레드를 선점하여 실행되므로 인터럽트 핸들러에서 계속 작업을 하게 되면 다른 응용 스레드들이 수행될 수 있는 시간을 확보할 수가 없으며 다른 인터럽트들에 대한 응답 성능을 저해하는 요소가 됩니다.

 

2. 인터럽트 문맥에서 사용금지 함수 확인할 것

앞서 1번 항목에서 인터럽트 핸들러는 최대한 간결하게 수행하고 넘어가야 한다는 말을 했습니다. 이와 유사한 이유로 인터럽트 문맥에서는 사용할 수 없는 함수들이 존재합니다. SemaphoreWait, MessageQueueReceive 등등 Pending 상태를 유발하는 함수들은 인터럽트 핸들러 내부에서 사용할 수 없습니다. 간단하게 생각해서 위와 같은 함수는 해당 이벤트가 들어올 때까지 계속 대기시키는 함수이기 때문에 만약 인터럽트 핸들러 내부에서 위와 같은 함수를 사용하면 이벤트가 발생할 때까지 인터럽트 핸들러가 계속 잡고 있어 시스템 Idle 상태가 발생할 수 있습니다. 

 

3. NEOS API 호출 시 리턴 값 확인

대부분의 NEOS API들은 호출 문맥 및 함수 인자 값들의 유효성을 체크하여 결과를 리턴해 줍니다. 

만약 SemaphoreWait() 함수를 인터럽트 문맥(Interrupt Service Routine 내부)에서 호출하게 되면 NEOS는 ISR_NOT_CALLABLE 이란 에러 타입을 리턴해줄 것입니다.

프로그램이 의도대로 동작하지 않거나 Exception 등의 문제가 발생할 경우 NEOS API의 리턴 값을 확인해 보십시오.

응용 개발 시 API의 리턴 값을 확인하는 코드를 추가하는 습관을 가지면 디버깅 시간을 절약할 수 있습니다.

 

4. Local 변수는 신중하게 사용할 것

Local 변수는 해당하는 함수 내부에서만 유효하다는 것은 대부분의 소프트웨어 개발자라면 알고 있는 사실일 것입니다. 즉, 어떤 함수 내부에서 int형 변수를 선언하였다면 그 변수는 해당 함수가 동작하는 동안만 유지가 되고 해당 함수의 동작이 완료되는 시점에는 메모리에서 해제되어 더 이상 유효하지 않게 됩니다. 하지만, 이를 간과하고 Local 변수를 함수 외부에서 사용하도록 실수할 수가 있는데 이런 경우 문제를 찾는 것이 쉽지 않을 수도 있습니다. 예를 들어 아래의 코드를 보면, 

 

/* main function of a philosopher */
int main(void)
{
    Thread phil[NUM_PHILOSOPHERS];
    int philosopher_id[NUM_PHILOSOPHERS];
    Status status;
    int i;

    /*
     * Initialize the display and create a thread for each philosopher.
     */

    init_screen();

    for ( i = 0; i < NUM_PHILOSOPHERS; i++ )
    {
         philosopher_id[i] = i;
         status = ThreadSpawn(NULL, THREAD_STACK_SIZE, THREAD_PRIORITY,
                      (Address)philosopher, (Address)&philosopher_id[i], &phil[i]);
         if ( status != SUCCESS )
         {
             printf( "cannot create thread for philosopher %d\n", i );
             return (ERROR);
         }
    }
}

 

main 함수에서는 ThreadSpawn이라는 NEOS에서 스레드를 생성 및 실행시킬 경우 사용되는 API를 이용하여 스레드들을 생성하고 있습니다. 스레드를 생성할 때 philosopher_id라는 배열의 특정 인덱스 변수의 주소를 포인터를 이용하여 생성할 스레드에게 전달하고 있습니다. 하지만 이 philosopher_id 라는 배열은 main 함수 내부에서 선언된 Local 변수이기 때문에 main 함수가 종료될 경우 메모리에서 해제되어 유효하지 않은 변수가 됩니다. 

 

이럴 경우, 새로 생성된 스레드에서는 입력 파라미터로 philosopher_id 배열의 특정 인덱스를 받았기 때문에 이 배열에 어떤 값이 들어있을지 확신할 수 없게 됩니다. 새로 생성된 함수들은 운이 좋게도 philosopher_id 배열이 위치해 있던 메모리를 건드는 프로그램이 없다면 정상적으로 동작할 수 있겠지만, 만약 메모리를 사용하는 프로그램이 생길 경우 예기치 못한 입력 값으로 동작하게 되는 문제가 생깁니다. 위와 같이 Local 변수를 해당하는 함수이외에서도 사용해야한다면, 전역변수 또는 Static 변수로 선언하여 메모리에 영구 상주시키도록하여야 안전합니다.

 

5. 메모리 할당 해제는 확실히 확인할 것

당연한 말이겠지만 메모리를 Dynamic하게 할당, 해제하여 사용할 경우 반드시 사용이 끝난 메모리는 free 함수를 이용하여 해제 시켜주어야만 합니다. 그렇지 않을 경우, 메모리 누수로 인해 예상치 못한 곳에서 문제가 생길 수 있어 디버깅이 어려운 상황에 빠지게 됩니다. 예전 고객사에 기술 지원 했던 경우를 예로 들어보면, 사용자가 malloc, free 함수를 이용하여 Ring Buffer로 데이터를 관리하는 프로그램을 작성하였는데 5 ~ 10분 정도 잘 동작하다가 프로그램이 Exception을 일으키는 문제가 발생한 경우가 있었습니다. 사용자의 말로는 짐작 가는 부분이 없는데 계속 문제가 발생하여 같이 디버깅을 한 결과, 메모리 누수 현상을 발견하여 수정하였던 적이 있습니다. 

 

응용 프로그램 개발자로서 가장 기본적인 부분인데, 잘 되겠지라는 생각으로 확인해보지 않고 넘어간 것으로 인해 많은 시간을 소모했던 경우였습니다. 메모리를 전부 정적으로 고정해놓고 사용하는 것이 Critical한 시스템에서는 안전하겠지만 효율성 측면에서 동적으로 메모리 핸들링을 하는 경우가 대부분일 것입니다. 이럴 경우 반드시 할당 해제 구문을 확인하여 메모리 누수가 없는지 확인하도록 해야할 것 입니다. 이를 위해 CodeSonar 등의 정적분석 툴을 이용하여 시험을 하는 것도 하나의 방법이 될 수 있습니다.

 

6. 적절한 시스템 클럭 주파수 설정

NEOS의 모든 시간 관련 API들은 시스템 클럭 주기를 기준으로 동작하게 됩니다.

예를 들어, 시스템 클럭 주파수가 100Hz로 설정이 되어 있다면 시스템 클럭 인터럽트가 발생하는 주기가 10ms로 설정되며 NEOS에서 처리 가능한 가장 작은 단위의 시간 또한 10ms로 설정됩니다.

따라서 ThreadDelay(); 함수에 15ms를 인자로 준다고 해도 스레드는 10 ms 동안만 Sleep 상태로 유지됩니다.

시스템에서 좀 더 세밀한 단위의 시간으로 컨트롤이 필요하다면 시스템 클럭 주파수를 더 높여줘야 합니다. 하지만, 시스템 클럭 주파수를 높일 경우 타이머 인터럽트가 더 자주 발생하게 되어 시스템 성능이 떨어질 수 있으므로 시스템 클럭 주파수 설정시에는 시스템 요구사항과 하드웨어 성능 등을 고려하여 신중하게 선택해야 합니다.

만약, 시스템 클럭 주파수를 더 이상 높일 수 없는 환경에서 좀 더 세밀한 시간 제어가 필요하다면 시스템 클럭 이외의 별도의 하드웨어 클럭을 이용한 제어가 필요합니다.

 

 

위로