sched_clock_postinit()

<kernel v4.0>

sched_clock_postinit()

kernel/time/sched_clock.c

void __init sched_clock_postinit(void)
{
        /*
         * If no sched_clock function has been provided at that point,
         * make it the final one one.
         */
        if (read_sched_clock == jiffy_sched_clock_read)
                sched_clock_register(jiffy_sched_clock_read, BITS_PER_LONG, HZ);

        update_sched_clock();

        /*
         * Start the timer to keep sched_clock() properly updated and
         * sets the initial epoch.
         */
        hrtimer_init(&sched_clock_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        sched_clock_timer.function = sched_clock_poll;
        hrtimer_start(&sched_clock_timer, cd.wrap_kt, HRTIMER_MODE_REL);
}

sched_clock 용 클럭소스로 최종 업데이트 하고 약 1시간 단위로 epoch 값을 갱신하게 한다.

  • 코드 라인 7~8에서 최종 클럭소스가 지정되지 않은 경우 jiffies 를 사용하여 sched_clock으로 사용한다.
  • 코드 라인 10에서 sched_clock용 클럭소스를 사용하여 갱신한다.
  • 코드 라인 16에서 hrtimer를 사용하여 약 1시간 단위로 epoch 값을 갱신하게 한다.

 

참고

 

Timer -8- (Timecounter)

<kernel v5.4>

Timecounter/Cyclecounter

h/w 독립적인 타임카운터 API를 제공하며 이 카운터를 사용하는 드라이버가 많지 않아 원래 코드가 있었던 clocksource에서 코드를 제거하여 별도의 파일로 분리하였다.

  • arm 아키텍트 타이머에서 56비트 cyclecounter를 사용하여 Timecounter를 초기화하여 사용한다.

 

주로 고속 이더넷 드라이버의 PTP(Precision Time Protocol) 기능을 위해 h/w 타이머를 연동하였고 인텔 HD 오디오 드라이버에서도 사용되었음을 확인할 수 있다.

  • 사용 드라이버
    • drivers/net/ethernet/amd/xgbe/xgbe-drv.c
      • AMD 10Gb Ethernet driver
    • drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c
      • Broadcom Everest network driver
    • drivers/net/ethernet/freescale/fec_ptp.c
      • Fast Ethernet Controller (ENET) PTP driver for MX6x
    • drivers/net/ethernet/ti/cpts.c
      • TI Common Platform Time Sync
    • drivers/net/ethernet/mellanox/mlx4/en_clock.c
      • mlx4 ptp clock
    • drivers/net/ethernet/intel/igb/igb_ptp.c
      • PTP Hardware Clock (PHC) driver for the Intel 82576 and 82580
    • drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c
      • Intel 10 Gigabit PCI Express Linux driver
    • drivers/net/ethernet/intel/e1000e/netdev.c
      • Intel PRO/1000 Linux driver
    • sound/pci/hda/hda_controller.c
      • Implementation of primary alsa driver code base for Intel HD Audio.
  • 참고: time: move the timecounter/cyclecounter code into its own file.

 

사용 API

  • timecounter_init()
    • 하드웨어 카운터 레지스터에 연동한다.
  • timecounter_read()
    • timcounter_init() 한 후 지난 시간을 ns 값으로 변환하여 반환한다.
  • cyclecounter_cyc2ns()
    • cycle 카운터 값을 ns 값으로 변환하여 반환한다.
  • timecounter_adjtime()
    • tv->nsec 값에 delta 값을 더해 교정한다.
  • timecounter_cyc2time()
    • 요청 cycle – 지난 cycle 카운터 값으로 delta cycle을 구한 후 ns 값으로 변환하여 반환한다.

 


타임카운터 초기화

timecounter_init()

kernel/time/timecounter.c

/**
 * timecounter_init - initialize a time counter
 * @tc:                 Pointer to time counter which is to be initialized/reset
 * @cc:                 A cycle counter, ready to be used.
 * @start_tstamp:       Arbitrary initial time stamp.
 *
 * After this call the current cycle register (roughly) corresponds to
 * the initial time stamp. Every call to timecounter_read() increments
 * the time stamp counter by the number of elapsed nanoseconds.
 */
void timecounter_init(struct timecounter *tc,
                      const struct cyclecounter *cc,
                      u64 start_tstamp)
{       
        tc->cc = cc;
        tc->cycle_last = cc->read(cc);
        tc->nsec = start_tstamp;
        tc->mask = (1ULL << cc->shift) - 1;
        tc->frac = 0;
}
EXPORT_SYMBOL_GPL(timecounter_init);

요청한 timecount 및 cyclecounter 구조체에 시작 값(ns)으로 초기화하고 cycle_last에는 h/w 타이머로부터 cycle 값을 읽어 저장한다.

  • cycle_last 값에 현재 지정된 64bit 타이머 카운터 값을 읽어 cycle_last에 대입한다.
  • nsec에는 처음 시작 값을 대입한다.
  • rpi2: 아키텍처 generic 타이머 사용
    • mask 값에는 24bit 값으로 0xff_ffff를 사용한다.
    • 이 값으로 frac(fractional nanoseconds) 필드의 비트마스크 값으로 사용된다.

 

아래 그림은 rpi2의 armv7 아키텍처 generic 타이머를 사용하여 56비트 타임카운터를 초기화하는 모습을 보여준다.

 

timecounter_read()

kernel/time/timecounter.c

/**
 * timecounter_read - return nanoseconds elapsed since timecounter_init()
 *                    plus the initial time stamp
 * @tc:          Pointer to time counter.
 *
 * In other words, keeps track of time since the same epoch as
 * the function which generated the initial time stamp.
 */
u64 timecounter_read(struct timecounter *tc)
{
        u64 nsec;

        /* increment time by nanoseconds since last call */
        nsec = timecounter_read_delta(tc);
        nsec += tc->nsec;
        tc->nsec = nsec;

        return nsec;
}
EXPORT_SYMBOL_GPL(timecounter_read);

마지막 호출로부터 경과한 delta(ns) 값을 추가한 값(ns)을 tc->nsec에 갱신하고 반환한다.

 

다음 그림은 timecouter_init()으로 초기화한 후 100 사이클(5208ns 소요)이 지난 후 처음 timecounter_read() 함수를 호출한 경우 처리되는 모습을 보여준다.

 

timecounter_read_delta()

kernel/time/timecounter.c

/**
 * timecounter_read_delta - get nanoseconds since last call of this function
 * @tc:         Pointer to time counter
 *
 * When the underlying cycle counter runs over, this will be handled
 * correctly as long as it does not run over more than once between
 * calls.
 *
 * The first call to this function for a new time counter initializes
 * the time tracking and returns an undefined result.
 */
static u64 timecounter_read_delta(struct timecounter *tc)
{
        cycle_t cycle_now, cycle_delta;
        u64 ns_offset;

        /* read cycle counter: */
        cycle_now = tc->cc->read(tc->cc);

        /* calculate the delta since the last timecounter_read_delta(): */
        cycle_delta = (cycle_now - tc->cycle_last) & tc->cc->mask;

        /* convert to nanoseconds: */
        ns_offset = cyclecounter_cyc2ns(tc->cc, cycle_delta,
                                        tc->mask, &tc->frac);

        /* update time stamp of timecounter_read_delta() call: */
        tc->cycle_last = cycle_now;

        return ns_offset;
}

cycle 카운트 값을 읽어 tc->cycle_last에 저장하고 마지막 호출로부터 경과한 delta(ns) 값을 반환한다.

  • 코드 라인 7에서 cyclecounter에 연결된 h/w 타이머 cycle 카운트 값을 읽어온다.
  • 코드 라인 10에서 읽은 cycle 값 – 지난 cycle 값에 mask로 필터한 값을 cycle_delta에 대입한다.
  • 코드 라인 13~14에서 cycle_delta 값으로 소요 시간(ns)을 알아온다.
    • (cycle_delta * cc->mult) >> cc->shift
  • 코드 라인 17에서 읽었었던 cycle 카운트는 tc->cycle_last에 저장한다.

 

cyclecounter_cyc2ns()

include/linux/timecounter.h

/**
 * cyclecounter_cyc2ns - converts cycle counter cycles to nanoseconds
 * @cc:         Pointer to cycle counter.
 * @cycles:     Cycles
 * @mask:       bit mask for maintaining the 'frac' field
 * @frac:       pointer to storage for the fractional nanoseconds.
 */
static inline u64 cyclecounter_cyc2ns(const struct cyclecounter *cc,
                                      cycle_t cycles, u64 mask, u64 *frac)
{
        u64 ns = (u64) cycles;

        ns = (ns * cc->mult) + *frac;
        *frac = ns & mask;
        return ns >> cc->shift;
}

cycle 카운터 값을 nano 초로 변환한다.

 

timecounter_adjtime()

include/linux/timecounter.h

/**
 * timecounter_adjtime - Shifts the time of the clock.
 * @delta:      Desired change in nanoseconds.
 */
static inline void timecounter_adjtime(struct timecounter *tc, s64 delta)
{
        tc->nsec += delta;
}

타임카운터의 시간 ns만 delta 만큼 더해 조정한다. (cycle 값은 바꾸지 않는다.)

 


구조체

timecounter 구조체

include/linux/timecounter.h

/**
 * struct timecounter - layer above a %struct cyclecounter which counts nanoseconds
 *      Contains the state needed by timecounter_read() to detect
 *      cycle counter wrap around. Initialize with
 *      timecounter_init(). Also used to convert cycle counts into the
 *      corresponding nanosecond counts with timecounter_cyc2time(). Users
 *      of this code are responsible for initializing the underlying
 *      cycle counter hardware, locking issues and reading the time
 *      more often than the cycle counter wraps around. The nanosecond
 *      counter will only wrap around after ~585 years.
 *
 * @cc:                 the cycle counter used by this instance
 * @cycle_last:         most recent cycle counter value seen by
 *                      timecounter_read()
 * @nsec:               continuously increasing count
 * @mask:               bit mask for maintaining the 'frac' field
 * @frac:               accumulated fractional nanoseconds
 */
struct timecounter {
        const struct cyclecounter *cc;
        cycle_t cycle_last;
        u64 nsec;
        u64 mask;
        u64 frac;
};
  • *cc
    • h/w 타이머 카운트(cycle) 값에 대응하는 cyclecounter 구조체와 연결해야 한다.
  • cycle_last
    • cyclecounter를 통해 읽은 최종 cycle 값을 저장해둔다.
    • 이 cycle 값을 사용하여 delta cycle을 구하기 위해 사용한다.
  • nsec
    • cycle_last에 대응하는 실제 시간(ns)가 담긴다.
  • mask
    • cycle 마스크로 이 마스크 값을 초과하는 cycle 값은 overflow된 cycle 값이다.
  • frac
    • fractional nano 초

 

cyclecounter 구조체

include/linux/timecounter.h

/**
 * struct cyclecounter - hardware abstraction for a free running counter
 *      Provides completely state-free accessors to the underlying hardware.
 *      Depending on which hardware it reads, the cycle counter may wrap
 *      around quickly. Locking rules (if necessary) have to be defined
 *      by the implementor and user of specific instances of this API.
 *
 * @read:               returns the current cycle value
 * @mask:               bitmask for two's complement
 *                      subtraction of non 64 bit counters,
 *                      see CYCLECOUNTER_MASK() helper macro
 * @mult:               cycle to nanosecond multiplier
 * @shift:              cycle to nanosecond divisor (power of two)
 */
struct cyclecounter {
        u64 (*read)(const struct cyclecounter *cc);
        u64 mask;
        u32 mult;
        u32 shift;
};
  • (*read)
    • h/w 타이머의 카운터 값을 읽어오는 함수와 연결되는 후크이다.
  • mask
    • cycle 카운터 마스크로 h/w 카운터에서 읽은 값에서 유효한 비트만을 마스크한다.
    • 예) 56비트 카운터 = 0x00ff_ffff_ffff_ffff
  • mult & shift
    • 1 cycle 당 ns 값을 산출하기 위해 mult 값으로 곱한 후 우측으로 shift한다.
    • 예) mult=0x3415_5555, shift=24
      • 1 cycle = 52ns

 

참고

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한 클럭 소스를 선택한다.

 

참고