26.9K
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
update_times(ticks);
}
jiffies_64에는 인터럽트가 발생한 횟수를 기록합니다. 생각해보세요. do_timer() 함수가 실행된다는 것은 인터럽트가 발생했다는 겁니다. 그러니까 jiffies_64 변수의 값을 증가시킵니다. 그리고 시간을 유지하기 위해 update_times() 함수를 호출합니다.
static inline void update_times(unsigned long ticks)
{
update_wall_time();
calc_load(ticks);
}
실제 시간 처리는 update_wall_time()에서 모두 수행합니다. wall time이라는 건 벽에 걸려있는 시간, 실제 시간을 의미합니다. 벽시계는 wall clock이라고 합니다. calc_load()는 vmstat, sar, uptime 등에서 보이는 부하 통계값을 갱신합니다.
static inline void update_times(void)
{
unsigned long ticks;
ticks = jiffies - wall_jiffies;
if (ticks) {
wall_jiffies += ticks;
update_wall_time(ticks);
}
calc_load(ticks);
}
ticks는 시스템에 반영된 jiffies 변수와 실제 지피값(wall_jiffies) 값의 차이를 계산합니다. 이는 지피값을 보정하기 위해 사용됩니다. 인터럽트가 발생될 때 마다 do_timer()가 실행되고, 꼬박꼬박 jiffies 변수 값이 1씩 증가했다면 이런 보정은 필요없습니다. 그러나 리눅스 커널은 시스템 부하가 높은 경우 인터럽트를 무시합니다. 인터럽트에 의해 드디어 do_timer() 함수가 실행되면 그 동안 처리하지 못했던 인터럽트를 한꺼번에 처리하게 되고 무시된 만큼의 시간값도 보정합니다.
#include실행결과 : 1.000000000007918110611627int main(void) { int i = 0; double result = 0.0; for( i = 0; i < 1000000; i++ ) { result = result + 0.000001; } printf( "%.24fn", result ); return 0; }
#include실행결과 : 1.000000000000006217248938int main(void) { int i = 0; int j = 0; double result = 0.0; double tmp = 0.0; for( i = 0; i < 1000; i++ ) { tmp = 0.0; for( j = 0; j < 1000; j++ ) { tmp = tmp + 0.000001; } result = result + tmp; } printf( "%.24fn", result ); return 0; }
static void update_wall_time(void)
{
cycle_t offset;
/* Make sure we"re fully resumed: */
if (unlikely(timekeeping_suspended))
return;
#ifdef CONFIG_GENERIC_TIME
offset = (clocksource_read(clock) - clock->cycle_last) & clock->mask;
#else
offset = clock->cycle_interval;
#endif
clock->xtime_nsec += (s64)xtime.tv_nsec << clock->shift;
/* normally this loop will run just once, however in the
* case of lost or late ticks, it will accumulate correctly.
*/
while (offset >= clock->cycle_interval) {
/* accumulate one interval */
clock->xtime_nsec += clock->xtime_interval;
clock->cycle_last += clock->cycle_interval;
offset -= clock->cycle_interval;
if (clock->xtime_nsec >= (u64)NSEC_PER_SEC << clock->shift) {
clock->xtime_nsec -= (u64)NSEC_PER_SEC << clock->shift;
xtime.tv_sec++;
second_overflow();
}
/* interpolator bits */
time_interpolator_update(clock->xtime_interval
>> clock->shift);
/* accumulate error between NTP and clock interval */
clock->error += current_tick_length();
clock->error -= clock->xtime_interval << (TICK_LENGTH_SHIFT - clock->shift);
}
/* correct the clock when NTP error is too big */
clocksource_adjust(clock, offset);
/* store full nanoseconds into xtime */
xtime.tv_nsec = (s64)clock->xtime_nsec >> clock->shift;
clock->xtime_nsec -= (s64)xtime.tv_nsec << clock->shift;
/* check to see if there is a new clocksource to use */
if (change_clocksource()) {
clock->error = 0;
clock->xtime_nsec = 0;
clocksource_calculate_interval(clock, tick_nsec);
}
}
보는 것 만으로도 눈이 핑핑 돌아갈 정도로 변했다는 것을 알 수 있습니다. 주석에 의하면 NTP(네트워크 타임 프로토콜)에서 얻어온 시간과 클럭 간격(clock interval) 사이의 오차까지 누적하고 있는 것을 알 수 있습니다. 위 소스에서 잘라보면 다음 부분입니다.
/* accumulate error between NTP and clock interval */
clock->error += current_tick_length();
clock->error -= clock->xtime_interval << (TICK_LENGTH_SHIFT - clock->shift);
NTP와 클럭 간격에 의한 누적 오차가 커지면 시간을 보정하는 데 적용시킵니다.
/* correct the clock when NTP error is too big */ clocksource_adjust(clock, offset);1 Ghz CPU가 있으면 1 HZ가 1 ns 마다 발생하니까 발생하는 클록수만으로 시간을 측정하면 어떨까? 라는 주제로 얘기를 한 적이 있습니다. 네, 실제로는 안됩니다. 첫번째 이유는 1 Hz가 발생할 때, 그 클록의 간격이 정말 일정하다고 보장할 수 있는가? 일정하지 않다면 무수히 쌓이는 그 작은 오차가 쌓여서 오차를 만들어내기 때문이라고 얘기했습니다. 두번째는 동적으로 CPU 동작 주파수를 변경시키는 절전기능이 탑재된 시스템에서는 그 정확성을 보장하기 어렵기 때문입니다. 물론, 현대 CPU는 펨토(10의 -15승)초 까지의 정확도를 제공하는 HPET 타이머를 제공합니다. 커널 2.6.20.4의 소스 코드를 보면 이런 고민들이 반영된 것입니다. 게다가, 주석에는 다음 버전에 변경할 내용까지 적혀 있습니다.
/* interpolator bits */
time_interpolator_update(clock->xtime_interval
>> clock->shift);
중간에 호출되는 이 함수는 지연된 시간을 보정하기 위해 사용됩니다.
댓글