사전 시스템 시계
개념

시스템 시계

gabury1고친 사람 github-actions[bot]

시스템 시계는 컴퓨터가 지금 몇 시인지 세어서 프로그램에 알려 줍니다. 운영체제는 이 시계를 하나만 둡니다. 그래서 한 컴퓨터 안의 프로그램들은 모두 같은 시각을 봅니다. 하드웨어 쪽에서는 같은 이름을 회로를 움직이는 클록 신호에 쓰기도 합니다(맨 끝 절).

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 컴퓨터 한 대가 함께 보는 시계입니다. 로그 한 줄 앞에 붙는 시각이 이 시계를 읽은 값입니다.

왜 이렇게 하나 — 프로그램마다 시각을 따로 세면 같은 순간을 서로 다르게 적습니다. 운영체제는 시계 하나를 두고 모두에게 같은 값을 나눠 줍니다. 그러면 기록끼리 앞뒤를 맞춰 볼 수 있습니다.

어떻게 도나

  1. 켤 때 메인보드의 배터리 시계에서 시각을 읽어 옵니다
  2. 그 뒤로는 운영체제가 흐른 시간을 세어 값을 올립니다
  3. 틈틈이 인터넷의 시각 서버와 견주어 틀어진 만큼 고칩니다

대가 — 틀어진 차이가 크거나 사람이 손으로 바꾸면 값이 앞뒤로 뜁니다. 그래서 걸린 시간은 앞으로만 가는 다른 시계로 잽니다. 코드가 이 시계를 몰래 읽으면 돌릴 때마다 결과가 달라집니다. 그런 코드는 테스트하기도 어렵습니다.

상세

회사 출입구에 출퇴근 기록기가 한 대 있습니다. 직원은 모두 이 기계에 카드를 찍습니다. 저마다 손목시계를 보고 시각을 적었다면 같은 순간이 사람마다 다르게 남았을 겁니다. 기록기가 한 대라서 누가 먼저 왔는지를 따질 수 있습니다.

시스템 시계는 컴퓨터 한 대에 놓인 이 기록기입니다. 이 절은 먼저 운영체제가 이 시계를 어떤 값으로 두는지와 무엇으로 세는지를 봅니다. 이어서 전원이 꺼진 동안과 시각이 틀어질 때를 봅니다. 끝으로 프로그램이 이 시계를 읽는 방법과 몰래 읽을 때 생기는 문제, 같은 이름의 다른 뜻을 봅니다.

커널이 관리하는 시각 값

응용 프로그램은 시각을 스스로 세지 않습니다. 운영체제의 중심부인 커널에 물어봅니다. 커널은 지금 시각을 메모리 안에 숫자 하나로 둡니다. 프로그램이 물으면 그 값을 내줍니다.

이 숫자가 시스템 시계입니다. 하드웨어 부품이 아니라 커널이 관리하는 값입니다. 그래서 소프트웨어 시계라고도 부릅니다.

값은 대개 기준 시각부터 흐른 초의 개수입니다. 이 기준 시각을 에포크라고 부릅니다. 유닉스 계열 운영체제는 1970년 1월 1일 0시를 에포크로 씁니다.

에포크는 UTC(Coordinated Universal Time, 협정 세계시)로 셉니다. 협정 세계시는 세계가 시각을 맞출 때 기준으로 삼는 시각입니다. 서울의 오후 3시와 협정 세계시 오전 6시는 같은 순간이라 시스템 시계의 값도 같습니다.

지역 시각은 이 값에 들어 있지 않습니다. 사람에게 보여 줄 때 시간대를 적용해 바꿉니다. 그래서 서버의 시간대 설정을 바꿔도 시스템 시계의 값은 움직이지 않습니다.

흐른 시간을 세는 방법

커널은 시각 값을 저절로 얻지 않습니다. 하드웨어가 일정한 간격으로 보내는 신호를 세서 값을 올립니다. 그 간격을 만드는 원천은 대개 수정 진동자입니다.

수정 진동자는 전기를 흘리면 거의 일정한 빠르기로 떨리는 작은 부품입니다. 떨림이 정해진 수만큼 쌓이면 1초가 지난 것으로 칩니다.

이 떨림을 세는 부품이 컴퓨터 안의 타이머입니다. 타이머는 떨림이 정해진 수만큼 쌓일 때마다 커널에 신호를 보냅니다. 앞에서 말한 일정한 간격의 신호가 이것입니다.

이 신호는 인터럽트로 전해집니다. 인터럽트는 하드웨어가 CPU(Central Processing Unit, 중앙 처리 장치)에게 하던 일을 멈추고 이것부터 처리하라고 보내는 신호입니다. 타이머가 거는 인터럽트를 타이머 인터럽트라고 합니다. 커널은 타이머 인터럽트를 받을 때마다 시스템 시계를 그 간격만큼 올립니다.

