Times and Timers - 시스템프로그래밍

Times and Timers - 시스템프로그래밍

Tag
Computer Science Engineering
System Programming

POSIX Times

  • 모든 컴퓨터는 내부적으로 시간을 가지고 있다. 기본적으로 현재 시간은 특정 시점으로 지금까지 몇 초가 흘렀는가에 대한 정보이다. ( Epoch 기준 )
  • Epoch는 1970년 1월 1일 자정이다.
    • c에서는 time 이라는 함수를 호출하여 확인할 수 있다.
  • POSIX에서는 어떻게 implementation을 하고있는지에 대한 내용은 명시하고있지 않다.
 

Time in seconds

#include <time.h> time_t time(time_t *tloc);
  • parameter
    • tloc: NULL이 아니라면, tloc에 시간 정보를 반환해 줌
      • time_t: long type
  • return values
    • 성공했을 시 Epoch으로부터 몇 초가 흘렀는지 return ( tloc, 반환 모두 같음 )
    • 실패했을 시 -1
    • mandatory error는 따로 정의되어있지 않다.
  • 32-bit long 시스템이라면 대략 68년 뒤에 overflow가 날 것이다.
 
#include <time.h> double difftime(time_t time1, time_t time0);
  • time1 에서 time0을 뺀 시간을 반환해주는 함수
  • return value
    • double인 이유는 시스템에 따라 시간 값이 long / int / 실수 형으로 반환하는 경우도 있기 때문에 double로 잡혀있다.

Displaying date and time

#include <time.h> // parameter가 tm 구조체. 기능은 ctime과 같음 char* asctime(const struct tm *timeptr); // string으로 반환해 줌. 사람이 보기 편한 형태 ( 26개의 영어 string + \n ) char* ctime(const time_t *clock); // localtime이 아니라, utc 기준으로 tm 구조체를 반환 struct tm *gmtime(const time_t *timer); // timer의 정보를 받아서 parsing => struct tm 으로 반환 struct tm *localtime(const time_t *timer);
  • asctime, ctime, localtime은 thread-safe하지 않다. 충돌이 일어날 수 있다.
    • 내부적으로 static 변수를 사용하기 때문
 
  • struct tm structure
    • Members
    • int tm_sec; /* seconds [0,59] */
    • int tm_min; /* minutes [0,59] */
    • int tm_hour; /* hours [0,23] */
    • int tm_mday; /* day of the months [1,31] */
    • int tm_mon; /* months [0, 11] */ ⇒ 실제 월은 + 1
    • int tm_year; /* years since 1900 */ ⇒ 1900년 부터 몇년이 흘렀냐
    • int tm_wday; /* days since Sunday [0,6] */
    • int tm_yday; /* days since January 1 [0, 365] */
    • int tm_isdst; /* flag for daylight-saving time */ ⇒ summer time 제를 시행하는지 아닌지 ( 필요 없음 )
 

struct timeval

#include <sys/time.h> int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
  • 현재 시간을 최대 ms 단위까지 표현하기 때문에, 프로그램 실행 시간을 재기 위해서 보통 사용
  • Epoch 시간부터 현재 시간까지 지난 시간을 ms 단위로 반환
  • struct timeval
    • time_t tv_sec; /* Epoch로부터 지난 seconds */
    • time_t tv_usec; /* microseconds 단위 */
  • parameters
    • tp: output parameter
    • tzp: 무조건 NULL 이어야한다.
  • return values
    • 성공했을 시 0
    • 에러에 대한 내용이 정의되어있지 않다.
    • -1을 리턴하고 errno가 세팅되는 경우도 있음
  • 만약 long이 32bit라면, 최대 시간 차이는 2^31 - 1 microseconds 이며, 대략 35분이다.
 

Using read-time clocks

#include <time.h> int clock_getres(clockid_t clock_id, struct timespec *res); int clock_gettime(clockid_t clock_id, struct timespec *tp); int clock_settime(clockid_t clock_id, const struct timespec *tp);
  • 시스템이 지원을 한다면, 나노세컨 까지의 값을 반환한다.
  • -l 옵션을 써서 link를 해야한다. -lrt
  • 고정된 interval로 값을 계속 증가시키는 counter이다. ( clock resolution ⇒ tick이 발생하는 interval )
    • clock resolution이 1s 이라면, 나노세컨 단위를 알 수가 없다.
  • clock_id ⇒ 어떠한 clock을 사용할 것인지 알려준다.
    • 현재 시간을 알려주는 CLOCK_REALTIME을 사용해도 된다.
    • clock 종류를 더 알고 싶다면 man clock_getres.. ( CLOCK_REALTIME_COARSE, CLOCK_MONOTONIC… )
  • struct timespec structure
    • time_t tv_sec; /* seconds */
    • long tv_nsec; /* nanoseconds */
  • retrun values
    • 성공했을 시 0
    • 실패했을 시 -1
    •  

Example using real-time clocks

