Timer -4- (Clock Sources Watchdog)

 

Clock Sources Watchdog

불안정한 클럭 소스 처리를 위한 워치독으로 현재는 x86 아키텍처에만 적용되어 있다.

  • 주의: 커널의 다른 워치독 시스템과 구분이 필요한다.

 

클럭 소스를 워치독 리스트에 등록

clocksource_enqueue_watchdog()

kernel/time/clocksource.c

static void clocksource_enqueue_watchdog(struct clocksource *cs)
{
        unsigned long flags;

        spin_lock_irqsave(&watchdog_lock, flags);
        if (cs->flags & CLOCK_SOURCE_MUST_VERIFY) {
                /* cs is a clocksource to be watched. */
                list_add(&cs->wd_list, &watchdog_list);
                cs->flags &= ~CLOCK_SOURCE_WATCHDOG;
        } else {
                /* cs is a watchdog. */
                if (cs->flags & CLOCK_SOURCE_IS_CONTINUOUS)
                        cs->flags |= CLOCK_SOURCE_VALID_FOR_HRES;
                /* Pick the best watchdog. */
                if (!watchdog || cs->rating > watchdog->rating) {
                        watchdog = cs;
                        /* Reset watchdog cycles */
                        clocksource_reset_watchdog();
                }
        }
        /* Check if the watchdog timer needs to be started. */
        clocksource_start_watchdog();
        spin_unlock_irqrestore(&watchdog_lock, flags);
}

요청 클럭 소스에 must_verify 플래그 요청이 있는 경우 워치독 리스트에 등록하고 0.5초 타이머 후에 워치독 스레드를 동작시켜 클럭의 안정 여부를 판단하게 한다. 플래그 요청이 없는 경우 rating이 가장 좋은 클럭 소스를 전역 watchdog이 가리키게한다.

  • 코드 라인 6~9에서 must_verify 플래그가 있는 경우 클럭 소스를 워치독 리스트에 추가하고 플래그 중 watchdog 플래그를 클리어한다.
  • 코드 라인 10~13에서 continuous 플래그가 있는 경우 valid_for_hres 플래그를 추가한다.
  • 코드 라인 15~19에서 아직 워치독이 지정되지 않았거나 워치독 클럭 소스의 rating 값보다 요청한 클럭 소스의 rating 값이 더 높은 경우 요청 클럭소스를 워치독 클럭 소스로 지정하고 워치독 리스트에 있는 모든 워치독 플래그를 클리어한다.
  • 코드 라인 22에서 워치독 타이머를 가동한다.

다음 그림은 must_verify 플래그가 있는 클럭 소스를 워치독 리스트에 추가하고 클럭 소스의 안정성 여부를 확인하도록 0.5초 만료시간으로 타이머를 가동시킨 후 워치독 스레드를 동작시키는 과정을 보여준다.

 

 

clocksource_reset_watchdog()

kernel/time/clocksource.c

static inline void clocksource_reset_watchdog(void)
{
        struct clocksource *cs;

        list_for_each_entry(cs, &watchdog_list, wd_list)
                cs->flags &= ~CLOCK_SOURCE_WATCHDOG;
}

워치독 리스트에 등록된 모든 클럭 소스의 플래그 중 watchdog 비트만 클리어한다.

 

clocksource_start_watchdog()

kernel/time/clocksource.c

static inline void clocksource_start_watchdog(void)
{
        if (watchdog_running || !watchdog || list_empty(&watchdog_list))
                return;
        init_timer(&watchdog_timer);
        watchdog_timer.function = clocksource_watchdog;
        watchdog_timer.expires = jiffies + WATCHDOG_INTERVAL;
        add_timer_on(&watchdog_timer, cpumask_first(cpu_online_mask));
        watchdog_running = 1;
}

클럭 소스 워치독으로 만료 시간 0.5초 lowres 타이머를 요청한다.

  • 타이머가 동작 중이거나 워치독 클럭 소스가 없거나 워치독 리스트가 비어 있는 경우 처리를 하지 않고 빠져나간다.

 

클럭 소스 워치독 핸들러

clocksource_watchdog()

