ktime / Clock 심화

ktime_t 함수 레퍼런스, clocksource 프레임워크, timekeeper 내부 구현, 7가지 CLOCK_* 시계, CPU 주파수 관리(cpufreq/DVFS/P-State/HWP), vDSO 최적화, NTP/PTP 동기화, 하드웨어 클럭(TSC/HPET/ACPI PM), 지연 함수까지 — Linux 커널의 시간 관리 체계를 소스 코드 수준에서 분석합니다.

관련 표준: IEEE 1588 (PTP 시간 동기화), POSIX.1-2017 (CLOCK_* 시계 정의), Intel SDM (TSC, HPET) — 커널 시간 관리 프레임워크가 참조하는 시간/클럭 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
참고: jiffies, timer_list, hrtimer 기본 사용법, clockevent, tickless 커널은 타이머 (Timers) 페이지를 참조하세요. 이 페이지에서는 시간 측정의 내부 구현, 클럭 하드웨어, 동기화 메커니즘을 심층적으로 다룹니다.
전제 조건: 타이머(jiffies, hrtimer, clocksource 기초)를 반드시 먼저 읽으세요.
일상 비유: ktime/Clock 시스템은 시계탑의 내부 메커니즘과 같습니다. clocksource는 시계의 진자(TSC, HPET 등 실제 진동하는 하드웨어), timekeeper는 진자의 진동을 "몇 시 몇 분"으로 변환하는 톱니바퀴 장치, NTP/PTP는 표준시 방송을 수신하여 시계를 보정하는 라디오 수신기입니다. ktime_t는 나노초 단위의 시간 값을 담는 표준 그릇입니다.

핵심 요약

  • ktime_t — 나노초 단위의 64비트 시간 값. 커널 타이머 API의 표준 시간 타입입니다.
  • timekeeper — clocksource를 읽어 벽시계 시간(wall clock)과 모노토닉 시간을 유지하는 핵심 구조체입니다.
  • CLOCK_REALTIME / CLOCK_MONOTONIC — 대표적인 두 시계. REALTIME은 벽시계, MONOTONIC은 부팅 후 단조 증가합니다.
  • vDSOgettimeofday() 등을 커널 진입 없이 사용자 공간에서 실행하는 최적화입니다.
  • NTP / PTP — 네트워크를 통해 시스템 시계를 외부 기준 시계와 동기화합니다.

단계별 이해

  1. 클럭 소스 확인cat /sys/devices/system/clocksource/clocksource0/current_clocksource로 현재 사용 중인 클럭 소스를 확인합니다.

    대부분의 x86 시스템에서는 TSC(Time Stamp Counter)가 사용됩니다.

  2. 시간 읽기 — 커널에서 ktime_get()(모노토닉) 또는 ktime_get_real()(벽시계)로 현재 시각을 읽습니다.

    사용자 공간에서는 clock_gettime(CLOCK_MONOTONIC, &ts)를 사용합니다.

  3. 시간 동기화chronyntpd가 NTP 서버와 통신하여 커널의 timekeeper를 보정합니다.

    timedatectl로 현재 NTP 동기화 상태를 확인할 수 있습니다.

1. Timekeeping 아키텍처 개요

User Space clock_gettime() gettimeofday() time() clock_nanosleep() timerfd vDSO (syscall 없이 직접 읽기) Kernel Space struct timekeeper CLOCK_REALTIME CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW CLOCK_BOOTTIME CLOCK_TAI clocksource 프레임워크 (read 추상화) TSC HPET ACPI PM ARM Arch KVM PV RTC NTP/PTP 보정
Linux timekeeping 아키텍처 — 사용자 공간에서 하드웨어 클럭까지의 계층 구조

2. ktime_t 함수 전체 레퍼런스

ktime_t는 나노초 해상도의 시간값을 표현하는 커널의 통일된 시간 타입입니다. 내부적으로 s64 (signed 64-bit) 나노초 값입니다.

/* include/linux/ktime.h */
typedef s64 ktime_t;  /* 나노초 단위, signed 64-bit */
/* 표현 가능 범위: ±292년 (2^63 ns ≈ 292.47 years) */

시간 읽기 함수

함수시계 기준NTP 보정suspend 포함용도
ktime_get()MONOTONICOX일반적인 경과 시간 측정
ktime_get_real()REALTIMEOO벽시계 시간 (UTC)
ktime_get_boottime()BOOTTIMEOO부팅 이후 총 경과 시간
ktime_get_clocktai()TAIOO윤초 없는 절대 시간
ktime_get_raw()MONOTONIC_RAWXX하드웨어 클럭 직접 읽기
ktime_get_ts64()MONOTONICOXtimespec64 결과
ktime_get_real_ts64()REALTIMEOOtimespec64 결과
ktime_get_coarse()MONOTONICOX틱 해상도, 매우 빠름
ktime_get_coarse_real()REALTIMEOO틱 해상도, 매우 빠름
ktime_get_coarse_boottime()BOOTTIMEOO틱 해상도, 매우 빠름
ktime_get_fast_ns()MONOTONICOXNMI-safe, seqcount 기반
ktime_get_mono_fast_ns()MONOTONICOXNMI-safe 별칭
ktime_get_raw_fast_ns()MONOTONIC_RAWXXNMI-safe, 원시 클럭
ktime_get_boot_fast_ns()BOOTTIMEOONMI-safe, 부팅 시간
ktime_get_real_fast_ns()REALTIMEOONMI-safe, 벽시계
coarse vs 일반 vs fast: ktime_get()은 하드웨어 카운터를 직접 읽어 나노초 해상도를 제공합니다(~20-80ns 비용). ktime_get_coarse()는 마지막 틱에 캐시된 값을 반환하여 매우 빠르지만(~1-5ns) 해상도가 1/HZ(4ms@250Hz)입니다. ktime_get_fast_ns()는 NMI/하드IRQ 컨텍스트에서도 안전하게 사용 가능하며, seqcount로 일관성을 보장합니다.

ktime 산술/변환 함수

#include <linux/ktime.h>

/* ===== 생성/변환 ===== */
ktime_t t1 = ns_to_ktime(5000000);         /* 5ms = 5,000,000 ns */
ktime_t t2 = ms_to_ktime(100);              /* 100ms */
ktime_t t3 = us_to_ktime(500);              /* 500us (v6.x+) */
ktime_t t4 = ktime_set(5, 123456789);       /* 5초 + 123,456,789ns */

s64 ns = ktime_to_ns(t1);                   /* → 나노초 */
s64 us = ktime_to_us(t1);                   /* → 마이크로초 */
s64 ms = ktime_to_ms(t1);                   /* → 밀리초 */

/* ktime ↔ timespec64 변환 */
struct timespec64 ts = ktime_to_timespec64(t1);
ktime_t kt = timespec64_to_ktime(ts);

/* ===== 산술 연산 ===== */
ktime_t sum  = ktime_add(a, b);              /* a + b */
ktime_t diff = ktime_sub(a, b);              /* a - b */
ktime_t add_ns = ktime_add_ns(a, 1000);     /* a + 1000ns */
ktime_t add_us = ktime_add_us(a, 500);      /* a + 500us */
ktime_t add_ms = ktime_add_ms(a, 100);      /* a + 100ms */
ktime_t sub_ns = ktime_sub_ns(a, 1000);     /* a - 1000ns */

/* ===== 비교 연산 ===== */
bool is_after  = ktime_after(a, b);          /* a > b */
bool is_before = ktime_before(a, b);         /* a < b */
int  cmp       = ktime_compare(a, b);        /* -1, 0, 1 */
bool is_zero   = ktime_is_null(a);           /* a == 0 */

/* ===== 델타 계산 ===== */
s64 delta_ns = ktime_to_ns(ktime_sub(end, start));
s64 delta_us = ktime_us_delta(end, start);   /* 마이크로초 차이 */
s64 delta_ms = ktime_ms_delta(end, start);   /* 밀리초 차이 */

/* ===== 실용 패턴: 구간 측정 ===== */
ktime_t start = ktime_get();
/* ... 측정할 코드 ... */
s64 elapsed_us = ktime_us_delta(ktime_get(), start);
pr_info("operation took %lld us\n", elapsed_us);

/* ===== 실용 패턴: 타임아웃 ===== */
ktime_t deadline = ktime_add_ms(ktime_get(), 500); /* 500ms 후 */
while (!condition_met()) {
    if (ktime_after(ktime_get(), deadline))
        return -ETIMEDOUT;
    cpu_relax();
}

3. CLOCK_* 시계 체계

