Computer Science/시스템 프로그래밍

시스템 프로그래밍 chapter09. (타임과 타이머)

만능 엔터테이너 2024. 11. 19. 16:33
728x90
반응형
SMALL

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 구조체 - gmtime 또는 localtime 함수의 변수로 각 필드에 해당되는 요소 값을 할당하여 반환하는 역할

 

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분까지의 시간 차이 정보를 측정 가능함)

1초를 마이크로세컨드 값으로 변환하려면 백만초를 곱하면 됨, 단위가 long타입이므로 %ld로 출력이 됨

 

10^(3) 단위로 곱하여서 단위가 증가됨 / s => ms => us => ns => ps

실시간 시계 사용(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; /* 타이머에 재설정될 값 */
#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 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 시간에 만료되도록 다시 설정

 

 

 

 

 

 

728x90
반응형
LIST