kernel/time/clocksource.c

static void clocksource_watchdog(unsigned long data)
{
        struct clocksource *cs;
        cycle_t csnow, wdnow, delta;
        int64_t wd_nsec, cs_nsec;
        int next_cpu, reset_pending;

        spin_lock(&watchdog_lock);
        if (!watchdog_running)
                goto out;

        reset_pending = atomic_read(&watchdog_reset_pending);

        list_for_each_entry(cs, &watchdog_list, wd_list) {

                /* Clocksource already marked unstable? */
                if (cs->flags & CLOCK_SOURCE_UNSTABLE) {
                        if (finished_booting)
                                schedule_work(&watchdog_work);
                        continue;
                }

                local_irq_disable();
                csnow = cs->read(cs);
                wdnow = watchdog->read(watchdog);
                local_irq_enable();

                /* Clocksource initialized ? */
                if (!(cs->flags & CLOCK_SOURCE_WATCHDOG) ||
                    atomic_read(&watchdog_reset_pending)) {
                        cs->flags |= CLOCK_SOURCE_WATCHDOG;
                        cs->wd_last = wdnow;
                        cs->cs_last = csnow;
                        continue;
                }

                delta = clocksource_delta(wdnow, cs->wd_last, watchdog->mask);
                wd_nsec = clocksource_cyc2ns(delta, watchdog->mult,
                                             watchdog->shift);

                delta = clocksource_delta(csnow, cs->cs_last, cs->mask);
                cs_nsec = clocksource_cyc2ns(delta, cs->mult, cs->shift);
                cs->cs_last = csnow;
                cs->wd_last = wdnow;

                if (atomic_read(&watchdog_reset_pending))
                        continue;

                /* Check the deviation from the watchdog clocksource. */
                if ((abs(cs_nsec - wd_nsec) > WATCHDOG_THRESHOLD)) {
                        clocksource_unstable(cs, cs_nsec - wd_nsec);
                        continue;
                }

워치독 이벤트 핸들러로 클럭 소스 리스트에 있는 모든 클럭에 대해 워치독 클럭 소스와 비교하여 스레졸드(0.625초) 시간을 초과한 경우 unstable 처리 후 워치독 스레드에 맡긴다.

  • 코드 라인 9~10에서 워치독이 가동되지 않은 경우 처리를 중단하고 빠져나간다.
    • 워치독 타이머를 종료 시킨 후에 이벤트가 들어온 경우를 위해 함수를 빠져나가게 한다.
  • 코드 라인 12에서 현재 시점의 워치독 리셋 펜딩 값을 읽어 보관해둔다.
  • 코드 라인 14~21에서 이미 unstable 마크된 클럭 소스인 경우 다음 클럭 소스로 skip 한다. 만일 부팅이 완료된 상태인 경우 워치독을 가동시킨다.
  • 코드 라인 23~26에서 현재 클럭 소스 카운터 값과 워치독 클럭 소스 카운터 값을 읽어온다.
  • 코드 라인 29~35에서 워치독 플래그 설정이 없는 클럭 소스이거나 워치독 리셋 펜딩 상태인 경우 현재 클럭 소스에 워치독 플래그를 설정하고 읽은 워치독 클럭 소스 카운터 값과 현재 클럭 소스 카운터 값을 wd_last 및 cs_last에 보관하고 다음 클럭 소스로 skip 한다.
  • 코드 라인 37~39에서 워치독 클럭 소스를 대상으로 기존에 저장해 둔 카운터 값과 좀 전에 읽은 값의 카운터(cycle) 차이를 delta에 담고 소요 시간을 wd_nsec에 담는다.
  • 코드 라인 41~42에서 윗 줄과 같은 방법으로 현재 클럭 소스를 대상으로 동일하게 산출한다.
  • 코드 라인 43~44에서 읽었던 값을 현재 클럭 소스의 마지막에 읽은 카운터 값(cs_last 및 wd_last)에 대입한다.
  • 코드 라인 46~47에서 워치독 리셋 펜딩이 된 경우 다음 클럭 소스로 skip 한다.
  • 코드 라인 50~53에서 현재 클럭 소스의 소요 시간과 워치독 클럭 소스의 소요시간의 차이가 워치독 스레졸드 시간(0.0625초)을 초과한 경우 현재 클럭 소스를 unstable 처리하고 다음 클럭 소스로 skip 한다.

 

                if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) &&
                    (cs->flags & CLOCK_SOURCE_IS_CONTINUOUS) &&
                    (watchdog->flags & CLOCK_SOURCE_IS_CONTINUOUS)) {
                        /* Mark it valid for high-res. */
                        cs->flags |= CLOCK_SOURCE_VALID_FOR_HRES;

                        /*
                         * clocksource_done_booting() will sort it if
                         * finished_booting is not set yet.
                         */
                        if (!finished_booting)
                                continue;

                        /*
                         * If this is not the current clocksource let
                         * the watchdog thread reselect it. Due to the
                         * change to high res this clocksource might
                         * be preferred now. If it is the current
                         * clocksource let the tick code know about
                         * that change.
                         */
                        if (cs != curr_clocksource) {
                                cs->flags |= CLOCK_SOURCE_RESELECT;
                                schedule_work(&watchdog_work);
                        } else {
                                tick_clock_notify();
                        }
                }
        }

        /*
         * We only clear the watchdog_reset_pending, when we did a
         * full cycle through all clocksources.
         */
        if (reset_pending)
                atomic_dec(&watchdog_reset_pending);

        /*
         * Cycle through CPUs to check if the CPUs stay synchronized
         * to each other.
         */
        next_cpu = cpumask_next(raw_smp_processor_id(), cpu_online_mask);
        if (next_cpu >= nr_cpu_ids)
                next_cpu = cpumask_first(cpu_online_mask);
        watchdog_timer.expires += WATCHDOG_INTERVAL;
        add_timer_on(&watchdog_timer, next_cpu);
