사전 CLOCK_MONOTONIC
개념

CLOCK_MONOTONIC

gabury1고친 사람 github-actions[bot]

CLOCK_MONOTONIC 은 걸린 시간을 재려고 운영체제에서 골라 읽는 시계입니다. 이 시계의 값은 앞으로만 갑니다. 그래서 두 번 읽어 뺀 차이가 그 사이에 흐른 시간이 됩니다. 대신 지금이 몇 시인지는 알려 주지 않습니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 운영체제가 들고 있는 여러 시계 가운데 걸린 시간만 재는 시계를 고르는 이름입니다. 「요청 하나에 0.22초 걸렸다」는 값이 이 시계를 두 번 읽어 뺀 결과입니다.

왜 이렇게 하나 — 달력 시각을 알려 주는 시계는 시각을 맞출 때마다 앞뒤로 뜁니다. 그 시계로 걸린 시간을 재면 음수가 나오기도 합니다. 이 시계는 누구도 값을 고칠 수 없어서 뒤로 가지 않습니다.

어떻게 도나

  1. 일을 시작하기 전에 이 시계를 읽습니다
  2. 일이 끝나면 한 번 더 읽습니다
  3. 뒤 값에서 앞 값을 빼면 걸린 시간입니다

대가 — 값 하나만으로는 아무 뜻이 없습니다. 기기를 다시 켜면 처음부터 다시 셉니다. 다른 서버의 값과 견줄 수도 없습니다. 리눅스에서는 기기가 잠든 동안 이 시계도 멈춰 있습니다.

상세

자동차 계기판의 주행 거리계를 떠올려 보세요. 숫자는 늘기만 합니다. 운전자가 되돌릴 수도 없습니다. 52,395km 라는 숫자만 보고는 차가 어디 있는지 모릅니다. 출발할 때와 도착할 때 숫자를 적어 두고 빼면 이번에 달린 거리가 나옵니다.

CLOCK_MONOTONIC 은 운영체제 안의 주행 거리계입니다. 거리 대신 시간을 셉니다.

운영체제의 시계 가운데 하나를 고르는 이름

응용 프로그램은 시간을 스스로 세지 않습니다. 운영체제의 중심부인 커널에 물어봅니다. 커널은 쓰임이 다른 시계를 여러 개 들고 있습니다. 어느 시계를 읽을지는 이름을 넘겨 고릅니다.

그 이름들은 POSIX(Portable Operating System Interface, 이식 가능한 운영체제 인터페이스)가 정해 둡니다. POSIX 는 리눅스와 macOS 같은 유닉스 계열 운영체제가 함께 지키는 규격입니다. CLOCK_MONOTONIC 은 그 이름 가운데 하나입니다.

C 프로그램에서는 이 이름을 상수로 씁니다. 상수는 C 헤더 time.h 에 정의되어 있습니다. 헤더는 함수와 상수의 선언을 모아 둔 파일입니다.

지금 쓰는 유닉스 계열 운영체제에는 이 시계가 사실상 다 있습니다. 어느 운영체제에나 늘 있는 짝은 달력 시각을 알려 주는 CLOCK_REALTIME 입니다.

이름 속 monotonic 의 뜻

monotonic 은 수학에서 온 말입니다. 우리말로는 「단조」입니다. 단조 증가하는 값은 줄지 않고 한 방향으로만 갑니다. 이 성질을 가진 시계를 통틀어 단조 시계라고 부릅니다. CLOCK_MONOTONIC 은 POSIX 가 그 시계에 붙인 이름입니다.

「줄지 않는다」는 「매번 는다」와 다릅니다. 연달아 읽은 두 값이 같을 수는 있다는 뜻입니다.

읽은 값이 가리키는 것

이 시계는 clock_gettime 이라는 호출로 읽습니다. 첫 인자로 시계 이름을 넘기면 초와 나노초가 돌아옵니다. 나노초는 10억분의 1초입니다.

