[Linux_System_programming] 11 시간

Linux System Programming (Robert Love) chapter 11 시간

  • 커널은 다음 세가지 방법으로 경과 시간을 측정

    • 실제 시간 : 현실에서 사용하는 진짜 시간과 날짜(wall_time), 사용자 상호작용/이벤티 타임스탬프에 사용
    • 프로세스 시간 : 프로세스를 실행하는데 소비한 시간, 프로세스는 프로파일링과 통계 산출 목적으로 특정 알고리즘을 처리하는데 걸린 시간을 측정
    • 모노토닉 시간 : 반드시 일정하게 증가, 운영체제는 시스템 가동시간(uptime 사용), 두 샘플링 사이의 시간 차이를 계산할 때 유용
  • 시간 측정 방법은 다음 두 형식중 하나로 표현

    • 상대시간 : 현재 시각과 같이 특정 기준점에 대한 상대값으로 표현, 모노토닉 시간은 상대 시간을 계산하는데 유용
    • 절대시간 : 특정 기준점 없이 절대값으로 표현(2000년 3월 25일 정오같은 표현, 절대 시간을 계산하는데 적합)
  • 유닉스 시스템은 UTC로 정의된 기원 부터 경과된 절대 시간을 초로 표현(epoch time)(UTC = GMT, ZULU)

  • 운영체제는 커널이 관리하는 소프트웨어 시계를 사용해서 시간 경과를 추적

    • Jiffie counter : 커널은 타이머를 이용해서 타이머 동작주기마다 틱이나 지피라는 한단위 만큼 늘림
    • HZ : 시스템 타이머의 빈도(아키텍쳐마다 다름)(100을 사용하다 최근 250을 사용, 4ms마타 시스템 타이머 동작)
    • CLOCK_PER_SEC : POSIX 인터페이스에서 고정 주파수에 맞춰 시간 측정값을 제공하는데 이 때의 고정 주파수
  • sysconf(_SC_CLK_TCK)로 HZ값을 알 수 있음

  • 커널은 시작하면서 하드웨어 시계에서 현재 시간을 받아 운영체제 시각을 초기화, 시스템 종료시 현재 시각을 하드웨어 시계에 다시 기록

  • hwclock 명령으로 하드웨어 시각과 운영체제 시각을 동기화 가능

시간에 대한 자료 구조

  • 유닉스 시스템이 발전함에 따라 시간 관리를 위한 독자적인 인터페이스를 구현했으며 단순한 시간 개념을 여러 자료 구조로 표현

전통적인 표현볍

  • 가장 단순한 자료구조는 time_t이며 <time.h>에 정의 (time_t 내부 구조를 숨기기 위함)
  • 대부분의 유닉스 시스템에서 long임 (typedef long time_t)

마이크로 초 정밀도

1
2
3
4
5
6
#include <sys/time.h>

struct timeval{
  time_t tv_sec; // 초
  suseconds_t tv_usec // 마이크로초
}
  • timeval 구조체는 time_t를 확장하여 마이크로초 정밀도를 추가

나노 초 정밀도

1
2
3
4
5
6
#include <sys/time.h>

struct timespec {
  time_t tv_sec; // 초
  long tv_nsec; // 나노 초
}
  • timespec 구조체가 나노초까지 해상도를 제공
  • timespec 구조체 등장 이후 대다수 시간 관련 인터페이스는 timespec을 사용하도록 변경(그라나 중요함수는 여전히 timeval 사용)

시간 세분하기

  • C표준은 좀 더 사람에게 가까운 형태로 세분화된 시간을 표현하는 tm 구조체를 제공
  • 구조체에 대한 설명은 책에 있음

프로세스 시간을 위한 타입

  • clock_t는 tick을 표현, int로 표현, 인터페이스에 따라 clock_t 틱은 시스템의 실제 타이머 주파수(HZ) 나 CLOCK_PER_SEC를 나타냄

