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);중간에 호출되는 이 함수는 지연된 시간을 보정하기 위해 사용됩니다.
이전 글 : 리눅스 커널 락을 없애려는 시도들
다음 글 : 왜 네트워크 디바이스 노드는 없을까?
최신 콘텐츠