타이머 (Timers)

jiffies, hrtimer, clocksource, clockevent, timer wheel, tickless 커널 등 Linux 커널의 시간 관리 체계를 설명합니다.

관련 표준: IEEE 1588 (PTP, 정밀 시간 동기화), ACPI 6.5 (타이머 하드웨어, C-states) — 커널 타이머 서브시스템이 참조하는 시간 동기화 및 하드웨어 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
전제 조건: 인터럽트(타이머 인터럽트, IRQ 처리 흐름)를 먼저 읽으세요.
일상 비유: 커널 타이머는 다양한 종류의 알람 시계와 같습니다. jiffies는 벽시계의 초침 — 일정 간격(보통 1ms)으로 똑딱거립니다. hrtimer는 스톱워치 — 나노초 정밀도로 정확한 시간을 잽니다. timer wheel은 여러 알람을 시간대별 칸에 정리해둔 원형 달력입니다. tickless(NO_HZ)는 "아무도 알람을 맞추지 않았으면 초침을 멈추는" 절전 모드입니다.

핵심 요약

  • jiffies — 부팅 이후 경과한 타이머 틱 수. HZ(보통 1000)가 초당 틱 수를 결정합니다.
  • timer_list — jiffies 기반 저해상도 타이머. timer wheel 알고리즘으로 관리됩니다.
  • hrtimer — 나노초 해상도의 고해상도 타이머. Red-Black 트리로 관리됩니다.
  • clocksource — 시간 측정 하드웨어(TSC, HPET 등)를 추상화하는 프레임워크입니다.
  • NO_HZ(tickless) — idle CPU에서 불필요한 타이머 인터럽트를 생략하여 전력을 절약합니다.

단계별 이해

  1. 타이머 틱 — 타이머 하드웨어가 HZ 주기로 인터럽트를 발생시키고, 커널이 jiffies를 증가시킵니다.

    cat /proc/timer_list로 현재 활성 타이머를 확인할 수 있습니다.

  2. 저해상도 타이머mod_timer()로 jiffies 기반 타이머를 설정합니다. 밀리초 단위 정확도.

    네트워크 타임아웃, 디바이스 폴링 등에 사용됩니다.

  3. 고해상도 타이머hrtimer_start()로 나노초 정밀 타이머를 설정합니다.

    POSIX 타이머, nanosleep(), 스케줄러 타임슬라이스에 사용됩니다.

  4. tickless 확인grep CONFIG_NO_HZ /boot/config-$(uname -r)로 tickless 설정을 확인합니다.

    idle 상태에서 타이머 인터럽트를 생략하여 C-state 깊은 절전에 진입합니다.

타이머 서브시스템의 정의와 역할

타이머 서브시스템은 커널이 시간의 흐름을 인식하고 미래 시점에 작업을 예약할 수 있게 하는 핵심 인프라입니다. 이 서브시스템이 없으면 프로세스 스케줄링, 네트워크 타임아웃, 디바이스 폴링, 슬립 등 시간 기반 동작이 모두 불가능합니다.

타이머 서브시스템은 크게 세 가지 계층으로 구성됩니다:

타이머 서브시스템 계층 구조 하드웨어 계층 TSC, HPET, Local APIC Timer, ARM Arch Timer 프레임워크 계층 (clocksource / clockevent) 시간 측정 추상화 + 미래 인터럽트 프로그래밍 저해상도 타이머 (Timer Wheel) jiffies 단위, softirq 컨텍스트 timer_list, mod_timer() 고해상도 타이머 (hrtimer) 나노초 단위, HW 클럭 직접 프로그래밍 hrtimer, hrtimer_start()
타이머 서브시스템: 하드웨어 → clocksource/clockevent 프레임워크 → Timer Wheel / hrtimer API
💡

핵심 설계 원리: 커널 타이머는 "정확한 시점에 실행"이 아니라 "지정된 시점 이후 가능한 빨리 실행"을 보장합니다. 하드웨어 인터럽트 지연, softirq 스케줄링 등으로 수 마이크로초~밀리초의 지터(jitter)가 발생할 수 있습니다.

jiffies와 HZ

jiffies는 시스템 부팅 이후 발생한 타이머 틱(tick) 횟수를 저장하는 전역 변수입니다. HZ는 초당 틱 수를 나타내며, 일반적으로 x86에서는 250 (CONFIG_HZ_250)으로 설정됩니다. HZ 값의 선택은 타이머 해상도와 시스템 오버헤드 사이의 트레이드오프입니다 — HZ가 높을수록 타이머 정밀도가 향상되지만, 매 틱마다 인터럽트를 처리하므로 CPU 오버헤드가 증가합니다.