POSIX 시계

  • POSIX 시계는 5가지 타입이 있음(clockid_t 타입)
    • CLOCK_REALTIME : 시스템 전역에서 사용하는 실제시간 시계, 설정하려면 특수 권한 필요
    • CLOCK_MONOTONIC : 시스템 시동과 같이 불특정 시작 시점 부터 단조롭게 증가하는 시계, 어떤 프로세스도 설정하지 못함
    • CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC과 유사하나 시간이 뒤틀렸을 때 조정되지 않음(리눅스 전용)
    • CLOCK_PROCESS_CPUTIME_ID : 프로세서 수준에서 지원되는 각 프로세스에서 사용 가능한 고해상되 시계(x86의 경우 TSC 레지스터 사용)
    • CLOCK_THREAD_CPUTIME_ID : CLOCK_PROCESS_CPUTIME_ID와 유사하나 스레드 마다 유일한 시계

시계해상도

1
2
3
#include <time.h>

int clock_getres(clock_t clock_id, struct timespec *res);
  • clock_getres() 함수 호출이 성공하면 res가 NULL이 아닐 경우 clock_id로 지정한 시계의 해상도를 res에 저장
  • 성공시 0반환, 실패하면 -1을 반환하고 errno를 EFAULT, EINVAL 중 하나로 설정

현재시간 얻기

1
2
3
#include <time.h>

time_t time(time_t *t);
  • time() 함수를 호출하면 epoch time 이후 경과한 현재 시각을 초 단위로 표현하여 반환
  • 에러 발생시 -1을 반환, errno를 설정(유일한 값은 EFAULT로 t가 유효한 포인터가 아님)

더 나은 인터페이스

1
2
3
#include <sys/time.h>

int gettimeofday(struct timeval* tv, struct timezone* tz);
  • gettimeofday()호출이 성공시 tv에 현재시각을 기록하고 0을 반환,
  • tz는 사용하지 않으므로 NULL로 설정(커널이 시간대를 관리하지 않음, glibc도 timezone 구조체의 tz_dsttime 필드를 미사용)
  • 호출 실패 시 -1 반환, errno 설정(EFAULT : tv,tz 가 유효하지 않은 포인터)

고급 인터페이스

1
2
3
#include <time.h>

int clock_gettime (clockid_t clock_id, struct timespec *ts);
  • clock_gettime()은 지정한 시계의 시간을 얻기 위한 함수, 나노 정밀도를 허용
  • 호출이 성공하면 0을 반환, clock_id로 지정한 시계의 현재 시간을 ts에 저장
  • 실패 시 -1을 반환, errno를 설정(EFAULT, EINVAL)

프로세스 시간 얻기

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <sys/time.h>

struct tms {
  clock_t tms_utime; /* 소비한 사용자 시간 */
  clock_t tms_stime; /* 소비한 시스템 시간 */
  clock_t tms_cutime; /* 자식 프로세스가 소비한 사용자 시간 */
  clock_t tms_cstime; /* 자식 프로세스가 소비한 시스템 시간 */
}

clock_t times(struct tms *buf);
  • times() 시스템 콜은 실행 중인 프로세스와 자식 프로세스의 프로세스 시간을 틱 단위로 가져옴(호출 성공 시 buf 채움)
    • 사용자 시간 : 사용자 영역에서 코드를 수행한 시간
    • 시스템 시간 : 커널 영역에서 코드를 수행한 시간
  • 자식 프로세스가 끝나고 부모가 wait() 또는 관련 함수를 호출하고 나서야 각 자식 프로세스에 대한 보고 시간이 구조체의 해당 필드에 포함
  • 호출 실패 시 -1 반환, errno를 설정(EFAULT : buf가 유효하지 않은 포인터)

현재 날짜와 시각 설정하기

1
2
3
4
#define _SVID_SOURCE
#include <time.h>

int stime(time_t *t);
  • stime() 호출 성공 시 시스템 시간을 t 값으로 설정하고 0을 반환 (time()함수 대응)
  • CAP_SYS_TIME 기능이 필요(일반적으로 root사용자)
  • 호출이 실패 시 -1을 반환, errno를 설정(EFAULT, EPERM)