Linux 커널은 용도에 따라 여러 종류의 시계를 유지합니다. 각 시계는 기준점(epoch), NTP 보정 여부, suspend 동작이 다릅니다.

시계기준점NTP 보정suspend윤초설명
CLOCK_REALTIME1970-01-01 UTCO진행적용벽시계. settimeofday()로 변경 가능. 로그 타임스탬프용
CLOCK_MONOTONIC부팅 시점O (주파수)정지X단조 증가. 경과 시간 측정의 기본 시계
CLOCK_MONOTONIC_RAW부팅 시점X정지XNTP 보정 없는 원시 하드웨어 틱. 하드웨어 벤치마크
CLOCK_MONOTONIC_COARSE부팅 시점O (주파수)정지X틱 해상도(~4ms). 매우 빠름. 대략적 시간용
CLOCK_REALTIME_COARSE1970-01-01 UTCO진행적용틱 해상도 벽시계. 매우 빠름
CLOCK_BOOTTIME부팅 시점O (주파수)진행Xsuspend 시간 포함. 모바일, 네트워크 타임아웃
CLOCK_TAI1970-01-01 TAIO진행X국제원자시. 윤초 없음. PTP, 금융 타임스탬프
CLOCK_PROCESS_CPUTIME_ID프로세스 생성X정지X프로세스 CPU 시간 (user+sys)
CLOCK_THREAD_CPUTIME_ID스레드 생성X정지X스레드 CPU 시간

시계 간 관계

/*
 * 시계 간 관계:
 *
 * REALTIME = MONOTONIC + wall_to_monotonic + 윤초
 * BOOTTIME = MONOTONIC + total_sleep_time
 * TAI      = REALTIME  + tai_offset (현재 37초)
 * RAW      = 하드웨어 카운터 × 주파수 변환 (NTP 보정 없음)
 *
 * 시간 흐름 예시 (suspend 포함):
 *
 *          부팅    10s   suspend(5s)   깨어남   20s
 * MONOTONIC: 0  →  10    (정지)        10   →  20
 * BOOTTIME:  0  →  10    (진행)        15   →  25
 * REALTIME:  T  → T+10   (진행)       T+15  → T+25
 * RAW:       0  →  10*   (정지)        10*  →  20*
 *                         (* NTP 보정 없는 원시값)
 */

시계 선택 가이드

/* 커널 코드에서 시계 선택 기준: */

/* 1. 일반적인 경과 시간 → ktime_get() (MONOTONIC) */
ktime_t start = ktime_get();

/* 2. 네트워크 타임아웃, 모바일 알람 → ktime_get_boottime()
 *    suspend 동안에도 타임아웃이 진행되어야 할 때 */
ktime_t deadline = ktime_add_ms(ktime_get_boottime(), timeout_ms);

/* 3. 로그/감사 타임스탬프 → ktime_get_real() (REALTIME) */
struct timespec64 ts;
ktime_get_real_ts64(&ts);

/* 4. 하드웨어 벤치마크 → ktime_get_raw() (NTP 보정 배제) */
ktime_t hw_start = ktime_get_raw();

/* 5. 대략적 시간 (고빈도 호출) → ktime_get_coarse() */
ktime_t approx = ktime_get_coarse();

/* 6. NMI/하드IRQ 컨텍스트 → ktime_get_fast_ns() */
u64 nmi_ts = ktime_get_mono_fast_ns();

/* 7. PTP/금융 타임스탬프 → ktime_get_clocktai() (윤초 없음) */
ktime_t tai = ktime_get_clocktai();

4. Timekeeper 내부 구현

struct timekeeper는 커널의 모든 시간 기준을 유지하는 핵심 자료구조입니다. clocksource에서 읽은 하드웨어 카운터를 나노초로 변환하고, NTP 보정을 적용합니다.

/* kernel/time/timekeeping.c */
struct timekeeper {
    /* 현재 활성 clocksource와 변환 정보 */
    struct tk_read_base  tkr_mono;        /* MONOTONIC 읽기 기반 */
    struct tk_read_base  tkr_raw;         /* RAW 읽기 기반 */

    /* 벽시계 오프셋 */
    u64                  xtime_sec;       /* REALTIME 초 부분 */
    unsigned long        ktime_sec;       /* MONOTONIC 초 (캐시) */

    /* 시계 간 오프셋 */
    struct timespec64    wall_to_monotonic; /* REALTIME→MONOTONIC */
    ktime_t              offs_real;       /* MONOTONIC→REALTIME 오프셋 */
    ktime_t              offs_boot;       /* MONOTONIC→BOOTTIME 오프셋 */
    ktime_t              offs_tai;        /* MONOTONIC→TAI 오프셋 */

    /* NTP 보정 */
    s64                  ntp_error;       /* 누적 NTP 오차 */
    u32                  ntp_error_shift;
    u32                  ntp_err_mult;

    /* suspend 관련 */
    ktime_t              total_sleep_time; /* 총 suspend 시간 */
};

/* tk_read_base — clocksource 읽기 최적화 구조 */
struct tk_read_base {
    struct clocksource  *clock;          /* 현재 clocksource */
    u64                 mask;            /* 카운터 비트 마스크 */
    u64                 cycle_last;      /* 마지막 읽은 사이클 값 */
    u32                 mult;            /* 사이클→나노초 곱셈 인수 */
    u32                 shift;           /* 사이클→나노초 시프트 인수 */
    u64                 xtime_nsec;      /* 나노초 누적 (시프트됨) */
    ktime_t             base;            /* 기준 ktime 값 */
};

시간 읽기 흐름

/* ktime_get() 내부 동작 (간략화): */
ktime_t ktime_get(void)
{
    struct timekeeper *tk = &tk_core.timekeeper;
    ktime_t base;
    u64 delta, nsec;
    unsigned int seq;

    do {
        seq = read_seqcount_begin(&tk_core.seq);

        /* 1. 기준 시간 읽기 (마지막 업데이트 시점의 값) */
        base = tk->tkr_mono.base;

        /* 2. 하드웨어 카운터 읽기 */
        u64 cycle_now = tk->tkr_mono.clock->read(tk->tkr_mono.clock);

        /* 3. 마지막 읽기 이후 경과한 사이클 계산 */
        delta = (cycle_now - tk->tkr_mono.cycle_last) & tk->tkr_mono.mask;

        /* 4. 사이클 → 나노초 변환
         *    nsec = (delta * mult) >> shift
         *    mult와 shift는 NTP 보정이 반영된 값 */
        nsec = delta * tk->tkr_mono.mult;
        nsec >>= tk->tkr_mono.shift;

    } while (read_seqcount_retry(&tk_core.seq, seq));

    /* 5. 기준 시간 + 경과 나노초 = 현재 시간 */
    return ktime_add_ns(base, nsec);
}

/*
 * seqcount를 사용하는 이유:
 * - timekeeper 업데이트(update_wall_time)는 틱 인터럽트에서 수행
 * - 읽기(ktime_get)는 아무 컨텍스트에서 호출 가능
 * - seqcount로 락 없이 일관된 스냅샷 보장
 * - 쓰기 중 읽으면 retry
 */

사이클 → 나노초 변환 수학

/*
 * 하드웨어 카운터 사이클을 나노초로 변환:
 *
 *   ns = cycles × (10^9 / freq)
 *
 * 정수 연산으로 구현:
 *   ns = (cycles × mult) >> shift
 *
 * mult와 shift 계산:
 *   mult = (10^9 << shift) / freq
 *
 * 예: TSC 3.0 GHz, shift=24
 *   mult = (10^9 × 2^24) / (3 × 10^9)
 *        = 16777216 / 3 = 5592405
 *
 *   100 cycles → (100 × 5592405) >> 24
 *             = 559240500 >> 24 = 33 ns ≈ 33.33ns (정확)
 *
 * NTP 보정 시 mult 값을 미세 조정하여 주파수 보정
 */

/* clocksource 등록 시 mult/shift 자동 계산 */
clocks_calc_mult_shift(
    &cs->mult,      /* 출력: 곱셈 인수 */
    &cs->shift,     /* 출력: 시프트 인수 */
    cs->freq,       /* 입력: 클럭 주파수 (Hz) */
    NSEC_PER_SEC,   /* 10^9 */
    cs->max_idle_ns /* 최대 유휴 시간 */
);

5. Clocksource 프레임워크

clocksource 프레임워크는 하드웨어 타이머를 통일된 인터페이스로 추상화합니다. 시스템에 여러 clocksource가 등록되면 rating이 가장 높은 것이 자동 선택됩니다.

clocksource 구조체