#include <linux/jiffies.h>

/* 현재 jiffies 값 */
unsigned long j = jiffies;

/* 시간 비교 (wraparound 안전) */
if (time_after(jiffies, timeout))
    pr_info("timeout expired\n");

/* jiffies ↔ 시간 변환 */
unsigned long ms = jiffies_to_msecs(j);
unsigned long j2 = msecs_to_jiffies(500);  /* 500ms */

/* 64-bit jiffies (overflow 방지) */
u64 j64 = get_jiffies_64();

Timer Wheel (저해상도 타이머)

전통적인 커널 타이머는 struct timer_list를 사용하며, Timer Wheel 자료구조로 관리됩니다. 만료 시 softirq 컨텍스트(TIMER_SOFTIRQ)에서 콜백이 실행됩니다.

Timer Wheel 동작 원리

Timer Wheel은 계층적 해시 테이블의 원리로 동작합니다. 핵심 아이디어는 "가까운 미래의 타이머는 정밀하게, 먼 미래의 타이머는 대략적으로 관리"하는 것입니다:

설계 배경: 초기 커널(~2.6.16)은 단일 수준의 정렬된 리스트를 사용했으나, 타이머 수가 증가하면서 O(n) 삽입이 병목이 되었습니다. 계층적 Timer Wheel은 O(1) 삽입/삭제를 달성하며, 현재 커널(4.8+)은 4레벨 구조로 최대 약 12일(HZ=250 기준)까지 커버합니다.

Hierarchical Timer Wheel Level 0 64 slots (0~63 ticks) Level 1 64 slots (64~4095) Level 2 64 slots (~262K) Level 3 64 slots (~16M) Level 0 상세 (tick 단위 슬롯): 0 1 2 3 ... 63 current
계층적 Timer Wheel: 가까운 만료 시간은 Level 0, 먼 시간은 상위 레벨에서 관리
#include <linux/timer.h>

static struct timer_list my_timer;

static void my_timer_callback(struct timer_list *t)
{
    pr_info("timer expired at jiffies=%lu\n", jiffies);
    /* Reschedule for periodic timer */
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}

/* 초기화 및 시작 */
timer_setup(&my_timer, my_timer_callback, 0);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));

/* 해제 (동기적, 콜백 완료 대기) */
del_timer_sync(&my_timer);

고해상도 타이머 (hrtimer)

hrtimer는 나노초 단위의 정밀한 타이머입니다. Timer Wheel 대신 red-black tree로 관리되며, 하드웨어 클럭 이벤트에 직접 프로그래밍합니다.

hrtimer 동작 원리

hrtimer가 Timer Wheel과 근본적으로 다른 점은 틱에 의존하지 않는다는 것입니다:

이 방식은 Timer Wheel의 틱 해상도(1/HZ초 = 4ms@250Hz) 제약을 극복하여, 하드웨어가 지원하는 한 나노초 수준의 정밀도를 달성합니다. POSIX 타이머, nanosleep(), 스케줄러의 bandwidth throttling 등이 hrtimer를 기반으로 합니다.

#include <linux/hrtimer.h>
#include <linux/ktime.h>

static struct hrtimer my_hrtimer;

static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer)
{
    pr_info("hrtimer fired!\n");

    /* Periodic: restart after 10ms */
    hrtimer_forward_now(timer, ms_to_ktime(10));
    return HRTIMER_RESTART;

    /* One-shot: don't restart */
    /* return HRTIMER_NORESTART; */
}

/* 초기화 및 시작 */
hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_hrtimer.function = my_hrtimer_callback;
hrtimer_start(&my_hrtimer, ms_to_ktime(10), HRTIMER_MODE_REL);

/* 취소 */
hrtimer_cancel(&my_hrtimer);

clocksource와 clockevent

clocksource는 시간 측정을 위한 하드웨어 클럭 추상화이고, clockevent는 미래 시점에 인터럽트를 발생시키는 하드웨어 타이머 추상화입니다. 대표적인 clocksource로는 TSC(x86, ~GHz), HPET(14MHz), ACPI PM Timer(3.58MHz), ARM arch timer 등이 있으며, clockevent는 Local APIC Timer(Per-CPU tick), HPET, ARM arch timer 등이 담당합니다.

💡

clocksource/clockevent 프레임워크, 하드웨어 클럭별 상세 비교, clocksource 등록 API, rating 시스템 등은 ktime / Clock 심화 — clocksource 프레임워크에서 자세히 다룹니다.