정확하게 시각 설정하기

1
2
3
#include <sys/time.h>

int settimeofday(const struct timeval *tv, const struct timezone *tz);
  • settimeofday() 호출이 성공시 시스템 시간을 tz값으로 설정 후 0반환, tz에는 NULL이 권장 됨(gettimeofday() 대응)
  • 호출이 실패 시 -1을 반환, errno를 설정(EFAULT. EINVAL, EPERM)

시각 설정을 위한 고급 인터페이스

1
2
3
#include <time.h>

int clock_settime(clockid_t clock_id, const struct timespec *ts);
  • clokc_settime() 호출 성공 시 clock_id로 지정한 시계를 ts 시간으로 설정, 성공시 0반환
  • 호출이 실패 시 -1 반환, errno를 설정(EFAULT, EINVAL, EPERM)
  • 대부분의 시스템에서 유일하게 설정가능한 시계는 CLOCK_REALTIME, 나노 초 해상도를 제공한다는 장점이 있음

시간 다루기

1
2
3
4
#include <time.h>

char* asctime(const struct tm *tm);
char* asctime_r(const struct tm *tm, char *buf);
  • asctime()함수는 tm 구조체를 ASCII 문자열로 변환, 정적으로 할당된 문자열을 가리키는 포인터 반환
  • asctime()은 스레드 세이프 하지 않으므로 asctime_r을 사용(문자열을 buf에 저장, 최소 26글자)
  • 에러 발생의 경우 NULL을 반환
1
2
3
#include <time.h>

time_t mktime(struct tm *tm);
  • mktime()은 tm 구조체를 time_t로 변환, tm에 따라 tzset()을 호출해 시간대를 설정
  • 에러가 발생하면 -1을 반환
1
2
3
4
#include <time.h>

char* ctime(const time *timep);
char* ctime_r(const time_t *timep, char *buf);
  • ctime()은 time_t를 ASCII 표현으로 변환, 실패하면 NULL을 반환
  • 줄바꿈 문자를 반환 문자에 붙임
  • ctime()은 스레드 세이프 하지 않으므로 ctime_r을 사용(문자열을 buf에 저장, 최소 26글자)
1
2
3
4
#include <time.h>

struct tm* gmtime(const time_t *timep);
struct tm* gmtime_r(const time_t *timep, struct tm *result);
  • gmtime()은 time_t를 tm구조체로 변환하여 UTC 시간대로 표현, 실패하면 NULL을 반환
  • gmtime()은 스레드 세이프 하지 않으므로 gmtime_r을 사용
1
2
3
4
#include <time.h>

struct tm* localtime(const time_t* timep);
struct tm* localtime_r(const time_t* timep, struct tm* result);
  • localtime(), localtime_r은 gmtime(), gmtime_r()과 비슷하게 동작, 그러나 time_t를 사용자 시간대에 맞춰 표현
  • localtime()도 tzset()을 호출하여 시간대를 초기화 함(localtime_r()의 동작은 표준에 명세되어 있지 않음)
1
2
3
#include <time.h>

double difftime(time_t time1, time_t time0);
  • difftime()은 두 값사이에 경과한 초를 double타입으로 변환하여 반환
  • 오버플로우가 감지를 고려하지 않은 점만 제외하고 (double)(time1 - time0)와 같다
  • 리눅스에서 time_t는 정수타입이므로 double 타입으로 변환할 필요는 없지만 이식성을 고려하여 difftime() 사용을 권장

시스템 시계 조율

  • 실제 시간을 갑작스럽게 조정하면 동작 과정에서 절대 시각에 의존하는 어플리케이션은 혼란에 빠짐(ex. Makefile 증분 빌드)
  • 이를 위해 유닉스는 adjtime() 함수를 통해 제공된 시간 차이에 따라 현재 시각을 점진적으로 조정(adjtime()을 사용하여 주기적으로 시간 보정)