clock_gettime 은 시스템 콜입니다. 시스템 콜은 프로그램이 커널에 일을 부탁하는 호출입니다. 시계는 커널이 들고 있으니 커널에 물어야 읽을 수 있습니다.

돌아온 값은 과거의 어떤 시점부터 흐른 시간입니다. 그 시점이 언제인지는 정해 두지 않았습니다. 기기가 켜진 때일 수도 있고 1970년 첫날일 수도 있습니다. 정해진 것은 기기가 켜진 뒤로 그 시점이 바뀌지 않는다는 것 하나입니다.

리눅스는 그 시점을 부팅한 때로 잡습니다. 그래서 리눅스에서 읽은 값은 기기가 켜진 뒤 흐른 초와 가깝습니다. 둘이 벌어지는 경우는 아래 「잠든 시간은 세지 않는다」 소절에서 봅니다.

파이썬의 time.clock_gettime 에 이 이름을 넘기면 C 의 clock_gettime 을 바로 부를 수 있습니다. 아래 코드는 요청 하나를 처리하는 함수 handle_request() 앞뒤로 이 시계를 읽습니다.

Python
import time
m = time.CLOCK_MONOTONIC
a = time.clock_gettime(m)  # 52395.722
handle_request()
b = time.clock_gettime(m)  # 52395.942
round(b - a, 3)            # 0.22

a 와 b 는 그 자체로 아무 시각도 가리키지 않습니다. 리눅스라면 부팅한 뒤 기기가 깨어 있던 시간이 14시간 33분쯤이라는 뜻일 뿐입니다. 쓸모가 있는 값은 마지막 줄의 0.22초 하나입니다.

누구도 값을 고칠 수 없는 시계

CLOCK_REALTIME 은 관리자나 시각 맞추기 프로그램이 고칠 수 있습니다. 고치는 순간 값이 몇 초씩 건너뛰거나 되돌아갑니다. 그 시계로 걸린 시간을 재면 음수가 나오는 까닭입니다.

CLOCK_MONOTONIC 은 고칠 방법이 없습니다. 시계 값을 바꾸는 호출 clock_settime 에 이 이름을 넘기면 호출이 실패하고 EINVAL(invalid argument, 잘못된 인자) 오류가 돌아옵니다.

이 약속 덕분에 두 번 읽는 사이에 누가 시각을 바꿔도 뺀 값이 흔들리지 않습니다. 달력 시각을 알려 주는 시계를 흔히 벽시계라고 부릅니다. 관리자가 벽시계를 한 시간 되돌려도 CLOCK_MONOTONIC 으로 잰 0.22초는 0.22초로 남습니다.

빠르기는 조금씩 바뀐다

컴퓨터의 시계는 수정 진동자의 떨림을 세서 갑니다. 수정 진동자는 전기를 걸면 일정한 빠르기로 떨리는 작은 부품입니다.

이 떨림의 빠르기는 기기마다 조금씩 다릅니다. 그래서 가만두면 시계가 점점 어긋납니다. 이렇게 어긋나는 현상을 클록 드리프트라고 합니다.

NTP(Network Time Protocol, 네트워크 시간 프로토콜)는 인터넷의 시각 서버에 맞춰 이 어긋남을 고치는 규약입니다. 차이가 작으면 시계를 한 번에 옮기지 않습니다. 조금 빠르게 또는 조금 느리게 돌려 따라잡습니다.

리눅스의 CLOCK_MONOTONIC 은 이 빠르기 조정을 받습니다. 값이 뛰지는 않지만 1초가 아주 조금 길거나 짧게 흐를 수 있습니다. NTP 조정 없이 하드웨어의 떨림만 세는 시계가 필요하면 리눅스 전용인 CLOCK_MONOTONIC_RAW 를 씁니다.

잠든 시간은 세지 않는다

