전체 흐름
clocksource는 경과 cycle을 읽는 시계 소스다. timekeeper는 마지막 기준 cycle과 환산값·기준 시각을 보관한다. ktime_get()은 현재 cycle 차이를 읽어 monotonic 시각을 계산하며, 이 호출이 timer IRQ나 hrtimer 콜백을 실행하는 것은 아니다.
read() → cycle 값. clockevent의 만료 인터럽트와 역할이 다르다.update_wall_time() → 누적·보정 → 읽기용 기준값 게시ktime_get() → seqcount 스냅샷 + 현재 cycle 차이오른쪽 두 칸은 순차 호출 관계가 아니다. 읽기는 갱신 사이에도 현재 cycle 차이를 더한다.
jiffies는 tick 계수, sched_clock은 스케줄러용 시계, clockevent·hrtimer는 만료 알림 쪽이다. 모두 ktime_get() 내부 단계로 그리면 안 된다.
소스 코드 위치
Linux 6.6의 고정 소스 기준이다. clocksource의 실제 종류와 성능은 보드·설정에 따라 달라진다.
| 파일 | 확인할 책임 |
|---|---|
| kernel/time/timekeeping.c | timekeeping_init, ktime_get, update_wall_time 및 timekeeper 필드 |
| kernel/time/clocksource.c | 등록·평가·선택. 초기 기본 clocksource와 이후 선택을 구분 |
| kernel/time/tick-common.c | tick/clockevent 연결을 추적할 때 별도로 확인 |
대표 코드
timekeeping_init
/* Flag for if there is a persistent clock on this platform */
static bool persistent_clock_exists;
/*
* timekeeping_init - Initializes the clocksource and common timekeeping values
*/
void __init timekeeping_init(void)
{
struct timespec64 wall_time, boot_offset, wall_to_mono;
struct timekeeper *tk = &tk_core.timekeeper;
struct clocksource *clock;
unsigned long flags;
read_persistent_wall_and_boot_offset(&wall_time, &boot_offset);
if (timespec64_valid_settod(&wall_time) &&
timespec64_to_ns(&wall_time) > 0) {
persistent_clock_exists = true;
} else if (timespec64_to_ns(&wall_time) != 0) {
pr_warn("Persistent clock returned invalid value");
wall_time = (struct timespec64){0};
}
if (timespec64_compare(&wall_time, &boot_offset) < 0)
boot_offset = (struct timespec64){0};
/*
* We want set wall_to_mono, so the following is true:
* wall time + wall_to_mono = boot time
*/
wall_to_mono = timespec64_sub(boot_offset, wall_time);
raw_spin_lock_irqsave(&timekeeper_lock, flags);
write_seqcount_begin(&tk_core.seq);
ntp_init();
clock = clocksource_default_clock();
if (clock->enable)
clock->enable(clock);
tk_setup_internals(tk, clock);
읽는 법: 이 발췌는 함수 중간에서 끝난다. 뒤따르는 tk_set_xtime, timekeeping_update, seqcount 종료까지 읽어야 초기화가 완결된다. clocksource_default_clock()는 최종 최적 시계 선택이 아니다.
fast-timekeeper 래치와 빠른 읽기 보조 코드
* Employ the latch technique; see @raw_write_seqcount_latch.
*
* So if a NMI hits the update of base[0] then it will use base[1]
* which is still consistent. In the worst case this can result is a
* slightly wrong timestamp (a few nanoseconds). See
* @ktime_get_mono_fast_ns.
*/
static void update_fast_timekeeper(const struct tk_read_base *tkr,
struct tk_fast *tkf)
{
struct tk_read_base *base = tkf->base;
/* Force readers off to base[1] */
raw_write_seqcount_latch(&tkf->seq);
/* Update base[0] */
memcpy(base, tkr, sizeof(*base));
/* Force readers back to base[0] */
raw_write_seqcount_latch(&tkf->seq);
/* Update base[1] */
memcpy(base + 1, base, sizeof(*base));
}
static __always_inline u64 fast_tk_get_delta_ns(struct tk_read_base *tkr)
{
u64 delta, cycles = tk_clock_read(tkr);
delta = clocksource_delta(cycles, tkr->cycle_last, tkr->mask);
return timekeeping_delta_to_ns(tkr, delta);
}
static __always_inline u64 __ktime_get_fast_ns(struct tk_fast *tkf)
{
struct tk_read_base *tkr;
unsigned int seq;
u64 now;
do {
읽는 법: 이 원문은 일반 ktime_get() 본문이 아니다. fast-timekeeper의 래치 복제와 보조 읽기 시작 부분이다. 일반 ktime_get()은 원본의 별도 함수(836행 부근)에서 seqcount 재시도를 사용한다.
함수별 분석
초기화·기준값 갱신·읽기를 하나의 함수 체인으로 연결하지 않는다. 서로 다른 진입점과 보호 규칙이다.
timekeeping_init 또는 update_wall_timespinlock + seqcount 쓰기 구간
ktime_get: base + 현재 cycle 차이seqcount 시작값 검사·재시도
갱신은 매번 ktime_get()에서 호출되는 선행 단계가 아니다.
| 함수 | 실제 역할 | 경계 |
|---|---|---|
timekeeping_init | timekeeper_lock과 seqcount 쓰기 구간에서 기본 clocksource와 시작 시각을 설정하고 timekeeping_update로 읽기용 자료를 갱신한다. | 기본 clocksource로 시작하며 나중에 timekeeping_notify로 바뀔 수 있다. |
update_wall_time | timekeeping_advance가 전역 shadow에 누적한 뒤 timekeeping_update와 memcpy로 본체를 갱신한다. | tick 기반 갱신. NO_HZ에서 여러 cycle interval을 한꺼번에 누적할 수 있다. |
ktime_get | tkr_mono.base와 timekeeping_get_ns를 같은 seqcount 스냅샷에서 읽는다. | read-only monotonic 경로; suspend 중 사용에는 WARN이 있다. |
구조체와 필드
전역 tk_core가 일관된 읽기 스냅샷을 제공한다. tk_read_base는 clocksource 참조·기준 cycle·환산 계수를 묶는다.
| 객체·필드 | 뜻 | 읽기 규칙 |
|---|---|---|
tkr_mono.clock, cycle_last | 시계 소스와 기준 cycle | clocksource 교체·누적 때 함께 맞춘다. |
tkr_mono.mult, shift, mask | cycle 차이를 ns로 환산하고 wrap 처리 | clocksource 특성과 보정이 반영된다. |
tkr_mono.base | monotonic 기준값 | ktime_get()이 현 cycle 차이를 더한다. |
tk_core.seq | 여러 필드의 일관성 | 일반 읽기는 값이 바뀌면 다시 읽는다. |
offs_real, tkr_raw | realtime offset, raw 기준 | monotonic·realtime·raw의 의미를 구분한다. |
실행 단계와 상태 변화
tk_clock_read·clocksource_delta로 경과량 획득timekeeping_update + memcpy와 seqcount 종료읽기는 마지막 게시 기준값에 현재 cycle 차이를 더하는 별도 경로다.
v6.6은 전역 shadow/timekeeper lock을 쓴다. v6.18.37은 tk_data 배열에 본체·shadow·lock을 묶고 보조 timekeeper 경로를 갖는다. 두 버전의 저장 구조를 동일하게 묘사하지 않는다.
불변 조건과 실패 사례
| 조건 | 혼동하면 생기는 문제 |
|---|---|
| cycle 기준·환산 계수·base를 같은 seqcount 스냅샷에서 읽기 | 갱신 전후 필드를 섞으면 시각이 튈 수 있다. |
| clocksource와 clockevent 분리 | counter 읽기를 timer IRQ → callback 호출로 잘못 그리게 된다. |
| monotonic, realtime, raw 구분 | RTC/NTP·시각 설정의 영향과 단조 경과 시간 보장을 혼동한다. |
| 일반 읽기와 fast/NMI 읽기 구분 | 래치 보조 코드를 일반 ktime_get() 본문으로 오해한다. |
clocksource 고장·교체, suspend/resume, 긴 cycle 간격은 별도 경계 조건이다. 고정 소스의 호출 관계는 모든 보드의 정확도 검증 결과가 아니다.
계측과 검증
실기기에서는 /sys/devices/system/clocksource/clocksource0/current_clocksource로 활성 clocksource를 확인한다. 이름만으로 정밀도·안정성을 증명할 수는 없다.
| 관찰 | 검증할 가설 |
|---|---|
source: timekeeping_init·timekeeping_notify | 부팅 기본값과 이후 교체를 구분 |
source: update_wall_time·timekeeping_advance | 쓰기에서 cycle 누적·게시 확인 |
반복 ktime_get() 호출 | 단조 읽기 경로 점검; 측정 비용·가상화 영향도 고려 |
ftrace 함수 추적 지원은 설정에 따라 다르다. perf stat cycles는 활성 clocksource의 cycle 값을 직접 보여 주는 지표가 아니다.
추가 확인 사항
- 이 보드의 부팅 기본 clocksource와 이후 선택된 clocksource가 같은가?
ktime_get()의 seqcount 재시도와 fast/NMI 래치는 각각 언제 필요한가?- 긴 NO_HZ 구간 뒤
update_wall_time()은 얼마의 cycle을 누적하는가? - 6.6과 6.18.37의 timekeeper 저장·게시 방식 차이는 어느 필드에 드러나는가?