#include <stdio.h> #include <time.h> #define MILLION 1000000L void function_to_time(void); int main (void) { long timedif; struct timespec tpend, tpstart; if (clock_gettime(CLOCK_REALTIME, &tpstart) == -1) { perror("Failed to get starting time"); return 1; } function_to_time(); /* timed code goes here */ if (clock_gettime(CLOCK_REALTIME, &tpend) == -1) { perror("Failed to get ending time"); return 1; } timedif = MILLION*(tpend.tv_sec - tpstart.tv_sec) + (tpend.tv_nsec - tpstart.tv_nsec)/1000; printf("The function_to_time took %ld microseconds\n", timedif); return 0; }
 

Sleep functions

#include <unistd.h> unsigned sleep(unsigned seconds); #include <time.h> int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
  • sleep
    • seconds 만큼 suspend
    • 성공했을 시 0
    • 실패했을 시 unslept time 만큼 return
  • nanosleep
    • rqtp 만큼 suspend
    • signal에 의해 중간에 interrupt될 수 있다.
    • rmtp가 NULL이 아니라면, 남아있는 시간을 반환해주는 output 파라미터.
    • 성공했을 시 0
    • 실패했을 시 -1
 

POSIX:XSI Interval Timer

  • Timers
    • clock vs timer?
      • clock은 점점 증가 / timer는 점점 감소
    • OS는 내부적으로 hardware timers를 사용하여 여러가지 software timer를 구현하여 제공한다.
    • time-sharing OS에서 process scheduling을 위해서 interval timers를 사용한다.
  • POSIX:XSI Timers
    • itimerval 구조체를 사용한다. ( 마이크로 단위로 timer를 세팅한다 )
      • struct timeval it_value; /* 초기 값 */
      • struct timeval it_interval; /* 반복해서 실행할 것인지 ( 두번째부터는 언제 만료가 될 것인지 ), 한번만 실행할 것인지 ( 0 ) */
      •  
#include <sys/time.h> int getitimer(int which, struct itimerval *value); int setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
  • setitimer()
    • which
      • 타이머의 종류를 결정할 수 있다.
      • ITIMER_REAL: 보통 이것을 사용한다. real time ( 실시간 ) 으로 작동.
        • 만료시 프로세스에게 SIGALARM 시그널을 보냄
      • ITIMER_VIRTUAL: 가상 시간으로 작동하는 timer. 중단되기도 했다가, 감소되기도 했다가..
        • 프로세스가 실행중일 때만 타이머가 돌아간다.
        • 만료시 프로세스에게 SIGVTALARM 시그널을 보냄
      • ITIMER_PROF: virtual timer와 비슷.
        • 프로세스가 실행중일 때 타이머가 돌아감.
        • 프로세스가 실행중이지는 않더라도, 시스템이 프로세스가 요청한 활동을 할때에도 타이머가 돌아감
        • 만료시 프로세스에게 SIGPROF 시그널을 보냄
    • ovalue
      • 이전에 세팅되어있던 타이머 값이 있었다면, 그 값을 반환해 줌.
      • ovalue가 NULL이 아니라면, 이전 값이 저장된다.
      • value→it_interval이 0이 아니라면, 타이머가 다시 시작할 때 it_interval로 시작이 된다.
      • value→it_interval이 0이라면, 한번만 시작되고 재시작하지 않는다
      • value→it_value가 0이라면, 기존의 타이머를 stop한다.
 

POSIX:TMR interval timers

  • clock을 이용해서, 프로세스가 필요할 경우 independent하게 객체를 이용해서 사용한다.
  • XSI 타이머와는 resolution이 차이가 난다.
    • nanoseconds 단위까지 타이머를 표현할 수 있다.
  • struct itimerspec structure
    • struct timespec it_interval; /* timer period */
    • struct timespec it_value; /* expiration */
    • timeval 구조체보다 resolution이 좋다.
 
#include <signal.h> #include <time.h> int timer_create(clockid_t clock_id, struct sigevent *restrict evp, timer_t *restrict timerid); int timer_delete(timer_t timerid)
  • 프로세스 별로 필요한 만큼 타이머 객체를 만들어야 한다.
  • fork시에 inherit 되지 않는다.
  • timer_create()
    • clock_id: clock의 종류
    • evp: 타이머가 만료가 되었을 때, 시그널을 통지 받을 것인지, 어떠한 시그널로 받을 것인지
      • NULL 이라면, 기본 signal인 SIGALARM을 받는다 ( CLOCK_REALTIME )
      • evp→sigev_signo: 시그널 번호
      • evp→sigev_notify: 시그널로 통지를 받을 것이냐?
        • SIGEV_SIGNAL: 받을래
        • SIGEV_NONE: 안받을래
    • timerid: output 파라미터 / 새로 만들어진 타이머 객체의 id
  • time_delete: 타이머 객체를 삭제하는 것을 OS에게 요청할 수 있다.
  • return value
    • 성공했을 시 0
    • 실패했을 시 -1
 