/* include/linux/clocksource.h */
struct clocksource {
    u64   (*read)(struct clocksource *cs);  /* 카운터 읽기 함수 */
    u64   mask;                    /* 비트 마스크 (예: 0xFFFFFFFF) */
    u32   mult;                    /* 사이클→ns 곱셈 인수 */
    u32   shift;                   /* 사이클→ns 시프트 인수 */
    u64   max_idle_ns;             /* 최대 유휴 시간 (wrap 방지) */
    u32   maxadj;                  /* NTP 최대 조정 범위 */
    int   rating;                  /* 품질 등급 (높을수록 우선) */
    const char *name;               /* 이름 ("tsc", "hpet", ...) */
    unsigned long flags;            /* CLOCK_SOURCE_* 플래그 */
    int   (*enable)(struct clocksource *cs);
    void  (*disable)(struct clocksource *cs);
    void  (*suspend)(struct clocksource *cs);
    void  (*resume)(struct clocksource *cs);
    void  (*mark_unstable)(struct clocksource *cs);
    void  (*tick_stable)(struct clocksource *cs);
    struct list_head list;          /* 등록된 clocksource 리스트 */
    ...
};

/* rating 기준:
 *   1-99:    비적합 (테스트/폴백)
 *   100-199: 기본 (jiffies)
 *   200-299: 합리적 (ACPI PM)
 *   300-399: 양호 (HPET)
 *   400-499: 우수 (TSC)
 */

Clocksource 선택/변경

# 현재 clocksource 확인
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc

# 사용 가능한 clocksource 목록
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm

# clocksource 수동 변경 (디버깅/테스트용)
$ echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource

# 커널 커맨드라인으로 강제 지정
# clocksource=hpet    (HPET 강제)
# tsc=reliable         (TSC 안정성 신뢰)
# tsc=unstable         (TSC 불안정 표시 → 폴백)

Clocksource Watchdog

/*
 * clocksource watchdog는 0.5초마다 활성 clocksource를
 * 참조 clocksource와 비교하여 드리프트를 감지합니다.
 *
 * 드리프트 > 62.5 ppm → clocksource를 unstable로 표시 → 폴백
 *
 * 주로 TSC의 안정성 검증에 사용됩니다.
 * 불안정한 TSC (C-state 변경, 주파수 스케일링 등)가 감지되면
 * HPET 또는 ACPI PM으로 자동 전환됩니다.
 */

# watchdog 로그
$ dmesg | grep -i clocksource
clocksource: Switched to clocksource tsc
# 또는 불안정 시:
clocksource: timekeeping watchdog on CPU0: Marking clocksource 'tsc'
    as unstable because the skew is too large
clocksource: Switched to clocksource hpet

6. 하드웨어 클럭 상세

TSC (Time Stamp Counter) — x86

/*
 * TSC는 x86 프로세서의 64-bit 카운터로,
 * 프로세서 클럭 사이클(또는 고정 주파수)마다 증가합니다.
 *
 * TSC 변형:
 * - Variant TSC: 주파수 스케일링에 따라 속도 변동 (구형 CPU)
 * - Constant TSC: APIC 버스 주파수로 고정 (Core 2+)
 * - Invariant TSC: C-state/주파수와 무관, 항상 일정 (Nehalem+)
 * - Nonstop TSC: 깊은 C-state에서도 정지하지 않음
 *
 * 현대 x86에서 TSC는 가장 빠르고 정확한 clocksource입니다.
 */

/* arch/x86/kernel/tsc.c — TSC clocksource */
static u64 read_tsc(struct clocksource *cs)
{
    return (u64)rdtsc_ordered();
    /* rdtsc_ordered = lfence + rdtsc 또는 rdtscp
     * lfence: 이전 명령어 완료 대기 (순서 보장)
     * rdtscp: 읽기 직렬화 + IA32_TSC_AUX(코어 ID) 반환 */
}

static struct clocksource clocksource_tsc = {
    .name   = "tsc",
    .rating = 300,   /* invariant TSC는 350으로 승격 */
    .read   = read_tsc,
    .mask   = CLOCKSOURCE_MASK(64),
    .flags  = CLOCK_SOURCE_IS_CONTINUOUS |
              CLOCK_SOURCE_MUST_VERIFY,
};

# TSC 상태 확인
$ dmesg | grep -i tsc
tsc: Detected 3000.000 MHz processor
tsc: Detected 3000.000 MHz TSC
tsc: Refined TSC clocksource calibration: 2999.998 MHz
clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x2b3e459bf4c
    max_idle_ns: 440795310624 ns

# TSC CPUID 피처 확인
$ grep -o 'constant_tsc\|nonstop_tsc\|tsc_known_freq\|rdtscp' /proc/cpuinfo | sort -u
constant_tsc
nonstop_tsc
rdtscp
tsc_known_freq

HPET (High Precision Event Timer)

/*
 * HPET는 ACPI에 정의된 멀티미디어 타이머입니다.
 * - 주파수: 최소 10 MHz (일반적으로 14.318 MHz)
 * - 64-bit 또는 32-bit 카운터
 * - 최대 32개의 비교기(comparator) — periodic/oneshot
 * - MMIO 접근 (메모리 매핑)
 *
 * TSC보다 느리지만 안정적이어서 폴백 clocksource로 사용됩니다.
 * MMIO 접근 비용: ~100-300ns (TSC: ~20-30ns)
 */

/* arch/x86/kernel/hpet.c */
static u64 read_hpet(struct clocksource *cs)
{
    return (u64)hpet_readl(HPET_COUNTER);
    /* MMIO 읽기: 0xFED00000 + 0xF0 */
}

static struct clocksource clocksource_hpet = {
    .name   = "hpet",
    .rating = 250,
    .read   = read_hpet,
    .mask   = HPET_MASK,    /* 32-bit: 0xFFFFFFFF */
    .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
};

# HPET 정보
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm

$ dmesg | grep -i hpet
hpet: HPET id: 0x8086a201 base: 0xfed00000
hpet clockevent registered
hpet0: at MMIO 0xfed00000, IRQs 2, 8, 0
hpet0: 3 comparators, 64-bit 14.318180 MHz counter

ACPI PM Timer

/*
 * ACPI Power Management Timer
 * - 고정 주파수: 3.579545 MHz (NTSC 컬러 서브캐리어의 3배)
 * - 24-bit 또는 32-bit 카운터
 * - I/O 포트 접근 (매우 느림: ~500ns-1us)
 * - 가상화 환경에서도 안정적
 *
 * rating이 낮지만(200), 모든 ACPI 시스템에서 사용 가능하여
 * 최후의 폴백 clocksource로 활용됩니다.
 */

static u64 acpi_pm_read(struct clocksource *cs)
{
    return (u64)inl(pmtmr_ioport) & ACPI_PM_MASK;
    /* I/O 포트 읽기: 일반적으로 0x408 */
}

static struct clocksource clocksource_acpi_pm = {
    .name   = "acpi_pm",
    .rating = 200,
    .read   = acpi_pm_read,
    .mask   = (u64)ACPI_PM_MASK,  /* 24-bit: 0x00FFFFFF */
    .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
};

ARM Generic Timer

/*
 * ARM Architecture Timer (ARMv7+, ARMv8)
 * - 시스템 카운터: 모든 CPU에서 공유되는 단일 주파수 카운터
 * - 주파수: CNTFRQ_EL0 (일반적으로 1-100 MHz)
 * - 64-bit 카운터: CNTVCT_EL0 (가상) 또는 CNTPCT_EL0 (물리)
 * - CPU 레지스터 접근: 매우 빠름 (~5-20ns)
 * - 타이머 비교기: 각 CPU에 EL1 Physical/Virtual, EL2 타이머
 */

/* drivers/clocksource/arm_arch_timer.c */
static u64 arch_counter_read(struct clocksource *cs)
{
    return arch_timer_read_counter();
    /* AArch64: mrs x0, cntvct_el0 */
}

static struct clocksource clocksource_counter = {
    .name   = "arch_sys_counter",
    .rating = 400,           /* 매우 높은 우선순위 */
    .read   = arch_counter_read,
    .mask   = CLOCKSOURCE_MASK(56),
    .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
};

# ARM 타이머 정보
$ dmesg | grep -i "arch_timer\|clocksource"
arch_timer: cp15 timer(s) running at 24.00MHz (phys)
clocksource: arch_sys_counter: mask: 0xffffffffffffff
    max_cycles: 0x588fe9dc0, max_idle_ns: 440795202592 ns

KVM pvclock