타이머 인터럽트 간격보다 잘게 알아야 할 때도 있습니다. 그때는 쉬지 않고 올라가는 하드웨어 카운터를 직접 읽습니다. 마지막 인터럽트 뒤로 흐른 시간을 카운터로 재서 값에 더합니다.

전원이 꺼진 동안

시스템 시계는 메모리 안의 값이라 전원이 꺼지면 사라집니다. 다시 켰을 때 지금 몇 시인지 알려면 꺼진 동안에도 도는 시계가 따로 있어야 합니다.

그 시계가 메인보드에 붙은 실시간 시계입니다. 줄여서 RTC(Real-Time Clock)라고 부릅니다. 작은 배터리로 돌아서 컴퓨터가 꺼져 있어도 멈추지 않습니다.

운영체제는 켤 때 실시간 시계를 한 번 읽어 시스템 시계의 첫 값으로 삼습니다. 그 뒤로 두 시계는 따로 갑니다. 끌 때 시스템 시계의 값을 실시간 시계에 되돌려 적는 운영체제도 많습니다.

틀어진 시각을 고치는 일

수정 진동자가 떨리는 빠르기는 기기마다 조금씩 다릅니다. 온도에 따라서도 변합니다. 그래서 시스템 시계는 가만두면 바깥 세상의 시각에서 조금씩 벌어집니다. 이렇게 벌어지는 현상을 클록 드리프트라고 부릅니다.

벌어진 만큼은 바깥의 기준 시각에 맞춰 고칩니다. 흔히 쓰는 방법이 NTP(Network Time Protocol, 네트워크 시간 프로토콜)입니다. 이 규약으로 인터넷의 시각 서버에 지금 시각을 묻습니다. 그 답과 견주어 시스템 시계를 고칩니다.

고치는 방법은 둘입니다. 차이가 작으면 시계를 조금 빠르게 또는 느리게 돌려 천천히 따라잡습니다. 차이가 크면 값을 한 번에 옮깁니다. 관리자가 손으로 시각을 바꿀 때도 값이 한 번에 옮겨집니다.

한 번에 옮기면 시스템 시계는 앞으로 건너뛰거나 뒤로 돌아갑니다. 그래서 이 시계를 두 번 읽어 뺀 차이는 걸린 시간과 다를 수 있습니다. 음수가 나오기도 합니다.

지금까지 본 셋을 한 그림에 모으면 아래와 같습니다. 시스템 시계는 첫 값을 실시간 시계에서 받습니다. 타이머 인터럽트는 타이머에서, 고칠 양은 NTP 에서 받습니다.

flowchart TD
    RTC["실시간 시계 · 배터리로 돈다"] -->|켤 때 한 번 읽는다| SYS["시스템 시계 · 커널이 관리하는 값"]
    TMR["타이머 · 수정 진동자"] -->|타이머 인터럽트를 건다| SYS
    NTP["NTP 시각 서버"] -->|틀어진 만큼 고친다| SYS
    SYS -->|시각을 읽어 간다| APP["프로그램"]

시스템 시계와 나란히 내주는 단조 시계

프로그램은 시스템 시계를 시스템 콜로 읽습니다. 시스템 콜은 프로그램이 커널에 일을 부탁하는 호출입니다.

POSIX(Portable Operating System Interface, 이식 가능한 운영체제 인터페이스)는 리눅스와 macOS 같은 유닉스 계열 운영체제가 함께 지키는 규격입니다. 운영체제마다 호출이 다르면 프로그램을 옮길 때마다 고쳐야 합니다. 이 규격이 호출의 이름과 뜻을 맞춰 둡니다.

이 규격에서 시계를 읽는 호출은 clock_gettime 입니다. 부를 때 읽을 시계의 이름을 함께 넘깁니다. 운영체제가 시스템 시계 말고 다른 시계도 내주기 때문입니다.

백엔드에서 자주 만나는 이름은 둘입니다. CLOCK_REALTIME 이 시스템 시계입니다. CLOCK_MONOTONIC 은 걸린 시간을 재려고 따로 내주는 단조 시계입니다.

둘 다 같은 타이머 인터럽트를 셉니다. 누가 시각을 한 번에 옮길 때 움직임이 다릅니다.

시계 이름 알려 주는 것 시각을 한 번에 옮기면
CLOCK_REALTIME 에포크부터 흐른 초, 곧 지금 몇 시인지 같이 건너뛰거나 되돌아간다
CLOCK_MONOTONIC 정해지지 않은 어느 시점부터 흐른 시간 영향을 받지 않고 앞으로만 간다

CLOCK_REALTIME 은 사람이 읽는 시각을 알려 주는 시계라서 벽시계라고도 부릅니다. CLOCK_MONOTONIC 은 세기 시작한 시점이 정해져 있지 않습니다. 값 하나만으로는 아무 뜻이 없습니다. 두 번 읽어 뺀 차이만 걸린 시간이 됩니다.

언어에서 시스템 시계를 읽는 함수

