Timer -6- (Sched Clock & Delay Timers)

 

Sched Clock

sched_clock은 태스크 스케쥴러가 시간 계산에 사용하는 정밀한 카운터를 제공하며 클럭 소스 서브시스템에서 제공하는 카운터를 대부분 사용하여 sched_clock으로 등록한다.

 

 

Sched Clock 등록

sched_clock_register()

kernel/time/sched_clock.c

void __init sched_clock_register(u64 (*read)(void), int bits,
                                 unsigned long rate)
{
        u64 res, wrap, new_mask, new_epoch, cyc, ns;
        u32 new_mult, new_shift;
        ktime_t new_wrap_kt;
        unsigned long r;
        char r_unit;

        if (cd.rate > rate)
                return;

        WARN_ON(!irqs_disabled());

        /* calculate the mult/shift to convert counter ticks to ns. */
        clocks_calc_mult_shift(&new_mult, &new_shift, rate, NSEC_PER_SEC, 3600);

        new_mask = CLOCKSOURCE_MASK(bits);

        /* calculate how many ns until we wrap */
        wrap = clocks_calc_max_nsecs(new_mult, new_shift, 0, new_mask);
        new_wrap_kt = ns_to_ktime(wrap - (wrap >> 3));

        /* update epoch for new counter and update epoch_ns from old counter*/
        new_epoch = read();
        cyc = read_sched_clock();
        ns = cd.epoch_ns + cyc_to_ns((cyc - cd.epoch_cyc) & sched_clock_mask,
                          cd.mult, cd.shift);

        raw_write_seqcount_begin(&cd.seq);
        read_sched_clock = read;
        sched_clock_mask = new_mask;
        cd.rate = rate;
        cd.wrap_kt = new_wrap_kt;
        cd.mult = new_mult;
        cd.shift = new_shift;
        cd.epoch_cyc = new_epoch;
        cd.epoch_ns = ns;
        raw_write_seqcount_end(&cd.seq);

        r = rate;
        if (r >= 4000000) {
                r /= 1000000;
                r_unit = 'M';
        } else if (r >= 1000) {
                r /= 1000;
                r_unit = 'k';
        } else
                r_unit = ' ';

        /* calculate the ns resolution of this counter */
        res = cyc_to_ns(1ULL, new_mult, new_shift);

        pr_info("sched_clock: %u bits at %lu%cHz, resolution %lluns, wraps every %lluns\n",
                bits, r, r_unit, res, wrap);

        /* Enable IRQ time accounting if we have a fast enough sched_clock */
        if (irqtime > 0 || (irqtime == -1 && rate >= 1000000))
                enable_sched_clock_irqtime();

        pr_debug("Registered %pF as sched_clock source\n", read);
}

클럭 소스의 카운터 읽기 함수를 sched_clock으로 등록하여 사용한다.

  • 코드 라인 10~11에서 이미 등록한 sched_clock보다 rate가 느린 경우 처리하지 않고 함수를 빠져나간다.
  • 코드 라인 16에서 요청한 클럭 주파수를 3600초의 ns 단위로 바꾸는데 필요한 mult/shift를 산출한다.
    • rpi2 예) rate=19.2M -> mult=0x682_aaab, shift=21
  • 코드 라인 18에서 요청한 bit로 마스크 값을 구한다.
    • rpi2 예) bits=56 -> new_mask = 0x00ff_ffff_ffff_ffff
  • 코드 라인 21~22에서 wrap 타임을 구하고 그 값의 12.5%를 뺀 값으로 ktime으로 변환한다.
    • rpi2 예) rate=19.2Mhz -> wrap=3,579,139,424,256(약 1시간)   wrap_kt=3,131,746,996,224 (약 52분)
  • 코드 라인 25~26에서 요청한 새 클럭 카운터를 읽어 new_epoch에 대입하고 기존 클럭 카운터를 읽어 cyc에 대입한다.
  • 코드 라인 27~28에서 새로 읽은 cyc 값에서 기존 저장해둔 cd.epoch_cyc을 감소시켜 delta_cyc를 ns 단위로 변환한 후 기존 cd.epoch_ns에 추가한 값을 산출한다.
    • 처음 sched_clock을 등록 시 읽어온 jiffies cyc 값은 0이므로 ns 값은 항상 0이다.
    • sched_clock으로 사용될 클럭 소스가 더 높은 rate의 클럭 소스가 지정되는 경우 그 동안 소요된 ns 값이 반영된다.
  • 코드 라인 30~39에서 sched_clock에서 사용할 clock_data를 구성한다.
  • 코드 라인 41~49에서 출력을 위해 rate 값으로 r과 r_unit을 산출한다.
    • rpi2 예) rate=19.2 -> r=19, r_unit=’M’
  • 코드 라인 52에서 1 cycle에 해당하는 ns를 산출한다.
  • 코드 라인 54~55에서 sched_clock에 대한 정보를 출력한다.
    • rpi2 예) “sched_clock: 56 bits at 19MHz, resolution 52ns, wraps every 3579139424256ns”
  • 코드 라인 58~59에서 irqtime 값이 0을 초과하거나 처음 설정한 sched_clock의 rate가 1M 이상일 때 irq 타임 성능 측정을 할 수 있도록 전역 변수 sched_clock_irqtime에 1을 대입한다.
    • irqtime의 디폴트 값은 -1이다.
    • irq 타임 성능 측정은 NO_HZ_FULL 커널 옵션을 사용하지 않고 IRQ_TIME_ACCOUNTING 커널 옵션이 적용된 커널에서만 동작한다.

 

 