/*
 * KVM 반가상화 클럭 (paravirtual clock)
 * - 게스트 VM이 하이퍼바이저의 시간 정보를 직접 읽음
 * - 공유 메모리 페이지를 통해 호스트 TSC 오프셋 전달
 * - VM exit 없이 시간 읽기 가능 → 매우 빠름
 * - rating: 450 (TSC보다 높음 — VM에서 더 안정적)
 */

static struct clocksource kvm_clock = {
    .name   = "kvm-clock",
    .rating = 450,
    .read   = kvm_clock_get_cycles,
    .mask   = CLOCKSOURCE_MASK(64),
    .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
};

# KVM 게스트에서 확인
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock

Clocksource 비교 종합

클럭플랫폼주파수비트접근읽기 비용Rating
TSCx86~GHz64rdtsc 명령~20-30ns300-350
HPETx8614.3 MHz32/64MMIO~100-300ns250
ACPI PMx863.58 MHz24/32I/O port~500ns-1us200
ARM ArchARM1-100 MHz56시스템 레지스터~5-20ns400
KVM pvclockKVM 게스트호스트 TSC64공유 메모리~15-25ns450
jiffies모든HZ64전역 변수~1ns1

왜 시간 관리 문서에서 cpufreq를 다루는가? TSC(Time Stamp Counter)는 CPU 코어 클럭에서 파생되며, CPU 주파수 변경은 TSC 안정성과 clocksource 선택에 직접 영향을 줍니다. Invariant TSC가 없는 구형 CPU에서는 주파수 변경 시 시간 측정이 부정확해집니다. 또한 schedutil 거버너는 스케줄러 tick/hrtimer와 밀접하게 연동됩니다.

7. CPU 주파수 관리 (cpufreq)

CPU 주파수 스케일링(DVFS, Dynamic Voltage and Frequency Scaling)은 성능 요구와 전력 소비 사이의 균형을 조절합니다. 커널의 cpufreq 서브시스템은 CPU 주파수 변경을 추상화하며, 거버너(governor)가 정책에 따라 주파수를 결정합니다.

cpufreq 아키텍처

User Space /sys/devices/system/cpu/cpufreq/ cpupower / turbostat power-profiles-daemon thermald Kernel Space Frequency Governors (주파수 결정 정책) schedutil performance powersave ondemand conservative userspace cpufreq core (struct cpufreq_policy) Scaling Drivers (하드웨어 추상화) intel_pstate amd-pstate acpi-cpufreq cpufreq-dt (ARM) 스케줄러 util 피드백
cpufreq 서브시스템 아키텍처 — 거버너, 코어, 스케일링 드라이버 계층
/* include/linux/cpufreq.h — 핵심 구조체 */
struct cpufreq_policy {
    struct cpufreq_cpuinfo  cpuinfo;     /* 하드웨어 한계 */
    unsigned int            min;         /* 정책 최소 주파수 (kHz) */
    unsigned int            max;         /* 정책 최대 주파수 (kHz) */
    unsigned int            cur;         /* 현재 주파수 (kHz) */
    struct cpufreq_governor *governor;    /* 현재 거버너 */
    struct cpufreq_frequency_table *freq_table; /* 지원 주파수 목록 */
    struct cpumask_var_t   cpus;         /* 이 정책이 적용되는 CPU들 */
    struct cpumask_var_t   related_cpus; /* 같은 클럭 도메인의 CPU들 */
    unsigned int            transition_latency; /* 주파수 전환 지연 (ns) */
    ...
};

struct cpufreq_cpuinfo {
    unsigned int  max_freq;       /* 하드웨어 최대 주파수 (kHz) */
    unsigned int  min_freq;       /* 하드웨어 최소 주파수 (kHz) */
    unsigned int  transition_latency; /* 전환 지연 (ns) */
};

struct cpufreq_governor {
    char  name[CPUFREQ_NAME_LEN];
    int   (*init)(struct cpufreq_policy *policy);
    void  (*exit)(struct cpufreq_policy *policy);
    int   (*start)(struct cpufreq_policy *policy);
    void  (*stop)(struct cpufreq_policy *policy);
    void  (*limits)(struct cpufreq_policy *policy);
    ...
};

주파수 거버너 (Governors)

거버너전략주파수 결정 방식용도
performance항상 최대policy->max 고정최대 성능, 벤치마크
powersave항상 최소policy->min 고정최대 절전
schedutil스케줄러 연동CPU utilization 기반 즉시 반영기본 권장 (v4.7+)
ondemand부하 추적주기적 샘플링, 임계값 초과 시 최대레거시, 데스크탑
conservative점진적 조절주기적 샘플링, 단계적 증감레거시, 배터리
userspace수동 제어사용자가 직접 주파수 설정디버깅, 특수 용도
/* ===== schedutil 거버너 — 스케줄러 직접 연동 (v4.7+) =====
 *
 * 핵심 아이디어:
 * - CFS 스케줄러의 PELT (Per-Entity Load Tracking) util 값을 직접 읽음
 * - util이 변할 때마다 즉시 주파수 갱신 요청 (iowait 부스트 포함)
 * - 주기적 샘플링 없이 스케줄러 이벤트에 반응 → 지연 최소화
 *
 * 주파수 계산 공식:
 *   next_freq = 1.25 × max_freq × util / max_util
 *   (25% 마진으로 성능 여유 확보)
 */

/* kernel/sched/cpufreq_schedutil.c */
static unsigned int get_next_freq(
    struct sugov_policy *sg_policy,
    unsigned long util, unsigned long max)
{
    struct cpufreq_policy *policy = sg_policy->policy;
    unsigned int freq;

    /* util / max 비율 × 최대 주파수, 1.25배 마진 */
    freq = map_util_freq(util, policy->cpuinfo.max_freq, max);

    /* 정책 범위 [min, max]로 클램프 */
    if (freq == sg_policy->cached_raw_freq &&
        sg_policy->next_freq != UINT_MAX)
        return sg_policy->next_freq;

    sg_policy->cached_raw_freq = freq;
    return cpufreq_driver_resolve_freq(policy, freq);
}

/* 스케줄러 콜백 — 태스크 큐잉/실행 시 호출 */
static void sugov_update_shared(
    struct update_util_data *hook,
    u64 time, unsigned int flags)
{
    /* 1. CPU의 현재 util 값 수집 */
    /* 2. iowait 부스트 적용 */
    /* 3. get_next_freq()로 목표 주파수 계산 */
    /* 4. rate_limit 확인 후 주파수 변경 요청 */
}

P-State와 성능 상태

/*
 * ACPI P-States (Performance States):
 *
 * P-State는 CPU의 전압-주파수 조합입니다.
 * P0이 최고 성능, P-번호가 클수록 저전력/저주파수입니다.
 *
 *   P-State    주파수     전압      전력
 *   P0 (Turbo) 4.5 GHz   1.25V     125W    ← Turbo Boost
 *   P1 (Base)  3.0 GHz   1.10V      65W    ← 기본 주파수
 *   P2         2.4 GHz   1.00V      45W
 *   P3         1.8 GHz   0.90V      30W
 *   Pn (LFM)   0.8 GHz   0.70V      15W    ← 최저 주파수
 *
 * 전력 ∝ V² × f (전압 제곱에 비례)
 * → 주파수를 절반으로 줄이면 전력은 ~1/4로 감소
 *
 * C-State와의 관계:
 * - P-State: 실행 중(C0) CPU의 주파수/전압 제어
 * - C-State: 유휴 CPU의 전력 절감 (C0=활성, C1=Halt, C6=Deep Sleep)
 * - 깊은 C-State에서 깨어나면 P-State 복원 필요
 */

# ACPI P-State 확인
$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
800000 1200000 1800000 2400000 3000000  # kHz 단위

# 현재 주파수
$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
3000000

# 하드웨어 한계
$ cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq
800000
$ cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
4500000

Intel P-State 드라이버

/*
 * intel_pstate — Intel CPU 전용 cpufreq 드라이버
 *
 * 두 가지 모드:
 *
 * 1. Active mode (기본):
 *    - 내장 알고리즘이 직접 P-State 결정
 *    - MSR 직접 접근으로 빠른 주파수 전환
 *    - 전통적 거버너 우회 (performance/powersave만 노출)
 *    - HWP 지원 시 하드웨어에 완전 위임
 *
 * 2. Passive mode:
 *    - 일반 cpufreq 드라이버처럼 동작
 *    - 모든 거버너(schedutil 등) 사용 가능
 *    - 커널 커맨드라인: intel_pstate=passive
 *
 * HWP (Hardware P-states, Intel Speed Shift):
 *    - Skylake+에서 지원
 *    - 하드웨어가 μs 단위로 P-State 자율 결정
 *    - 소프트웨어보다 훨씬 빠른 반응 (~1ms → ~30μs)
 *    - EPP (Energy Performance Preference)로 성능/절전 힌트 전달
 */