백엔드 코드는 clock_gettime 을 직접 부를 일이 드뭅니다. 언어의 표준 라이브러리가 감싸 둔 함수를 부릅니다. 아래 함수들은 모두 시스템 시계를 읽습니다.

언어 시스템 시계를 읽는 함수
자바 System.currentTimeMillis() · Instant.now()
파이썬 time.time() · datetime.now()
자바스크립트 Date.now()
C++ std::chrono::system_clock::now()

C++ 는 이름에 system_clock 을 씁니다. 앞 절의 단조 시계에 해당하는 것은 steady_clock 입니다. 걸린 시간을 잴 때는 이쪽을 씁니다.

코드가 몰래 시계를 읽을 때

앞 표의 함수는 부를 때마다 다른 값을 돌려줍니다. 함수 안에서 이 값을 읽으면 그 함수의 결과도 부를 때마다 달라질 수 있습니다.

같은 입력을 넣으면 언제나 같은 결과가 나오는 성질을 결정성이라고 부릅니다. 함수가 안에서 시스템 시계를 읽으면 이 성질이 깨집니다. 인자가 같아도 부른 시각에 따라 결과가 달라지기 때문입니다.

이렇게 인자로 받지 않았는데 결과를 바꾸는 값을 암묵적 입력이라고 부릅니다. 시스템 시계는 가장 흔한 암묵적 입력입니다.

이런 함수는 테스트하기 어렵습니다. 「만료 시각이 지났으면 거절한다」를 확인하려면 테스트가 그 시각이 지나기를 기다려야 합니다. 자정이나 월말에만 깨지는 테스트도 여기서 생깁니다.

흔한 해법은 시계를 인자로 받는 것입니다. 운영 코드에는 시스템 시계를 넘깁니다. 테스트에는 언제나 같은 시각을 돌려주는 가짜 시계를 넘깁니다. 자바는 이 용도로 Clock 이라는 타입을 둡니다.

아래 코드는 고정된 시각을 돌려주는 시계를 만듭니다. 그다음 인자 없이 한 번, 그 시계를 넘겨 한 번 지금 시각을 읽습니다.

Java
Clock fixed = Clock.fixed(
    Instant.parse("2026-01-01T00:00:00Z"),
    ZoneOffset.UTC);

Instant.now();       // 읽을 때마다 바뀜
Instant.now(fixed);  // 2026-01-01T00:00:00Z

인자 없는 Instant.now() 는 시스템 시계를 읽습니다. 시계를 넘긴 쪽은 언제 불러도 같은 값을 줍니다. 운영 코드에서는 Clock.systemUTC() 를 넘기면 시스템 시계를 읽는 시계가 됩니다.

같은 이름의 다른 뜻

하드웨어 설계에서 시스템 클록은 다른 것을 가리킵니다. CPU 와 메모리 같은 회로가 발맞춰 움직이도록 일정한 간격으로 켜졌다 꺼지는 전기 신호입니다. 이런 신호를 클록 신호라고 부릅니다.

이 신호가 1초에 몇 번 뛰는지를 클록 주파수라고 부릅니다. 이 값은 회로가 얼마나 빨리 도는지를 정할 뿐 몇 시인지와는 관계가 없습니다. 임베디드 보드나 CPU 설명서에서 시스템 클록을 만나면 대개 이 뜻입니다.

관련 항목

시스템 시계에서 갈라져 나오는 시계

CLOCK_REALTIME · CLOCK_MONOTONIC · CLOCK_BOOTTIME · 벽시계 · 단조 시계

시스템 시계에 첫 값과 타이머 인터럽트를 주는 하드웨어

실시간 시계 · 수정 진동자 · 타이머 · 인터럽트 · 타이머 인터럽트 · 메인보드

시스템 시계를 바깥 시각에 맞추는 수단

NTP · PTP · 시계 동기화 · chrony · ntpd

시스템 시계가 세는 기준 시각

에포크 · UTC · 유닉스 시간 · 시간대 · 윤초 · 타임스케일 · 타임스탬프

시스템 시계를 읽고 고치는 호출과 함수

시스템 콜 · clock_gettime · clock_settime · gettimeofday · System.currentTimeMillis · time.time · Date.now · java.time.Clock

시스템 시계를 관리하는 운영체제와 규격

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

시스템 시계를 몰래 읽을 때 깨지는 성질과 해법

결정성 · 비결정성 · 암묵적 입력 · 테스트 더블 · 의존성 주입 · 불안정한 테스트

시스템 시계와 맞세워지는 논리 시계

논리 시계 · 램포트 시계 · 벡터 시계 · 하이브리드 논리 시계

시스템 시계 때문에 터지는 문제

2038 문제 · 클록 드리프트 · 클록 스큐 · 타임아웃

시스템 시계와 이름이 겹치는 개념

클록 신호 · 클록 주파수 · 클록 사이클 · CPU 시간

시스템 시계가 속하는 상위 분류

시간과 시계 · 물리 시계 · 컴퓨터 시계

다른 이름: system clock · 시스템 클록 · 시스템 시각