티스토리 뷰

Embedded linux 개발을 하면서 시간 관련한 부분은 의외로 많은 신경을 써야한다. 

이 부분을 정리해 본다.


1. 점진적으로 증가하는 시간 만들기


일반 PC 환경에서는 전원을 껏다 켤 일도 많지 않고, 전원을 끈 경우에도 배터리로 유지되는 RTC(Real Time Clock)이 있어 다시 켜면 현재 시간으로 유지된다. 시간이 맞지 않아 다시 재조정 하는 경우를 빼고는 시간이 갑자기 바뀌는 일은 없다고 보면 된다.

하지만 수시로 전원을 껏다 켤 수 있고, 제한된 하드웨어 사양으로 인하여 RTC 가 없다면, 단말은 매번 켜질 때 마다 수동으로 설정 하거나, SNTP(Simple Network Time Protocol) 등을 이용하여 시간을 설정하여야 한다. 그렇지 않으면 켜질 때마다 1970년 1월 1일 부터 시작을 하게 될 것이다.


네트워크에 연결되어 SNTP로 시간을 얻어오는 것이 문제가 생길 확률이 거의 없어 보이지만, WiFi와 같은 무선랜의 경우 방화벽이나 무선랜 간섭, 거리 문제로 인하여 연결이 지연되거나 시간을 얻어 오는 과정이 실패할 가능성이 높다. 


이와 같이 시간을 정상적으로 얻어오지 못한 상태에서 time log 등을 남기게 되는 경우 시간이 왔다 갔다 하게 되어 각 이벤트 간의 시간 관계를 아는 것이 쉽지 않게 된다. 특히 하나의 파일에 순차적으로 기록되는 로그가 아니라 여러 파일에 기록되거나, 파일 생성일을 기준으로 판단을 하는 경우라면 판단할 수 있는 방법이 없게 된다. 


이 경우 다음과 같은 정책으로 유지되도록 하는 것이 좋을 것이다. 

  • 전원이 꺼지기 전에 최종 시간을 별도로 기록
  • 전원이 켜질 때 다시 이 시간을 복구 
  • SNTP로 시간을 얻어온 경우 반영


이와 같은 방식으로 하면 최소한 시간이 뒤로 돌아가는 일은 없으므로 이벤트의 선후 관계는 확인이 가능하게 된다. 물론 전원 어댑터를 뽑는 것과 같이 전원이 차단되면 최종 시간을 기록하지 못하기 때문에, 이런 경우도 고려한다면 flash memory의 특성을 감안하여 수시로 최종 시간을 업데이트 하여야 할 것이다. 


Yocto project 로 빌드하는 경우 이와 같은 script를 제공한다. 


- /etc/init.d/save-rtc.sh

date -u +%4Y%2m%2d%2H%2M > /etc/timestamp


- /etc/init.d/bootmisc.sh

#

# This is as good a place as any for a sanity check

#

# Set the system clock from hardware clock

# If the timestamp is more recent than the current time,

# use the timestamp instead.

test -x /etc/init.d/hwclock.sh && /etc/init.d/hwclock.sh start

if test -e /etc/timestamp

then

SYSTEMDATE=`date -u +%4Y%2m%2d%2H%2M`

read TIMESTAMP < /etc/timestamp

if [ ${TIMESTAMP} -gt $SYSTEMDATE ]; then