/* drivers/cpufreq/intel_pstate.c */
struct cpudata {
    int  cpu;
    struct pstate_data pstate;      /* 현재 P-State 정보 */
    int  min_perf;                   /* 최소 성능 비율 */
    int  max_perf;                   /* 최대 성능 비율 */
    int  min_perf_ratio;             /* HWP 최소 비율 */
    int  max_perf_ratio;             /* HWP 최대 비율 */
    u64  hwp_req_cached;             /* 캐시된 HWP 요청 MSR */
    int  epp_cached;                 /* 캐시된 EPP 값 */
    ...
};

/* HWP MSR 레이아웃 (IA32_HWP_REQUEST, 0x774):
 *
 * Bits  7:0   — Minimum_Performance   (최소 성능 비율)
 * Bits 15:8   — Maximum_Performance   (최대 성능 비율)
 * Bits 23:16  — Desired_Performance   (희망 성능, 0=자율)
 * Bits 31:24  — Energy_Perf_Preference (EPP)
 *               0x00: 최대 성능
 *               0x80: 균형
 *               0xFF: 최대 절전
 * Bit  42     — Activity_Window 유효
 * Bits 53:43  — Activity_Window
 */

# intel_pstate 상태 확인
$ cat /sys/devices/system/cpu/intel_pstate/status
active     # active 또는 passive 또는 off

# Turbo Boost 제어
$ cat /sys/devices/system/cpu/intel_pstate/no_turbo
0          # 0=터보 활성, 1=터보 비활성

# HWP EPP 설정 (CPU별)
$ cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
balance_performance
$ cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences
default performance balance_performance balance_power power

# passive 모드로 전환
$ echo passive > /sys/devices/system/cpu/intel_pstate/status

AMD P-State 드라이버

/*
 * amd-pstate — AMD Zen CPU 전용 cpufreq 드라이버 (v5.17+)
 *
 * CPPC (Collaborative Processor Performance Control) 기반:
 * - ACPI CPPC 인터페이스를 통해 CPU와 OS가 협력하여 성능 결정
 * - AMD Zen 2+ 지원 (Zen 3/4에서 최적)
 *
 * 세 가지 모드:
 *
 * 1. amd-pstate (passive):
 *    - 커널이 CPPC를 통해 원하는 성능 수준 전달
 *    - 기존 거버너와 호환
 *
 * 2. amd-pstate-epp (active, v6.3+):
 *    - Intel HWP와 유사, EPP 기반 자율 제어
 *    - 하드웨어가 성능/절전 비율 직접 결정
 *
 * 3. guided (v6.4+):
 *    - 커널이 최소/최대 범위 지정, 하드웨어가 범위 내 자율 결정
 *
 * CPPC 성능 레벨:
 *   Highest Perf:  터보 부스트 포함 최대 성능
 *   Nominal Perf:  기본(base) 클럭 성능
 *   Lowest Nonlinear Perf: 효율이 최적인 최저점
 *   Lowest Perf:   절대 최소 성능
 */

# amd-pstate 활성화 (커널 커맨드라인)
# amd_pstate=passive   (passive 모드)
# amd_pstate=active    (EPP 모드)
# amd_pstate=guided    (guided 모드)

# 현재 모드 확인
$ cat /sys/devices/system/cpu/amd_pstate/status
active

# EPP 설정 (active 모드)
$ cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
balance_performance

# CPPC 성능 레벨 확인
$ cat /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_highest_perf
166
$ cat /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_lowest_nonlinear_freq
1500000

CPU 주파수와 TSC의 관계

/*
 * CPU 주파수 변경이 TSC에 미치는 영향:
 *
 * ┌─────────────────┬───────────────┬─────────────────────┐
 * │ TSC 유형         │ 주파수 변경    │ 설명                │
 * ├─────────────────┼───────────────┼─────────────────────┤
 * │ Variant TSC     │ 영향받음       │ TSC가 CPU 클럭에    │
 * │ (Pentium 4 이전) │ (속도 변동)    │ 동기화되어 변동     │
 * ├─────────────────┼───────────────┼─────────────────────┤
 * │ Constant TSC    │ 영향 없음      │ 고정 주파수로 동작   │
 * │ (Core 2+)       │               │ (APIC 버스 주파수)   │
 * ├─────────────────┼───────────────┼─────────────────────┤
 * │ Invariant TSC   │ 영향 없음      │ C-state/주파수 변경  │
 * │ (Nehalem+)      │               │ 과 완전히 독립       │
 * └─────────────────┴───────────────┴─────────────────────┘
 *
 * 현대 프로세서 (Invariant TSC):
 * - TSC는 기본 클럭(base clock) × 고정 배수로 동작
 * - DVFS 주파수 변경이 TSC에 영향 없음
 * - 따라서 ktime_get()은 CPU 주파수와 무관하게 정확
 *
 * 주의: Variant TSC 시스템에서는 cpufreq 주파수 변경 시
 * TSC 기반 시간 측정이 부정확해질 수 있음
 * → clocksource watchdog가 감지하여 HPET으로 폴백
 */

Frequency Invariance (주파수 불변 스케줄링)

/*
 * Frequency Invariance란?
 *
 * 스케줄러의 CPU utilization은 현재 CPU 주파수에 따라 왜곡됩니다:
 * - CPU가 1 GHz에서 50% 사용 → util = 50%
 * - 같은 작업을 3 GHz에서 실행 → util ≈ 17%
 * - 실제 작업량은 같지만 util 값이 다름!
 *
 * Frequency Invariance는 util을 최대 주파수 기준으로 정규화합니다:
 *
 *   util_scaled = util × (curr_freq / max_freq)
 *
 * 이를 통해:
 * 1. schedutil 거버너가 정확한 주파수 결정 가능
 * 2. EAS (Energy-Aware Scheduling)가 올바른 에너지 추정 가능
 * 3. 로드 밸런싱이 실제 처리량을 정확히 비교 가능
 *
 * 구현:
 * - x86: APERF/MPERF MSR 비율로 실제 주파수 추정
 *   freq_scale = APERF_delta / MPERF_delta
 * - ARM: delivered/reference 카운터 (AMU, Activity Monitor Unit)
 */

/* arch/x86/kernel/smpboot.c — x86 APERF/MPERF 기반 */
static void x86_scale_freq_tick(void)
{
    u64 aperf, mperf;
    u64 acnt, mcnt;

    rdmsrl(MSR_IA32_APERF, aperf);  /* 실제 수행 사이클 */
    rdmsrl(MSR_IA32_MPERF, mperf);  /* 최대 주파수 사이클 */

    acnt = aperf - this_cpu_read(arch_prev_aperf);
    mcnt = mperf - this_cpu_read(arch_prev_mperf);

    this_cpu_write(arch_prev_aperf, aperf);
    this_cpu_write(arch_prev_mperf, mperf);

    /* freq_scale = APERF / MPERF × SCHED_CAPACITY_SCALE */
    arch_set_freq_scale(..., acnt, mcnt, SCHED_CAPACITY_SCALE);
}

/*
 * APERF (Actual Performance):  실제 실행된 클럭 사이클
 * MPERF (Maximum Performance): 최대 주파수 기준 사이클
 *
 * 예: max_freq=3GHz, 현재 2GHz에서 1초 실행
 *   MPERF = 3,000,000,000 (3GHz 기준)
 *   APERF = 2,000,000,000 (실제 2GHz)
 *   freq_scale = 2/3 × 1024 ≈ 683
 */

# Frequency invariance 상태 확인
$ cat /sys/devices/system/cpu/cpu0/acpi_cppc/reference_perf
100
$ cat /sys/devices/system/cpu/cpu0/acpi_cppc/highest_perf
166

# 스케줄러의 freq_scale 확인 (ftrace)
$ echo 1 > /sys/kernel/tracing/events/power/cpu_frequency/enable
$ cat /sys/kernel/tracing/trace_pipe
# cpu_frequency: state=3000000 cpu_id=0

cpufreq 모니터링 및 디버깅

# ============ 기본 정보 확인 ============

# 현재 CPU별 주파수
$ cat /proc/cpuinfo | grep "cpu MHz"
cpu MHz  : 3000.000
cpu MHz  : 2400.000