out:
        spin_unlock(&watchdog_lock);
}
  • 코드 라인 1~5에서 현재 클럭 소스와 워치독 클럭 소스가 모두 continuous 플래그 설정되어 있고 현재 클럭 소스에 valid_for_hres 플래그가 없는 경우 그 플래그를 설정한다.
  • 코드 라인 11~12에서 부팅이 완료되지 않은 상태이면 다음 클럭 소스로 skip 한다.
  • 코드 라인 22~27에서 현재 클럭 소스가 curr_clocksource가 아닌 경우 reselect 플래그를 추가하고 워치독을 가동하고 같은 경우 클럭 소스가 변경되었음을 async로 통지한다.
  • 코드 라인 35~36에서 루틴 처음에 이미 리셋 펜딩 상태였던 경우 워치독 리셋 펜딩 값을 감소시킨다.
  • 코드 라인 42~46에서 다음 cpu에 대해 워치독 인터벌(0.5초)로 워치독 타이머를 가동시킨다.

 

불안정한 클럭 소스 처리

clocksource_unstable()

kernel/time/clocksource.c

static void clocksource_unstable(struct clocksource *cs, int64_t delta)
{
        printk(KERN_WARNING "Clocksource %s unstable (delta = %Ld ns)\n",
               cs->name, delta);
        __clocksource_unstable(cs);
}

불안정한 클럭 소스에 대해 경고 메시지를 출력하고 이에 대한 처리를 하도록 워치독 처리 함수를 스케쥴하여 호출한다.

 

__clocksource_unstable()

kernel/time/clocksource.c

static void __clocksource_unstable(struct clocksource *cs)
{
        cs->flags &= ~(CLOCK_SOURCE_VALID_FOR_HRES | CLOCK_SOURCE_WATCHDOG);
        cs->flags |= CLOCK_SOURCE_UNSTABLE;
        if (finished_booting)
                schedule_work(&watchdog_work);
}

불안정한 클럭 소스의 처리를 위해 아래 워크큐에 등록된 함수를 스케쥴하여 호출한다.

 

kernel/time/clocksource.c

static DECLARE_WORK(watchdog_work, clocksource_watchdog_work);

워치독 스레드를 생성하고 동작시키는 워크큐이다.

 

clocksource_watchdog_work()