리눅스의 CLOCK_MONOTONIC 은 기기가 절전 상태로 잠든 동안 멈춥니다. 노트북 뚜껑을 닫아 둔 시간은 이 시계에 안 잡힙니다. 잠든 시간까지 세는 시계는 CLOCK_BOOTTIME 으로 따로 있습니다. 두 시계는 이 한 가지만 다릅니다.

아래 그림은 부팅한 뒤 한 번 잠들었다 깬 기기에서 두 시계가 어떻게 세는지 보입니다.

flowchart TD
    A["부팅 · 두 시계가 함께 세기 시작"] --> B["깨어 있음 · 두 시계 모두 센다"]
    B --> C["잠듦 · CLOCK_BOOTTIME 만 센다"]
    C --> D["다시 깨어남 · 두 시계 모두 센다"]
    D --> E["지금 읽은 값 · CLOCK_MONOTONIC 52395초 · CLOCK_BOOTTIME 72691초"]

그림의 52395초는 앞의 파이썬 코드에서 읽은 그 값입니다. 같은 순간에 CLOCK_BOOTTIME 은 72691초를 가리킵니다. 두 값은 20296초쯤 벌어졌습니다. 기기가 5시간 38분쯤 잠들어 있었다는 뜻입니다.

그래서 「30분 뒤 만료」를 CLOCK_MONOTONIC 으로 재면 잠든 5시간 38분은 그 30분에 들어가지 않습니다.

잠든 시간을 셀지 말지는 POSIX 가 정하지 않았습니다. 그래서 이 대목은 운영체제마다 갈립니다. 리눅스 밖에서는 같은 이름의 시계가 잠든 시간을 셀 수도 있습니다.

연달아 읽으면 같은 값이 나올 수 있다

뒤로 가지 않는다는 것이 매번 는다는 뜻은 아닙니다. 아주 짧은 간격으로 두 번 읽으면 두 값이 같을 수 있습니다. 하드웨어에 따라 시계가 한 칸 넘어가기 전에 두 번째 읽기가 끝나기 때문입니다.

그러면 뺀 값이 0 이 됩니다. 걸린 시간으로 나누어 초당 처리량을 구하는 코드는 0 으로 나누게 됩니다. 그래서 아주 짧은 일은 여러 번 돌려 전체 시간을 잰 뒤 횟수로 나눕니다.

리눅스가 함께 내주는 형제 시계

리눅스에는 CLOCK_MONOTONIC 과 닮은 시계가 셋 더 있습니다. 넷 모두 뒤로 가지 않는다는 약속은 같습니다. 빠르기 조정을 받는지와 잠든 시간을 세는지가 다릅니다.

시계 빠르기 조정 잠든 시간 고르는 때
CLOCK_MONOTONIC 받는다 안 센다 걸린 시간을 재는 기본
CLOCK_MONOTONIC_RAW 안 받는다 안 센다 NTP 조정 없이 하드웨어만 따를 때
CLOCK_MONOTONIC_COARSE 받는다 안 센다 덜 촘촘해도 빨리 읽고 싶을 때
CLOCK_BOOTTIME 받는다 센다 잠든 동안에도 흘러야 하는 기한

표에서 CLOCK_MONOTONIC 을 뺀 세 시계는 POSIX 가 아니라 리눅스가 따로 더한 이름입니다. 다른 운영체제에는 이 이름이 없거나 뜻이 다를 수 있습니다.

이 시계로 기다리기

프로그램은 시계를 읽기만 하지 않고 시계에 맞춰 기다리기도 합니다. 「5초 뒤에 깨워 달라」가 그런 예입니다. 기다리는 함수 여럿이 어느 시계를 기준으로 삼을지 고르게 해 둡니다.

함수 무엇을 기다리나 CLOCK_MONOTONIC 을 고르는 법
clock_nanosleep 정해 둔 시간 동안 잠든다 첫 인자로 넘긴다
timerfd_create 타이머가 울리기를 파일 읽기처럼 기다린다 첫 인자로 넘긴다
pthread_cond_timedwait 다른 스레드의 신호를 기한까지 기다린다 pthread_condattr_setclock 으로 미리 정한다

