← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 14/21

Timer와 clocksource: architected timer, TSC와 RISC-V time

kernel이 단조 증가 시간을 읽고 다음 hrtimer deadline을 hardware comparator에 설정하며 CPU별 clock event를 처리하는 방식을 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 143 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

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 가능 여부가 다르다.

지연 시간 관점deadline error는 counter read/program 비용, hardware minimum delta, interrupt masking, firmware trap과 callback backlog의 합이다. tickless idle에서는 너무 먼 deadline과 counter wrap도 확인한다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64CNTVCT/CNTPCT counter와 CNTV_TVAL/CVAL comparatorCNTFRQ 불일치, unstable counter erratum 또는 comparator program 뒤 즉시 만료 race는 시간 역행과 interrupt storm을 만든다.CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다.
x86-64TSC clocksource와 local APIC TSC-deadline/one-shot timersocket 간 TSC offset, virtualization migration, 잘못된 calibration은 monotonicity 위반과 timer 조기/지연 만료를 만든다.CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다.
RISC-VTIME CSR/SBI와 Sstc STIMECMP comparatorfirmware 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

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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를 본다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractclocksource는 연속 counter read와 mult/shift 변환을 제공하고 clockevent는 one-shot deadline interrupt를 제공한다. timekeeping seqlock이 base cycle과 base ns를 일관되게 읽으며 hrtimer가 가장 이른 만료를 device에 program한다.
arm64CNTVCT/CNTPCT counter와 CNTV_TVAL/CVAL comparatorISB로 counter read ordering을 맞추고 erratum workaround가 alternative read path를 선택할 수 있다.
x86-64TSC clocksource와 local APIC TSC-deadline/one-shot timerRDTSCP/LFENCE ordering, TSC synchronization과 frequency scaling invariant 여부를 검증해야 한다.
RISC-VTIME CSR/SBI와 Sstc STIMECMP comparatorRV32에서 64-bit time/comparator를 읽고 쓸 때 high-low-high 또는 safe write sequence로 torn value를 막아야 한다.
lifetime boundaryregistered clocksource와 clock_event_device는 사용 CPU가 online인 동안 유지된다. CPU hotplug와 suspend는 device shutdown, counter continuity와 resume correction을 조정해야 한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비ISB로 counter read ordering을 맞추고 erratum workaround가 alternative read path를 선택할 수 있다.관찰: CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다.
x86-64state 준비RDTSCP/LFENCE ordering, TSC synchronization과 frequency scaling invariant 여부를 검증해야 한다.관찰: CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다.
RISC-Vstate 준비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를 본다.

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개 줄에 각각 설명을 붙였습니다.

L49 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를 연결해 본다.

