timekeeping_init()

 

timekeeping_init()

kernel/time/timekeeping.c

/*
 * timekeeping_init - Initializes the clocksource and common timekeeping values
 */
void __init timekeeping_init(void)
{
        struct timekeeper *tk = &tk_core.timekeeper;
        struct clocksource *clock;
        unsigned long flags;
        struct timespec64 now, boot, tmp;
        struct timespec ts;

        read_persistent_clock(&ts);
        now = timespec_to_timespec64(ts);
        if (!timespec64_valid_strict(&now)) {
                pr_warn("WARNING: Persistent clock returned invalid value!\n"
                        "         Check your CMOS/BIOS settings.\n");
                now.tv_sec = 0;
                now.tv_nsec = 0; 
        } else if (now.tv_sec || now.tv_nsec)
                persistent_clock_exist = true;
 
        read_boot_clock(&ts);
        boot = timespec_to_timespec64(ts);
        if (!timespec64_valid_strict(&boot)) {
                pr_warn("WARNING: Boot clock returned invalid value!\n"
                        "         Check your CMOS/BIOS settings.\n");
                boot.tv_sec = 0;
                boot.tv_nsec = 0;
        }
        
        raw_spin_lock_irqsave(&timekeeper_lock, flags);
        write_seqcount_begin(&tk_core.seq);
        ntp_init();
 
        clock = clocksource_default_clock();
        if (clock->enable)
                clock->enable(clock);
        tk_setup_internals(tk, clock);

        tk_set_xtime(tk, &now);
        tk->raw_time.tv_sec = 0;
        tk->raw_time.tv_nsec = 0;
        tk->base_raw.tv64 = 0;
        if (boot.tv_sec == 0 && boot.tv_nsec == 0)
                boot = tk_xtime(tk);

        set_normalized_timespec64(&tmp, -boot.tv_sec, -boot.tv_nsec);
        tk_set_wall_to_mono(tk, tmp);

        timekeeping_update(tk, TK_MIRROR);

        write_seqcount_end(&tk_core.seq);
        raw_spin_unlock_irqrestore(&timekeeper_lock, flags);
}

persistent 클럭이 있는 경우 그 시간으로 리눅스 시계를 설정하고 없는 경우 0 (1970년)값으로 시계를 설정한다.

  • 코드 라인 12~20에서 persistent 클럭 값을 읽어온다.
    • persistent 클럭 값을 읽어들이는데 현재 powerpc 아키텍처에서 사용하며 그 외의 아키텍처는 timespec 구조체가 0으로 초기화된 것과 동일하다.
    • 32비트 타임 스펙 값을 64비트 타임 스펙 값으로 바꾼다.
    • 유효하지 않은 persistent 클럭 값을 읽어온 경우 경고 메시지와 함께 0으로 초기화하여 사용한다.
    • 읽어온 persistent 값이 음수(1970년 이전)이거나 나노 초에 해당하는 값을 초과하거나 초 값이 최대치를 초과하는 경우가 유효하지 않은 값이다.
    • persistent 클럭 값이 정확히 읽히면 persistent_clock_exist = true로 설정한다.
  • 코드 라인 22~29에서 boot 클럭 값을 읽어온다.
    • 역시 powerpc 아키텍처에서만 사용하며 그 외의 아키텍처는 timespec 구조체가 0으로 초기화된 것과 동일하다.
    • 32비트 타임 스펙 값을 64비트 타임 스펙 값으로 바꾼다.
    • 유효하지 않은 boot 클럭 값을 읽어온 경우 경고 메시지와 함께 0으로 초기화하여 사용한다.
  • 코드 라인 31~32에서 timekeeping 값을 바꾸기 위해 lock을 걸고 write 시퀀스 락도 시작한다.
  • 코드 라인 33에서 ntp 초기화를 수행한다.
  • 코드 라인 35~37에서 디폴트 클럭 소스를 enable 시킨다.
    • rpi2의 경우 디폴트 클럭 소스로 jiffies를 사용하므로 항시 enable 되어 있다.
  • 코드 라인 38에서 클럭 소스를 사용하기 위해 timekeeper 내부 설정을 진행한다.
  • 코드 라인 40에서 persistent 클럭 값을 xtime에 설정한다.
  • 코드 라인 41~43에서 raw_time과 base_raw를 0으로 초기화한다.
  • 코드 라인 44~45에서 현재 시각을 monotonic 클럭으로 변환할 수 있도록 boot 타임을 사용하여 wtm을 설정한다.
  • 코드 라인 47에서 boot 타임을 timespec64 타입으로 변환한 nonotonic 클럭을 설정한다.
  • 코드 라인 49에서 timekeeping 값들을 업데이트 하고 통지등을 수행한다.
  • 코드 라인 51~52에서 lock들을 해제한다.

 