1
2
3
4
#define _BSD_SOURCE
#include <sys/time.h>

int adjtime(const struct timeval *delta, struct timeval *olddelta);
  • adjtime() 함수 호출이 성공하면 커널이 delta로 지정한 시간에 맞추어 천천히 조정 작업을 시작하도록 하고 0을 반환
  • delta가 양수면 시간을 빠르게 가게, 음수면 느리게 가도록(갑작스런 시간변화는 안생김)
  • delta가 NULL이 아니라면 이전에 등록된 조정값을 처리하는 작업은 멈춤, olddelta가 NULL이 아닐 시 이전에 등록되었으나 아직 적용되지 않은 갑이 가져옴
  • delta가 NULL이고 olddelta가 NULL이 아니면 현재 조정중인 값을 가져올 수 있음
  • adjtime()에 적용할 조정값은 크기가 작아야 함(NTP가 이상적 활용 사례)
  • 에러 발생 시 -1을 반환, errno를 설정(EFAULT, EINVAL, EPERM)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <sys/timex.h>

struct timex {
  int mode;        /* 모드 셀렉터 */
  long offset;     /* 시간 오프셋(usec) */
  long freq;       /* 주파수 오프셋 (스케일된 ppm) */
  long maxerror;   /* 최대 에러 (usec) */
  long esterror;   /* 예상 에러(usec) */
  int status;      /* 시계 상태 */
  long constant;   /* PPL 시간 상수 */
  long precision;  /* 시계 정밀도 (usec) */
  long tolerance;  /* 시계 주파수 허용 오차 (ppm) */
  struct timeval time; /* 현재 시각 */
  long tick;       /* 틱 경과 시각(usec) */
};

int adjtimex(struct timex *adj);
  • adjtimex()를 호출하면 커널 시간관련 파라미터를 읽어 adj가 가리키는 timex 구조체에 기록(adjtime()보다 훨씬 강력하고 복잡, 리눅스 전용)
  • timex 구조체의 mode 필드에 따라 선택적으로 특정 파라메터를 추가적으로 설정, mode 필드는 플래그 비트단위 OR(책에 설명 있음)
  • mode가 0이면 아무런 값도 설정하지 않음(설정값을 가져옴), CAP_SYS_TIME 기능이 있는 사용자(root)만 0이 아닌 값을 설정 가능
  • 호출이 성공 시 현재 시계 상태 반환(TIME_OK/INS/DEL 등), 실패시 -1 반한 및 errno설정(EFAULT, EINVAL, EPERM) (책에 설명)
  • 자세한 설명은 RFC130을 참조

잠들기와 대기

1
2
3
#include <unistd.h>

unsigned int sleep(usigned int seconds)
  • sleep()은 seconds로 지정한 초 동안 프로세스를 잠재움
  • sleep()은 호출이 성공하면 0을 반환, 그렇지 않다면 잠들지 않은 초를 반환(시그널이 잠들기를 방해한 상황)

마이크로 초 해상도로 잠들기

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* BSD 버전 */
#include <unistd.h>

void usleep(unsigned long usec);

/* SUSv2 버전 */
#define _XOPEN_SOURCE 500
#include <unistd.h>

int usleep(usecond_t usec);
  • usleep() 호출이 성공하면 프로세스는 usec 동안 잠든다
  • BSD는 반환 값이 없으며, SUS버전은 성공시 0을 실패 시 -1 반환 및 errno설정

나노 초 해상도로 잠들기

1
2
3
4
#define _POSIX_C_SOURCE 199309
#include <time.h>

int nanosleep(const struct timespec *req, struct timespec *rem);
  • nanosleep() 함수는 호출이 성공하면 req로 명시한 시간 동안 프로세스가 잠들며 0을 반환
  • 에러가 발생하면 -1을 반환 및 errno를 적절한 값으로 설정 (EFAULT, EINVAL, EINTR)
  • 시그널이 잠들기를 방해하면 -1을 반환하고 errno를 EINTR로 설정, rem이 NULL이 아니라면 남은 시간을 rem에 저장(두번째 인자를 이용하면 인터럽트가 걸리더라도 계속 잠들게 프로그래밍 가능, 책에 예제)
  • nanosleep()은 sleep()/usleep()보다 장점, 왠만하면 이 함수를 사용하자
    • 높은 해상도 제공, POSIX.1b에서 표준화, 시그널로 구현되어 있지 않음

