이 코드는 어떤 문제를 푸나요?
주기 타이머가 늦게 실행되었을 때 현재 시각에서 무조건 한 주기를 더하면 원래 주기가 계속 밀릴 수 있습니다. hrtimer_forward는 기존 만료 시각을 기준으로 필요한 주기 수만큼 앞으로 옮깁니다. 콜백을 밀린 횟수만큼 실행하는 함수와는 다릅니다.
읽을 범위: v6.6 · kernel/time/hrtimer.c · hrtimer_forward 1040–1072행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
만료 시각과 주기
만료 시각은 한 번의 사건이 예정된 시점이고 interval은 사건 사이의 간격입니다. 다음 기한을 계산할 때 어느 시점을 기준으로 더하는지가 주기 안정성에 영향을 줍니다.
overrun 수
여기서 반환하는 수는 기존 기한을 몇 주기 앞으로 옮겼는지를 나타냅니다. 실제 콜백을 그 횟수만큼 호출했다는 의미가 아닙니다.
큐에 들어 있는 타이머
정렬된 타이머 큐에 등록된 상태에서 만료 시각만 임의로 바꾸면 큐 순서와 맞지 않을 수 있습니다. 이 함수는 enqueue 상태를 검사합니다.
처음 읽을 때
기존 기한 10 ms, 주기 5 ms, 현재 23 ms라면 다음 기한은 25 ms가 되어야 합니다. 이 예시는 3주기 전진이며 28 ms로 새 주기를 시작하는 것과 다릅니다.
더 깊이 살펴볼 때
현재 시각이 기존 기한 또는 정확한 다음 주기 경계와 같을 때도 새 기한이 현재보다 뒤가 되는지 계산해 보세요. 큐 재등록은 호출자의 역할입니다.
그림으로 보는 변화

1. 얼마나 늦었는지 계산
delta = now - 기존 만료 시각
화살표는 시간 계산 순서입니다. 예시는 만료 10 ms, now 23 ms, interval 5 ms입니다.
2. 지나간 주기만큼 전진
13 / 5 = 2, 만료를 20 ms로 변경
정수 나눗셈 결과로 한꺼번에 전진합니다. 콜백을 두 번 실행하는 그림이 아닙니다.
3. 현재보다 뒤의 기한 확보
한 주기 더해 25 ms, 반환 3
화살표는 만료 시각의 이동을 뜻합니다. 타이머 큐에 재등록하는 동작은 별도입니다.
hrtimer_forward를 한 줄씩 읽기
줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
{
u64 orun = 1;
ktime_t delta;
delta = ktime_sub(now, hrtimer_get_expires(timer));
if (delta < 0)
return 0;
if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))
return 0;
if (interval < hrtimer_resolution)
interval = hrtimer_resolution;
if (unlikely(delta >= interval)) {
s64 incr = ktime_to_ns(interval);
orun = ktime_divns(delta, incr);
hrtimer_add_expires_ns(timer, incr * orun);
if (hrtimer_get_expires_tv64(timer) > now)
return orun;
/*
* This (and the ktime_add() below) is the
* correction for exact:
*/
orun++;
}
hrtimer_add_expires(timer, interval);
return orun;
}u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)타이머의 기존 만료 시각을 now보다 뒤로 전진시킵니다. interval은 반복 간격이고 반환값은 전진한 주기 수입니다.
u64 orun = 1;이미 만료한 정상 경로에서 적어도 한 주기 전진할 기본값을 둡니다.
ktime_t delta;현재 시각과 기존 만료 시각의 차이를 저장합니다.
delta = ktime_sub(now, hrtimer_get_expires(timer));now에서 기존 만료 시각을 빼 지연량을 구합니다. 음수이면 아직 기한 전입니다.
if (delta < 0)기한이 아직 오지 않았는지 확인합니다.
return 0;아직 이르면 상태를 바꾸지 않고 0을 반환합니다.
if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))정렬 큐에 등록된 타이머의 기한을 이 방식으로 바꾸려는 잘못된 사용인지 확인하고 경고합니다.
return 0;enqueue된 상태라면 기한을 변경하지 않고 반환합니다.
if (interval < hrtimer_resolution)요청 간격이 현재 hrtimer 해상도보다 짧은지 확인합니다.
interval = hrtimer_resolution;표현 가능한 최소 해상도에 맞춰 interval을 올립니다.
if (unlikely(delta >= interval)) {한 주기 이상 밀린 경우 한 번에 여러 주기를 더하는 경로로 들어갑니다. unlikely는 예상 빈도 힌트이며 분기 의미는 바뀌지 않습니다.
s64 incr = ktime_to_ns(interval);주기를 나노초 정수로 변환해 나눗셈과 곱셈에 사용합니다.
orun = ktime_divns(delta, incr);지연량을 주기로 나눈 정수 몫으로 지나간 주기 수를 구합니다.
hrtimer_add_expires_ns(timer, incr * orun);그 수만큼 기존 만료 시각에 더합니다. 현재 now를 새 기준으로 덮어쓰지 않습니다.
if (hrtimer_get_expires_tv64(timer) > now)전진한 기한이 이미 now보다 뒤인지 확인합니다. 필요한 보정 여부를 결정합니다.
return orun;충분히 앞으로 갔다면 현재 주기 수를 반환합니다.
orun++;기한이 아직 now 이하라면 한 주기를 더 전진할 것이므로 반환할 횟수도 하나 늘립니다.
hrtimer_add_expires(timer, interval);한 주기를 더해 최종 만료 시각을 now보다 뒤로 옮깁니다. 지연이 한 주기 미만인 경우에도 이 줄을 사용합니다.
return orun;전진한 주기 수를 반환합니다. 호출자는 필요하면 overrun 계상 등에 사용할 수 있습니다.
함께 생각해 볼 질문
늦은 횟수만큼 콜백을 실행하나요?
이 함수는 만료 시각을 전진시키고 주기 수를 반환합니다. 콜백 실행이나 큐 재등록 자체를 반복하는 함수가 아닙니다.
아직 기한 전이면 어떻게 되나요?
delta가 음수이므로 만료 시각을 바꾸지 않고 0을 반환합니다.
interval이 너무 작으면 어떻게 되나요?
hrtimer_resolution보다 작으면 그 해상도까지 올립니다. 하드웨어·모드가 표현할 수 없는 간격을 그대로 쓰지 않습니다.
출처와 읽은 범위
Linux stable v6.6 · kernel/time/hrtimer.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
