728x90
반응형

POSIX Times 

- 특정 시점부터 지금까지 몇 초가 흘렀나에 대한 정보를 유지하고 있다. 그 특정 시점은 Epoch라고 한다. 1970년 1월 1일 자정부터 시작해서 지금 순간까지 몇 초가 흘렀는지에 대한 정보를 알아오기 위해 time이라는 함수 사용. 

 

Time in seconds

#include <time.h>
time_t time(time_t *tloc);

현재 시간을 알아오기 위한 함수 : time 

파라미터가 NULL이 아니게 되면 현재 시간값을 반환해주게 된다. time_t는 long type을 의미한다. 

return값은 success한 경우, 현재 시간값을 성공적으로 알아왔으면 얼마 흘렀나에 관한 것을 반환한다. 

unsuccessful한 경우는 -1을 return 한다. mandatory error는 따로 지정이 안되어있다. 

(long type은 32bit짜리라고 한다면 시스템은 시간 값이 언젠가 overflow 될 것이다. 그 시점이 epoch시점부터 68년 뒤)

#include <time.h>
double difftime(time_t time1, time_t time());

difftime함수는 파라미터로 넘긴 2개의 시간값의 차이를 계산해주는 함수이다. 앞의 시간에서 뒤의 시간을 뺀 값을 반환해주는 함수이다. time_t 타입을 뺀 값인데 double 타입으로 반환해준다.

시스템에 따라서 time_t가 실수형으로 반환하는 경우도 있기 때문이다.

 

Displaying date and time

시간값을 parsing하지 않고서는 이해할 수 없다. 따라서, 유틸리티 역할을 하는 별도의 함수가 존재한다. 

#include <time.h>
char* asctime(const struct tm *timeptr);
char* ctime(const time_t *clock);
struct tm *gmtime(const time_t *timer);
struct tm *localtime(const time_t *timer);

현재의 시간을 어떤 목적에 따라서 알고 싶은가에 의해 사용하면 된다. 

localtime()은 파라미터로 넘어온 시간 정보를 초단위로 parsing을 해서 결과를 tm이라는 구조체 타입으로 반환해준다.

현재 local 시간에 맞춰서. 현재 시간 값을 element단위로 분할해서 tm 구조체에 해당 값들을 반환해준다. tm구조체는 초, 분, 시, 며칠째인지, 몇 월인지 등등에 대한 정보가 들어가 있다.

gmtime()은 똑같은 역할 수행하는데 localtime이 아니라 UTC형식에 따라 반환한다.

ctime은 파라미터로 time_t을 받아서 string으로 반환해주는 함수이다. (사람이 보기 편하게) ctime(time())

asctime을 갖고서도 수행할 수 있다. input parameter type이 tm type이 올 수 있다.

ex> asctime(*localtime(time(NULL))

asctime, ctime, localtime은 thread-safe 한 함수가 아니다. 내부적으로 static 저장소를 사용하기 때문이다.

 

tm구조체(struct tm structure)

- gmtime()이나 localtime()함수에서 parameter로 사용

- int tm_sec;

- int tm_min;

- int tm_hour;

- int tm_mday;

- int tm_mon;  +1 해야 원래 달 가질 수 있다.

- int tm_year; 는 1900부터 몇 년도가 흘렀는지 반환

- int tm_wday : 무슨 요일인지 나타냄 0부터 6까지 사이의 숫자 값으로 나타냄. 0이 일요일 6이 토요일

- int tm_yday : 1년 365일 중 지금이 몇 번째 날인지 표현

 

struct timeval

#include <sys/time.h>
int gettimeofday(struct timeval *restric tp, void *restrict tzp);

현재 시간을 micro second 단위까지 반환을 해주는 함수이다. 현재 시간을 이용해서 특정 프로그램의 실행시간을 측정하기 위해서 사용했었는데 POSIX 버전 이후부터 obsolete함수로 mark 했다(오래된 함수라는 의미이다.)

clock_gettime함수를 대신 사용해라.

 

Using real-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);

 

gettimeofday()는 obsolete하기 때문에 이 함수를 사용하라고 추천한다. 나노 seconds까지 반환받기 위해서 또다른 구조체 type을 파라미터로 받는다. tv_sec, tv_nsec이 timespec에 field로 포함되어 있다. 

Clock이라는 객체로 시간을 알아온다. 고정된 interval로 시간을 증가시키는 counter이다. 얼마나 고정되어있느냐에 따라 결정되는것이다. clock이 딸깍거리는 interval이 clock resolution마다 clock tik이 딸깍딸깍하는 것이다. resolution 값으로 얼마나 세밀하게 표현할 수 있느냐 결정된다. 1ns마다 clock이 값이 증가하는 것이다. resolution이 시간에서는 현재 시간을 표현할 수 있다. 1s 였다라고 한다면 1초마다 틱이 증가한다. clock을 사용해서 현재 시간을 알아올때 clock resolution값에 따라서 세밀하게 표현하는 것이 결정된다. 

clock_getres()를 이용해서 timespec 구조체를 반환해 준다. 

clock_gettime()을 이용해서 timespec 구조체로 반환이 된다. 

clock_settime()은 clock값을 2번째 파라미터로 설정하겠다 하는 함수도 제공을 한다. 시스템에서 사용하는 clock이 여러가지가 있는데 clock종류에 따라서 설정할 수 없는 함수도 있다. clock종류에 따라서 사용여부가 결정이 된다. 첫번째 파라미터로 어떤 clock을 사용할지 결정해 준다. 

link를 해서 compile을 해야 된다. option 중에서 -lrt로 컴파일 해야 한다.

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() 함수인 경우 파라미터 값만큼 쉬고 리턴하고 싶을 때 사용한다.

nanosleep()함수의 파라미터는 timespec구조체로 잠들(sleep) 시간을 표현할 수 있다. 중간에 리턴이 되는 경우 남아있는 시간정보를 반환해 준다. sleep()도 signal에 의해 interrupt 될 수 있다. 성공적이면 0이 리턴이 되고 에러가 생기면 -1이 리턴이 된다. unslept time값이 리턴이 된다. 

 

POSIX:XSI Interval Timer

현재 시간으로부터 timer를 설정할 수 있다. 

clock과 timer가 어떤 차이가 있느냐? clock은 계속 값이 증가하는 것. timer는 값이 감소한다. OS에서는 소프트웨어적인 Timer를 제공을 한다. OS내부에서도 process scheduling 하기 위해서 interval timer를 설정한다. 프로세스에게 정해진 시간만큼만 할당해주고 timer를 이용해서 time quantum 값만큼 실행시키고 다음 프로세스 실행시키고 이런 용도로 사용한다. 

itimerval 이라는 구조체도 사용한다. 안에 2개의 field를 가지고 있다.

struct timeval it_value; // time until next expiration 다음 완료때까지의 time

struct timeval it_interval; // value to reload into the timer 

#include <sys/time.h>
int getitime(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);

getitimer() : 'which'에 들어가는 'value'값을 저장한다.

- ITIMER_REAL : real time에서 감소하고 expire되면 SIGALRAM을 생성한다.

- ITIMER_VIRTUAL : 가상 시간에서 감소하고 expire되면 SIGVTALRM을 생성한다.

- ITIMER_PROF : 가상 시간에서 감소하고 expire되면 SIGPROF signal을 생성한다.

setitimer():  : 'which'에 들어가는 'value'값을 저장한다. (위와 동일)

- ovalue가 not NULL이면 이전의 value가 저장된다.

- value -> it_interval이 0이 아니라면, timer가 재시작된다.

- value -> it_interval이 0이라면, timer가 끝날때까지 재시작되지 않는다.

- value -> it_value가 0이라면, timer가 멈춘다.

 

Timer를 사용할려면 구조체의 필드값을 설정하고 돌린다. timeval 구조체 안에 2개의 필드가 있다. tv_sec, tv_usec.

2개의 field는 microsecond 단위로 timer정보를 설정할 수 있다.

 it_value는 timer의 초기값을 설정하는데 사용하는 함수. 예를들어 it_value의 tv_sec를 60초로 설정하면 60초짜리 타이머가 된다.

 it_interval은 timer를 한번만 돌리고 끝낼건지 아니면 계속 반복해서 timer가 완료되면 다시 시작할 것인지 나타내는 field. 이 timer를 한번만 실행하고 말거면 0으로 두고 반복해서 실행할거면 2번째부터 언제만료할건가 정의한다. timer는 value값에 따라 timer가 돌고 interval을 보고 또 돈다. timer를 반복시킬때 interval 값을 사용한다. 

settimer의 첫번째 파라미터는 타이머의 종류를 결정한다. ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF

ITIMER_VIRTUAL은 중간에 중단될수도 있다. 언제 timer가 작동하냐면 타이머 작동시킨 프로세스가 실행중일때만. 대기상태에 있다던지 이러면 타이머도 멈춘다. 실행중일때 작동하는 타이머. 완료하면 SIGVITALRM이라는 signal이 전달된다.

ITIMER_PROF : virtual timer와 비슷. timer값이 프로세스가 실행중일때 감소하고 process가 실행중이지는 않더라도 process가 요청한 task를 진행중이면 timer값이 감소하겠다. timer가 i/o요청하면 os에게 요청. 타이머가 완료하게 되면 SIGPROF signal이 프로세스에 전달된다. 마지막 파라미터 ovalue는 output parameter이고 NULL이 아니게 되면 여기 가리키는 itimerval로 이전 타이머 값을 반환을 해주는 것이다. 

getitimer는 지금 얼마나 흘렀는지 알고 싶을때 호출해서. 

setitimer사용할 때 두번째 파라미터를 어떻게 설정할 수 있느냐. 

 

Example of timer>

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>

/* ARGSUSED */
static void myhandler(int s) { //signal handler 함수
   char aster = '*';
   int errsave;
   errsave = errno;
   write(STDERR_FILENO, &aster, 1); //화면에다가 별표하나를 출력
   errno = errsave;
}

static int setupinterrupt(void) {          /* set up myhandler for  SIGPROF */
   struct sigaction act;
   act.sa_handler = myhandler;
   act.sa_flags = 0;
   return (sigemptyset(&act.sa_mask) || sigaction(SIGPROF, &act, NULL));
}

static int setupitimer(void) {    /* set ITIMER_PROF for 2-second intervals 2초마다 만료되는 timer*/
   struct itimerval value;
   value.it_interval.tv_sec = 2;
   value.it_interval.tv_usec = 0;
   value.it_value = value.it_interval;
   return (setitimer(ITIMER_PROF, &value, NULL));
}
int main(void) {
   if (setupinterrupt()) {
      perror("Failed to set up handler for SIGPROF");
      return 1;
   }
   if (setupitimer() == -1) {
      perror("Failed to set up the ITIMER_PROF interval timer");
      return 1;
   }
   for ( ; ; );                        /* execute rest of main program here */
   //무한loop에서 대기
}

 

  1 #include <stdio.h>
  2 #include <sys/time.h>
  3 #define MILLION 1000000L
  4
  5 void function_to_time(void);
  6
  7 int main(void) {
  8    long diftime;
  9    struct itimerval ovalue, value;
 10
 11    ovalue.it_interval.tv_sec = 0;
 12    ovalue.it_interval.tv_usec = 0;
 13    ovalue.it_value.tv_sec = MILLION;                /* a large number 초기값 설정 */
 14    ovalue.it_value.tv_usec = 0;
 15    if (setitimer(ITIMER_VIRTUAL, &ovalue, NULL) == -1) {
 16       perror("Failed to set virtual timer");
 17       return 1;
 18    }
 19    function_to_time();                        /* timed code goes here */
 20    if (getitimer(ITIMER_VIRTUAL, &value) == -1) { //함수끝나고 남아있는 시간 구하기
 21       perror("Failed to get virtual timer");
 22       return 1;
 23    }
 24    diftime = MILLION*(ovalue.it_value.tv_sec - value.it_value.tv_sec) +
 25                ovalue.it_value.tv_usec - value.it_value.tv_usec;
 26    printf("The function_to_time took %ld microseconds or %f seconds.\n",
 27             diftime, diftime/(double)MILLION);
 28    return 0;
 29 }

 

다른 종류의 TIMER : TMR interval timers

POSIX : TMR interval timers --> clock을 이용해서 process가 필요한 경우 independent 하게 timer 객체를 만들어서 사용할 수 있는 timer를 의미. interval timer 사용방법은 XSI Timer와 유사. 차이점은 timer의 resolution값이 차이가 난다. 

현재 시간 알기 위해서 getTImer, clockgetTimer에서 resolution 차이가 XSI Timer에서는 micro로 차이가 있었는데 TMR은 nano seconds까지 차이가 있다. interval timer에서 timer설정할때 구조체가 별도로 존재한다. (struct itimerspec)

field값은 동일하다. timespec it_value : 초기값 it)interval : 반복시킬지 결정. but 타입이 다르다. timespec it_interval , timespec it_value : timespec구조체를 사용한다. 나노seconds로 시간값을 받기 위해 사용한다. 설정하는 것은 동일하다. 

 value값이 0이면 기존에 돌고있었던 timer를 중지시키는 역할 수행한다. 앞선 timer와의 차이점은 더 나은 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)

TMR timer를 사용하기 위해서는 일단 timer 객체를 만들어야 한다. 필요한 만큼 만들어서 사용할 수 있다. timer 객체를 만들기 위해서 timer_create 함수를 사용한다. 객체 생성할 때 3가지 파라미터가 사용되는데 첫번째는 clock_id 타입. 클락의 종류를 지정해야한다. (CLOCK_REALTIME) 2번쨰파라미터는 struct sigevent* restrict evp : timer가 완료가 되었을때 signal을 날려줄건지 말건지 날려줄거면 어떤 signal을 날려줄건지. (비동기식 I/O에서 비슷한 얘기를 했다) NULL이면 default signal이 발생한다. 통지받을지 안받을지 notify field 값을 먼저 설정해주고 signo값으로 어떤 signal을 받을것인지 설정한다. default signal 사용할려면 NULL로 사용한다. 

마지막 파라미터는 timer_t *restrict timerid : output 파라미터이다. 반환된 timerid는 timer 타입으로 반환된다. 타이머 객체를 만들었으면 timer_delete로 삭제할 수 있다. timer_t parameter를 받을 수 있다. 함수들을 사용하려면 signal, time header를 include하고 사용하면 된다.

#include <time.h>
int timer_getoverrun(timer_t timed);
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 객체를 만들고 나면 timer 설정하는 함수 timer_settime() 함수도 있다. 4개의 파라미터를 취한다. TMR interval timer객체를 이용한 함수는 첫번째가 timerid를 설정한다. 어떤 timer객체에 요청하는 건지 지정하고, 2번째 파라미터는 flag로 특정 옵션을 주고자 할때 사용하는 것이고, 필요없으면 0값을 주고, 3번째 파라미터는 itimerspec 구조체는 여기 안에 value 와 interval field값이 있다. 초기값과 interval 값을 설정하면 된다. timespec구조체 타입이여서 tv_sec, tv_nsec가 있다. sec단위의 값을 내가 원하는 초단위로 설정해놓고 그 itimerspec을 세번째 파라미터로 넘겨준다. 마지막 파라미터는 새로설정하는 타이머 이전의 타이머값을 저장하기 위한 용도로 output parameter라고 보면 된다. null이 아닌값으로 parameter설정하면 이전 파라미터가 반환된다. 

timer_gettime() : 현재 남아있는 시간 정보 반환하고 싶을때 사용.

timer_getoverrrun() : overrun()이라는 횟수를 반환받기 위한 함수. timerid를 넘겨주고 overrun은 timer가 한번만 실행되고 말 경우 overrun이 발생하지 않는데 반복해서 실행되는 함수에서 실행될 수 있는 현상이고 timer가 완료되면 signal이 발생되는데 이게 정상적으로 작동할려면 만료되면 signal발생하고 process가 signal받아서 작동하고 timer다시 작동하고. 이게 계속 반복되는게 정상적인 timer의 작동이다. 상태에 따라서 이런경우가 있다. timer가 만료가 되어서 signal이 발생했는데 어떤 이유에서 signal이 process에 전달되지 못하고 pending 된 것이다. 그러면 pending 된 signal은 pending list에 들어가고 그 사이에 timer가 실행된다. 그리고 다시 만료가 되었으면 signal 발생하고 또 process에 전달된다. 이 signal도 pending이 되면 pending list에는 같은 타입의 signal이 안들어가기 때문에 또 들어와서 pending되면 덮어씌워지게 되고 원래 signal은 그럼 날라가게 된다. signal을 나중에 process가 2개 받아야되는데 하나가 날라가게 된것이다. 없어진 signal의 개수를 overrun이라고 이야기 한다. 그래서 timer가 몇번 완료되었는지 확인하고 싶으면 overrun()함수를 사용하면 참조할 수 있다는 것이다. 평상시에 별로 사용할 일은 없는데 overrun의 의미를 알고 있자. 

ex> interval timer이용

tmrtimer.c

#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;

   if (timer_create(CLOCK_REALTIME, NULL, &timeid) == -1) {//timer객체를 만듬
      perror("Failed to create a timer based on CLOCK_REALTIME");
      return 1;
   }
   //timer값 설정
   ovalue.it_interval.tv_sec = 0; //구조체 안에 interval field안에 sec단위 -> 0이면 한번만 쓰는거다.
   ovalue.it_interval.tv_nsec = 0; //nsec단위
   ovalue.it_value.tv_sec = MILLION; //100만초 이내에 끝날거다               /* a large number */
   ovalue.it_value.tv_nsec = 0;
   if (timer_settime(timeid, 0, &ovalue, NULL) == -1) {
      perror("Failed to set interval timer");
      return 1;
   }
   function_to_time();                       /* timed code goes here */
   if (timer_gettime(timeid, &nvalue) == -1) { //남아있는 시간값 얻어아 nvalue에 담긴다
      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 

: 이 문제를 고려해야 한다. -> 계속 반복해서 실행하는 timer인 경우. drift는 밀려서 이동하는 것이다. timer의 만료시점이 원했던 만료시점이 아니라 delay가 생겨서 뒤에 만료되는 문제를 timer drift문제라고 말한다.

drift문제가 생기는 이유가 태생적인 문제, 가만히 내버려두면 timer가 반복되면서 누적이 됨에 따라서 실제 원했던 timer 완료시점이랑 차이가 생긴다.

첫번째 원인은 timer가 반복해서 실행되면은 만료되고 다시 시작되는 시점까지 delay가 미세하게 나마 존재한다. 또다른 원인은 반복해서 실행되는 timer가 있는데 이 타이머의 resolution이 10ms. 타이머를 작동시킬때 timer의 interval을  22ms로 설정했을때 22에 가장 가깝게 되는 시점에 timer가 만료된다. -> 30ms 원래 원했던 시점보다 8ms 밀려서 만료가 된다. 이러한 timer를 계속해서 반복하게 되면 다음은 44ms에서 울리고 싶은데(22ms+22ms) 30ms에서 울렸기 때문에, 30+22 52에서 울리고 싶은데, 52는 resolution에 해당하지 않으므로, 52ms에서 못울리고 60ms인 시점이 되어서야 울린다. 원래 다음 timer 44ms보다 16ms나 drift가 생긴것이다. 계속 이렇게 timer가 반복됨에 따라서 누적이 된다. delay를 0으로 없앨 수는 없지만 계속 커지는 문제는 막을 수 있다. 특정 delay값보다 커지지 않도록 조절할 필요가 있다. 

처음에 timer가 만료가 된 다음 timer가 실행될때 interval을 조절할 수 있으면 된다. timer의 interval은 22니까 만료된 시점이 실제보다 늦어졌으니 다음 만료기대값과 지금 timer의 시간 차이값으로 다음 interval값을 조절하다. 44-30=14ms. 다음 interval값은 14ms로 조절하면 44에서 울릴려고 하지만 44ms는 timer가 표현할 수 없으니까(resolution이 10ms이기 때문에) 50ms에서 timer가 울리게 된다. 그 다음 timer 값도 66에서 울려야되는데 실제 timer완료 시점이 50인데 그 다음 timer의 interval을 16으로 해서 timer돌리면 다음은 70ms에서 울리게 된다. 완전히 없앨 수 있는 문제는 아니지만 계속 증가되는 문제는 막을 수 있다. 

-----> 22ms 44ms 66ms

-----> 30ms 50ms 70ms

solution 적용하기 위해서 TMR timer를 이용하면 solution방식을 적용해서 timer drift를 적용할 수 있다. timer interval 값을 absolute로 적용할 수 있기 때문이다. 앞에서는 relative time으로 설정했다(2초 뒤에 뭘해라-> 2라는 숫자는 realtive time이다. 기준시점이 현재 시점 그게 우리가 일반적으로 timer설정하는 방식) option을 줘서 absolute로 설정하면 현재시간이 얼마인지 알아야한다. 오후 1시였다고 하면 timer의 interval값을 13:02로 절대 시간으로 timer를 주는것. (알람설정하는것) 알람으로 절대시간으로 결정하니까. TMR timer의 solution에서 absolute time을 사용했었다. 

flag를 TIMER_ABSOLUTE로 설정하면 interval을 Absolute로 설정할 수 있다. 현재 시간이라는 absolute time은 현재시간이 설정되어있다. 이 시간으로 timer interval을 설정할 수 있따. value parameter의 interval를 value field에다가 real time으로 만료 시점을 설정하면 현재 시간을 알아오기 위해서 TMR timer가 clock을 사용했으니 clock_gettime함수를 이용해서 현재시간의 absolute time을 알 수있다. T값을 timer의 완료시점으로 설정하면 된다. timer가 만료되고 signal이 오면 다음 timer가 만료되는 시점은 T에서 22ms를 더한 값이 되는거고 그다음도 absolute로 22+T로 설정하면 되는 것이다. 

728x90
반응형

'CS > 시스템 프로그래밍' 카테고리의 다른 글

Thread Synchronization  (0) 2021.12.01
POSIX Threads  (0) 2021.11.23
UNIX Special Files  (0) 2021.11.03
Files and Directories  (0) 2021.10.15
UNIX I/O  (0) 2021.10.15

+ Recent posts