절대적 순방향 시간자원 얻기

대문 / 프로그래밍 / 절대적 순방향 시간자원 얻기

절대적 순방향 시간자원 얻기

1.1. 시작하기전에

프로그래밍을 하다가 보면 절대적으로 순방향으로만 증가하는 시간자원이 필요할때가 많습니다.

특정 시간 구간의 소요시간을 측정한다던가 특정 주기마다 어떤 실행을 구현하려고 할때 일반적인 시계자원의 경우 시간동기가 중간에 일어나면 시간이 역방향 또는 많은 시간을 건너뛰는 상태가 발생할 수 있습니다. 이럴때 충분히 고려되지 않으면 프로그램은 의도되지 않은 동작을 일으키기 매우 쉽습니다.

여기서는 최대한 많은 Platform에서 동작가능한 절대적 순방향 시간자원에 대한 구현을 다루고자 합니다.

절대적 순방향 시간자원의 구현에 필수적인 요구사항은 다음과 같습니다.
  • 시간이 역방향으로 흐르지 않는다. (어떠한 조건에도 과거시간으로 돌아가지 않아야 한다.)
  • Overflow에 대한 고려가 되어 있지 않다면 충분히 큰 값으로 다룰수 있어야 한다.
  • 특정 시간이 흐른 상태에서 정확히 그 흘러간 시간 간격을 측정할수 있어야 한다.
  • 외부에서 이 시간자원을 임의로 조작할수 없거나 조작하여도 영향을 받지 않아야 한다.
  • 적어도 msec 단위의 시간해상도를 지원할 수 있어야 한다. (만약 RTOS계열인 경우 usec 단위 지원에 대한 고려를 하는게 좋다.)
  • 최대한 성능에 영향을 적게 받는 방법으로 구현되어야 한다.
  • 시스템이 Suspend 상태일때 시간이 증가할지는 선택적일 수 있다. (일반적으로는 Suspend 상태에서는 count하지 않는게 좀더 유용할 수 있으나 모바일 환경등에서는 count하는 것이 필요할 수 있다.)

1.2. Windows 에서의 구현

#include <windows.h>
#define hwport_uintmax_t ULONGLONG
int __hwport_get_absolute_time_msec(hwport_uintmax_t *s_msec_ptr)
{
    LARGE_INTEGER s_performance_frequency;
    LARGE_INTEGER s_performance_count;

    if(QueryPerformanceFrequency((LARGE_INTEGER *)(&s_performance_frequency)) != TRUE) {
        return(-1);
    }

    if(s_performance_frequency.QuadPart == ((LONGLONG)0)) {
        return(-1);
    }

    if(QueryPerformanceCounter((LARGE_INTEGER *)(&s_performance_count)) != TRUE) {
        return(-1);
    }

    *s_msec_ptr = ((((hwport_uintmax_t)s_performance_count.QuadPart) * ((hwport_uintmax_t)1000u)) / ((hwport_uintmax_t)s_performance_frequency.QuadPart));

    return(0);
}

1.3. OS X 에서의 구현 (Mach kernel 기반)

#if defined(__MACH__)
# include <mach/mach.h>
# include <mach/mach_time.h>
#define hwport_uintmax_t unsigned long long
int __hwport_get_absolute_time_msec(hwport_uintmax_t *s_msec_ptr)
{
    mach_timebase_info_data_t s_timebase;

    /* mach kernel */
    mach_timebase_info(&s_timebase);

    if(s_timebase.denom == 0) {
        return(-1);
    }

    *s_msec_ptr = (((hwport_uintmax_t)mach_absolute_time()) * ((hwport_uintmax_t)s_timebase.numer)) / (((hwport_uintmax_t)s_timebase.denom) * ((hwport_uintmax_t)1000000));

    return(0);
}
#endif

1.4. Linux(또는 Android) 에서의 구현 (librt.so 를 직접 링크하지 않고 SystemCall을 직접 사용하는 방법)

#if defined(__linux__)
/* need define _GNU_SOURCE */
#if !defined(_GNU_SOURCE)
# define _GNU_SOURCE (1L)
#endif

# include <unistd.h>
# include <sys/syscall.h>
# ifndef CLOCK_MONOTONIC
#  warning "Old glibc (< 2.3.4) does not provide this constant. We use syscall directly so this definition is safe."
#  define CLOCK_MONOTONIC 1
# endif
# if !defined(SYS_clock_gettime)
#  define SYS_clock_gettime __NR_clock_gettime
# endif
#define hwport_uintmax_t unsigned long long
int __hwport_get_absolute_time_msec(hwport_uintmax_t *s_msec_ptr)
{
    struct timespec s_timespec;

    /* libc has incredibly messy way of doing this, typically requiring -lrt. We just skip all this mess */
    if(syscall(SYS_clock_gettime /* __NR_clock_gettime */, (int)CLOCK_MONOTONIC, (void *)(&s_timespec)) != 0) {
        return(-1);
    }

    *s_msec_ptr = (((hwport_uintmax_t)s_timespec.tv_sec) * ((hwport_uintmax_t)1000u)) + (((hwport_uintmax_t)s_timespec.tv_nsec) / ((hwport_uintmax_t)1000000u));

    return(0);
}
#endif