tk_setup_internals()

kernel/time/timekeeping.c

/**
 * tk_setup_internals - Set up internals to use clocksource clock.
 *
 * @tk:         The target timekeeper to setup.
 * @clock:              Pointer to clocksource.
 *
 * Calculates a fixed cycle/nsec interval for a given clocksource/adjustment
 * pair and interval request.
 *
 * Unless you're the timekeeping code, you should not be using this!
 */
static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)
{
        cycle_t interval;
        u64 tmp, ntpinterval;
        struct clocksource *old_clock;

        old_clock = tk->tkr.clock;
        tk->tkr.clock = clock;
        tk->tkr.read = clock->read;
        tk->tkr.mask = clock->mask;
        tk->tkr.cycle_last = tk->tkr.read(clock);

        /* Do the ns -> cycle conversion first, using original mult */
        tmp = NTP_INTERVAL_LENGTH;
        tmp <<= clock->shift;
        ntpinterval = tmp;
        tmp += clock->mult/2;
        do_div(tmp, clock->mult);
        if (tmp == 0)
                tmp = 1;

        interval = (cycle_t) tmp;
        tk->cycle_interval = interval;

        /* Go back from cycles -> shifted ns */
        tk->xtime_interval = (u64) interval * clock->mult;
        tk->xtime_remainder = ntpinterval - tk->xtime_interval;
        tk->raw_interval =
                ((u64) interval * clock->mult) >> clock->shift;

         /* if changing clocks, convert xtime_nsec shift units */
        if (old_clock) {
                int shift_change = clock->shift - old_clock->shift;
                if (shift_change < 0)
                        tk->tkr.xtime_nsec >>= -shift_change;
                else
                        tk->tkr.xtime_nsec <<= shift_change;
        }
        tk->tkr.shift = clock->shift;

        tk->ntp_error = 0;
        tk->ntp_error_shift = NTP_SCALE_SHIFT - clock->shift;
        tk->ntp_tick = ntpinterval << tk->ntp_error_shift;

        /*
         * The timekeeper keeps its own mult values for the currently
         * active clocksource. These value will be adjusted via NTP
         * to counteract clock drifting.
         */
        tk->tkr.mult = clock->mult;
        tk->ntp_err_mult = 0;
}

클럭 소스를 사용하기 위해 timekeeper 내부 설정을 진행한다.

  • 코드 라인 19~22에서 timekeeper의 clock을 설정한다.
  • 코드 라인 25~27에서 ntp 인터벌 길이 << shift 하여 ntp_interval 값(ns)을 구한다.
    • 예) ntp 인터벌 길이 = 1E9 / 100hz = 1E7
      • -> ntp_interval = 1E7 << 24 = 0xf42_4000_0000
  • 코드 라인 28~34에서 interval = 1 tick에 해당하는 cycle 수 값을 구해 interval 및 cycle_interval에 대입한다.
    • 예) 0xf42_4000_0000 / mult = 192,000
  • 코드 라인 37에서 1 tick 에 해당하는 cycle 수가 담긴 interval에 mult 값을 곱하여 xtime_interval에 대입한다.
  • 코드 라인 38에서 ntpinterval 에서 xtime_interval을 빼서 xtime_remander에 대입한다.
  • 코드 라인 39~40에서 interval을 ns로 산출 시 소요되는 시간을 구한다. (1 스케쥴 tick (ns)에 가깝다)
    • interval 에 mult를 곱하고 shift 하여 raw_interval 값을 구한다.
  • 코드 라인 43~49에서 기존 clock이 있었던 경우 배율에 따라서 xtime_nsec 값을 컨버팅한다.
  • 코드 라인 52~62에서 ntp 관련 값을 초기화한다.

 