Delay 타이머

루프 기반의 delay 타이머이다.

 

 

Delay 타이머 등록 (generic 타이머)

arch_timer_delay_timer_register()

arch/arm/kernel/arch_timer.c

static void __init arch_timer_delay_timer_register(void)
{
        /* Use the architected timer for the delay loop. */
        arch_delay_timer.read_current_timer = arch_timer_read_counter_long;
        arch_delay_timer.freq = arch_timer_get_rate();
        register_current_timer_delay(&arch_delay_timer);
}

armv7 아키텍처에 내장된 generic 타이머를 delay 타이머로 사용할 수 있도록 등록한다.

  • freq=19.2Mhz

 

다음 그림은 100hz로 구성된 generic 타이머를 딜레이 타이머로 등록하는 과정을 보여준다.

 

register_current_timer_delay()

arch/arm/lib/delay.c

void __init register_current_timer_delay(const struct delay_timer *timer)
{
        u32 new_mult, new_shift;
        u64 res;

        clocks_calc_mult_shift(&new_mult, &new_shift, timer->freq,
                               NSEC_PER_SEC, 3600);
        res = cyc_to_ns(1ULL, new_mult, new_shift);

        if (!delay_calibrated && (!delay_res || (res < delay_res))) {
                pr_info("Switching to timer-based delay loop, resolution %lluns\n", res);
                delay_timer                     = timer;
                lpj_fine                        = timer->freq / HZ;
                delay_res                       = res;

                /* cpufreq may scale loops_per_jiffy, so keep a private copy */
                arm_delay_ops.ticks_per_jiffy   = lpj_fine;
                arm_delay_ops.delay             = __timer_delay;
                arm_delay_ops.const_udelay      = __timer_const_udelay;
                arm_delay_ops.udelay            = __timer_udelay;
        } else {
                pr_info("Ignoring duplicate/late registration of read_current_timer delay\n");
        }
}

딜레이 타이머를 등록하고 calibration 한다. 처음 설정 시에는 반드시 calibration을 한다.

  • 코드 라인 6~7에서 1시간에 해당하는 정확도로 1 cycle에 소요되는 nano초를 산출할 수 있도록 new_mult/new_shift 값을 산출한다.
  • 코드 라인 8에서 해상도 res 값을 구한다. (1 cycle에 해당하는 nano 초)
    • rpi2: 100hz, 19.2Mhz clock -> res=52
  • 코드 라인 10~20에서 calibration이 완료되지 않았고 처음이거나 요청한 타이머가 더 고해상도 타이머인 경우 딜레이 타이머에 대한 설정을 한다.
    • res 값이 작으면 작을 수록 고해상도 타이머이다.
    • 클럭 소스가 여러 개가 등록되는 경우 딜레이 타이머에 가장 좋은 고해상도 타이머를 선택하게 한다.
    • calivrate_delay() 함수에서 calibration을 완료하고 나면 더 이상 클럭 소스로 부터 더 이상 딜레이 카운터의 등록을 할 수 없게 한다.
    • rpi2 예) “Switching to timer-based delay loop, resolution 52ns”

 

참고

 

답글 남기기

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