1.5. Unix/BSD/Linux 계열에서의 구현 (SUv2 및 POSIX.1-2001 을 만족하는 환경, )

#include <unistd.h>
#include <time.h>

#if (defined(_POSIX_VERSION) && (_POSIX_VERSION >= 199309L)) || (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 199309L))
    /* 
        clock_getres, clock_gettime, clock_settime 함수는 개발환경에 따라서 다르지만 적어도
        _POSIX_VERSION (또는 _POSIX_C_SOURCE) define값이 199309L(POSIX 1003.1b = IEEE 1003.1b-1993) 이상이 되어야 정상적인 활용이 가능합니다.

        또한 rt library를 링크해야 하는 경우가 필요할수 있습니다. ( Link with -lrt )
    */
#define hwport_uintmax_t unsigned long long
int __hwport_get_absolute_time_msec(hwport_uintmax_t *s_msec_ptr)
{
    struct timespec s_timespec;

    /* maybe requiring -lrt */ /* SUSv2, POSIX.1-2001 */
    if(clock_gettime((clockid_t)CLOCK_MONOTONIC, (struct timespec *)(&s_timespec)) != 0) {
        return(-1);
    }

    *s_msec_ptr = (((hwport_uintmax_t)s_timespec.tv_sec) * ((hwport_uintmax_t)1000u)) + (((hwport_uintmax_t)s_timespec.tv_nsec) / ((hwport_uintmax_t)1000000u));

    return(0);
}
#endif

1.6. HP-UX, Solaris 환경에서는 gethrtime() 함수를 사용하여 구현이 가능

/* See also: https://docs.oracle.com/cd/E23824_01/html/821-1465/gethrtime-3c.html */
#if defined(__hpux) || defined(__sun) || defined(__SVR4)
# include <sys/time.h>
# define hwport_uintmax_t unsigned long long
int __hwport_get_absolute_time_msec(hwport_uintmax_t *s_msec_ptr)
{
    *s_msec_ptr = ((hwport_uintmax_t)gethrtime() /* nano seconds */) / ((hwport_uintmax_t)1000000u);

    return(0);
}
#endif

1.7. 기타환경에서의 구현

  • 시스템이 Suspend 상태로 진입한 동안의 시간을 count할지에 대한 고민도 필요할 수 있는데 위에 제시한 예제들은 모두 Suspend상태에서는 count 되지 않는 것을 기준으로 작성되었습니다. (즉, Suspend상태일때는 시간이 증가하지 않는 정지상태로 유지하는 방식)
  • 만약 Suspend상태인 동안의 시간도 count되기를 원한다면 CLOCK_MONOTONIC 대신에 CLOCK_BOOTTIME (Linux의 경우 kernel version이 적어도 2.6.39 이상의 버젼에서 사용가능합니다.) 을 고려하거나 이에 상응하는 Suspend시간을 별도로 count하는 등의 고려가 되어야 합니다.
  • times 함수의 반환값을 이러한 용도로 활용할수 있는 경우도 있습니다. 하지만 이 사항은 커널의 구현방식을 확인하는게 필요하며 경우에 따라서 특정 시간이 지나면 Overflow에 대한 고려가 있어야 합니다. (의외로 필자의 경험상 times의 구현은 매우 혼란스러운 Overflow 대응고려가 필요합니다.)
    • 일부환경에서는 times 함수의 반환값 clock() / HZ 가 부팅직후 289초 쯤에서 Overflow가 -1135, -1134, 1133, 1132, 1131, 1130, .., 1, 0, 1, 2, 3, 4, ... 와 같은식으로 발생합니다. 그리 좋은 Overflow 방식은 아닌듯 싶습니다.
  • Timer IRQ를 직접 사용할수 있는 경우 Interrupt loss 에 대한 loss count를 반드시 고려하는게 필요합니다.
  • 모든 구현사항에는 Overflow 되지 않는 충분히 큰 counter값을 사용하거나 Overflow 에 대응하는 보정이 필요합니다.

1.8. 참고링크

Retrieved from https://www.minzkn.com:443/moniwiki/wiki.php/GetAbsoluteTime
last modified 2021-10-21 00:12:15