L50(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L51 return prof_pc;

이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L52}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L53EXPORT_SYMBOL(profile_pc);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L54(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L55void __init time_init(void)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L56{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L57 u32 arch_timer_rate;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L58(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L59 of_clk_init(NULL);

devicetree의 platform clock provider를 먼저 초기화해 timer 외 장치가 사용할 clock tree를 준비한다.

L60 timer_probe();

DT/ACPI에서 architected timer 등 clocksource/clockevent driver를 탐색하고 등록한다.

L61(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L62 tick_setup_hrtimer_broadcast();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L63(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L64 arch_timer_rate = arch_timer_get_rate();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L65 if (!arch_timer_rate)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L66 panic("Unable to initialise architected timer.\n");

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L67(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L68 /* Calibrate the delay loop directly */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L69 lpj_fine = arch_timer_rate / HZ;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L70(blank)

빈 줄은 arm64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L71 pv_time_init();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L72}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L73(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개 줄에 각각 설명을 붙였습니다.

L1519(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1520 snp_secure_tsc_init();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1521(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1522 if (!determine_cpu_tsc_frequencies(true))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1523 return;

이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1524 tsc_enable_sched_clock();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1525}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1526(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1527void __init tsc_init(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1528{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1529 if (!cpu_feature_enabled(X86_FEATURE_TSC)) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1530 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를 연결해 본다.

L1531 return;

이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1532 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1533(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1534 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1535 * native_calibrate_cpu_early can only calibrate using methods that are

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1536 * available early in boot.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1537 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1538 if (x86_platform.calibrate_cpu == native_calibrate_cpu_early)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1539 x86_platform.calibrate_cpu = native_calibrate_cpu;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1540(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1541 if (!tsc_khz) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1542 /* We failed to determine frequencies earlier, try again */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1543 if (!determine_cpu_tsc_frequencies(false)) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1544 mark_tsc_unstable("could not calculate TSC khz");

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1545 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를 연결해 본다.

L1546 return;

이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1547 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1548 tsc_enable_sched_clock();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1549 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1550(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1551 cyc2ns_init_secondary_cpus();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1552(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1553 if (!no_sched_irq_time)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1554 enable_sched_clock_irqtime();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1555(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1556 lpj_fine = get_loops_per_jiffy();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1557(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1558 check_system_tsc_reliable();

platform/CPU 특성을 반영해 clocksource watchdog 없이 신뢰할 수 있는지 판단한다.

L1559(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1560 if (unsynchronized_tsc()) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1561 mark_tsc_unstable("TSCs unsynchronized");

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1562 return;

이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1563 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1564(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1565 if (tsc_clocksource_reliable || no_tsc_watchdog)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1566 tsc_disable_clocksource_watchdog();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1567(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1568 clocksource_register_khz(&clocksource_tsc_early, tsc_khz);

변환 mult/shift를 계산할 수 있도록 TSC frequency와 clocksource를 timekeeping core에 등록한다.

L1569 detect_art();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1570}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1571(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1572#ifdef CONFIG_SMP

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L1573/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1574 * Check whether existing calibration data can be reused.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1575 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1576unsigned long calibrate_delay_is_known(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1577{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1578 int sibling, cpu = smp_processor_id();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1579 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를 연결해 본다.

L1580 const struct cpumask *mask = topology_core_cpumask(cpu);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1581(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1582 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1583 * If TSC has constant frequency and TSC is synchronized across

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1584 * sockets then reuse CPU0 calibration.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1585 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1586 if (constant_tsc && !tsc_unstable)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1587 return cpu_data(0).loops_per_jiffy;

이 함수가 Timer와 clocksource: architected timer, TSC와 RISC-V time 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1588(blank)

빈 줄은 x86-64 Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1589 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1590 * If TSC has constant frequency and TSC is not synchronized across

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1591 * sockets and this is not the first CPU in the socket, then reuse

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1592 * the calibration value of an already online CPU on that socket.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1593 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1594 * This assumes that CONSTANT_TSC is consistent for all CPUs in a

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1595 * socket.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1596 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1597 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개 줄에 각각 설명을 붙였습니다.

L14#include <asm/timex.h>

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L15#include <asm/paravirt.h>

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L16(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L17unsigned long riscv_timebase __ro_after_init;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L18EXPORT_SYMBOL_GPL(riscv_timebase);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L19(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L20void __init time_init(void)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L21{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L22 struct device_node *cpu;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L23 struct acpi_table_rhct *rhct;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L24 acpi_status status;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L25 u32 prop;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L26(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L27 if (acpi_disabled) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L28 cpu = of_find_node_by_path("/cpus");

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L29 if (!cpu || of_property_read_u32(cpu, "timebase-frequency", &prop))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L30 panic("RISC-V system with no 'timebase-frequency' in DTS\n");

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L31(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L32 of_node_put(cpu);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L33 riscv_timebase = prop;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L34 of_clk_init(NULL);

timer와 주변 장치가 참조할 devicetree clock provider를 초기화한다.

L35 } else {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Timer와 clocksource: architected timer, TSC와 RISC-V time의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L36 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를 연결해 본다.

L37 if (ACPI_FAILURE(status))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L38 panic("RISC-V ACPI system with no RHCT table\n");

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L39(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L40 riscv_timebase = rhct->time_base_freq;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L41 acpi_put_table((struct acpi_table_header *)rhct);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L42 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L43(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L44 lpj_fine = riscv_timebase / HZ;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L45(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L46 timer_probe();

Sstc 또는 SBI 기반 RISC-V timer driver를 찾아 clocksource/clockevent로 등록한다.

L47(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L48 tick_setup_hrtimer_broadcast();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L49(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L50 pv_time_init();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L51}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L52(blank)

빈 줄은 RISC-V Timer와 clocksource: architected timer, TSC와 RISC-V time 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

cycle을 ns로 바꾸고 deadline을 program하는 계산

counter frequency 24MHz, 현재 cycle 9,600,000, 2.5ms 뒤 hrtimer를 가정한다.

  1. now9,600,000 / 24,000,000 = 0.4초이므로 base가 0이면 400,000,000ns다.
  2. delta2.5ms는 0.0025 x 24,000,000 = 60,000 cycle이다.
  3. deadlineabsolute comparator 방식은 9,660,000을 쓰고 relative TVAL 방식은 60,000을 쓴다.
  4. lateness실제 handler가 cycle 9,666,000에서 시작하면 6,000/24MHz = 250us 늦었다.

결론timer 정확도는 tick 단위가 아니라 counter frequency, program 방식과 interrupt 시작 지연을 분리해 계산한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 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 가능 여부가 다르다.

02

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을 본다.

03

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을 확인한다.

04

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를 본다.

05

객체 수명과 소유권을 먼저 고정한다

registered clocksource와 clock_event_device는 사용 CPU가 online인 동안 유지된다. CPU hotplug와 suspend는 device shutdown, counter continuity와 resume correction을 조정해야 한다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

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

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64CNTFRQ 불일치, unstable counter erratum 또는 comparator program 뒤 즉시 만료 race는 시간 역행과 interrupt storm을 만든다.CNTFRQ, CNTVCT, CVAL/TVAL, control enable/mask/status, PPI affinity와 clocksource rating을 본다.
x86-64socket 간 TSC offset, virtualization migration, 잘못된 calibration은 monotonicity 위반과 timer 조기/지연 만료를 만든다.CPUID invariant bit, tsc_khz, clocksource watchdog, IA32_TSC_DEADLINE, APIC LVT와 per-CPU offset을 확인한다.
RISC-Vfirmware 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

재현과 계측 절차

  1. clocksource read와 clockevent handler 시작 cycle을 동시에 기록해 program-to-interrupt latency histogram을 만든다.
  2. CPU idle, frequency scaling, VM migration 전후에 clocksource watchdog과 monotonicity를 확인한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.