# cpufreq 정책 요약 (모든 CPU)
$ cpupower frequency-info
analyzing CPU 0:
  driver: intel_pstate
  CPUs which run at the same hardware frequency: 0
  CPUs which need to have their frequency coordinated by software: 0
  maximum transition latency: Cannot determine
  hardware limits: 800 MHz - 4.50 GHz
  available cpufreq governors: performance powersave
  current policy: frequency should be within 800 MHz and 4.50 GHz.
                  The governor "performance" may decide which speed to use.
  current CPU frequency: 3.00 GHz (asserted by call to hardware)
  boost state support:
    Supported: yes
    Active: yes

# ============ 실시간 모니터링 ============

# turbostat — Intel CPU 상세 모니터링 (주파수, C-state, 전력)
$ turbostat --interval 1 --quiet
Core  CPU   Avg_MHz  Busy%  Bzy_MHz  TSC_MHz  IRQ   C1%   C6%
-     -     1200     40.0   3000     3000     5432  20.0  40.0
0     0     2500     83.3   3000     3000     2345  10.0  6.7
0     1     100      3.3    3000     3000     123   30.0  66.7
# Avg_MHz: 실제 평균, Bzy_MHz: 바쁜 상태 주파수, TSC_MHz: TSC 주파수

# perf stat으로 주파수 관련 이벤트 수집
$ perf stat -e cycles,instructions,cpu-clock,task-clock -a sleep 1

# ============ sysfs 인터페이스 종합 ============

# 정책별 sysfs 경로
$ ls /sys/devices/system/cpu/cpufreq/policy0/
affected_cpus                  scaling_available_governors
cpuinfo_max_freq               scaling_cur_freq
cpuinfo_min_freq               scaling_driver
cpuinfo_transition_latency     scaling_governor
related_cpus                   scaling_max_freq
scaling_available_frequencies  scaling_min_freq
                               scaling_setspeed

# 거버너 변경
$ echo schedutil > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor

# 주파수 범위 제한
$ echo 2000000 > /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq
$ echo 3500000 > /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq

# Boost/Turbo 전역 제어
$ cat /sys/devices/system/cpu/cpufreq/boost
1   # 1=활성, 0=비활성

# ============ 주파수 전환 통계 ============
$ cat /sys/devices/system/cpu/cpufreq/policy0/stats/time_in_state
800000 12345    # 주파수(kHz) 체류시간(10ms 단위)
1200000 23456
2400000 34567
3000000 45678

$ cat /sys/devices/system/cpu/cpufreq/policy0/stats/total_trans
98765   # 총 주파수 전환 횟수
cpufreq 관련 커널 설정:
  • CONFIG_CPU_FREQ=y — cpufreq 프레임워크 활성화
  • CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y — 기본 거버너를 schedutil로 설정
  • CONFIG_X86_INTEL_PSTATE=y — Intel P-State 드라이버
  • CONFIG_X86_AMD_PSTATE=y — AMD P-State 드라이버 (v5.17+)
  • CONFIG_X86_ACPI_CPUFREQ=y — ACPI 기반 cpufreq (레거시/범용)
  • CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y — schedutil 거버너
  • CONFIG_CPU_FREQ_STAT=y — 주파수 전환 통계
  • CONFIG_ENERGY_MODEL=y — Energy-Aware Scheduling 에너지 모델

8. vDSO (virtual Dynamic Shared Object)

vDSO는 커널이 사용자 공간에 매핑하는 공유 라이브러리로, 시스템 콜 없이 시간 읽기를 수행합니다. clock_gettime()의 대부분의 호출이 vDSO를 통해 처리됩니다.

vDSO 동작 원리

/*
 * vDSO 시간 읽기 흐름:
 *
 * 1. 커널이 vdso_data 페이지를 사용자 주소 공간에 읽기 전용으로 매핑
 * 2. 매 틱마다 커널이 vdso_data를 업데이트
 * 3. 사용자 프로세스가 clock_gettime() 호출
 * 4. glibc가 vDSO 함수를 호출 (커널 진입 없음)
 * 5. vDSO 함수가:
 *    a. vdso_data의 seqcount 확인
 *    b. 하드웨어 카운터 직접 읽기 (rdtsc 등)
 *    c. vdso_data의 mult/shift로 나노초 변환
 *    d. 결과 반환
 *
 * 비용: syscall ~100-200ns → vDSO ~20-30ns (x86 TSC)
 */

/* include/vdso/datapage.h */
struct vdso_data {
    u32     seq;                /* seqcount (업데이트 감지) */
    s32     clock_mode;         /* vDSO 지원 클럭 모드 */
    u64     cycle_last;         /* 마지막 사이클 값 */
    u64     mask;               /* 카운터 마스크 */
    u32     mult;               /* 사이클→ns 곱셈 인수 */
    u32     shift;              /* 사이클→ns 시프트 인수 */
    struct vdso_timestamp
            basetime[VDSO_BASES]; /* 시계별 기준 시간 */
    s32     tz_minuteswest;     /* 타임존 오프셋 */
    s32     tz_dsttime;         /* DST 정보 */
    u32     hrtimer_res;        /* hrtimer 해상도 */
};

# vDSO 확인
$ ldd /bin/ls | grep vdso
    linux-vdso.so.1 (0x00007ffd...)

# vDSO가 제공하는 함수
$ objdump -T /lib/modules/$(uname -r)/vdso/vdso64.so 2>/dev/null || \
  LD_SHOW_AUXV=1 /bin/true | grep SYSINFO
# __vdso_clock_gettime, __vdso_gettimeofday, __vdso_time,
# __vdso_clock_getres, __vdso_getcpu

vDSO 성능 측정

/* clock_gettime 벤치마크 */
#include <time.h>
#include <stdio.h>

int main(void) {
    struct timespec ts;
    int i;
    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; i < 10000000; i++)
        clock_gettime(CLOCK_MONOTONIC, &ts);
    clock_gettime(CLOCK_MONOTONIC, &end);

    long ns = (end.tv_sec - start.tv_sec) * 1000000000L
              + (end.tv_nsec - start.tv_nsec);
    printf("avg: %ld ns/call\n", ns / 10000000);
    /* TSC vDSO: ~20-30ns, HPET syscall: ~600-1000ns */
}

/* 결과 비교 (x86, TSC):
 * CLOCK_MONOTONIC:       ~25ns  (vDSO + rdtsc)
 * CLOCK_MONOTONIC_COARSE: ~5ns  (vDSO, 카운터 읽기 불필요)
 * CLOCK_REALTIME:        ~25ns  (vDSO + rdtsc)
 * CLOCK_REALTIME_COARSE:  ~5ns  (vDSO)
 *
 * vDSO 비활성 또는 HPET clocksource 시:
 * CLOCK_MONOTONIC:      ~600ns  (syscall + MMIO)
 */

9. NTP 시간 동기화

NTP(Network Time Protocol)는 네트워크를 통해 시스템 시계를 외부 기준 시계에 동기화합니다. 커널의 NTP 서브시스템은 adjtimex() 시스템 콜을 통해 시간 보정을 수행합니다.

커널 NTP 보정 메커니즘

/*
 * NTP 보정 동작:
 *
 * 1. ntpd/chronyd가 NTP 서버에서 시간 오프셋 측정
 * 2. adjtimex() 시스템 콜로 커널에 보정 파라미터 전달
 * 3. 커널이 clocksource의 mult 값을 미세 조정
 *    → 클럭 주파수를 가속/감속하여 점진적 보정 (slew)
 * 4. 큰 오프셋(> 0.5초)인 경우 시간 점프 (step)
 *
 * 보정 모드:
 * - PLL (Phase-Locked Loop): 위상+주파수 보정, 안정적
 * - FLL (Frequency-Locked Loop): 주파수만 보정, 빠른 수렴
 */

# NTP 상태 확인
$ adjtimex --print
         mode: 0
       offset: -123 us       # 현재 시간 오프셋
    frequency: -12345678     # 주파수 보정값 (2^-16 ppm)
     maxerror: 500000 us
     esterror: 100 us
       status: 8193          # STA_PLL | STA_NANO
     constant: 7             # PLL 시간 상수 (2^n 초)
    precision: 1 us
    tolerance: 500 ppm
         tick: 10000 us      # 틱 간격

# chronyc로 NTP 상태 확인
$ chronyc tracking
Reference ID    : A.B.C.D (ntp.example.com)
Stratum         : 2
Ref time (UTC)  : Fri Feb 07 10:30:00 2026
System time     : 0.000000123 seconds fast of NTP time
Last offset     : -0.000000045 seconds
RMS offset      : 0.000000089 seconds
Frequency       : 1.234 ppm slow
Residual freq   : -0.001 ppm
Root delay      : 0.012345678 seconds