지연 함수 (Delay Functions)

커널은 다양한 지연 함수를 제공합니다. 인터럽트 컨텍스트에서는 busy-wait(udelay(), ndelay())만 사용 가능하고, 프로세스 컨텍스트에서는 슬립 기반(usleep_range(), msleep())을 사용해야 합니다. 10ms 이상은 msleep(), 10us~10ms는 usleep_range(), 10us 미만은 udelay()를 권장합니다.

💡

각 지연 함수의 내부 구현, 정밀도 비교, fsleep() 통합 API 등은 ktime / Clock 심화 — 지연 함수에서 상세히 다룹니다.

Tickless 커널 (NO_HZ)

전통적인 커널은 매 tick(1/HZ초)마다 타이머 인터럽트를 발생시켰지만, tickless 커널은 불필요한 tick을 제거하여 전력 소모를 줄입니다.

Tickless 동작 원리

Tickless의 핵심 원리는 "다음에 해야 할 일이 없으면 깨우지 않는다"입니다:

  1. CPU가 idle에 진입하기 전, 다음으로 만료될 타이머(Timer Wheel + hrtimer)의 시간을 확인합니다.
  2. 그 시간까지 주기적 틱 인터럽트를 중단하고, 대신 하나의 oneshot clockevent만 해당 시점에 프로그래밍합니다.
  3. CPU는 깊은 C-state(저전력 상태)에 진입하여 전력을 절약합니다.
  4. 타이머 만료 시점에 clockevent 인터럽트가 CPU를 깨우고, 밀린 jiffies를 한꺼번에 보정합니다.

ktime API

ktime_t는 나노초 단위의 시간을 표현하는 통일된 타입입니다. ktime_get()(monotonic), ktime_get_real()(wall clock), ktime_get_boottime()(suspend 포함) 등으로 현재 시간을 얻고, ktime_add()/ktime_sub()/ktime_us_delta() 등으로 산술 연산합니다.

💡

ktime_t 전체 함수 레퍼런스, 변환 매크로, 경과 시간 측정 패턴, clockevent 프레임워크(PERIODIC/ONESHOT 모드, 디바이스 등록) 등은 ktime / Clock 심화에서 상세히 다룹니다.

POSIX 타이머 (유저스페이스)

커널은 유저스페이스 POSIX 타이머를 hrtimer 기반으로 구현합니다:

/* 유저스페이스: timer_create, timer_settime */
/* 커널 내부: posix-timers.c → hrtimer */

/* 주요 클럭 ID */
CLOCK_REALTIME       /* 벽시계 (NTP 조정 가능) */
CLOCK_MONOTONIC      /* 단조 증가 (NTP 조정 없음) */
CLOCK_BOOTTIME       /* MONOTONIC + suspend 시간 */
CLOCK_PROCESS_CPUTIME_ID  /* 프로세스 CPU 시간 */
CLOCK_THREAD_CPUTIME_ID   /* 스레드 CPU 시간 */

타이머 마이그레이션과 그룹핑

전력 효율을 위해 커널은 여러 타이머를 같은 시점에 만료되도록 그룹핑합니다:

/* 타이머를 "느슨하게" 설정하면 커널이 그룹핑 가능 */
mod_timer(&timer, jiffies + msecs_to_jiffies(1000));

/* timer_setup_on_stack: 스택 기반 타이머 (짧은 수명) */

/* sysctl 파라미터 */
/* /proc/sys/kernel/timer_migration = 1 */
/* idle CPU의 타이머를 busy CPU로 마이그레이션 */
/* → idle CPU가 더 오래 슬립 가능 (전력 절약) */
💡

10ms 이상의 지연에는 msleep(), 10us~10ms에는 usleep_range(), 10us 미만에는 udelay()를 사용하세요. mdelay()는 CPU를 오래 점유하므로 가급적 사용하지 마세요.

RTC (Real-Time Clock) 서브시스템

RTC는 시스템 전원이 꺼져 있을 때도 배터리로 유지되는 하드웨어 시계입니다. 커널은 부팅 시 RTC에서 시간을 읽어 시스템 시계를 초기화하고, RTC 알람으로 시스템을 깨울 수 있습니다.

RTC 아키텍처