kernel/time/clocksource.c

static void clocksource_watchdog_work(struct work_struct *work)
{
        /*
         * If kthread_run fails the next watchdog scan over the
         * watchdog_list will find the unstable clock again.
         */
        kthread_run(clocksource_watchdog_kthread, NULL, "kwatchdog");
}

워치독 스레드를 생성하고 동작시킨다.

 

워치독 스레드

clocksource_watchdog_kthread()

kernel/time/clocksource.c

static int clocksource_watchdog_kthread(void *data)
{
        mutex_lock(&clocksource_mutex);
        if (__clocksource_watchdog_kthread())
                clocksource_select();
        mutex_unlock(&clocksource_mutex);
        return 0;
}

워치독 리스트에 있는 불안정한 클럭들은 rating을 0으로 바꾼 후 다시 클럭 소스 리스트로 옮기고 클럭 소스를 다시 선택하는 과정을 거치게 한다.

 

__clocksource_watchdog_kthread()

kernel/time/clocksource.c

static int __clocksource_watchdog_kthread(void)
{
        struct clocksource *cs, *tmp;
        unsigned long flags;
        LIST_HEAD(unstable);
        int select = 0;

        spin_lock_irqsave(&watchdog_lock, flags);
        list_for_each_entry_safe(cs, tmp, &watchdog_list, wd_list) {
                if (cs->flags & CLOCK_SOURCE_UNSTABLE) {
                        list_del_init(&cs->wd_list);
                        list_add(&cs->wd_list, &unstable);
                        select = 1;
                }
                if (cs->flags & CLOCK_SOURCE_RESELECT) {
                        cs->flags &= ~CLOCK_SOURCE_RESELECT;
                        select = 1;
                }
        }
        /* Check if the watchdog timer needs to be stopped. */
        clocksource_stop_watchdog();
        spin_unlock_irqrestore(&watchdog_lock, flags);

        /* Needs to be done outside of watchdog lock */
        list_for_each_entry_safe(cs, tmp, &unstable, wd_list) {
                list_del_init(&cs->wd_list); 
                __clocksource_change_rating(cs, 0);
        }
        return select;
}

워치독 리스트에 있는 클럭 소스 중 불안정한 클럭 소스들의 rating을 0으로 바꿔서 다시 클럭 소스 리스트로 옮긴다.

  • 코드 라인 8~14에서 워치독 리스트에서 불안정한 클럭 소스를 임시 리스트인  unstable 리스트로 옮긴다.
  • 코드 라인 15~18에서 reselect 플래그가 있는 클럭들은 플래그만 다시 클리어한다.
  • 코드 라인 21에서 워치독 타이머를 스탑한다.
  • 코드 라인 25~28에서 untable 리스트에 있는 불안정한 클럭 소스의 rating을 0으로 바꾼 후 다시 클럭 소스 리스트로 옮긴다.

 

__clocksource_change_rating()

kernel/time/clocksource.c

static void __clocksource_change_rating(struct clocksource *cs, int rating)
{
        list_del(&cs->list);
        cs->rating = rating;
        clocksource_enqueue(cs);
}

지정한 클럭 소스의 rating을 변경하고 다시 클럭 소스  리스트에 추가한다.

 

부팅 완료 시 클럭 소스 선택

clocksource_done_booting()

kernel/time/clocksource.c

/*
 * clocksource_done_booting - Called near the end of core bootup
 *
 * Hack to avoid lots of clocksource churn at boot time.
 * We use fs_initcall because we want this to start before
 * device_initcall but after subsys_initcall.
 */
static int __init clocksource_done_booting(void)
{
        mutex_lock(&clocksource_mutex);
        curr_clocksource = clocksource_default_clock();
        finished_booting = 1;
        /*
         * Run the watchdog first to eliminate unstable clock sources
         */
        __clocksource_watchdog_kthread();
        clocksource_select();
        mutex_unlock(&clocksource_mutex);
        return 0;
}
fs_initcall(clocksource_done_booting);

unstable한 클럭 소스를 제거하고 가장 best한 클럭 소스를 선택한다.

 

참고

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다