adjtimex 구조체

/* include/uapi/linux/timex.h */
struct __kernel_timex {
    unsigned int modes;      /* 보정 모드 비트맵 */
    long long    offset;     /* 시간 오프셋 (us 또는 ns) */
    long long    freq;       /* 주파수 오프셋 (2^-16 ppm) */
    long long    maxerror;   /* 최대 추정 오차 (us) */
    long long    esterror;   /* 추정 오차 (us) */
    int          status;     /* 상태 플래그 (STA_*) */
    long long    constant;   /* PLL 시간 상수 */
    long long    precision;  /* 클럭 정밀도 (us) */
    long long    tolerance;  /* 클럭 주파수 허용 오차 (ppm) */
    struct __kernel_timeval time; /* 현재 시간 */
    long long    tick;       /* 틱 간 us */
    long long    ppsfreq;    /* PPS 주파수 (2^-16 ppm) */
    long long    jitter;     /* PPS 지터 (us) */
    int          shift;      /* PPS 인터벌 (초) */
    long long    stabil;     /* PPS 안정성 (ppm) */
    long long    jitcnt;     /* PPS 지터 초과 횟수 */
    long long    calcnt;     /* PPS 보정 간격 수 */
    long long    errcnt;     /* PPS 보정 오류 수 */
    long long    stbcnt;     /* PPS 안정성 초과 횟수 */
    int          tai;        /* TAI 오프셋 (초) */
};

윤초 (Leap Second) 처리

/*
 * 윤초 시 CLOCK_REALTIME 동작:
 *
 * 양의 윤초 (1초 삽입):
 *   23:59:59 → 23:59:60 → 00:00:00
 *   CLOCK_REALTIME이 1초 동안 정지하거나 smear
 *
 * 음의 윤초 (1초 삭제, 이론적):
 *   23:59:58 → 00:00:00 (23:59:59 건너뜀)
 *
 * 커널 처리 옵션:
 * 1. STA_INS/STA_DEL: NTP가 윤초 예고 → 커널이 자정에 처리
 * 2. Leap second smearing: 24시간에 걸쳐 1초를 분산 조정
 *    (Google/Amazon NTP 서버가 제공)
 *
 * 영향받지 않는 시계:
 * - CLOCK_MONOTONIC: 윤초 무관
 * - CLOCK_TAI: 윤초 없는 TAI 시간 (REALTIME + tai_offset)
 */

# 현재 TAI 오프셋 확인 (2024년 기준: 37초)
$ adjtimex --print | grep tai
         tai: 37

10. PTP (Precision Time Protocol)

PTP(IEEE 1588)는 네트워크를 통해 마이크로초~나노초 수준의 시간 동기화를 제공합니다. NTP(밀리초 수준)보다 훨씬 정밀하며, 금융, 통신, 산업 제어에서 사용됩니다.

PHC (PTP Hardware Clock)

/*
 * PTP 지원 NIC는 자체 하드웨어 클럭(PHC)을 내장합니다.
 * PHC는 패킷의 정확한 송수신 타임스탬프를 하드웨어 수준에서 기록하여
 * 소프트웨어 지연에 의한 오차를 제거합니다.
 *
 * Linux에서 PHC는 /dev/ptpN 장치로 노출됩니다.
 */

# PHC 장치 확인
$ ls /dev/ptp*
/dev/ptp0

# PHC 정보 (ethtool)
$ ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
    hardware-transmit     (SOF_TIMESTAMPING_TX_HARDWARE)
    hardware-receive      (SOF_TIMESTAMPING_RX_HARDWARE)
    hardware-raw-clock    (SOF_TIMESTAMPING_RAW_HARDWARE)
    software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)
    software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
PTP Hardware Clock: 0
Hardware Transmit Timestamp Modes:
    on
Hardware Receive Filter Modes:
    all

# PHC와 시스템 클럭 동기화 (phc2sys)
$ phc2sys -s /dev/ptp0 -c CLOCK_REALTIME -O 0 -m
phc2sys[1234]: CLOCK_REALTIME phc offset  -5 s2 freq  -1234 delay  500

# PTP 클럭을 clocksource로 사용
$ ptp4l -i eth0 -m
ptp4l[5678]: master offset   3 s2 freq  -567 path delay  800

커널 PTP 인터페이스

/* include/linux/ptp_clock_kernel.h */
struct ptp_clock_info {
    struct module *owner;
    char name[32];
    s32  max_adj;         /* 최대 주파수 조정 (ppb) */
    int  n_alarm;         /* 알람 채널 수 */
    int  n_ext_ts;        /* 외부 타임스탬프 채널 수 */
    int  n_per_out;       /* 주기적 출력 채널 수 */
    int  n_pins;          /* 프로그래밍 가능 핀 수 */
    int  pps;             /* PPS 지원 여부 */

    /* 클럭 조작 콜백 */
    int  (*adjfine)(struct ptp_clock_info *ptp, long delta);
    int  (*adjtime)(struct ptp_clock_info *ptp, s64 delta);
    int  (*gettime64)(struct ptp_clock_info *ptp,
                       struct timespec64 *ts);
    int  (*settime64)(struct ptp_clock_info *ptp,
                       const struct timespec64 *ts);
    int  (*getcrosststamp)(struct ptp_clock_info *ptp,
                            struct system_device_crosststamp *cts);
};

11. 지연 함수 (Delay/Sleep)

커널에서 시간 지연은 컨텍스트에 따라 적절한 함수를 선택해야 합니다. 잘못된 지연 함수 사용은 성능 저하나 시스템 행(hang)을 유발합니다.

지연 함수 레퍼런스

함수범위방식컨텍스트정밀도
ndelay(ns)1-999 nsbusy-wait모든 (atomic OK)~ns
udelay(us)1-999 usbusy-wait모든 (atomic OK)~us
mdelay(ms)1+ msbusy-wait (반복 udelay)모든 (atomic OK)~ms
usleep_range(min, max)10+ ushrtimer sleep프로세스만~us
msleep(ms)1+ mstimer sleep프로세스만~1/HZ
msleep_interruptible(ms)1+ mstimer sleep프로세스만~1/HZ
ssleep(s)1+ stimer sleep프로세스만~1/HZ
fsleep(us)자동 선택범위에 따라 자동프로세스만최적

지연 함수 상세

#include <linux/delay.h>

/* ===== Busy-wait 지연 (인터럽트/atomic 컨텍스트 OK) ===== */

ndelay(500);           /* 500 나노초 바쁜 대기 */
udelay(100);           /* 100 마이크로초 바쁜 대기 */
mdelay(10);            /* 10 밀리초 바쁜 대기 — 가능하면 피하라! */

/* 주의: udelay는 내부적으로 TSC/루프 기반 바쁜 대기.
 * 부팅 시 calibrate_delay()로 loops_per_jiffy를 계산하여 교정.
 * udelay(1000) 이상은 mdelay(1) 사용 권장 (오버플로 방지) */

/* ===== Sleep 지연 (프로세스 컨텍스트에서만) ===== */

usleep_range(500, 1000);  /* 500-1000us 범위 슬립 (hrtimer 기반)
                            * min~max 범위를 지정하여 타이머 합산(coalescing) 허용
                            * → 전력 효율적 */

msleep(20);                /* 최소 20ms 슬립
                            * schedule_timeout 기반 — 실제 해상도 1/HZ
                            * HZ=250이면 최소 4ms 단위 */

msleep_interruptible(100); /* 시그널로 깨어날 수 있는 슬립 */

ssleep(1);                 /* 1초 슬립 (= msleep(1000)) */

/* ===== fsleep — 범위에 따라 최적 함수 자동 선택 (v5.8+) ===== */
fsleep(500);    /* < 10us → udelay(500) */
fsleep(50);     /* 10us-20ms → usleep_range(50, 2*50) */
fsleep(50000);  /* > 20ms → msleep(50) */

/*
 * fsleep 내부 구현:
 *   if (usecs <= 10)
 *       udelay(usecs);
 *   else if (usecs <= 20000)
 *       usleep_range(usecs, 2 * usecs);
 *   else
 *       msleep(DIV_ROUND_UP(usecs, 1000));
 */