맨 아래 줄은 조건 변수를 쓰는 함수입니다. 조건 변수는 한 스레드가 다른 스레드의 신호를 기다리는 장치입니다. 이 함수는 기한을 「5초」라는 길이로 받지 않습니다. 「지금 시각 + 5초」라는 시계 값으로 받습니다. 그리고 시계가 그 값에 이를 때까지 기다립니다.

그 시계는 따로 정하지 않으면 CLOCK_REALTIME 입니다. 기다리는 도중에 벽시계가 한 시간 뒤로 가면, 시계가 그 값에 이르기까지 한 시간이 더 걸립니다. 5초짜리 기다림이 한 시간 5초로 늘어납니다.

기한을 CLOCK_MONOTONIC 으로 잡으면 이 시계는 뒤로 가지 않으니 5초 뒤에 기한이 옵니다. 그래서 타임아웃처럼 길이가 중요한 기다림에는 이 시계를 고릅니다.

언어별로 이 시계를 읽는 함수

백엔드 코드는 clock_gettime 을 직접 부를 일이 드뭅니다. 언어마다 걸린 시간을 재는 함수를 따로 둡니다. 리눅스에서는 아래 함수들이 CLOCK_MONOTONIC 을 읽습니다.

언어 함수
자바 System.nanoTime()
파이썬 time.monotonic()
Go time.Now() 가 함께 담는 단조 값 · time.Since()
Node.js process.hrtime.bigint()

Go 의 time.Now() 가 돌려주는 값에는 벽시계 시각과 CLOCK_MONOTONIC 값이 같이 들어 있습니다. time.Since() 는 둘 가운데 CLOCK_MONOTONIC 값으로 뺍니다.

이 함수들이 돌려주는 값도 두 번 읽어 뺀 차이로만 씁니다. 로그에 찍을 시각이나 데이터베이스에 넣을 만료 시각은 자바 System.currentTimeMillis() · 파이썬 time.time() 처럼 CLOCK_REALTIME 을 읽는 함수로 얻습니다.

관련 항목

CLOCK_MONOTONIC 과 나란히 선 운영체제 시계

CLOCK_REALTIME · CLOCK_BOOTTIME · CLOCK_MONOTONIC_RAW · CLOCK_MONOTONIC_COARSE · CLOCK_TAI · CLOCK_PROCESS_CPUTIME_ID · CLOCK_THREAD_CPUTIME_ID

CLOCK_MONOTONIC 을 읽고 기준으로 삼는 호출

clock_gettime · clock_getres · clock_settime · clock_nanosleep · timerfd_create · timer_create · pthread_cond_timedwait · pthread_condattr_setclock · vDSO

CLOCK_MONOTONIC 값을 담는 타입과 단위

struct timespec · clockid_t · time.h · 나노초

CLOCK_MONOTONIC 이 속하는 상위 개념

단조 시계 · 시간과 시계 · 벽시계 · 시스템 시계 · 타임스탬프

CLOCK_MONOTONIC 의 빠르기와 흐름을 흔드는 원인

NTP · 클록 드리프트 · 수정 진동자 · adjtimex · chrony · 절전 모드

CLOCK_MONOTONIC 을 읽는 언어별 함수

System.nanoTime · time.monotonic · time.Since · process.hrtime

CLOCK_MONOTONIC 으로 기한과 간격을 재는 처리

타임아웃 · 조건 변수 · 지수 백오프 · 속도 제한 · 하트비트 · 리스 · 벤치마크

CLOCK_MONOTONIC 을 정하고 내주는 규격과 소프트웨어

POSIX · 리눅스 · 유닉스 · 커널 · 운영체제 · 시스템 콜

다른 이름: clock_monotonic · MONOTONIC 시계