고급 잠들기 기법

1
2
3
#include <time.h>

int clock_nanosleep (clockid_t clock_id, int flag, const struct timespec *req, struct timespec *rem);
  • clock_nanosleep()은 nanosleep()과 유사하게 동작하는 고급인터페이스
  • clock_id는 측정 시계를 명시, CLOCK_PROCESS_CPUTIME_ID를 제외하고 유효(잠들면 프로세스 시간도 잠듬)
  • 어떤 시간까지 잠들어야 한다면 CLOCK_REALTIME, 상대 시간 동안 잠들어야 한다면 CLOCK_MONOTONIC이 제격
  • flag는 TIMER_ABSTIME이면 req로 지정된 값은 절대값으로 취급(잠재적 경쟁상태 해결, 책에 자세한 설명), 0이면 상대값

이식성을 고려한 잠들기

  • 2장에서 설명했던 select는 이식성이 높은 마이크로 초 단위로 잠들기 방법을 제공(책에 잘 설명되어 있다.)

시간 초과

  • 스케줄러 동작(커널이 정시에 깨웠지만 스케줄러가 다른 작업 선택 수행)이나 타이머 초과(타이머 정밀도가 요청 받은 시간 간격보다 떨어지는 경우) 때문에 요청보다 초과해서 잠들 수 있음
  • POSIX에서 제공하는 고해상도 시계와 더 높은 HZ 값을 사용하여 시간 초과 가능성을 최소로 줄임

잠들기 대안

  • 되도록이면 잠들기를 피하자, 1초 미만으로 잠든다면 크게 문제되지 않음
  • 이벤트를 기다리며 busy wait를 위해 잠들기로 도배한 코드는 나쁘다. 파일 디스크립터를 븍록해서 커널이 잠들기를 처리하고 프로세스를 깨우도록 하자

타이머

간단한 알람

1
2
3
#include <uinstd.h>

unsigned int alarm(unsigned int seconds);
  • alarm() 함수를 호출하면 실제 시간에서 seconds 초가 경과한 후에 호출한 프로세스에 SIGALRM 시그널을 전송하도록 예약
  • 앞서 걸어둔 시그널이 대기 중인 상황이라면 호출은 그 알람을 취소하고 새로 요청한 알람으로 대체 후 이전 알람에서 남아 있는 초 숫자 반환
  • second가 0이라면 이전 알람은 취소되나 새로운 알람은 안걸어 둠
  • SIGALRM 시그널 처리를 위한 시그널 핸들러 등록 필요

인터벌 타이머

1
2
3
4
5
6
7
8
9
#include <sys/time.h>

struct itiemerval {
  struct timeval it_interval; /* 다음 값 */
  struct timeval it_value; /* 현재 값 */
}

int gettimer(int which, struct itimerval *value);
int settimer(int which, const struct itimerval *value, struct itimervla *ovalue);
  • 인터벌타이머는 alarm()처럼 동작하나 옵션으로 자동 재장전 기능 제공하며 명확하게 구분된 세가지 모드 중 하나로 동작
    • ITIMER_REAL : 실제 시간을 측정, 지정된 실제 시간이 경과하면 프로세스에 시그널(SIGALRM) 전송
    • ITIMER_VIRTUAL : 프로세스 사용자 영역 코드가 수행되는 동안에만 타이머가 흐름, SIGVTALRM 시그널 전송
    • ITIMER_PROF : 프로세스가 실행 중이거나 커널이(시스템 콜 완료 등) 프로세스를 대신해서 실행 중인 경우 타이머 흘러감(SIGPROF 시그널 전송)
  • itimerval 구조체는 타이머 만료 시간과 만료 이후에 타이머를 다시 설정하기 위한 경과 시간을 사용자가 명시하도록 함
  • settimer()는 which가 가리키는 타이머에 it_value로 지정한 만료시간을 설정 지정 시간이 경과하면 it_interval로 지정한 시간으로 타이머 재설정
  • ovalue가 NULL이 아니면 which가 가리키는 인터벌 타이머에 설정된 이전 값 반환
  • 성공 하면 0을 반환, 실패 시 -1반환 및 errno를 설정(EFAULT, EINVAL)