지연 함수 선택 규칙:
  • atomic/인터럽트 컨텍스트: udelay()만 사용 가능. msleep()은 스케줄링이 필요하므로 deadlock 발생
  • 10us 미만: udelay() — hrtimer 오버헤드보다 바쁜 대기가 효율적
  • 10us~20ms: usleep_range() — hrtimer 기반으로 CPU를 양보하면서 정밀 대기
  • 20ms 이상: msleep() — 틱 기반이지만 충분히 큰 단위에서는 적절
  • 범위 불확실: fsleep() — 자동 선택으로 안전
  • mdelay()는 최후의 수단: CPU를 ms 단위로 점유하므로 시스템 응답성 저하

12. Time Namespace

Time namespace(v5.6+)는 컨테이너에 독립적인 CLOCK_MONOTONIC/CLOCK_BOOTTIME 오프셋을 제공합니다. 컨테이너가 호스트의 부팅 시간과 다른 시간 기준을 가질 수 있습니다.

/*
 * Time namespace가 영향을 주는 시계:
 * - CLOCK_MONOTONIC:  오프셋 적용
 * - CLOCK_BOOTTIME:   오프셋 적용
 *
 * 영향받지 않는 시계:
 * - CLOCK_REALTIME:   전체 시스템 공유 (변경 불가)
 * - CLOCK_MONOTONIC_RAW: 원시 하드웨어 값
 *
 * 용도: 컨테이너 마이그레이션 시 MONOTONIC/BOOTTIME 연속성 유지
 */

# Time namespace 오프셋 설정
$ cat /proc/self/timens_offsets
monotonic  0         0
boottime   0         0

# 새 time namespace 생성 (unshare)
$ unshare --time --fork --mount-proc bash

# namespace 내에서 오프셋 설정 (생성 직후에만 가능)
$ echo "monotonic 86400 0" > /proc/self/timens_offsets
# → 이 namespace의 CLOCK_MONOTONIC은 86400초(1일) 미래

# 확인
$ clock_gettime CLOCK_MONOTONIC  # 호스트 + 86400초

13. 시간 관련 디버깅

일반적인 문제와 해결

증상원인진단해결
시간이 갑자기 점프NTP step 보정, settimeofday()dmesg에서 clock set 확인CLOCK_MONOTONIC 사용
TSC unstable 경고CPU 주파수 변동, C-state 문제dmesg | grep -i tsctsc=reliable 또는 nohz=off
clock_gettime() 느림HPET/ACPI PM clocksource 사용clocksource 확인TSC로 변경
suspend 후 타이머 폭발CLOCK_MONOTONIC 기반 타이머BOOTTIME vs MONOTONICCLOCK_BOOTTIME 사용
VM에서 시간 드리프트vCPU 스케줄링 지연chronyc trackingkvm-clock + 게스트 NTP
윤초 시 시스템 이상CLOCK_REALTIME 점프adjtimex tai 필드CLOCK_TAI 또는 smeared NTP
usleep_range()가 예상보다 오래HZ 해상도, 시스템 부하ftrace 타이머 트레이싱범위 조정, hrtimer 확인

디버깅 명령 모음

# ============ Clocksource 확인 ============
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource

# ============ 시간 해상도 ============
$ cat /proc/timer_list | head -20
Timer List Version: v0.9
HRTIMER_MAX_CLOCK_BASES: 8
now at 123456789012345 nsecs

# clock_getres()로 각 시계 해상도 확인
$ python3 -c "
import time
for c in ['CLOCK_REALTIME','CLOCK_MONOTONIC','CLOCK_MONOTONIC_RAW',
          'CLOCK_BOOTTIME','CLOCK_MONOTONIC_COARSE']:
    r = time.clock_getres(getattr(time, c))
    print(f'{c}: {r*1e9:.0f} ns')
"
CLOCK_REALTIME: 1 ns
CLOCK_MONOTONIC: 1 ns
CLOCK_MONOTONIC_RAW: 1 ns
CLOCK_BOOTTIME: 1 ns
CLOCK_MONOTONIC_COARSE: 4000000 ns

# ============ NTP/PTP 상태 ============
$ chronyc tracking              # NTP 동기화 상태
$ chronyc sources -v            # NTP 소스 목록
$ adjtimex --print              # 커널 NTP 파라미터
$ ethtool -T eth0               # PTP 하드웨어 타임스탬프 지원
$ pmc -u -b 0 'GET TIME_STATUS_NP'  # PTP 상태

# ============ TSC 진단 (x86) ============
$ dmesg | grep -iE 'tsc|clocksource|calibrat'
$ grep -o 'constant_tsc\|nonstop_tsc\|rdtscp\|tsc_known_freq' /proc/cpuinfo | sort -u

# ============ ftrace로 타이머 트레이싱 ============
$ echo 1 > /sys/kernel/tracing/events/timer/enable
$ echo 1 > /sys/kernel/tracing/events/hrtimer/enable
$ cat /sys/kernel/tracing/trace_pipe
# hrtimer_start: hrtimer=... function=tick_sched_timer expires=...
# hrtimer_expire_entry: hrtimer=... function=tick_sched_timer now=...

# ============ 지연 교정 확인 ============
$ dmesg | grep -i 'calibrat\|loops_per\|bogomips'
Calibrating delay loop (skipped), value calculated using timer frequency..
    6000.00 BogoMIPS (lpj=12000000)

# /proc/timer_list — 모든 활성 타이머 덤프
$ cat /proc/timer_list | grep -A5 "clock 0:"

14. 커널 설정 종합

# ===== 시간/클럭 관련 커널 설정 종합 =====

# -- 기본 시간 관리 --
CONFIG_HZ_250=y                     # 타이머 틱 주파수 (100/250/300/1000)
CONFIG_HIGH_RES_TIMERS=y            # 고해상도 타이머 (hrtimer)
CONFIG_GENERIC_CLOCKEVENTS=y        # clockevent 프레임워크
CONFIG_POSIX_TIMERS=y               # POSIX 타이머 지원

# -- Tickless 커널 --
CONFIG_NO_HZ_IDLE=y                 # 유휴 시 틱 생략 (전력 절약)
# CONFIG_NO_HZ_FULL=y              # 완전 tickless (고성능/RT)

# -- Clocksource --
CONFIG_X86_TSC=y                    # TSC 지원 (x86)
CONFIG_HPET=y                       # HPET 지원
CONFIG_HPET_TIMER=y                 # HPET clockevent
CONFIG_X86_PM_TIMER=y               # ACPI PM Timer
CONFIG_ARM_ARCH_TIMER=y             # ARM Generic Timer
CONFIG_PARAVIRT_CLOCK=y             # KVM pvclock
CONFIG_KVM_GUEST=y                  # KVM 게스트 지원

# -- vDSO --
CONFIG_GENERIC_VDSO_TIME_NS=y       # Time namespace vDSO 지원

# -- NTP --
CONFIG_NTP_PPS=y                    # PPS (Pulse Per Second) 지원

# -- PTP --
CONFIG_PTP_1588_CLOCK=y             # PTP 하드웨어 클럭 프레임워크
CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_DP83640_PHY=y                # PTP PHY 드라이버 (예시)

# -- Time Namespace --
CONFIG_TIME_NS=y                    # Time namespace

# -- RTC --
CONFIG_RTC_CLASS=y                  # RTC 프레임워크
CONFIG_RTC_HCTOSYS=y                # 부팅 시 RTC → 시스템 시계
CONFIG_RTC_SYSTOHC=y                # 주기적 시스템 → RTC 동기화

# -- CPU 주파수 관리 (cpufreq) --
CONFIG_CPU_FREQ=y                   # cpufreq 프레임워크
CONFIG_CPU_FREQ_STAT=y              # 주파수 전환 통계
CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y # 기본 거버너: schedutil
CONFIG_CPU_FREQ_GOV_PERFORMANCE=y   # performance 거버너
CONFIG_CPU_FREQ_GOV_POWERSAVE=y     # powersave 거버너
CONFIG_CPU_FREQ_GOV_USERSPACE=y     # userspace 거버너
CONFIG_CPU_FREQ_GOV_ONDEMAND=y      # ondemand 거버너
CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y  # conservative 거버너
CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y     # schedutil 거버너
CONFIG_X86_INTEL_PSTATE=y           # Intel P-State 드라이버
CONFIG_X86_AMD_PSTATE=y             # AMD P-State 드라이버 (v5.17+)
CONFIG_X86_ACPI_CPUFREQ=y           # ACPI cpufreq 드라이버
CONFIG_ENERGY_MODEL=y               # EAS 에너지 모델

# -- 디버깅 --
CONFIG_CLOCKSOURCE_WATCHDOG=y       # clocksource 안정성 감시
CONFIG_DEBUG_TIMEKEEPING=y          # timekeeping 디버그 경고