#include <time.h> int timer_getoverrun(timer_t timerid); int timer_gettime(timer_t timerid, struct itimerspec *value); int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue)
  • timer_settime
    • 타이머를 시작하거나 중지시킬 수 있다.
      • 중지의 경우 value→it_value→tv_sec을 0으로 하면 된다.
    • timerid: 타이머 아이디 지정
    • flags: 특정 옵션을 주고 싶을 때 / 옵션을 주지 않을 것이라면 0
    • value: itimespec 구조체인 value / interval 객체를 줄 수 있다.
    • ovalue: 이전의 타이머 값을 저장하기 위한 output 파라미터
  • timer_gettime
    • value: output 파라미터 / 현재 남아있는 타이머 정보 값을 반환한다.
  • time_getoverrun
    • overrun 횟수를 반환받기 위함
    • 타이머가 한번만 실행되고 말 경우에는 아니지만, 반복되는 타이머의 경우에 발생할 수 있음
    • 만료되고 나서 시그널이 발생되는데, 프로세스가 그 시그널을 받아서 처리를 하고, 다시 타이머가 작동하고 …의 반복
      • 타이머가 만료가 되고 나서 시그널을 발생시켜 프로세스에게 전달이 되었는데, 시그널이 프로세스에게 전달되지 못하고 pending된 경우 pending list에 들어가있고, 다시 타이머가 진행되는 경우 다시 또 시그널이 발생될 것이다. 다음 시그널이 발생되어 프로세스에게 전달이 될 것인데 또 pending이 된다면? pending list에는 같은 타입의 시그널이 들어있을 수 없기 때문에 기존 시그널이 덮여쓰여진다.
    • 즉, pending이 되면서 덮여 쓰여진 ( 없어진 ) 시그널의 개수를 overrun 이라고 한다.
 

Example

#include <stdio.h> #include <time.h> #define MILLION 1000000L #define THOUSAND 1000 void function_to-time(void); int main(void){ long diftime; struct itimerspec nvalue, ovalue; timer _t timeid; // tmr timer를 사용하기 위해 timer를 만든다. if (timer_create(CLOCK_REALTIME, NULL, &timeid) == -1){ perror("Failed to create a timer based on CLOCK_REALTIME"); return 1; } ovalue.it_interval.tv_sec = 0; ovalue.it_interval.tv_nsec = 0; ovalue.it_value.tv_sec = MILLION; ovalue.it_value.tv_nsec = 0; if(timer_settime(timeid, O, &ovalue, NULL) == -1){ perror"Failed to set interval timer"i; return 1; } function_to_time(); if(timer_gettime(timeid,&nvalue, == -1){ perror("Failed to get interval timer value"); return 1; } diftime = MILLION*(ovalue.it_value.tv_sec - nvalue.it_value.tv_sec) + (ovalue.it_value.tv._nsec - nvalue.it_value.tv_nsec)/THOUSAND; printf("The function_to_time took %ld microseconds or %f seconds.\n", diftime, diftime/ (double}MILLION); return 0; }
 

Timer drift

notion image
  • 계속 반복해서 타이머를 사용하는 경우에는 timer drift를 고려해야한다.
    • 만료 시점이 delay가 생겨, 생각보다 나중에 타이머가 만료되는 경우
  • 이유
    • 타이머의 태생적인 이유 ⇒ 완전히 0으로 만들 수는 없다
    • 타이머가 반복해서 실행이 되면, 만료된 이후 다시 시작하는 시점까지의 delay가 미세하게 존재한다.
 

Example

  • 22ms의 interval을 가진 timer의 resolution이 10ms 라고 하자.
    • 22가 표현이 되지 않기 때문에 8ms의 delay가 각각의 만료 시점마다 생기게 된다.
    • + 다음 시점은 44를 원하지만, 30인 시점에 시작을 하면 60인 시점에 만료가 된다.
  • Solution
    • interval을 계속 22로 하는 것이 아니라, 다음 시점은 원래의 기대값으로 갈 수 있을 만큼만 ( 14 )
    • 계속 timer drift가 증가되는 문제를 해결할 수는 있다.
    • Save T = current time + 22 ms
    • Set the timer to expire in 22 ms
    • In the signal handler, set the timer to expire in (T – current time + 22ms), and T = T + 22 ms
 

Solution

  • POSIX:TMR timer absolute time를 사용해라
    • timer_settime의 flags에 TIMER_ABSOLUTE를 세팅하면 된다.
    • *value field의 it_value에 absolute time으로 설정할 수 있다.
  • 현재 시간을 clock_gettime으로 가져온 뒤 22ms를 더하고 이것을 T로 저장한다.
  • T값을 timer의 만료 시점으로 정하면 된다. ( TIMER_ABSOLUTE 태그를 사용하여 )
  • 타이머에 22ms ( T )를 더하고, timer에 설정하면 된다.