서론#
현재 vxworks 5.5를 사용하여 프로젝트를 진행하고 있으며, 타이머에 관련된 오퍼레이션이 필요하였기에 vxworks에서 제공하고 있는 POSIX 타이머를 사용하였다. 본 TM은 해당 프로젝트에서 POSIX 타이머를 사용했을시 발생할 수 있는 잠재적인 문제점에 대해 서술하고 있다.
본론#
이번 프로젝트가 개발 환경을 vxworks와 windows 를 목표로 둔 크로스 플랫폼을 지향하는 것이었기 때문에, 코드 재사용성을 고려하여 POSIX 타이머를 선택하였으나, 프로젝트를 진행중 POSIX 타이머가 예상치 못한 동작을 일으킨다는 사실을 발견하였다. 발견한 증상은 다음과 같았다.
문제점#
- 어느 한 태스크에서 timer_settime으로 타이머를 실행시킨다.
- 그 후 태스크는 taskDelay로 주어진 tick 만큼 delay 상태로 진입한다.
- 타이머 핸들러가 수행되며, 동시에 taskDelay로 delay된 상태로 된 태스크가 남은 tick 수에 관계없이 수행 준비 상태(wake-up)로 전이하게 된다.
아래의 소스는 위의 상황을 재현하기 위해 작성된 테스트 소스이며, 원래의 목적은 1초간 10번 수행되는 타이머 이벤트가 수행될 때 동안 해당 태스크는 600tick(60초)를 기다리게 작성된 것이다.
POSIX 타이머 예제 1#
#include "taskLib.h"
#include "timers.h"
#include "tickLib.h"
int i = 0;
timer_t Tmr1;
struct itimerspec tv;
void timer_handler(timer_t tmr, int arg)
{
i ++;
printf("Timer %d\n", i);
if ( i != 10 )
{
printf(".");
timer_settime(Tmr1, 0, &tv, NULL);
}
}
void test_timer()
{
i = 0;
tv.it_value.tv_sec = 1;
tv.it_value.tv_nsec = 0;
timer_create(CLOCK_REALTIME, NULL, &Tmr1);
timer_connect(Tmr1, (VOIDFUNCPTR)(timer_handler), (int)0);
timer_settime(Tmr1, 0, &tv, NULL);
printf("Loop start\n");
STATUS result;
result = taskDelay(600);
if ( result == ERROR )
{
printf("Errono : %d\n", errno);
}
printf("result %d\n", result);
timer_cancel(Tmr1);
timer_delete(Tmr1);
printf("Over..\n");
}
예상과는 달리 타이머 이벤트가 한 번 발생하고, taskDelay로 준 600 tick을 다 못채우고 해당 태스크는 delay 상태에서 깨어나 taskDealy 이하를 실행하였으며, timer_cancel로 인해 나머지 타이머 이벤트들은 모두 취소되었다. 해당 문제를 해결하기 위해 아래와 같이 taskDelay를 사용하지 않고, 조건을 주어 폴링을 수행하니 의도한 대로 동작하였다.
POSIX 타이머 예제 2#
#include "taskLib.h"
#include "timers.h"
#include "tickLib.h"
int i = 0;
timer_t Tmr1;
struct itimerspec tv;
void timer_handler(timer_t tmr, int arg)
{
i ++;
printf("Timer %d\n", i);
}
void test_timer()
{
i = 0;
tv.it_value.tv_sec = 1;
tv.it_value.tv_nsec = 0;
tv.it_interval.tv_sec = 1;
tv.it_interval.tv_nsec = 0;
timer_create(CLOCK_REALTIME, NULL, &Tmr1);
timer_connect(Tmr1, (VOIDFUNCPTR)(timer_handler), (int)0);
timer_settime(Tmr1, 0, &tv, NULL);
printf("Loop start\n");
while ( i != 10);
timer_cancel(Tmr1);
timer_delete(Tmr1);
printf("Over..\n");
}
하지만 위와 같은 방법은 폴링 방식을 쓰므로, CPU자원을 많이 소모하게 된다. 또한 real-time 시스템에서 주어진 시간을 정확히 지키는 것이 가장 중요한 요소이므로 위와 같은 방법으로는 원하는 작업을 수행할 수 없다. POSIX 타이머의 위와 같은 문제점은 다음에 설명된 POSIX 타이머의 특징에서 기인한다.
원인 #
Vxworks kernel 에 구현된 POSIX 타이머의 경우 타이머를 생성한 태스크가 타이머 시작 수행 후 (timer_settime), delay 상태로 천이되면, 타이머 callback 함수 수행 후, delay state의 경우 남은 틱(remaining tick) 수에 관련없이 wakeup(tick scheduling Q 에서 삭제) 된다. 단 이때, taskDelay 의 반환값은 ERROR(-1) 이 되고, errno 는 "EINTR" 로 설정된다. 이것의 이유는 타이머 생성 태스크를 대상으로한 signal delivery 에 의해 타이머 callback 이 수행되는데, signal 전달시 해당 태스크 상태가 delay일 경우 재시작시키게 되고, 이는 delay state에서 wakeup(이때 ERROR값을 반환) 해서 수행재기(ready 로 천이) 한다. 이로 인해 타이머 태스크가 정상 종료하게 되면, 이후 해당 태스크가 존재하지 않게 됨으로 signal delivery fail 로 timer callback 이 더이상 수행될 수 없다.
해결책 #
-
태스크를 pend 상태로 천이시켜(semTake 등), 타이머 callback 함수 종료시 pend 상태를 풀어주어 해당 태스크를 재시작시켜준다.
- pend 상태로 천이된 태스크도 타이머 이벤트시 wakeup되나 vxworks 커널에서 자동으로 다시 (semTake등으로) pend 상태를 유지시켜 준다.
- taskDelay 의 return value 및 errno 를 check 해서 signal 에 의한 restart 를 확인 후, 다시 delay를 시도하는 방법을 사용한다.
- wdLib의 와치독 타이머를 사용한다.
- usrLib의 period함수를 사용한다.
결론#
상기에서 언급한 POSIX 타이머의 특징을 인지하지 못하고 프로그램을 진행했을때는 전혀 엉뚱한 결과를 낳기 쉽다. 특히, POSIX 타이머를 사용하는 태스크가 하드웨어 규약 때문에 특정 시간동안 sleep해야 할 경우 POSIX 타이머에 의해 해당 태스크가 원치 않게 깨어날 수 있는 상태가 될 수 있기 때문이다. 만약 signal로 인해 task의 상태 변화가 문제되지 않는 시스템이면 POSIX 타이머를 사용하도 될 것이다. 다음은 Vxworks에서 제공하는 타이머 관련 서비스들에 대한 간략한 비교 결과이다. 프로젝트에 목적에 맞는 타이머 서비스를 선택하면 될 것이다.
Watch Dog 타이머#
- Vxworks 상에서만 사용할 수 있는 API로 코드 재사용성은 낮다.
- ISR 수준에서 동작하므로 ISR과 같은 제약 조건을 갖는다.
- ISR 처리 시점에서 동작한다.
- 저수준에서 타이머를 처리할 때 사용한다.
POSIX 타이머#
- POSIX 표준을 따르므로, 다른 시스템에서도 POSIX 규칙을 지원하면 사용가능하므로 코드의 재사용성이 높다.
- signal 방식을 사용하므로, ISR 수준에서 수행되는 watch dog 타이머 보단 시간 제약이 더 느슨하다.
- 타이머를 시작시킨 태스크 context에서 수행이 된다.
이 글은 스프링노트에서 작성되었습니다.