아래 그림은 timekeeper의 클럭 소스로 jiffies 클럭 소스를 사용하다 arch_sys_conter 클럭 소스로 변경되었을 때의 처리 과정을 보여준다.

 

set_normalized_timespec64()

kernel/time/time.c

/**
 * set_normalized_timespec - set timespec sec and nsec parts and normalize
 *
 * @ts:         pointer to timespec variable to be set
 * @sec:        seconds to set
 * @nsec:       nanoseconds to set
 *
 * Set seconds and nanoseconds field of a timespec variable and
 * normalize to the timespec storage format
 *
 * Note: The tv_nsec part is always in the range of
 *      0 <= tv_nsec < NSEC_PER_SEC
 * For negative values only the tv_sec field is negative !
 */
void set_normalized_timespec64(struct timespec64 *ts, time64_t sec, s64 nsec)
{
        while (nsec >= NSEC_PER_SEC) {
                /*
                 * The following asm() prevents the compiler from
                 * optimising this loop into a modulo operation. See
                 * also __iter_div_u64_rem() in include/linux/time.h
                 */
                asm("" : "+rm"(nsec));   
                nsec -= NSEC_PER_SEC;
                ++sec;
        }
        while (nsec < 0) {                      
                asm("" : "+rm"(nsec));
                nsec += NSEC_PER_SEC;
                --sec;
        }                       
        ts->tv_sec = sec;
        ts->tv_nsec = nsec;
}
EXPORT_SYMBOL(set_normalized_timespec64);

범위를 벗어난 나노초 단위를 1초 단위로 조정하여 한 번에 급격한 시간 변동이 되지 않도록 한다. 그런 후 출력 인수 ts에 timespec64 단위로 변환한다.

  • 나노초가 항상 0 ~ 1G에 해당하도록 초 단위를 1씩 증감시키며 맞춘다.

 

Timekeeping 변경

timekeeping_update()

kernel/time/timekeeping.c

/* must hold timekeeper_lock */
static void timekeeping_update(struct timekeeper *tk, unsigned int action)
{
        if (action & TK_CLEAR_NTP) {
                tk->ntp_error = 0;
                ntp_clear();
        }

        tk_update_ktime_data(tk);

        update_vsyscall(tk);
        update_pvclock_gtod(tk, action & TK_CLOCK_WAS_SET);

        if (action & TK_MIRROR)
                memcpy(&shadow_timekeeper, &tk_core.timekeeper,
                       sizeof(tk_core.timekeeper));

        update_fast_timekeeper(&tk->tkr);
}

timekeeping 값들을 업데이트 하고 통지등을 수행한다.

  • 코드 라인 4~7에서 TK_CLEAR_NTP 요청이 있는 경우 ntp를 클리어한다.
  • 코드 라인 9에서 time keeper의 ktime_t 기반의 스칼라 나노초들을 업데이트한다.
  • 코드 라인 11에서 vsyscall을 지원하는 아키텍처를 위해 timekeeper를 업데이트한다.
    • 아직 arm에는 적용되지 않았지만 arm64, x86 등 많은 커널이 유저 스페이스에서  커널 코드나 데이터의 직접 접근을 허용하도록 매핑해준다. 이렇게 사용하면 context swtich로 인한 오버헤드를 줄여 매우 빠르게 유저스페이스에서 커널의 자원을 사용할 수 있는 장점이 있다. 하지만 시큐리티 이슈로 매우 제한적인 사용을 보여준다. arm에서는 유사하게 User Helpers라는 인터페이스가 있으며 VDSO와 매우 유사하다.
    • 참고
  • 코드 라인 12에서 pvclock_gtod_chain 리스트에 등록된 notify block에 등록된 함수를 호출한다.
    • 현재는 x86 아키텍처에서 가상화 지원을 위해 사용된다.
  • 코드 라인 14~16에서 TK_MIRROR가 요청된 경우 timekeeper를 shadow timekeeper로 복사한다.
  • 코드 라인 18에서 tracing 지원을 위해 timekeeper를 fast timekeeper로도 복사한다.

 