date -u ${TIMESTAMP#????}${TIMESTAMP%????????}

test -x /etc/init.d/hwclock.sh && /etc/init.d/hwclock.sh stop

fi

fi


save-rtc.sh 의 경우 파일 저장 도중 전원이 차단 될 가능성이 있는 기기라면, 저장 중 전원 차단문제를 막도록 보완을 하여야 잘못된 시간으로 인하여 문제 발생을 막을 수 있을 것이다. 


2. CLOCK_MONOTONIC


위 문제 이외에도 어플리케이션 내에서 시간 관리하는 부분도 문제일 수 있다. 이 부분은 일반 PC에서도 동일하게 고려하여야 할 사항이긴 하다. 


예를 들어 select()나 poll()의 timeout parameter를 이용한 event loop를 구현할 때 경과된 시간 전에 함수가 리턴되면 남은 시간을 재계산하여 재설정 하여야 한다. 아래는 아주 단순한 예로 select를 이용하여 10초를 기다리는 루틴이다. Select의 경우 signal로 인하여 timeout이 되기 전에 리턴될 수 있으므로 남은 시간을 확인하여 루프를 돌려야 한다. 

gettimeofday(&t, NULL);

cur.tv_sec += 10;

res.tv_sec = 10;

restv_usec = 0; 


while (1) {

    select(0, NULL, NULL, NULL, &res);

    gettimeofday(&now, NULL);

    timersub(&t, &now, &res);

    if (res->tv_sec < 0)

        break;

}


이와 같이 time 함수를 이용하여 구현을 하였을 경우, 루프를 도는 시점에서 SNTP 동기화 등으로 시간이 바뀌게 되면 문제가 발생한다. 예를 들어 사용자가 시간을 한시간 늦추게 되면 위 루프는 10초후에 끝나지 않고, 1시간 10초 후에 끝나게 된다. 


이런 문제를 방지하기 위하여 clock_gettime() 으로 CLOCK_MONOTONIC 소스를  사용할 수 있다. 이 함수로 사용할 수 있는 주요 clock 소스는 다음과 같다. 

- CLOCK_REALTIME: system-wide clock. 일반 시간 함수와 유사하다고 보면 된다. 시간 설정이 변경되면 같이 변경된다. 

- CLOCK_MONOTONIC: 부팅 이후 일정하게 증가하는 클럭. 시간 설정을 변경하여도 영향을 받지 않는다.


대부분의 라이브러리는 CLOCK_MONOTONIC과 같이 시간 변경에 영향을 받지 않는 소스를 이용하여 시간 관리를 한다고 보면 된다. 


3. CLOCK_BOOTTIME


CLOCK_MONOTONIC 소스를 사용하여 시간관리를 하면 모든 문제가 해결될 것 같으나 그렇지 않다. CLOCK_MONOTONIC의 경우 시간 변경에 영향이 가지는 않으나, 문제는 단말이 suspend되어 있는 시간에는 카운트가 증가하지 않는다는 것이다. Suspend를 instance on/off 기능으로 사용하는 경우에는 이와 같은 부분이 문제가 되지 않으나, 저전력으로 동작 하면서 event가 없을 때 수시로 suspend/resume을 반복하는 경우 suspend 시간동안 시간 증가가 되지 않게 되면 위와 같은 event loop 의 시간이 밀리게 된다. 


이를 보완하여 추가된 소스가 CLOCK_BOOTTIME 으로 관련 기능은 kernel 2.6.38 에 포함되었다. 

클럭소스 간의 차이점을 그림을 정리해 보면 아래와 같다. 아래 시간 표시는 이해하기 쉽도록 일반 시간으로 표시한 것이다. 





CLOCK_REALTIME은 resume 된 경우 suspend 시간 만큼 보정되어 현재 시간으로 수정된다. 하지만 시스템 시간을 변경하는 경우 같이 변경된다. 

CLOCK_MONOTONIC은 시스템 시간과는 관계없이 동작하지만 suspend되어 있는 10초 동안 시간이 변경되지 않아 위 그림에서 10초가 느려지게 된다. 


이같은 suspend/resume을 수시로 반복하는 시스템은 android의 wakelock을 이용하여 구현을 할 수 있다. Wakelock은 간단하게 말하면 말 그대로 wake lock을 거는 방식이다. 기존 방식은 명시적으로 suspend를 수행하여야 시스템이 suspend 상태로 가고, 인터럽트로 wake up 이 되나, wakelock 은 반대라고 볼 수 있다. 즉, 가만 나두면 kernel이 자동으로 suspend로 들어가고, 각각의 모듈은 필요한 시간만큼 suspend에 들어가지 않도록 wake up 을 lock 하는 방식이다. 이 부분은 kernel 3.4 부터 정식으로 반영되었다. 


4. 정리


결론은 모듈 내의 상대 시간 관리는 CLOCK_BOOTTIME 을 사용하는 것이 좋을 것이다.

저작자 표시
신고
댓글
댓글쓰기 폼