고급 타이머

가장 강력한 타이머 인터페이스는 POSIX 시계에서 시작

타이머 생성하기

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <signal.h>
#include <time.h>

struct sigevent{
  union sigval sigev_value;
  int sigev_signo;
  int sigev_notify;
  void (*sigev_notify_function)(union sigval);
  pthread_attr_t *sigev_notify_attributes;
}

union sigval {
  int sival_int;
  void *sival_ptr;
}

int timer_create (clockid_t clockid, struct sigevent *evp, timer_t *tiemrid);
  • timer_create() 호출이 성공하면 clockid로 지정한 POSIX 시계와 관련된 새로운 타이머를 생헝하며 timerid에 새롭고 유일한 타이머 식별자를 저장한 다음 0을 반환
  • 호출이 실패하면 -1을 반환 및 errno 설정(EAGAIN, EINVAL, ENOTSUP)
  • evp 인자가 NULL이 아니라면 타이머가 만료될 때 발생하는 비동기식 통지를 지정, 프로세스는 sigev_notify로 타이머 만료시 동작을 명세
    • SIGEV_NONE : NULL 통지, 타이머 만료 시 아무일도 일어나지 않음
    • SIGEV_SIGNAL : 타이머가 만료될 때 커널은 프로세스에 sigev_signo로 지정한 시그널을 보냄
    • SIGEV_THREAD : 타이머가 만료될 때 커널은 새로운 스레드를 띄워서 sigev_notify_function 함수에 sigev_value를 인자로 넘겨서 실행, sigev_notify_attributes가 스레드 속성
  • evp 가 NULL이면 SIGEV_SIGNAL, SIGALRM, 타이머 id로 설정되서 타이머 만료를 알림

타이머 설정하기

1
2
3
#include <time.h>

int timer_settime(tiemr_t timerid, int flag, const struct itimerspec *value, struct itiemrspec *ovalue);
  • timer_settime() 호출 시 만료시간이 value이고 timerid로 지정한 타이머가 설정
  • flag는 TIMER_ABSTIME이면 req로 지정된 값은 절대값으로 취급(고급 잠들기 기법 참조)

타이머 만료 정보 얻기

1
2
3
#include <time.h>

int timer_gettime(timer_t timerid, struct itimerspec *value);
  • timer_gettime() 호출이 성공하면 timerid로 지정한 타이머의 만료 시간을 value에 저장
  • 호출 성공 시 0반환, 실패 시 -1 반환 및 errno설정(EFAULT, EINVAL)

타이머 초과 횟수 얻기

1
2
3
#include <time.h>

int timer_getoverrun (timer_t timerid);
  • 호출이 성공하면 타이머의 초기 만료 시점과 타이머 만료 상태를 프로세스에 통지한 시점 사이에 일어난 추가적인 타이머 만료 횟수를 반환
  • POSIX는 초과횟수가 DELAYTIMER_MAX 이상일 시 DELAYTIMER_MAX 반환(리눅스는 0부터 다시 시작)
  • 실패하면 -1 반환 및 errno를 설정(EINVAL : timerid가 유효하지 않음)

타이머 삭제하기

1
2
3
#include <time.h>

int timer_delete (timer_t timerid);
  • timer_delete() 호출이 성공하면 timerid 관련 타이머를 삭제하고 0을 반환
  • 실패하면 -1 반환 및 errno를 설정(EINVAL : timerid가 유효하지 않음)
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy