POSIX 시간
- 모든 컴퓨터 시스템은 내부적으로 Epoch(시작 시점) 이후의 초 단위로 시간을 유지해야 한다.
- Epoch은 1970년 1월 1일 00:00(자정)부터 시작하여 지금 시간까지 몇 초가 흘렀는지 알아내기 위하여 time()함수를 사용, 협정 세계시(UTC, 그리니치 표준시 또는 GMT)로 정의된다.
- POSIX는 구현이 시스템 시간을 실제 시간 및 날짜에 어떻게 맞춰야 하는지에 대해 명시하지 않습니다.
초 단위 시간(Time in seconds)
#include <time.h>
time_t time(time_t *tloc);
- 시스템 시간을 가져옴(Epoch 시점부터 지금까지의 시간을 초 단위로 반환하여 표현됨)
매개변수(Parameters)
- tloc: NULL이 아니면, 함수는 시간을 *tloc에 저장
- time_t: long 타입
반환값(Return values)
- 성공 시: Epoch(1970년 1월 1일 00:00) 이후의 초 단위 시간 값을 반환
- 실패 시: (time_t)-1
- 필수적인 오류 정의는 없음
추가 정보
- 32비트 long 타입에서, 시간은 1970년 1월 1일부터 약 68년 후(2038년)에 오버플로우 발생
#include <time.h>
double difftime(time_t time1, time_t time0);
- 두 time_t 타입 캘린더 시간의 차이를 계산
매개변수(Parameters)
- 두 개의 time_t 매개변수
반환값(Return values)
- 첫 번째 매개변수(time_t time1)에서 두 번째 매개변수(time_t time0)를 뺀 결과를 포함하는 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);
- localtime()
- Epoch 이후의 초를 나타내는 매개변수를 받음 -> tm이라는 구조체 타입으로 반환됨
- 지역 시간 요건에 맞춘 시간 구성 요소를 포함하는 구조체를 반환 (tm이라는 구조체 타입)
- asctime()
- localtime에 의해 반환된 구조체를 문자열로 변환
- ctime()
- asctime(localtime(clock))과 동일
- 줄 바꿈 문자로 끝나는 26자 길이의 영어 문자열에 대한 포인터를 반환
- 시간 문자열을 저장하기 위해 정적 저장소(static storage)를 사용
- gmtime()
- UTC로 표현된 시간 구성 요소를 포함하는 구조체를 반환
- 주의
- asctime, ctime, localtime은 스레드-안전(thread-safe)하지 않음 -> 충돌이 발생할 수 있음!
- POSIX(스레드-안전 확장)는 추가 버퍼 매개변수를 가지는 스레드-안전 대안을 명시함
tm_sec : 0 ~ 59초 사이에서 표현, tm_min : 0 ~ 59분 사이에서 표현, tm_hour : 0 ~ 24시까지 사이에서 표현
tm_mon : 1 ~ 12달 사이에가 아닌 -> 0 ~ 11까지의 범위로 값을 포함하기에 그에 맞게 표현됨 -> 원하는 달을 얻기 위해서는 구해진 달 + 1을 계산해야 됨
struct timeval
#include <sys/time.h>
int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
- struct timeval
- POSIX확장은 이 구조체를 사용하여 더 세밀한 단위의 시간(초 단위 -> 밀리 세컨드, 마이크로 세컨드 단위)을 표현
- timeval 구조체 내부 구성 요소(Members):
- time_t tv_sec: Epoch시점 이후의 흘러간 초의 정보
- time_t tv_usec: Epoch시점 이후의 흘러간 마이크로초의 정보
- gettimeofday()
- Epoch 이후의 초 및 마이크로초 단위로 시스템 시간을 가져옴 -> 함수의 실행시간을 측정하기 위한 용도로 많이 사용했었음
- 매개변수(Parameters):
- tp: 가져온 시간을 저장(첫 번째 파라미터만 output 파라미터로 사용됨)
- tzp: NULL이어야 함(두번째 파라미터,역사적인 이유로 포함됨)
- 반환값(Return values):
- 성공 시: 0
- 오류는 정의되어 있지 않음(일부 시스템은 자체 오류를 구현했을 수 있음)
- 참고
- Program 9.1: gettimeofday를 사용하여 함수를 측정하는 방법을 보여줌
- 만약 long 타입이 32비트라면, 최대 기간은 2^(31) 마이크로초(약 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
- 고정된 간격으로 증가하는 카운터로, 이를 "시계 해상도(clock resolution)"라고 부름
- POSIX타이머 확장은 clockid_t 타입의 변수로 표현되는 시계를 포함
- 모든 구현은 시스템의 실시간 시계에 해당하는 CLOCK_REALTIME 값의 시스템 전역 시계를 지원해야 함
- struct timespec 구조체
- time_t tv_sec: 초 단위
- long tv_nsec: 나노초 단위 (컴퓨터 하드웨어가 지원 가능하다는 전제 하에 가능함)
- 시계 함수(Clock functions)
- 성공 시: 0 반환
- 실패 시: -1 반환하며, errno 설정
function_to_time() 함수를 수행하는데 걸린 시간 정보를 clock_gettime함수로 측정하기 전에 &tpstart 구조체에 시작시간을 저장하고 종료 시간을 알기 위해서 clock_gettime함수로 &tpend 구조체에 반환되어 종료된 시점을 저장합니다.
이때, 경과된 시간을 마이크로 세컨드 단위로 변환하기 위해 MILLION을 곱하고 tpend.tv_nsec - tpstart.tv_nsec /1000 나누는 작업으로 반환 결과를 나노초(nanoseconds) 단위에서 마이크로초(microseconds) 단위로 변환하기 위한 과정을 거쳐 나노초로 표현합니다.
슬립 함수(Sleep functions)
#include <unistd.h>
unsigned sleep(unsigned seconds);
#include <time.h>
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
- sleep()
- 호출된 스레드를 지정된 초 수가 경과하거나 호출 스레드가 신호를 받을 때까지 중단시킴
- 요청된 시간이 경과하면 0을 반환
- 중단된 경우 남은 시간을 반환
- nanosleep()
- 호출된 스레드를 rqtp로 지정된 시간 간격이 경과하거나 스레드가 신호를 받을 때까지 실행을 중단시킴
- rmtp가 NULL이 아닌 경우, 남은 시간을 포함 => nanosleep 함수가 중간에 반환될 경우, 그때 남아있는 시간을 반환하는 정보를 포함
- 성공 시 0을 반환, 실패 시 -1을 반환하며 errno를 설정
POSIX: XSI 간격 타이머(POSIX Interval Timer)
- Timers
- 컴퓨터 시스템은 일반적으로 소수의 하드웨어 간격 타이머를 가짐
- Clock(시계) vs. Timer(타이머) 어떠한 차이가 있는지? -> Clock은 값이 증가하고, Timer는 값이 감소함 0이 되면 타이머가 종료됨
- 운영 체제(OS)는 이러한 하드웨어 타이머를 사용하여 여러 소프트웨어 타이머를 구현
- 시간 공유 운영 체제(Time-sharing OS)는 프로세스 스케줄링을 위해 간격 타이머를 사용할 수 있음
- 컴퓨터 시스템은 일반적으로 소수의 하드웨어 간격 타이머를 가짐
- POSIX: XSI Timers
- itimerval 구조체를 사용 -> 내부에 2개의 필드를 포함 -> 타이머를 반복시킬 때 interval 값 필드를 사용함
- struct timeval it_value; /* 다음 만료까지의 시간 */
- struct timeval it_interval; /* 타이머에 재설정될 값 */
- itimerval 구조체를 사용 -> 내부에 2개의 필드를 포함 -> 타이머를 반복시킬 때 interval 값 필드를 사용함
#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()
- 지정된 타이머(which)의 현재 값을 value에 저장
- 타이머 종류:
- ITIMER_REAL: 실시간에서 감소하며, 만료 시 SIGALRM 신호를 생성
- ITIMER_VIRTUAL: 가상 시간(프로세스가 사용한 시간- Only 프로그램이 실행중인 시간)에서 감소하며, 만료 시 SIGVTALRM 신호를 생성
- ITIMER_PROF: 프로세스의 가상 시간 및 시스템 시간에서 감소하며, 만료 시 SIGPROF 신호를 생성
- setitimer()
- which로 지정된 타이머를 value 값으로 설정
- ovalue가 NULL이 아니면 이전 값을 저장
- 동작:
- value->it_interval이 0이 아니면, 만료 시 해당 값으로 타이머가 재시작
- value->it_interval이 0이면, 만료 시 타이머가 재시작하지 않음
- value->it_value가 0이면, 실행 중인 타이머가 멈춤
타이머 예제(Example of timer)
- Program 9.7
- 2초마다 별표(*)를 출력 -> SIGPROF 시그널이 프로세스에 전달 -> 시그널을 캐치하여 시그널 핸들러를 호출하고 시그널 핸들러에서는 별표를 출력함! => 결국 2초마다 별표(*)가 1개씩 계속해서 출력됨
- ITIMER_PROF 타이머 사용
static int setuptimer(void) {
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));
}
Program 9.8
- ITIMER_VIRTUAL 타이머를 사용하여 function_to_time()의 실행 시간을 측정
struct itimerval ovalue, value;
ovalue.it_interval.tv_sec = 0;
ovalue.it_interval.tv_usec = 0;
ovalue.it_value.tv_sec = MILLION;
ovalue.it_value.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &ovalue, NULL);
function_to_time();
getitimer(ITIMER_VIRTUAL, &value);
반복해서 작동할 것이 아니기에 interval 값을 0으로 설정하고, value값을 MILLION으로 저장하여 적당히 큰 값을 설정하여 백만초부터 시작하여 타이머가 감소하여 그 사이에 함수를 실행하고, 함수가 종료되면 얼마나 남아 있는지 남아 있는 시간값을 VALUE 구조체에 저장하고 &value에는 남아 있는 시간값을 저장하여 이 2개의 시간을 확인하여 남아 있는 시간 값을 계산하여 보여줌 -> 마이크로 세컨드 단위까지 경과된 시간값을 측정하여 표현가능
POSIX : TMR 간격 타이머(POSIX Interval Timers)
- CLOCK_REALTIME와 같은 소수의 시계가 있으며, 각 시계에 대해 프로세스는 여러 개의 독립적인 타이머를 생성할 수 있음
- struct itimerspec 구조체를 기반으로 함
- struct timespec it_interval; /* 타이머 주기 */
- struct timespec it_value; /* 만료 시간 */
- struct timeval보다 더 높은 해상도 제공
#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()
- 프로세스별 타이머를 생성
- fork 호출 시 상속되지 않음
- 매개변수(Parameters):
- clock_id: 타이머가 기반으로 하는 시계
- timerid: 생성된 타이머의 ID를 저장
- evp:
- 타이머가 만료되었을 때 비동기 알림을 지정
- NULL인 경우, 타이머는 기본 신호(SIGNALRM for CLOCK_REALTIME)를 생성
- evp->sigev_signo: 원하는 신호 번호
- evp->sigev_notify: 타이머가 만료될 때 수행할 작업을 지정
- SIGEV_SIGNAL: 타이머 만료 시 신호를 생성
- SIGEV_NONE: 타이머가 신호를 생성하지 않도록 설정
- timer_delete()
- 지정된 timerid를 가진 타이머를 삭제
#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()
- 타이머를 시작하거나 중지(itmerspec 구조체의 필드를 0으로 저장하고 호출하면 됨)
- flags는 타이머가 상대적 시간 또는 절대적 시간을 사용하는지 지정
- ovalue가 NULL이 아니면 이전 값을 저장
- timer_getoverrun()
- 타이머 오버런(Timer overrun)을 확인
- 동일 타이머의 이전 만료에서 신호가 대기 중인 동안 타이머가 만료되면 생성된 신호 중 일부가 손실될 수 있음
- 오버런( 타이머가 만료된 시점에 발생한 신호(Signal)가 처리되지 않고 대기 상태(pending)로 남아있는 동안 타이머가 다시 만료되어 추가 신호가 생성되는 상황, pending이 되면서 읽어버린 시그널의 갯수)된 횟수를 반환
- 타이머 오버런(Timer overrun)을 확인
타이머 드리프트(Timer Drift)
- 원인(Reason):
- 타이머가 만료되어야 하는 시점과 타이머가 실제로 재설정되는 시점 사이의 지연
- 예: 2초 간격이 설정된 타이머에서 지연이 5마이크로초일 경우 실제 간격은 2.000005초가 됨 -> 점차 딜레이 값이 10마이크로초, 15마이크로초 ... 이렇게 누적이되면서 지연시간이 커짐
(도표: 원하는 만료 시점(Desired expiration)과 실제 시간(Time) 간의 드리프트 표시)
- 예제(Example):
- 주기가 22ms이고 해상도가 10ms인 반복 타이머를 고려 -> 10, 20, 30, 40... 이렇게 표현되고 15(표현x)인 타이머
- 매번 만료 시 드리프트가 8ms씩 누적 -> 주기가 22ms인데 해상도가 10ms이므로 22에서 8ms가 추가된 30ms가 추가됨, 이렇게 반복하여 52ms도 8ms가 추가되어 60ms가 생성됨
- 절대 시간을 이용한 해결(Solution using absolute time):
- 타이머가 실제로 만료되어야 할 시점을 추적하고, 타이머를 설정할 때마다 값을 조정
- T = 현재 시간 + 22ms로 값을 저장
- 타이머를 22ms 후에 만료되도록 설정
- 신호 처리기(signal handler)에서 T - 현재 시간 + 22ms로 타이머를 다시 설정하고, T 값을 T + 22ms로 갱신
- 타이머가 실제로 만료되어야 할 시점을 추적하고, 타이머를 설정할 때마다 값을 조정
POSIX 타이머를 사용한 절대 시간 기반 해결책
- timer_settime의 flags 매개변수를 TIMER_ABSOLUTE로 설정
- value 매개변수의 it_value 멤버에 지정된 시간이 시간 간격이 아니라 실제 시간을 나타냄
- 현재 시간을 clock_gettime을 사용해 가져온 후 22ms를 더하여 이를 T로 저장
- TIMER_ABSOLUTE 플래그를 사용해 타이머를 T 시간에 만료되도록 설정
- 타이머 신호 처리기에서 T에 22ms를 더한 후, 타이머를 T 시간에 만료되도록 다시 설정
'Computer Science > 시스템 프로그래밍' 카테고리의 다른 글
Chapter 13. 스레드 동기화 (0) | 2024.12.02 |
---|---|
Chapter 12. POSIX Threads (0) | 2024.11.26 |
시스템 프로그래밍 chapter08. (신호) (0) | 2024.11.07 |
시스템 프로그래밍 chapter 06. (UNIX 스페셜 파일) (0) | 2024.11.05 |
시스템 프로그래밍 chapter 05. (파일과 디렉토리) (1) | 2024.10.29 |