01 · QUESTION
무엇을 확인할 것인가
cycle counter를 nanosecond로 변환할 때 overflow 없이 monotonicity를 유지하고, idle CPU의 다음 deadline을 어떻게 깨우는가?
clocksource는 연속 counter read와 mult/shift 변환을 제공하고 clockevent는 one-shot deadline interrupt를 제공한다. timekeeping seqlock이 base cycle과 base ns를 일관되게 읽으며 hrtimer가 가장 이른 만료를 device에 program한다.
counter read, timekeeper update, per-CPU clockevent program, interrupt handler와 timer callback을 나눈다. callback은 hardirq 또는 softirq context이므로 sleep 가능 여부가 다르다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | CNTVCT/CNTPCT counter와 CNTV_TVAL/CVAL comparator | CNTFRQ 불일치, unstable counter erratum 또는 comparator program 뒤 즉시 만료 race는 시간 역행과 interrupt storm을 만든다. | CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다. |
| x86-64 | TSC clocksource와 local APIC TSC-deadline/one-shot timer | socket 간 TSC offset, virtualization migration, 잘못된 calibration은 monotonicity 위반과 timer 조기/지연 만료를 만든다. | CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다. |
| RISC-V | TIME CSR/SBI와 Sstc STIMECMP comparator | firmware trap latency, 잘못된 timebase-frequency 또는 RV32 comparator write race가 긴 wakeup 지연이나 즉시 재interrupt를 만든다. | timebase-frequency, TIME, STIMECMP/SBI call, SIE.STIE, timer pending과 clockevent min_delta를 본다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- CNTVCT/CNTPCT counter와 CNTV_TVAL/CVAL comparator
- state
- architected timer frequency CNTFRQ를 기준으로 virtual 또는 physical counter를 clocksource로 등록한다. per-CPU PPI가 comparator 만료를 알리고 TVAL 또는 CVAL에 다음 event를 쓴다.
- checkpoint
- CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다.
x86-64
- mechanism
- TSC clocksource와 local APIC TSC-deadline/one-shot timer
- state
- invariant/nonstop TSC가 충분히 신뢰되면 RDTS(C/P)가 clocksource가 되고 CPUID/calibration으로 frequency를 구한다. local APIC timer는 TSC-deadline MSR 또는 divide-count mode로 per-CPU event를 만든다.
- checkpoint
- CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다.
RISC-V
- mechanism
- TIME CSR/SBI와 Sstc STIMECMP comparator
- state
- time CSR의 shared frequency를 DT timebase-frequency에서 얻어 clocksource_sched_clock에 등록한다. clockevent는 Sstc가 있으면 STIMECMP를 직접 쓰고 아니면 SBI set_timer로 firmware에 deadline을 요청한다.
- checkpoint
- timebase-frequency, TIME, STIMECMP/SBI call, SIE.STIE, timer pending과 clockevent min_delta를 본다.
04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
CNTVCT/CNTPCT counter와 CNTV_TVAL/CVAL comparator
architected timer frequency CNTFRQ를 기준으로 virtual 또는 physical counter를 clocksource로 등록한다. per-CPU PPI가 comparator 만료를 알리고 TVAL 또는 CVAL에 다음 event를 쓴다.
원본 코드: arch/arm64/kernel/time.c:49-73
49 arch_stack_walk(profile_pc_cb, &prof_pc, current, regs);
50
51 return prof_pc;
52}
53EXPORT_SYMBOL(profile_pc);
54
55void __init time_init(void)
56{
57 u32 arch_timer_rate;
58
59 of_clk_init(NULL);
60 timer_probe();
61
62 tick_setup_hrtimer_broadcast();
63
64 arch_timer_rate = arch_timer_get_rate();
65 if (!arch_timer_rate)
66 panic("Unable to initialise architected timer.\n");
67
68 /* Calibrate the delay loop directly */
69 lpj_fine = arch_timer_rate / HZ;
70
71 pv_time_init();
72}
73 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 25개 줄에 각각 설명을 붙였습니다.
arch_stack_walk(profile_pc_cb, &prof_pc, current, regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return prof_pc;이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
EXPORT_SYMBOL(profile_pc);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void __init time_init(void)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
u32 arch_timer_rate;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
of_clk_init(NULL);devicetree의 platform clock provider를 먼저 초기화해 timer 외 장치가 사용할 clock tree를 준비한다.
timer_probe();DT/ACPI에서 architected timer 등 clocksource/clockevent driver를 탐색하고 등록한다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
tick_setup_hrtimer_broadcast();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
arch_timer_rate = arch_timer_get_rate();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (!arch_timer_rate)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
panic("Unable to initialise architected timer.\n");불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Calibrate the delay loop directly */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
lpj_fine = arch_timer_rate / HZ;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
pv_time_init();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86-64 · Linux 6.18.37
TSC clocksource와 local APIC TSC-deadline/one-shot timer
invariant/nonstop TSC가 충분히 신뢰되면 RDTS(C/P)가 clocksource가 되고 CPUID/calibration으로 frequency를 구한다. local APIC timer는 TSC-deadline MSR 또는 divide-count mode로 per-CPU event를 만든다.
원본 코드: arch/x86/kernel/tsc.c:1519-1597
1519
1520 snp_secure_tsc_init();
1521
1522 if (!determine_cpu_tsc_frequencies(true))
1523 return;
1524 tsc_enable_sched_clock();
1525}
1526
1527void __init tsc_init(void)
1528{
1529 if (!cpu_feature_enabled(X86_FEATURE_TSC)) {
1530 setup_clear_cpu_cap(X86_FEATURE_TSC_DEADLINE_TIMER);
1531 return;
1532 }
1533
1534 /*
1535 * native_calibrate_cpu_early can only calibrate using methods that are
1536 * available early in boot.
1537 */
1538 if (x86_platform.calibrate_cpu == native_calibrate_cpu_early)
1539 x86_platform.calibrate_cpu = native_calibrate_cpu;
1540
1541 if (!tsc_khz) {
1542 /* We failed to determine frequencies earlier, try again */
1543 if (!determine_cpu_tsc_frequencies(false)) {
1544 mark_tsc_unstable("could not calculate TSC khz");
1545 setup_clear_cpu_cap(X86_FEATURE_TSC_DEADLINE_TIMER);
1546 return;
1547 }
1548 tsc_enable_sched_clock();
1549 }
1550
1551 cyc2ns_init_secondary_cpus();
1552
1553 if (!no_sched_irq_time)
1554 enable_sched_clock_irqtime();
1555
1556 lpj_fine = get_loops_per_jiffy();
1557
1558 check_system_tsc_reliable();
1559
1560 if (unsynchronized_tsc()) {
1561 mark_tsc_unstable("TSCs unsynchronized");
1562 return;
1563 }
1564
1565 if (tsc_clocksource_reliable || no_tsc_watchdog)
1566 tsc_disable_clocksource_watchdog();
1567
1568 clocksource_register_khz(&clocksource_tsc_early, tsc_khz);
1569 detect_art();
1570}
1571
1572#ifdef CONFIG_SMP
1573/*
1574 * Check whether existing calibration data can be reused.
1575 */
1576unsigned long calibrate_delay_is_known(void)
1577{
1578 int sibling, cpu = smp_processor_id();
1579 int constant_tsc = cpu_has(&cpu_data(cpu), X86_FEATURE_CONSTANT_TSC);
1580 const struct cpumask *mask = topology_core_cpumask(cpu);
1581
1582 /*
1583 * If TSC has constant frequency and TSC is synchronized across
1584 * sockets then reuse CPU0 calibration.
1585 */
1586 if (constant_tsc && !tsc_unstable)
1587 return cpu_data(0).loops_per_jiffy;
1588
1589 /*
1590 * If TSC has constant frequency and TSC is not synchronized across
1591 * sockets and this is not the first CPU in the socket, then reuse
1592 * the calibration value of an already online CPU on that socket.
1593 *
1594 * This assumes that CONSTANT_TSC is consistent for all CPUs in a
1595 * socket.
1596 */
1597 if (!constant_tsc || !mask)라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 79개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
snp_secure_tsc_init();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!determine_cpu_tsc_frequencies(true))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return;이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
tsc_enable_sched_clock();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void __init tsc_init(void)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (!cpu_feature_enabled(X86_FEATURE_TSC)) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
setup_clear_cpu_cap(X86_FEATURE_TSC_DEADLINE_TIMER);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* native_calibrate_cpu_early can only calibrate using methods that areLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* available early in boot.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (x86_platform.calibrate_cpu == native_calibrate_cpu_early)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
x86_platform.calibrate_cpu = native_calibrate_cpu;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!tsc_khz) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
/* We failed to determine frequencies earlier, try again */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (!determine_cpu_tsc_frequencies(false)) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
mark_tsc_unstable("could not calculate TSC khz");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
setup_clear_cpu_cap(X86_FEATURE_TSC_DEADLINE_TIMER);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
tsc_enable_sched_clock();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
cyc2ns_init_secondary_cpus();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!no_sched_irq_time)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
enable_sched_clock_irqtime();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
lpj_fine = get_loops_per_jiffy();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
check_system_tsc_reliable();platform/CPU 특성을 반영해 clocksource watchdog 없이 신뢰할 수 있는지 판단한다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (unsynchronized_tsc()) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
mark_tsc_unstable("TSCs unsynchronized");helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (tsc_clocksource_reliable || no_tsc_watchdog)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
tsc_disable_clocksource_watchdog();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
clocksource_register_khz(&clocksource_tsc_early, tsc_khz);변환 mult/shift를 계산할 수 있도록 TSC frequency와 clocksource를 timekeeping core에 등록한다.
detect_art();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_SMPKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Check whether existing calibration data can be reused.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
unsigned long calibrate_delay_is_known(void)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int sibling, cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
int constant_tsc = cpu_has(&cpu_data(cpu), X86_FEATURE_CONSTANT_TSC);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
const struct cpumask *mask = topology_core_cpumask(cpu);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If TSC has constant frequency and TSC is synchronized acrossLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* sockets then reuse CPU0 calibration.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (constant_tsc && !tsc_unstable)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return cpu_data(0).loops_per_jiffy;이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If TSC has constant frequency and TSC is not synchronized acrossLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* sockets and this is not the first CPU in the socket, then reuseLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the calibration value of an already online CPU on that socket.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* This assumes that CONSTANT_TSC is consistent for all CPUs in aLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* socket.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (!constant_tsc || !mask)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
RISC-V · Linux 6.18.37
TIME CSR/SBI와 Sstc STIMECMP comparator
time CSR의 shared frequency를 DT timebase-frequency에서 얻어 clocksource_sched_clock에 등록한다. clockevent는 Sstc가 있으면 STIMECMP를 직접 쓰고 아니면 SBI set_timer로 firmware에 deadline을 요청한다.
원본 코드: arch/riscv/kernel/time.c:14-52
14#include <asm/timex.h>
15#include <asm/paravirt.h>
16
17unsigned long riscv_timebase __ro_after_init;
18EXPORT_SYMBOL_GPL(riscv_timebase);
19
20void __init time_init(void)
21{
22 struct device_node *cpu;
23 struct acpi_table_rhct *rhct;
24 acpi_status status;
25 u32 prop;
26
27 if (acpi_disabled) {
28 cpu = of_find_node_by_path("/cpus");
29 if (!cpu || of_property_read_u32(cpu, "timebase-frequency", &prop))
30 panic("RISC-V system with no 'timebase-frequency' in DTS\n");
31
32 of_node_put(cpu);
33 riscv_timebase = prop;
34 of_clk_init(NULL);
35 } else {
36 status = acpi_get_table(ACPI_SIG_RHCT, 0, (struct acpi_table_header **)&rhct);
37 if (ACPI_FAILURE(status))
38 panic("RISC-V ACPI system with no RHCT table\n");
39
40 riscv_timebase = rhct->time_base_freq;
41 acpi_put_table((struct acpi_table_header *)rhct);
42 }
43
44 lpj_fine = riscv_timebase / HZ;
45
46 timer_probe();
47
48 tick_setup_hrtimer_broadcast();
49
50 pv_time_init();
51}
52 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 39개 줄에 각각 설명을 붙였습니다.
#include <asm/timex.h>compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#include <asm/paravirt.h>compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
unsigned long riscv_timebase __ro_after_init;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
EXPORT_SYMBOL_GPL(riscv_timebase);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void __init time_init(void)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct device_node *cpu;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct acpi_table_rhct *rhct;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
acpi_status status;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
u32 prop;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (acpi_disabled) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
cpu = of_find_node_by_path("/cpus");helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (!cpu || of_property_read_u32(cpu, "timebase-frequency", &prop))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
panic("RISC-V system with no 'timebase-frequency' in DTS\n");불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
of_node_put(cpu);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
riscv_timebase = prop;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
of_clk_init(NULL);timer와 주변 장치가 참조할 devicetree clock provider를 초기화한다.
} else {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Timer와 clocksource: architected timer, TSC와 RISC-V time의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
status = acpi_get_table(ACPI_SIG_RHCT, 0, (struct acpi_table_header **)&rhct);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (ACPI_FAILURE(status))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
panic("RISC-V ACPI system with no RHCT table\n");불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
riscv_timebase = rhct->time_base_freq;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
acpi_put_table((struct acpi_table_header *)rhct);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
lpj_fine = riscv_timebase / HZ;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
timer_probe();Sstc 또는 SBI 기반 RISC-V timer driver를 찾아 clocksource/clockevent로 등록한다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
tick_setup_hrtimer_broadcast();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
pv_time_init();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
cycle을 ns로 바꾸고 deadline을 program하는 계산
counter frequency 24MHz, 현재 cycle 9,600,000, 2.5ms 뒤 hrtimer를 가정한다.
- now9,600,000 / 24,000,000 = 0.4초이므로 base가 0이면 400,000,000ns다.
- delta2.5ms는 0.0025 x 24,000,000 = 60,000 cycle이다.
- deadlineabsolute comparator 방식은 9,660,000을 쓰고 relative TVAL 방식은 60,000을 쓴다.
- lateness실제 handler가 cycle 9,666,000에서 시작하면 6,000/24MHz = 250us 늦었다.
결론timer 정확도는 tick 단위가 아니라 counter frequency, program 방식과 interrupt 시작 지연을 분리해 계산한다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
clocksource는 연속 counter read와 mult/shift 변환을 제공하고 clockevent는 one-shot deadline interrupt를 제공한다. timekeeping seqlock이 base cycle과 base ns를 일관되게 읽으며 hrtimer가 가장 이른 만료를 device에 program한다.
counter read, timekeeper update, per-CPU clockevent program, interrupt handler와 timer callback을 나눈다. callback은 hardirq 또는 softirq context이므로 sleep 가능 여부가 다르다.
arm64: CNTVCT/CNTPCT counter와 CNTV_TVAL/CVAL comparator
architected timer frequency CNTFRQ를 기준으로 virtual 또는 physical counter를 clocksource로 등록한다. per-CPU PPI가 comparator 만료를 알리고 TVAL 또는 CVAL에 다음 event를 쓴다.
ISB로 counter read ordering을 맞추고 erratum workaround가 alternative read path를 선택할 수 있다. 디버깅할 때는 CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다.
x86-64: TSC clocksource와 local APIC TSC-deadline/one-shot timer
invariant/nonstop TSC가 충분히 신뢰되면 RDTS(C/P)가 clocksource가 되고 CPUID/calibration으로 frequency를 구한다. local APIC timer는 TSC-deadline MSR 또는 divide-count mode로 per-CPU event를 만든다.
RDTSCP/LFENCE ordering, TSC synchronization과 frequency scaling invariant 여부를 검증해야 한다. 디버깅할 때는 CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다.
RISC-V: TIME CSR/SBI와 Sstc STIMECMP comparator
time CSR의 shared frequency를 DT timebase-frequency에서 얻어 clocksource_sched_clock에 등록한다. clockevent는 Sstc가 있으면 STIMECMP를 직접 쓰고 아니면 SBI set_timer로 firmware에 deadline을 요청한다.
RV32에서 64-bit time/comparator를 읽고 쓸 때 high-low-high 또는 safe write sequence로 torn value를 막아야 한다. 디버깅할 때는 timebase-frequency, TIME, STIMECMP/SBI call, SIE.STIE, timer pending과 clockevent min_delta를 본다.
객체 수명과 소유권을 먼저 고정한다
registered clocksource와 clock_event_device는 사용 CPU가 online인 동안 유지된다. CPU hotplug와 suspend는 device shutdown, counter continuity와 resume correction을 조정해야 한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
deadline error는 counter read/program 비용, hardware minimum delta, interrupt masking, firmware trap과 callback backlog의 합이다. tickless idle에서는 너무 먼 deadline과 counter wrap도 확인한다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | CNTFRQ 불일치, unstable counter erratum 또는 comparator program 뒤 즉시 만료 race는 시간 역행과 interrupt storm을 만든다. | CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다. |
| x86-64 | socket 간 TSC offset, virtualization migration, 잘못된 calibration은 monotonicity 위반과 timer 조기/지연 만료를 만든다. | CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다. |
| RISC-V | firmware trap latency, 잘못된 timebase-frequency 또는 RV32 comparator write race가 긴 wakeup 지연이나 즉시 재interrupt를 만든다. | timebase-frequency, TIME, STIMECMP/SBI call, SIE.STIE, timer pending과 clockevent min_delta를 본다. |
08 · LAB
재현과 계측 절차
- clocksource read와 clockevent handler 시작 cycle을 동시에 기록해 program-to-interrupt latency histogram을 만든다.
- CPU idle, frequency scaling, VM migration 전후에 clocksource watchdog과 monotonicity를 확인한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/time.c:49-73
- x86-64arch/x86/kernel/tsc.c:1519-1597
- RISC-Varch/riscv/kernel/time.c:14-52
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.