tk_update_ktime_data()

kernel/time/timekeeping.c

/*
 * Update the ktime_t based scalar nsec members of the timekeeper
 */
static inline void tk_update_ktime_data(struct timekeeper *tk)
{
        u64 seconds;
        u32 nsec;

        /*
         * The xtime based monotonic readout is:
         *      nsec = (xtime_sec + wtm_sec) * 1e9 + wtm_nsec + now();
         * The ktime based monotonic readout is:
         *      nsec = base_mono + now();
         * ==> base_mono = (xtime_sec + wtm_sec) * 1e9 + wtm_nsec
         */
        seconds = (u64)(tk->xtime_sec + tk->wall_to_monotonic.tv_sec);
        nsec = (u32) tk->wall_to_monotonic.tv_nsec;
        tk->tkr.base_mono = ns_to_ktime(seconds * NSEC_PER_SEC + nsec);

        /* Update the monotonic raw base */
        tk->base_raw = timespec64_to_ktime(tk->raw_time);

        /* 
         * The sum of the nanoseconds portions of xtime and
         * wall_to_monotonic can be greater/equal one second. Take
         * this into account before updating tk->ktime_sec.
         */
        nsec += (u32)(tk->tkr.xtime_nsec >> tk->tkr.shift);
        if (nsec >= NSEC_PER_SEC)
                seconds++;
        tk->ktime_sec = seconds;
}

timekepper의 ktime_t 기반 나노초를 갱신한다.

  • 코드 라인 16~18에서 xtime 과 wtm을 더해서 base_mono에 대입한다.
  • 코드 라인 21에서 raw_time 값을 base_raw 타입으로 변환하여 대입한다.
  • 코드 라인 28~31에서 xtime과 wtm의 초를 더해 ktime_sec에 대입하되 wtime_nsec + (xtime_nsec >> shift) 가 1초를 초과하는 경우 추가로 1을 증가시킨다.

 

아래 그림과 같이 timekeeper의 base_mono, ktime_sec, base_raw 값이 갱신되는 것을 보여준다.

 

Fast Timekeeper

참고: timekeeping: Provide fast and NMI safe access to CLOCK_MONOTONIC

update_fast_timekeeper()

kernel/time/timekeeping.c

/**
 * update_fast_timekeeper - Update the fast and NMI safe monotonic timekeeper.
 * @tkr: Timekeeping readout base from which we take the update
 *
 * We want to use this from any context including NMI and tracing /
 * instrumenting the timekeeping code itself.
 *
 * So we handle this differently than the other timekeeping accessor
 * functions which retry when the sequence count has changed. The
 * update side does:
 *
 * smp_wmb();   <- Ensure that the last base[1] update is visible
 * tkf->seq++;
 * smp_wmb();   <- Ensure that the seqcount update is visible
 * update(tkf->base[0], tkr);
 * smp_wmb();   <- Ensure that the base[0] update is visible
 * tkf->seq++;
 * smp_wmb();   <- Ensure that the seqcount update is visible
 * update(tkf->base[1], tkr);
 *
 * The reader side does:
 *
 * do {
 *      seq = tkf->seq;
 *      smp_rmb();
 *      idx = seq & 0x01;
 *      now = now(tkf->base[idx]);
 *      smp_rmb();
 * } while (seq != tkf->seq)
 *
 * As long as we update base[0] readers are forced off to
 * base[1]. Once base[0] is updated readers are redirected to base[0]
 * and the base[1] update takes place.
 *
 * So if a NMI hits the update of base[0] then it will use base[1]
 * which is still consistent. In the worst case this can result is a
 * slightly wrong timestamp (a few nanoseconds). See
 * @ktime_get_mono_fast_ns.
 */
