CLOCK_REALTIME
고친 사람 github-actions[bot]
CLOCK_REALTIME 은 프로그램이 운영체제에게 지금 몇 시인지 물을 때 고르는 시계의 이름입니다. 이 시계는 달력과 벽시계가 가리키는 시각을 알려 줍니다. 로그에 찍히는 시각도 대개 이 시계에서 옵니다. 대신 이 시계는 누가 시각을 고칠 때마다 앞뒤로 뜁니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 운영체제가 들고 있는 여러 시계 가운데 「지금 날짜와 시각」을 알려 주는 시계를 고르는 이름입니다. 로그 한 줄 앞에 붙는 시각이 이 시계를 읽은 값입니다.
왜 이렇게 하나 — 사람과 컴퓨터, 컴퓨터와 컴퓨터가 같은 시각을 말하려면 모두가 합의한 기준 시각이 있어야 합니다. 기기가 켜진 뒤 흐른 시간만으로는 「몇 시」를 말할 수 없습니다.
어떻게 도나
- 프로그램이 이 이름을 넘겨 운영체제의 시계를 읽습니다
- 1970년 첫날 0시부터 흐른 초와 그 아래 나노초가 돌아옵니다
- 운영체제는 인터넷의 시각 서버에 맞춰 이 시계를 계속 고칩니다
대가 — 고치는 순간 값이 뜁니다. 두 번 읽어 빼면 걸린 시간이 음수로 나오기도 합니다. 걸린 시간은 앞으로만 가는 다른 시계로 잽니다.
상세
사무실 벽에 걸린 시계를 떠올려 보세요. 모두가 그 시계를 보고 회의 시각을 맞춥니다. 시계가 늦으면 누군가 의자에 올라가 바늘을 돌려 맞춥니다. 바늘을 돌린 순간 시계는 몇 분을 건너뛰거나 되돌아갑니다.
CLOCK_REALTIME 은 운영체제 안에 걸린 이 벽시계입니다. 이 절은 먼저 이 이름이 무엇을 고르는지, 왜 이런 시계가 따로 있어야 하는지를 봅니다. 이어서 읽는 코드와 돌아오는 값, 값이 뛰는 까닭을 봅니다. 끝으로 이 시계를 쓰면 안 되는 일과 백엔드 언어에서 이 시계를 읽는 함수를 봅니다.
운영체제가 내주는 시계를 고르는 이름
응용 프로그램은 시각을 스스로 세지 않습니다. 운영체제의 중심부인 커널에 물어봅니다. 커널은 쓰임이 다른 시계를 여러 개 들고 있습니다.
어느 시계를 읽을지는 이름으로 고릅니다. POSIX(Portable Operating System Interface, 이식 가능한 운영체제 인터페이스)는 리눅스와 macOS 같은 유닉스 계열 운영체제가 함께 지키는 규격입니다. 이 규격이 시계마다 붙여 둔 이름 가운데 하나가 CLOCK_REALTIME 입니다.
POSIX 를 따르는 시스템이라면 이 시계는 반드시 있습니다. 다른 시계는 있을 수도 없을 수도 있습니다. 어느 시스템에서나 기댈 수 있는 기본 시계라는 뜻입니다.
이름 속 realtime 의 뜻
realtime 은 여기서 「실제 세계의 시각」이라는 뜻입니다. 사람이 달력과 손목시계로 쓰는 그 시각입니다. 영어권에서는 이 시각을 흔히 wall-clock time이라고 부릅니다.
정해진 시간 안에 반드시 응답해야 하는 실시간 운영체제의 「실시간」과는 관계가 없습니다. CLOCK_REALTIME 을 읽는다고 프로그램이 더 빨리 돌거나 마감을 지켜 주지 않습니다.
이 시계가 따로 있어야 하는 까닭
로그에 찍는 시각과 쿠폰의 만료 시각은 기기 밖으로 나갑니다. 다른 서버가 읽습니다. 사람도 읽습니다. 그러려면 모든 기기가 같은 기준으로 센 시각이어야 합니다.
기기가 켜진 뒤 흐른 시간으로는 이 일을 못 합니다. 서버마다 켜진 때가 달라서 같은 순간에도 값이 다릅니다. 「지금 몇 시」를 말하려면 온 세계가 합의한 시각을 따르는 시계가 하나 있어야 합니다. 그 시계가 CLOCK_REALTIME 입니다.
읽는 방법과 돌아오는 값
코드에서 CLOCK_REALTIME 은 상수 하나입니다. C 프로그램이 불러 쓰는 헤더 파일 time.h 에 들어 있습니다. 이 상수를 넘겨서 시계를 고릅니다.
이 시계는 clock_gettime 이라는 시스템 콜로 읽습니다. 시스템 콜은 프로그램이 커널에 일을 부탁하는 호출입니다. 첫 인자로 시계 이름을 넘깁니다. 둘째 인자로 값을 받을 구조체를 넘깁니다.
값은 struct timespec 이라는 구조체에 담겨 돌아옵니다. 필드가 둘입니다. tv_sec 에 초가, tv_nsec 에 1초보다 작은 부분이 나노초로 들어갑니다. 나노초는 10억분의 1초입니다.
아래는 어느 한 순간에 이 시계를 읽었을 때 두 필드에 든 값의 예입니다.
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec; // 1000000000
ts.tv_nsec; // 250000000
초 필드는 에포크부터 흐른 초의 개수입니다. 에포크는 1970년 1월 1일 0시 0분 0초입니다. 이 시각의 기준은 UTC(Coordinated Universal Time, 협정 세계시)입니다. 협정 세계시는 세계가 시각을 맞출 때 기준으로 삼는 시각입니다.
초 필드 1000000000 은 협정 세계시로 2001년 9월 9일 01:46:40 입니다. 나노초 필드 250000000 은 0.25초이니 그 순간에서 4분의 1초가 더 지난 때입니다.
C 의 time() 과 gettimeofday 도 같은 시각을 돌려줍니다. 앞의 것은 초까지, 뒤의 것은 마이크로초까지 담습니다. 마이크로초는 100만분의 1초입니다. 나노초까지 필요하면 clock_gettime 을 씁니다.
값이 앞뒤로 뛰는 까닭
이 소절은 CLOCK_REALTIME 의 값이 왜 고쳐지는지, 고칠 때 값이 어떻게 변하는지를 봅니다.
컴퓨터의 시계는 수정 진동자의 떨림을 세서 갑니다. 수정 진동자는 전기를 흘리면 거의 일정한 빠르기로 떨리는 작은 부품입니다. 떨림을 정해진 수만큼 세면 1초가 지난 것으로 칩니다.
그런데 이 떨림의 빠르기가 기기마다 조금씩 다릅니다. 가만두면 하루에 몇 초까지 어긋나기도 합니다. 이렇게 시계가 조금씩 벌어지는 현상을 클록 드리프트라고 합니다.
그래서 운영체제는 CLOCK_REALTIME 을 계속 고칩니다. 맞추는 기준은 인터넷의 시각 서버입니다. NTP(Network Time Protocol, 네트워크 시간 프로토콜)는 이 서버와 시각을 맞추는 규약입니다.
이 규약으로 시각 서버에 늘 물어보는 프로그램을 NTP 데몬이라고 부릅니다. 데몬은 뒤에서 멈추지 않고 도는 프로그램입니다.
NTP 데몬은 이 시계를 시각 서버와 견줘 차이를 잽니다. 그리고 그 차이만큼 시계를 고쳐 달라고 커널에 부탁합니다. 값을 실제로 바꾸는 쪽은 커널입니다.
고치는 방법은 둘입니다. 시계가 얼마나 벌어졌느냐에 따라 고릅니다.
| 고치는 방법 | 언제 쓰나 | 값이 어떻게 변하나 |
|---|---|---|
| 조금씩 빠르게·느리게 돌리기 | 차이가 작을 때 NTP 데몬이 | 뒤로 가지는 않고 1초가 조금 길거나 짧게 흐른다 |
| 한 번에 옮기기 | 차이가 클 때 NTP 데몬이, 또는 관리자가 손으로 | 몇 초든 한순간에 건너뛰거나 되돌아간다 |
위 줄은 값이 매끄럽게 이어집니다. 아래 줄은 값이 끊깁니다. 어느 쪽이든 시각을 고치려면 관리자 권한이 있어야 합니다.
아래 그림은 1초마다 이 시계를 읽는 동안 3초째에 누군가 시각을 5초 되돌린 경우입니다.
xychart-beta
title "1초마다 읽은 CLOCK_REALTIME 초 값"
x-axis "실제로 흐른 초" [0, 1, 2, 3, 4, 5]
y-axis "읽은 초 값" 995 --> 1005
bar [1000, 1001, 1002, 998, 999, 1000]
세계는 5초 흘렀습니다. 그런데 읽은 값은 1000 에서 시작해 1000 으로 끝났습니다. 3초째에 1003 이 될 값이 998 로 떨어졌기 때문입니다.
윤초도 값을 흔듭니다. 윤초는 지구 자전에 시각을 맞추려고 가끔 끼워 넣는 1초입니다. CLOCK_REALTIME 은 하루를 언제나 86400초로 치고 윤초를 세지 않습니다.
윤초가 끼어든 날에는 실제 세계에 1초가 더 흐릅니다. 이 시계는 그 1초를 세지 않으니 그대로 두면 실제 시각보다 1초 앞서게 됩니다.
그래서 윤초가 들어가는 날에는 이 시계를 1초 뒤로 맞춥니다. 1초를 한 번에 되돌리기도 합니다. 몇 시간에 걸쳐 조금씩 늦추기도 합니다. 앞의 방법이면 같은 초 값이 두 번 나옵니다.
걸린 시간을 재면 안 되는 까닭
앞 그림의 0초와 5초를 빼면 0초가 걸린 것으로 나옵니다. 2초와 3초를 빼면 -4초가 나옵니다. 요청 하나가 몇 초 걸렸는지 이 시계로 재면 이런 값이 로그에 남습니다.
걸린 시간은 단조 시계로 잽니다. 단조 시계는 누가 시각을 고쳐도 뒤로 가지 않는 시계입니다. POSIX 에서는 그 이름이 CLOCK_MONOTONIC 입니다. 같은 clock_gettime 에 이 이름을 넘기면 됩니다.
고르는 기준은 한 줄입니다. 두 시점 사이의 거리를 재면 단조 시계입니다. 사람이나 다른 기기에 보여 줄 시각이면 CLOCK_REALTIME 입니다.
| 하려는 일 | 고를 시계 |
|---|---|
| 로그 한 줄에 타임스탬프를 찍는다 | CLOCK_REALTIME |
| 쿠폰 만료 시각을 데이터베이스에 넣는다 | CLOCK_REALTIME |
| 함수 하나가 몇 초 걸렸는지 잰다 | CLOCK_MONOTONIC |
| 타임아웃까지 얼마나 남았는지 센다 | CLOCK_MONOTONIC |
위 두 줄은 값이 기기 밖으로 나가거나 오래 남는 일입니다. 아래 두 줄은 한 기기 안에서 두 시점을 빼는 일입니다.
이 시계로 기다릴 때
프로그램은 시계를 읽기만 하지 않고 시계에 맞춰 기다리기도 합니다. 기다리는 방식은 둘입니다. 「지금부터 5초」처럼 길이로 기다리는 방식과 「오전 9시까지」처럼 시각으로 기다리는 방식입니다.
길이로 기다리면 CLOCK_REALTIME 이 고쳐져도 영향을 받지 않습니다. 5초를 기다리기로 했으면 누가 시각을 되돌려도 5초 뒤에 깹니다.
시각으로 기다리면 고친 시각을 따라갑니다. 시계가 오전 9시를 넘어가게 앞으로 옮겨지면 바로 깹니다. 뒤로 옮겨지면 그만큼 더 기다립니다. 「매일 9시에 보고서를 보낸다」처럼 달력 시각에 묶인 일에는 이쪽이 맞습니다.
C 에서는 clock_nanosleep 에 시계 이름과 함께 어느 방식으로 기다릴지를 넘깁니다.
타임아웃은 이 둘 사이에서 자주 탈이 납니다. 「지금부터 5초」를 「지금 시각 + 5초」라는 마감 시각으로 바꿔 넘기는 코드가 많습니다. 이 마감을 CLOCK_REALTIME 으로 세면 누가 시각을 1시간 되돌릴 때 1시간 5초를 기다립니다.
그래서 마감 시각으로 기다리는 타임아웃은 CLOCK_MONOTONIC 으로 셉니다. 단조 시계의 지금 값에 5초를 더한 값을 마감으로 삼습니다. 단조 시계는 누가 시각을 고쳐도 뒤로 가지 않으니 5초 뒤에 깹니다.
백엔드 언어에서 이 시계를 읽는 함수
백엔드 코드는 clock_gettime 을 직접 부를 일이 드뭅니다. 언어마다 벽시계를 읽는 함수와 단조 시계를 읽는 함수를 따로 둡니다. 리눅스에서 벽시계 쪽 함수들이 읽는 시계가 CLOCK_REALTIME 입니다.
| 언어 | 벽시계를 읽는 함수 | 단조 시계를 읽는 함수 |
|---|---|---|
| 자바 | System.currentTimeMillis() · Instant.now() |
System.nanoTime() |
| 파이썬 | time.time() |
time.monotonic() |
| 자바스크립트 | Date.now() |
performance.now() |
표의 가운데 열 값으로 두 시점을 빼면 앞 소절과 같은 문제가 납니다. 걸린 시간을 재는 코드라면 오른쪽 열 함수를 씁니다.
관련 항목
CLOCK_REALTIME 과 맞세워지는 시계
CLOCK_MONOTONIC · CLOCK_BOOTTIME · CLOCK_TAI · CLOCK_REALTIME_COARSE · CLOCK_PROCESS_CPUTIME_ID · 단조 시계
CLOCK_REALTIME 을 읽고 고치는 호출
clock_gettime · clock_settime · clock_getres · clock_nanosleep · gettimeofday · settimeofday · time(2) · adjtimex · vDSO
CLOCK_REALTIME 이 돌려주는 값의 단위와 타입
struct timespec · struct timeval · time_t · 나노초 · 유닉스 시간
CLOCK_REALTIME 이 세는 기준 시각
에포크 · UTC · 윤초 · TAI · 시간대 · GPS 시각
CLOCK_REALTIME 값을 흔드는 원인과 보정
NTP · 클록 드리프트 · 클록 스큐 · 클럭 스텝 · 윤초 문지르기 · 수정 진동자 · chrony · ntpd
CLOCK_REALTIME 을 정하고 내주는 규격과 소프트웨어
POSIX · time.h · 운영체제 · 커널 · 시스템 콜 · 리눅스 · 유닉스
CLOCK_REALTIME 을 읽는 언어별 함수
System.currentTimeMillis · java.time.Instant · System.nanoTime · time.time · time.monotonic · Date.now · performance.now
CLOCK_REALTIME 이 속하는 상위 분류
벽시계 · 시간과 시계 · 시스템 클록 · POSIX 시계
CLOCK_REALTIME 과 이름이 겹치는 개념
실시간 운영체제 · 실시간 시스템 · 실시간 시계 · 실시간 스케줄링
CLOCK_REALTIME 을 잘못 써서 터지는 문제
타임아웃 · 타임스탬프 · 2038 문제 · 로그 · 분산 시스템 · 논리 시계 · pthread_cond_timedwait
다른 이름: clock_realtime · REALTIME 시계