POSIX Times
- 모든 컴퓨터 시스템은 내부적으로 시간 정보를 가지고 있다.
- 가장 단순한 시간 정보는 특정 시점으로 지금까지 "몇 초"가 흘렀는지이다. 특정 시점을 Epoch라고 한다.
- Epoch는 1970년 1월 1일 00:00분부터 시작되었으며, 시간을 받는다는 것은 위 시간으로부터 지금까지의 차를 알려주는 것이다.
- 처음 Epoch는 1971년 1월 1일이었는데, 시간 정보에 대해서 처음 생각할 때 가장 가까운 년도를 고른 것이다.
- 이후 10단위로 년도를 끊게 되면서 1970년도로 조정되게 되었다.
Time in seconds
time
#include <time.h>
time_t time(time_t *tloc);
- 시스템의 시간을 Epoch부터 지금까지 얼마나 흘렀는지로 반환받는다.
- 성공하면, Epoch부터 지금까지 얼마나 흘렀는지 반환받는다.
- time_t *tloc : NULL이 아니면, 현재 시간을 반환하는 것 뿐만 아니라 여기에 저장한다.(output parameter)
- 실패하면 (time_t)-1을 반환받는다.
- time_t type은 long type과 같다.
- long type의 크기(32bit) 문제 때문에, Epoch가 시작된 날 부터 대략 68년(2038년)이 지나면 overflow가 발생할 것이다.
difftime
#include <time.h>
double difftime(time_t time1, time_t time0);
- 매개변수로 넘긴 두 시간의 차이를 비교해주는 함수이다.
- time1-time2를 반환한다.
- 반환 값이 double로 구현되어 있는데, 이는 반환할 때 int, long, float, double로 반환 될 가능성이 있어서 대비하기 위해 double로 구현해 놓은 것이다.
Displaying date and time
#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);
- char* asctime(const struct tm *timeptr)
- tm 구조체를 받아서 string으로 반환해준다.
- string은 몇 시, 몇 분, 몇 초 등... 여러가지 종류가 있다.
- 사람이 잘 알아볼 수 있도록 반환해준다.
- char*\ ctime(const time_t *clock)
- asctime(localtime(clock))과 동일하다.
- 지금이 얼마나 흘렀는지를 알려준다.
- struct tm *gmtime(const time_t *timer)
- time_t type을 받아서 parsing을 해준다.
- tm이라는 구조체 값으로 변환해주며, 변환이 끝나면 반환한다.
- UTC 시간을 기준으로 변환해준다.
- struct tm *localtime(const time_t *timer)
- time_t type을 받아서 parsing을 해준다.
- tm이라는 구조체 값으로 변환해주며, 변환이 끝나면 반환한다.
- 년도, 월, 일, 시, 분, 초 등으로 parsing 해주기 때문에 특정한 시간만 참조하는게 가능하다.
- asctime, ctime, localtime은 thread-safe가 아니다.
- 여러 스레드에서 요청을 할 경우 제대로 된 값을 받아오지 못할 수 있다.
struct tm
- gmtime나 localtime의 반환값으로 나오는 구조체이다.
- Members
- int tm_sec; / 초 [0,59]
- int tm_min; / 분 [0,59]
- int tm_hour; / 시간 [0,23]
- int tm_mday; / 일 [1,31]
- int tm_mon; / 월 [0, 11] (1부터 시작이 아니므로 주의)
- int tm_year; / 1900년도 부터 지금까지 (+1900)
- int tm_wday; / 요일 [0,6] (0이 일요일, 6이 토요일)
- int tm_yday; / 1월 1일부터 지금까지 [0, 365]
- int tm_isdst; / 서머타임제를 실시하는지 안하는지를 알려줌
struct timeval
#include <sys/time.h>
int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
- struct timeval
- 현재 시간을 좀 더 정확하게
- Members
- time_t tv_sec; / Epoch 부터의 초 /
- time_t tv_usec; / microsecond /
- int gettimeofday(struct timeval *restrict tp, void *restrict tzp)
– 현재 시간을 Epoch로부터 microseconds 단위까지 좀 더 정확하게 알기 위해서 사용하는 함수다.
- Parameters
- struct timeval *restrict tp: 현재의 정확한 시간을 반환받는다(output parameter).
- void *restrict tzp : 역사적인 이유로 지금은 사용하지 않는다. NULL로 지정한다.
- Return values
- 성공했으면 0을 return한다.
- 에러에 대해서 지정되어있지 않다.
- gettimeofday를 사용하면 long type을 이용하므로 대략 35분 정도까지 잴 수 있다.
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);
- Clock
- 고정된 시간 간격(interval)으로 시간당 값을 증가시키는 Counter이다.
- 이 Clock이 증가하는 시간 간격이 작을수록 정확한 시간을 잴 수 있다.
- Clock마다 고유한 id가 있으며, 각각의 함수를 사용할 때 어떤 clock을 사용할 지 정해야 한다. 대부분 실시간 정보를 나타내는 CLOCK_REALTIME이 많이 쓰인다.
- struct timespec structure
- time_t tv_sec : 초 단위 시간
- long tv_nsec : 나노초 단위 시간
- 성공하면 0을 return하고, 실패하면 오류와 함께 -1을 return한다.
Sleep function
#include <unistd.h>
unsigned sleep(unsigned seconds);
#include <time.h>
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
- sleep
- sleep함수는 지정된 초 시간 만큼 기다리는 함수이다.
- unsigned seconds : 기다리는 시간 초이다.
- 성공하면 0을 반환한다.
- 실패하면 남아있는 초 시간이 반환된다.
- nanosleep
- nanosleep함수는 지정된 나노초 시간만큼 기다리는 함수이다.
- const struct timespec *rqtp : 기다리는 나노 시간 초이다.
- struct timespec *rmtp : 중간에 return이 되면 남아있는 초 시간이 반환된다.
- 성공하면 0을 반환한다.
- 실패하면 오류와 함께 -1을 반환한다.
Interval Timer
- Clock은 정해진 시간 간격(interval)에 따라서 값이 증가한다.
- 이와 달리 Timer는 정해진 시간 간격(interval)에 따라서 값이 감소한다.
- OS는 여러 하드웨어에 알맞는 시간을 재기 위해서 여러개의 소프트웨어 타이머를 갖고 있다.
- struct itimerval
- struct timeval it_value : 타이머의 초기값을 설정한다.
- struct timeval it_interval : 타이머의 반복을 설정한다.
POSIX:XSI Interval Timer
- 가장 기본적인 타이머로 프로세스 단위로 실행된다.
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
- getitimer()
- itimerval에 들어간 타이머의 남아있는 시간을 읽어온다.
- 'which'에 특정한 값을 넣어 타이머의 종류를 결정한다. 종류는 아래와 같다.
- ITIMER_REAL : 실시간에 따라서 타이머를 작동시킨다. 타이머가 만료되면 SIGALRM 시그널이 프로세스에게 전달된다.
- ITIMER_VIRTUAL : 프로세스가 실행중일 때만 타이머를 작동시킨다. 타이머가 만료되면 SIGVTALRM 시그널이 프로세스에게 전달된다.
- ITIMER_PROF : 프로세스가 실행중일 때 타이머를 작동시킨다. 또한 프로세스가 실행중이지는 않더라도 OS가 프로세스의 요청을 받고 있을 때도 타이머를 작동시킨다. 타이머가 만료되면 SIGPROF 시그널이 프로세스에게 전달된다.
- setitimer()
- 두번째 매개변수에 들어간 타이머를 설정하며, 기존 값을 세번째 매개변수에 넣어준다.
- 'which'에 특정한 값을 넣어 타이머의 종류를 결정한다.
- ovalue가 NULL이 아니면, 이전 타이머 값이 저장된다.
- value->it_interval가 0이 아니면, 저 값에 따라 interval이 설정되고 동작한다.
- value->it_interval가 0이면, 타이머는 다시 실행되지 않는다.
- value->it_value가 0이면, 기존 실행되고 있는 타이머를 멈춘다.
POSIX:TMR interval timers
- Clock을 사용해서 프로세스와 독립적으로 사용할 수 있는 타이머를 TMR interval timers라고 한다.
- Clock을 사용하기 때문에 나노초 단위까지 timer를 설정할 수 있다.
- itimerspec 구조체를 사용한다.
- struct timespec it_interval : 반복하는 시간
- struct timespec it_value : 끝나는 시간
- timespec은 timeval보다 미세한 시간을 잴 수 있기 때문에 더 나은 방법으로 여겨진다,
Create interval timer
#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)
- timer_create()
- 새로운 독립적인 타이머 객체를 생성한다.
- 포크로 상속받는 개념이 아니다.
- clock_id : 어떤 clock을 사용할 것인지 결정한다.
- imer_t *restrict timerid : 생성한 타이머의 ID값을 가지고 있는다.
- struct sigevent *restrict evp
- 내가 설정한 타이머가 완료되면 시그널을 받을지 말지, 어떤 시그널을 받을지 등등을 설정한다.
- NULL이면 default signal을 보낸다.
- evp->sigev_signo : 설정하는 시그널 번호
- evp->sigev_notify : 시그널을 보낼 것인지 보내지 않을 것인지
- SIGEV_SIGNAL : 시그널을 보낸다
- SIGEV_NONE : 시그널을 보내지 않는다.
Setting interval timer
#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_gettime() : 설정한 타이머의 남아있는 시간을 가져온다.
- timer_t timerid : 타이머의 ID값을 받는다.
- struct itimerspec *value : 타이머의 정보들을 넣어준다.
- timer_settime() : 타이머를 시작시키거나 취소시킨다.
- timer_t timerid : 타이머의 ID값을 받는다.
- int flags : 특정한 설정을 할 때 사용한다.
- const struct itimerspec *value : 타이머의 초기값과 인터벌값을 설정한다.
- struct itimerspec *ovalue : 기존에 설정한 타이머의 값들을 가져온다.
- timer_overrun()
- 타이머가 한번만 실행 될 경우에는 발생하지 않는다.
- 타이머가 만료되면 시그널이 발생한다. 이 시그널이 프로세스에게 전달되지 못했다면 pending list에 들어간다.
- 이 상태에서 동일 시그널이 pending list에 들어오면 같은 시그널을 저장할 수 없으므로 기존에 있던 시그널에 새로운 시그널이 덮어쓰기가 된다.
- overrun은 이렇게 없어진 시그널의 개수를 알려준다.
Timer drift
- Timer drift는 타이머의 시간이 밀리는 현상이다.
- Timer drift는 생길 수 밖에 없다. 그러나 여러가지 조치를 통해 줄이려는 노력을 하고 있다.
- Timer drift가 생기는 제일 많은 원인은 타이머가 끝나고 다시 시작하는 사이의 시간이 고려되지 않는 문제 때문이다.
- 2초의 interval을 가지고 있는 타이머가 있다고 가정하자
- 2초가 끝나 signal을 보내고, 다시 타이머를 시작하는 사이에 필연적으로 시간이 지연될 수 밖에 없다.
- 대략 작은 값인 0.01초라고 해도, 100초가 지나면 1초의 시간 차이가 발생한다.

- 또 다른 원인은 타이머가 표현할 수 있는 단위 때문에 일어나는 문제이다.
- 예를 들어 타이머가 10ms 단위로 표현할 수 있다고 하자.
- 이 타이머의 interval을 22ms로 설정하면 22ms를 지난 30ms에 만료가 된다.
- 이게 반복되면 결국 interval을 22ms로 해도 30ms이 되는 문제가 발생한다.
- 이 문제를 해결하기 위해 차이나는 시간 간격을 저장해놓았다가 더해주는 방법이 있다.
- 예를 들어 22ms로 해 놓았는데 30ms에 만료되었으면 8ms만큼의 차이가 있는 것이다.
- 그러면 다음 interval을 14ms로 해서 60ms에 만료되는 것이 아닌 50ms에 만료되도록 조정해준다.
- timer drift의 해결 방법은 여러가지가 있지만, 대부분 timer drift를 해결할 수 있는건 TMR timer에서만 가능하다. 왜냐하면 절대 시간을 설정할 수 있기 때문이다.
- 만약 절대 시간을 설정하지 않으면 모든 시간이 상대적이므로 잘 돌아간다고 생각할 수 있다. 그러나 절대 시간을 사용하면 시간 차이를 확인할 수 있고 그에 따라 타이머의 시간이 밀리는 지 확인할 수 있다.