static void update_fast_timekeeper(struct tk_read_base *tkr)
{
        struct tk_read_base *base = tk_fast_mono.base;

        /* Force readers off to base[1] */
        raw_write_seqcount_latch(&tk_fast_mono.seq);

        /* Update base[0] */
        memcpy(base, tkr, sizeof(*base));

        /* Force readers back to base[0] */
        raw_write_seqcount_latch(&tk_fast_mono.seq);

        /* Update base[1] */
        memcpy(base + 1, base, sizeof(*base));
}

tracing 지원을 위해 timekeeper를 fast timekeeper로 복사한다. 복사 할 때 두 개의 공간에 복사하는데 매우 빠르게 lockless로 사용할 수 있도록 구현되었다.

  • 코드 라인 45에서 시퀀스를 증가시킨다. 이 때 시퀀스 값이 홀수가된다.
  • 코드 라인 48에서 fast timekeeper 첫 번째 공간에 tkr을 복사한다.
  • 코드 라인 51에서 시퀀스를 증가시킨다. 이 때 시퀀스 값이 짝수가된다.
  • 코드 라인 52에서 fast timekeeper 두 번째 공간에 첫 번째 공간을 복사한다.

 

다음 그림은 timekeeper를 fast timekeeper로 업데이트하는 것을 보여준다.

 

ktime_get_mono_fast_ns()

kernel/time/timekeeping.c

/**
 * ktime_get_mono_fast_ns - Fast NMI safe access to clock monotonic
 *
 * This timestamp is not guaranteed to be monotonic across an update.
 * The timestamp is calculated by:
 *
 *      now = base_mono + clock_delta * slope
 *
 * So if the update lowers the slope, readers who are forced to the
 * not yet updated second array are still using the old steeper slope.
 *
 * tmono
 * ^
 * |    o  n
 * |   o n
 * |  u
 * | o
 * |o
 * |12345678---> reader order
 *
 * o = old slope
 * u = update
 * n = new slope
 *
 * So reader 6 will observe time going backwards versus reader 5.
 *
 * While other CPUs are likely to be able observe that, the only way
 * for a CPU local observation is when an NMI hits in the middle of
 * the update. Timestamps taken from that NMI context might be ahead
 * of the following timestamps. Callers need to be aware of that and
 * deal with it.
 */
u64 notrace ktime_get_mono_fast_ns(void)
{
        struct tk_read_base *tkr;
        unsigned int seq;
        u64 now;

        do {
                seq = raw_read_seqcount(&tk_fast_mono.seq);
                tkr = tk_fast_mono.base + (seq & 0x01);
                now = ktime_to_ns(tkr->base_mono) + timekeeping_get_ns(tkr);

        } while (read_seqcount_retry(&tk_fast_mono.seq, seq));
        return now;
}
EXPORT_SYMBOL_GPL(ktime_get_mono_fast_ns);

fast timekeeper로부터 monotonic 클럭 값을 얻어온다. fast timekeeeper는 tracing 목적으로 사용되며 lockless 구현되어 매우 빠르게 monotonic 값을 얻고자 할 때 사용하다.

  • 코드 라인 40에서 시퀀스 값을 읽어온다.
  • 코드 라인 41~42에서 시퀀스가 짝수이면 fast timekeeper의 base[0]에서 홀수이면 base[1]을 사용하여 monotonic 값을 산출한다.
  • 코드 라인 44에서 시퀀스 값에 변화가 있는 경우 다시 반복한다.

 

참고

답글 남기기

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