RTC 유형인터페이스정밀도커널 드라이버
CMOS RTC I/O 포트 0x70/0x71 (x86) 1초 drivers/rtc/rtc-cmos.c
I2C RTC I2C 버스 (DS1307, PCF8523 등) 1초 drivers/rtc/rtc-ds1307.c
SoC 내장 RTC MMIO (SoC 레지스터) 서브초 가능 drivers/rtc/rtc-* (벤더별)
PL031 (ARM) MMIO (AMBA PrimeCell) 1초 drivers/rtc/rtc-pl031.c

RTC 드라이버 구현

#include <linux/rtc.h>

/* RTC 오퍼레이션 구조체 */
static const struct rtc_class_ops my_rtc_ops = {
    .read_time  = my_read_time,     /* 현재 시각 읽기 */
    .set_time   = my_set_time,      /* 시각 설정 */
    .read_alarm = my_read_alarm,    /* 알람 읽기 */
    .set_alarm  = my_set_alarm,     /* 알람 설정 */
    .alarm_irq_enable = my_alarm_irq_enable,
};

/* 시간 읽기 콜백 */
static int my_read_time(struct device *dev, struct rtc_time *tm)
{
    /* H/W 레지스터에서 BCD 또는 바이너리로 읽기 */
    tm->tm_sec  = readl(base + RTC_SEC);
    tm->tm_min  = readl(base + RTC_MIN);
    tm->tm_hour = readl(base + RTC_HOUR);
    tm->tm_mday = readl(base + RTC_DAY);
    tm->tm_mon  = readl(base + RTC_MON) - 1;  /* 0-based */
    tm->tm_year = readl(base + RTC_YEAR) - 1900;
    return 0;
}

/* RTC 디바이스 등록 */
struct rtc_device *rtc = devm_rtc_device_register(&pdev->dev,
    "my-rtc", &my_rtc_ops, THIS_MODULE);

/* 또는 현대적 API (5.x+) */
rtc = devm_rtc_allocate_device(&pdev->dev);
rtc->ops = &my_rtc_ops;
rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
rtc->range_max = RTC_TIMESTAMP_END_2099;
devm_rtc_register_device(rtc);

유저 공간 RTC 관리

# RTC 시간 읽기
hwclock --show                 # /dev/rtc0에서 읽기
cat /sys/class/rtc/rtc0/time   # sysfs에서 읽기
cat /proc/driver/rtc           # CMOS RTC 상세 정보 (x86)

# RTC에 시스템 시간 쓰기
hwclock --systohc              # 시스템 시간 → RTC
hwclock --hctosys              # RTC → 시스템 시간 (부팅 시)

# UTC vs Local Time (주의!)
hwclock --systohc --utc        # RTC를 UTC로 설정 (Linux 권장)
hwclock --systohc --localtime  # 로컬 시간 (Windows 듀얼부팅 시)
timedatectl set-local-rtc 0    # systemd에서 UTC 모드 설정

# RTC 알람 설정 (시스템 웨이크업)
echo +60 > /sys/class/rtc/rtc0/wakealarm     # 60초 후 깨우기
echo 0 > /sys/class/rtc/rtc0/wakealarm       # 알람 해제
rtcwake -m mem -s 300          # suspend 후 300초 뒤 깨우기

RTC 주의사항

RTC 개발/운영 주의사항:
  • Y2038 문제 — 32비트 time_t를 사용하는 구형 RTC는 2038년에 오버플로. 커널은 rtc_time64_to_tm()으로 64비트 전환 완료. 드라이버에서 range_min/range_max 명시 필요
  • BCD vs 바이너리 — 일부 RTC는 BCD 인코딩. bcd2bin()/bin2bcd()로 변환. 잘못된 변환은 날짜 오류
  • 레지스터 읽기 경합 — RTC 레지스터 읽기 중 초가 변경되면 불일치 데이터 반환. Update-In-Progress(UIP) 비트 확인 또는 두 번 읽어서 비교
  • 배터리 고갈 — CMOS 배터리(CR2032) 소진 시 시간 초기화. 부팅 시 NTP 동기화로 보상하지만, NTP 없는 임베디드 환경에서 문제
  • UTC/로컬 타임 혼동 — Linux는 RTC를 UTC로, Windows는 로컬 타임으로 가정. 듀얼부팅 시 시간이 틀어지는 원인
  • RTC 알람과 suspend — S3(suspend-to-RAM)에서 RTC 알람으로 깨울 수 있지만, 모든 RTC가 알람 IRQ를 지원하지는 않음. /sys/class/rtc/rtc0/wakealarm 지원 여부 확인
  • NTP 드리프트 보상 — RTC는 수십 ppm의 오차 가능(월 수 초). adjtimex로 커널이 NTP와 RTC 간 주기적 보정