Timer -8- (Tick Device)

Per-cpu Tick Device 및 Tick Broadcast Device

 

틱을 관리하는 드라이버들로 클럭 이벤트 디바이스의 기능에 따라 각 cpu들은 틱 cpu 디바이스 또는 틱 브로드캐스트 디바이스 둘 중 하나에 연결하여 사용한다.

  • Per-cpu Tick 디바이스 (전역 변수 per-cpu tick_cpu_device)
    • per-cpu 타이머, 절전기능(c3stop) 타이머와 dummy 타이머를 사용한 클럭 이벤트 디바이스들로 사용 가능하다.
  • Tick 브로드캐스트 디바이스 (전역 변수 tick_broadcast_device)
    • per-cpu 타이머, 절전기능(c3stop) 타이머 및 dummy 타이머를 사용하지 않는 클럭 이벤트 디바이스만 사용 가능하다.
    • 틱 브로드캐스트 모드가 oneshot인 경우 클럭 이벤트 디바이스도 oneshot 기능이 준비되어 있어야 한다.
    • 시스템에서 nohz 구현을 위해 Tick 브로드캐스트 디바이스를 사용하지 않는 경우도 있다. (rpi2, rpi3도 사용하지 않는다.)

 

 

틱 디바이스의 모드 운영

  • hz 기반의 periodic으로 운영
    • periodic 또는 oneshot 기능을 가진 클럭 이벤트 디바이스 모두 동작할 수 있다.
  • nohz 기반의 oneshot 모드로 운영
    • oneshot 기능을 가진 클럭 이벤트 디바이스가 동작 가능한다.
    • 틱 모드는 처음에 periodic으로 운영하다가 hrtimer가 준비된 후에 틱 디바이스의 모드를 oneshot 모드로 변경하여 운영한다. 이 과정에서 tick 핸들러가 다음과 같은 순으로 바뀐다.
      • tick_handle_periodic() -> clockevents_handle_noop() -> tick_sched_timer()

 

c3stop (절전 상태 진입하여 코어, IC 및 타이머 등 파워 다운)

arm 아키텍처에 내장된 타이머 중 CP15 레지스터를 통해 제어되는 경우 cpu가 c3(deep-sleep) 절전 상태로 진입하면 절전을 위해 코어, IC 및 타이머의 power를 다운시킨다.  이렇게 타이머 전원을 다운시키면 해당 타이머에 의해 틱 처리를 하기 위해 스스로 wake-up할 수 없으므로 다른 깨어있는 cpu의 도움을 받아야 한다. arm 아키텍처의 내장 타이머는 기본적으로 c3stop을 지원하는데 cpu가 c3(deep-sleep) 상태로 진입할 때 최소 한 개의 cpu는 지속적으로 tick을 처리하고 c3 상태에 진입한 cpu를 대신해서 틱의 만료시간을 계산하여 알릴 필요가 있다. 이렇게 만료된 cpu를 깨워 틱을 처리하게 하기 위해 틱 브로드캐스트 디바이스를 사용한다.

  • 현재까지 armv7, armv8 아키텍처의 대부분은 core 단위의 idle에서는 wfi를 사용하여 절전활동을 하고, 클러스터에 소속한 모든 core가 idle되는 경우 deep-sleep 상태로 진입한다. suspend와 동일하게 클러스터 내의 파워를 끄는 형태인데 이 때 시스템에 따라 해당 클러스터의 타이머 및 인터럽트 컨트롤러도 같이 전원이 꺼질 수 있다.
  • arm 아키텍처에 내장된 CP15로 제어되는 타이머라 하더라도 특정 시스템은 절전 설계되지 않아 항상 타이머 전원이 on되어 있다. 이러한 경우 c3stop을 false(“always-on”)로 설정하여 내부적으로 스케줄 틱의 관리에 틱 브로드캐스트 디바이스를 사용할 필요 없이 그냥 틱 cpu 디바이스를 사용한다.

 

c3stop 기능을 사용하는 클럭소스

  • x86의 LAPIC 타이머
  • ARM 내장 generic 타이머
    • arm_arch_timer
  • 기타
    • mips-gic-timer
    • clps711x-timer

 

절전을 위한 전원 관리 시스템이 없는 SoC에서는 c3stop 기능을 사용하면 안된다. 이러한 시스템에서 절전을 위해 타이머의 전원을 off 후 on 하는 경우 타이머의 comparison 레지스터의 내용이 유실되는 현상이 벌어진다. 이러한 증상으로 인해 리눅스 커널은 디바이스 트리 스크립트의 타이머 노드에 “always-on;” 속성을 추가하여 c3stop 기능을 사용하지 못하게 막는다. (항상 전원이 켜져서 동작하는 타이머로 인식한다.)

  • rpi2, rpi3: “always-on;” 속성을 사용하여 브로드캐스트 기능을 사용하지 못하게 한다.

 

틱 브로드캐스트

절전을 위해 tick을 발생시키지 않는 nohz-idle을 구현할 때 cpu가 c3(deep-sleep) 상태에 진입되면 타이머 전원도 같이 꺼지는 시스템을 위해 다음과 같은 브로드캐스트 구현이 필요하다.

  • cpu가 처리할 일이 없어 idle 상태로 진입하기 전에 브로드캐스트를 수신하기 위해 해당 cpu의 비트를 설정한다.
    • 브로드캐스트 디바이스의 모드가 periodic인 경우 shutdown 한다.
    • 모든 cpu가 처리할 일이 없어도 최소 하나의 대표 cpu는 브로드캐스트 디바이스가 아니라 틱 디바이스로 동작해야 한다.
  • 대표 cpu는 c3(deep-sleep) 상태의 cpu를 깨우기 위해 broadcast를 한다.
    • c3 상태에 있는 cpu를 깨울 때 IPI(Inter Process Interrupt)를 통해 브로드캐스트하여 해당 cpu를 깨운다.
  • 깨어난 cpu는 더 이상 브로드캐스트를 수신하지 않아도 되므로 해당 cpu의 비트를 클리어한다.
    • 브로드캐스트 디바이스의 모드가 periodic인 경우 다시 프로그램한다.

 

C-State

인텔에서 정의한 절전 상태 (참고: Everything You Need to Know About the CPU C-States Power Saving Modes

  • C0
    • 풀 파워로 인스트럭션을 수행한다.
  • C1
    • 내부 코어 클럭을 정지시킨 halt 상태
    • 외부 클럭과 ACPI는 동작 중으로 인터럽트는 처리할 수 있는 상태
  • C2
    • Stop-Grant & Stop-Clock 상태로 내부 및 외부 클럭이 정지된 상태
    • ACPI는 동작중으로 인터럽트는 처리할 수 있는 상태
  • C3
    • 플러시 캐시. 내부 클럭 및 외부 클럭도 받아들이지 않고 deep sleep 상태
    • ACPI를 통한 인터럽트도 받아들이지 않으며, 특별히 wake-up을 통한 장치를 통해서만 깨울 수 있다.
    • c3stop이라고도 한다.
  • C4/C5
    • 인텔 듀오프로세서에서 멀티 코어 전체가 deep sleep 상태
  • C6
    • 인텔 Core i7에서 모든 코어가 좀 더 deeper sleep 상태

 

다음 그림은 tick 핸들러의 설정에 대해 softirq(timer)가 준비되기 전의 순서 흐름이다.

 

다음 그림은 tick 핸들러의 설정에 대해 softirq(timer)가 준비된 후의 순서 흐름으로 hrtimer의 준비 여부가 영향을 끼친다.

 

다음 그림은 틱 cpu 디바이스 및 틱 브로드캐스트 디바이스가 사용하는 틱 핸들러들이다.

  • hz/nohz 기반의 high resolution을 사용하는 핸들러는 hrtimer_interrupt이지만 틱 디바이스와 별개로 모든 hrtimer의 만료 시간을 관리하므로 틱 디바이스만을 대상으로 좁히는 경우 실제 틱 처리에 대한 핸들러는 tick_sched_timer()라고 할 수 있다.

 

틱 cpu 디바이스 또는 틱 브로드캐스트 디바이스 등록

tick_check_new_device()

kernel/time/tick-common.c

/*
 * Check, if the new registered device should be used. Called with
 * clockevents_lock held and interrupts disabled.
 */
void tick_check_new_device(struct clock_event_device *newdev)
{
        struct clock_event_device *curdev;
        struct tick_device *td;
        int cpu;

        cpu = smp_processor_id();
        if (!cpumask_test_cpu(cpu, newdev->cpumask))
                goto out_bc;

        td = &per_cpu(tick_cpu_device, cpu);
        curdev = td->evtdev;
        
        /* cpu local device ? */
        if (!tick_check_percpu(curdev, newdev, cpu))
                goto out_bc;

        /* Preference decision */
        if (!tick_check_preferred(curdev, newdev))
                goto out_bc;

        if (!try_module_get(newdev->owner))
                return;

        /* 
         * Replace the eventually existing device by the new
         * device. If the current device is the broadcast device, do
         * not give it back to the clockevents layer !
         */
        if (tick_is_broadcast_device(curdev)) {
                clockevents_shutdown(curdev);
                curdev = NULL;
        }
        clockevents_exchange_device(curdev, newdev);
        tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
        if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
                tick_oneshot_notify();
        return;

out_bc:
        /*
         * Can the new device be used as a broadcast device ?
         */
        tick_install_broadcast_device(newdev);
}

현재 cpu의 tick 디바이스로 기존 tick 디바이스보다 새 tick 디바이스가 더 좋은 rating 등급인 경우 변경하여 사용할지 체크한다.

  • 처음 호출 시에는 요청 디바이스가 tick 디바이스로 사용된다.
  • nohz 구현을 위해 경우에 따라 등록되는 클럭 이벤트 디바이스가 틱 브로드캐스트 디바이스로 동작할 수도 있다.
  • 코드 라인 11~13에서 현재 cpu가 클럭 이벤트 디바이스에서 허용된 cpu가 아닌 경우 out_bc 레이블로 이동한다.
  • 코드 라인 15~20에서 tick_cpu_device에 등록된 클럭 이벤트 디바이스를 tick 디바이스로 설정할 수 없으면 out_bc 레이블로 이동한다.
  • 코드 라인 23~24에서 기존 디바이스와 새 디바이스를 비교하여 새 디바이스의 rating이 더 높은 경우가 아니면  out_bc 레이블로 이동한다.
  • 코드 라인 26~27에서 새 디바이스의 모듈이 동작하지 않는 경우 처리를 중단하고 함수를 빠져나간다.
  • 코드 라인 34~37에서 기존 디바이스가 틱 디바이스인 경우 기존 디바이스를 shutdown 시킨다.
  • 코드 라인 38에서 틱 디바이스에 대해 기존 클럭 이벤트 디바이스에서 새 클럭 이벤트 디바이스로 변경한다. 만일 새 클럭 이벤트 디바이스가 동작 중인 경우 shutdown 상태로 변경한다.
  • 코드 라인 39에서 새 클럭 이벤트 디바이스를 틱 디바이스로 변경한다.
  • 코드 라인 40~42에서 새 디바이스에 oneshot 기능이 있는 경우 클럭 이벤트 디바이스가 변경되었음을 틱 스케쥴 정보에 async하게 알리고 함수를 빠져나간다.
  • 코드 라인 44~48에서 out_bc 레이블에 도착하면 새 클럭 이벤트 디바이스가 틱 브로드캐스트 디바이스로 동작할 수 있는 경우 이미 지정된 틱 브로드캐스트 디바이스보다 더 높은 rating 등급인 경우 해당 클럭 이벤트 디바이스를 틱 브로드 캐스트 디바이스로 지정한다.

 

다음 그림은 각 cpu별로 여러 개의 틱 이벤트 디바이스가 존재하는 경우 best rating 디바이스를 선택하고 그 나머지들은 release 리스트에 두는 모습을 보여준다.

 

틱 디바이스 조건 체크

tick_check_percpu()

kernel/time/tick-common.c

static bool tick_check_percpu(struct clock_event_device *curdev,
                              struct clock_event_device *newdev, int cpu)
{
        if (!cpumask_test_cpu(cpu, newdev->cpumask))
                return false;
        if (cpumask_equal(newdev->cpumask, cpumask_of(cpu)))
                return true;
        /* Check if irq affinity can be set */
        if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq))
                return false;
        /* Prefer an existing cpu local device */
        if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
                return false;
        return true;
}

새 클럭 이벤트 디바이스를 tick 디바이스로 설정할 수 있는지 여부를 반환한다.

  • 코드 라인 4~5에서 요청 cpu가 새 디바이스가 허용하는 cpu가 아닌 경우 false를 반환한다.
  • 코드 라인 6~7에서 요청 cpu가 새 디바이스에 per-cpu로 등록된 경우 true를 반환한다.
  • 코드 라인 9~10에서 새 디바이스가 취급하는 irq로 affinity 설정할 수 없으면 false를 반환한다.
    • FEAT_DYNTICK이 부여된 드라이버들
      • armv7 또는 armv8 아키텍처에 내장된 generic 타이머의 메모리 mapped 드라이버 – “arm,armv7-timer-mem”
      • “st,nomadik-mtu” 드라이버
  • 코드 라인 12~13에서 요청 cpu가 현재 디바이스에 이미 per-cpu로 등록되어 있는 경우 false를 반환한다.

 

tick_check_preferred()

kernel/time/tick-common.c

static bool tick_check_preferred(struct clock_event_device *curdev,
                                 struct clock_event_device *newdev)
{
        /* Prefer oneshot capable device */
        if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {
                if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
                        return false;
                if (tick_oneshot_mode_active())
                        return false;
        }

        /*
         * Use the higher rated one, but prefer a CPU local device with a lower
         * rating than a non-CPU local device
         */
        return !curdev ||
                newdev->rating > curdev->rating ||
               !cpumask_equal(curdev->cpumask, newdev->cpumask);
}

기존 틱 디바이스와 새 디바이스를 비교하여 새 디바이스의 동작 모드 및 등급(rating)이 더 높은 경우 true를 반환한다. (oneshot -> rating 순)

  • 코드 라인  5~7에서 기존  틱 디바이스는 oneshot 기능이 있는데 새 디바이스가 oneshot 기능이 없는 경우 false를 반환한다. (oneshot 우선)
  • 코드 라인 8~9에서 기존 틱 디바이스가 이미 oneshot 모드로 동작하는데 새 디바이스가 oneshot 기능이 없는 경우 false를 반환한다. (oneshot 우선)
  • 코드 라인 16~18에서 새 디바이스의 rating이 더 높은 경우 true를 반환한다.

 

다음 그림은 새 클럭 이벤트 디바이스가 기존보다 더 구형(periodic) 모드로 동작하거나 rating이 더 낮을 때 선호되지 않는 케이스들을 보여준다.

  • 성공 case:
    • 기존 틱 디바이스로 등록된 클럭 이벤트 디바이스가 하나도 없는 경우
    • 기존 틱 디바이스에 등록된 클럭 이벤트 디바이스의 등급(rating)보다 높고 feature도 서로 같거나 더 높은 oneshot인 경우

 

tick_oneshot_mode_active()

kernel/time/tick-oneshot.c

/**
 * tick_check_oneshot_mode - check whether the system is in oneshot mode
 *
 * returns 1 when either nohz or highres are enabled. otherwise 0.
 */
int tick_oneshot_mode_active(void)
{
        unsigned long flags;
        int ret;

        local_irq_save(flags);
        ret = __this_cpu_read(tick_cpu_device.mode) == TICKDEV_MODE_ONESHOT;
        local_irq_restore(flags);

        return ret;
}

현재 cpu의 tick 디바이스가 oneshot 모드인지 여부를 반환한다.

 

try_module_get()

kernel/module.c

bool try_module_get(struct module *module)
{
        bool ret = true;

        if (module) {
                preempt_disable();
                /* Note: here, we can fail to get a reference */
                if (likely(module_is_live(module) &&
                           atomic_inc_not_zero(&module->refcnt) != 0))
                        trace_module_get(module, _RET_IP_);
                else
                        ret = false;

                preempt_enable();
        }
        return ret;
}
EXPORT_SYMBOL(try_module_get);

모듈의 동작 여부를 반환한다.

 

Tick 디바이스로 지정

tick_setup_device()

kernel/time/tick-common.c

/*
 * Setup the tick device
 */
static void tick_setup_device(struct tick_device *td,
                              struct clock_event_device *newdev, int cpu,
                              const struct cpumask *cpumask)
{
        ktime_t next_event;
        void (*handler)(struct clock_event_device *) = NULL;

        /*
         * First device setup ?
         */
        if (!td->evtdev) {
                /*
                 * If no cpu took the do_timer update, assign it to
                 * this cpu:
                 */
                if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
                        if (!tick_nohz_full_cpu(cpu))
                                tick_do_timer_cpu = cpu;
                        else
                                tick_do_timer_cpu = TICK_DO_TIMER_NONE;
                        tick_next_period = ktime_get();
                        tick_period = ktime_set(0, NSEC_PER_SEC / HZ);
                }

                /*
                 * Startup in periodic mode first.
                 */
                td->mode = TICKDEV_MODE_PERIODIC;
        } else {
                handler = td->evtdev->event_handler;
                next_event = td->evtdev->next_event;
                td->evtdev->event_handler = clockevents_handle_noop;
        }

        td->evtdev = newdev;

        /*
         * When the device is not per cpu, pin the interrupt to the
         * current cpu:
         */
        if (!cpumask_equal(newdev->cpumask, cpumask))
                irq_set_affinity(newdev->irq, cpumask);

        /*
         * When global broadcasting is active, check if the current
         * device is registered as a placeholder for broadcast mode.
         * This allows us to handle this x86 misfeature in a generic
         * way. This function also returns !=0 when we keep the
         * current active broadcast state for this CPU.
         */
        if (tick_device_uses_broadcast(newdev, cpu))
                return;

        if (td->mode == TICKDEV_MODE_PERIODIC)
                tick_setup_periodic(newdev, 0);
        else
                tick_setup_oneshot(newdev, handler, next_event);
}

요청한 클럭 이벤트 디바이스를 현재 cpu에 대한 틱 디바이스로 지정한다. 현재 cpu에 대해 처음 틱 디바이스가 지정된 경우 틱 디바이스의 모드를 periodic으로 시작한다. (hrtimer가 초기화되어 사용되기 전에는 oneshot 모드로 바꾸지 않는다.)

  • 코드 라인 14~31에서  현재 cpu에 대해 tick 디바이스의 클럭 이벤트 디바이스가 설정되지 않은 경우 모드를 periodic으로 설정한다. 그리고 모든 cpu를 대상으로 가장 처음 틱 디바이스가 설정되면 틱에 대한 주기 등 관련 값들을 설정한다. (tick_do_timer_cpu, tick_next_period, tick_period)
    • tick_do_timer_cpu는 디폴트 값으로 boot 상태로 설정되어 있다.
  • 코드 라인 32~36에서 tick 디바이스의 클럭 이벤트 디바이스가 이미 설정되어 있었던 경우 임시로 이벤트 핸들러에 아무 것도 수행하지 않는 빈(noop) 핸들러를 대입한다.
    • 해당 cpu에 대한 틱 디바이스의 클럭 이벤트 디바이스가 변경되는 과정에서 이벤트 핸들러가 동작되어야 할 때 자연스럽게 아무것도 처리하지 않게 한다.
  • 코드 라인 38에서 tick 디바이스에 클럭 이벤트 디바이스를 연결한다.
  • 코드 라인 44~45에서 새 클럭 이벤트 디바이스가 cpu마다 부탁된 타입이 아닌 경우 현재 cpu의 틱 디바이스에서 irq를 처리할 수 있도록 affinity 설정을 한다.
    • FEAT_IRQDYN 기능이 있는 클럭 이벤트 디바이스만이 irq를 요청한 cpu로 연결할 수 있다.
    • 브로드캐스트 목적의 인터럽트가 특정 cpu에 고정되지 않고 cpu를 선택(set_irq_affinity)하여 사용할 수 있으며 다음 드라이버에서 사용되고 있다.
      • armv7 또는 armv8 아키텍처에 내장된 generic 타이머의 메모리 mapped 드라이버 – “arm,armv7-timer-mem”
      • “st,nomadik-mtu”
  • 코드 라인 54~55에서 새 클럭 이벤트 디바이스가 브로드캐스트 디바이스로 동작하는 경우 처리가 완료되었으므로 함수를 빠져나간다.
  • 코드 라인 57~60에서 tick 디바이스의 모드에 맞게 틱 디바이스를 periodic 및 oneshot 모드로 설정한다.

 

tick_nohz_full_cpu()

include/linux/tick.h

static inline bool tick_nohz_full_cpu(int cpu)
{
        if (!tick_nohz_full_enabled())
                return false;

        return cpumask_test_cpu(cpu, tick_nohz_full_mask);
}

현재 cpu가 nohz full로 운영되는지 여부를 반환한다.

 

tick_nohz_full_enabled()

include/linux/tick.h

static inline bool tick_nohz_full_enabled(void)
{
        if (!context_tracking_is_enabled())
                return false;

        return tick_nohz_full_running;
}

nohz full로 운영되는지 여부를 반환한다.

 

틱 디바이스를 브로드캐스트 디바이스로 등록

tick_device_uses_broadcast()

kernel/time/tick-broadcast.c

/*
 * Check, if the device is disfunctional and a place holder, which
 * needs to be handled by the broadcast device.
 */
int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu)
{
        struct clock_event_device *bc = tick_broadcast_device.evtdev;
        unsigned long flags;
        int ret;

        raw_spin_lock_irqsave(&tick_broadcast_lock, flags);

        /*
         * Devices might be registered with both periodic and oneshot
         * mode disabled. This signals, that the device needs to be
         * operated from the broadcast device and is a placeholder for
         * the cpu local device.
         */
        if (!tick_device_is_functional(dev)) {
                dev->event_handler = tick_handle_periodic;
                tick_device_setup_broadcast_func(dev);
                cpumask_set_cpu(cpu, tick_broadcast_mask);
                if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
                        tick_broadcast_start_periodic(bc);
                else
                        tick_broadcast_setup_oneshot(bc);
                ret = 1;
        } else {
                /*
                 * Clear the broadcast bit for this cpu if the
                 * device is not power state affected.
                 */
                if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))
                        cpumask_clear_cpu(cpu, tick_broadcast_mask);
                else
                        tick_device_setup_broadcast_func(dev);

                /*
                 * Clear the broadcast bit if the CPU is not in
                 * periodic broadcast on state.
                 */
                if (!cpumask_test_cpu(cpu, tick_broadcast_on))
                        cpumask_clear_cpu(cpu, tick_broadcast_mask);

틱 디바이스 상태에 따라 브로드캐스트 디바이스로 동작시킬지 아닐지 여부를 다음과 같이 결정한다.

  • 더미 틱 디바이스인 경우 현재 cpu에 대해 브로드캐스트 디바이스로 동작시킨다. 결과=1
  • oneshot 틱 디바이스인 경우 현재 cpu를 브로드캐스트에서 제외한다. 결과=0
  • periodic 모드인 경우 브로드캐스트할 cpu가 없는 경우 shutdown한다. 결과=현재 cpu가 브로드캐스트에 포함되어 있는지 여부

 

  • 코드 라인 19~22에서 클럭 이벤트 디바이스가 더미인 경우 핸들러 함수와 브로드캐스트 함수를 설정한다. 그리고 현재 cpu를 tick_broadcast_mask에 추가한다.
    • 틱 브로드캐스트 디바이스가 더미 디바이스 상태에 있을 때에도 핸들러 함수가 정상적으로 호출되도록 설정한다.
  • 코드 라인 23~26에서 더미 디바이스를 틱 브로드캐스트 디바이스로 사용하고 결과로 1을 반환한다. 모드가 periodic을 지원하면 periodic 모드로 동작시키고 그렇지 않으면 oneshot 모드로 동작시킨다.
  • 코드 라인 28~36에서 c3stop 기능이 없는 경우 tick_broadcast_mask에서 요청 cpu를 제외시킨다. 반대로 c3stop 기능이 있는 경우 현재 디바이스에 브로드캐스트 함수를 설정한다.
    • c3stop 기능이 없으면 타이머 전원을 끄지 않고 계속 동작시킨다.
  • 코드 라인 42~43에서 periodic용 tick_broadcast_on에 현재 cpu가 포함되지 않은 경우 tick_broadcast_mask에서 현재 cpu를 클리어한다.

 

                switch (tick_broadcast_device.mode) {
                case TICKDEV_MODE_ONESHOT:
                        /*
                         * If the system is in oneshot mode we can
                         * unconditionally clear the oneshot mask bit,
                         * because the CPU is running and therefore
                         * not in an idle state which causes the power
                         * state affected device to stop. Let the
                         * caller initialize the device.
                         */
                        tick_broadcast_clear_oneshot(cpu);
                        ret = 0;
                        break;

                case TICKDEV_MODE_PERIODIC:
                        /*
                         * If the system is in periodic mode, check
                         * whether the broadcast device can be
                         * switched off now.
                         */
                        if (cpumask_empty(tick_broadcast_mask) && bc)
                                clockevents_shutdown(bc);
                        /*
                         * If we kept the cpu in the broadcast mask,
                         * tell the caller to leave the per cpu device
                         * in shutdown state. The periodic interrupt
                         * is delivered by the broadcast device.
                         */
                        ret = cpumask_test_cpu(cpu, tick_broadcast_mask);
                        break;
                default:
                        /* Nothing to do */
                        ret = 0;
                        break;
                }
        }
        raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
        return ret;
}
  • 코드 라인 1~13에서 틱 브로드캐스트 디바이스가 oneshot 모드로 동작하는 경우 요청 cpu를 브로드캐스트 대상에서 제외하고 결과로 0을 반환한다.
    • tick_broadcast_oneshot_mask와 tick_broadcast_pending_mask에서 요청 cpu를 제외시킨다.
  • 코드 라인 15~30에서 틱 브로드캐스트 디바이스가 periodic 모드로 동작하는 경우 요청 cpu가 브로드캐스트 대상에 포함되어 있는지 여부를 반환한다. 만일 브로드캐스트 대상이 없는 경우 디바이스를 shutdown 한다.
  • 코드 라인 31~35에서 틱 브로드캐스트 디바이스가 알 수 없는 모드로 동작하는 경우 결과로 0을 반환한다.

 

tick_device_is_functional()

kernel/time/tick-internal.h

/*
 * Check, if the device is functional or a dummy for broadcast
 */
static inline int tick_device_is_functional(struct clock_event_device *dev)
{
        return !(dev->features & CLOCK_EVT_FEAT_DUMMY);
}

클럭 이벤트 디바이스가 기능적인지 여부를 반환한다. dummy 디바이스가 아닌 경우 true를 반환한다.

 

tick_device_setup_broadcast_func()

kernel/time/tick-broadcast.c

static void tick_device_setup_broadcast_func(struct clock_event_device *dev)
{
        if (!dev->broadcast)
                dev->broadcast = tick_broadcast;
        if (!dev->broadcast) {
                pr_warn_once("%s depends on broadcast, but no broadcast function available\n",
                             dev->name);
                dev->broadcast = err_broadcast;
        }
}

틱 디바이스에 브로드캐스트 함수가 설정되지 않은 경우 브로드캐스트 함수를 설정한다.

  • 틱 브로드캐스트 함수에서 대상 cpu로 IPI(Inter Process Interrupt)를 발생시킨다.

 

tick_broadcast()

arch/arm/kernel/smp.c

#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
void tick_broadcast(const struct cpumask *mask)
{
        smp_cross_call(mask, IPI_TIMER);
}
#endif

대상 cpu들을 IPI(Inter Process Interrupt)로 깨워 IPI _TIMER 기능에 대한 처리를 수행하게 요청한다.

 

tick_broadcast_start_periodic()

kernel/time/tick-broadcast.c

/*
 * Start the device in periodic mode
 */
static void tick_broadcast_start_periodic(struct clock_event_device *bc)
{
        if (bc)
                tick_setup_periodic(bc, 1);
}

요청한 클럭 이벤트 디바이스를 periodic 모드의 브로드캐스트 디바이스로 사용하여 tick을 발생시킨다.

 

틱 디바이스 설정

periodic 모드로 틱 디바이스 설정

tick_setup_periodic()

/*
 * Setup the device for a periodic tick
 */
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
        tick_set_periodic_handler(dev, broadcast);

        /* Broadcast setup ? */
        if (!tick_device_is_functional(dev))
                return;

        if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
            !tick_broadcast_oneshot_active()) {
                clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
        } else {
                unsigned long seq;
                ktime_t next; 

                do {
                        seq = read_seqbegin(&jiffies_lock);
                        next = tick_next_period;
                } while (read_seqretry(&jiffies_lock, seq));

                clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);

                for (;;) {
                        if (!clockevents_program_event(dev, next, false))
                                return;
                        next = ktime_add(next, tick_period);
                }
        }
}

요청한 클럭 이벤트 디바이스를 periodic 모드의 틱 디바이스로 사용하여 tick을 발생시킨다. broadcast=1로 요청한 경우 브로드캐스트한다.

  • 코드 라인 6에서 broadcast 핸들러 함수를 설정한다.
  • 코드 라인 9~10에서 클럭 이벤트 디바이스가 dummy 디바이스인 경우 이미 브로드 캐스트 디바이스이므로 함수를 빠져나간다.
  • 코드 라인 12~14에서 클럭 이벤트 디바이스 기능에 periodic이 있고 틱 브로드캐스트 디바이스가 oneshot 모드가 아닌 경우 클럭 이벤트 디바이스를 periodic로 설정한다.
  • 코드 라인 15~24에서 1 tick을 더해 다음 만료 시간을 지정한 후 클럭 이벤트 디바이스를 oneshot 모드로 설정한다.
  • 코드 라인 26~30에서 클럭 이벤트 디바이스에 프로그램하여 성공하면 함수를 빠져나간다. 만일 실패하는 경우 1 tick 만큼 만료 시간을 더해 성공할 때까지 루프를 돌며 시도한다.

 

oneshot 모드로 틱 디바이스 설정

tick_setup_oneshot()

kernel/time/tick-oneshot.c

/**
 * tick_setup_oneshot - setup the event device for oneshot mode (hres or nohz)
 */
void tick_setup_oneshot(struct clock_event_device *newdev,
                        void (*handler)(struct clock_event_device *),
                        ktime_t next_event)
{
        newdev->event_handler = handler;
        clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
        clockevents_program_event(newdev, next_event, true);
}

oneshot 모드로 클럭이벤트 디바이스를 설정하고 핸들러를 대입한 후 이벤트를 프로그램한다.

 

tick_is_broadcast_device()

kernel/time/tick-broadcast.c

/*
 * Check, if the device is the broadcast device
 */
int tick_is_broadcast_device(struct clock_event_device *dev)
{
        return (dev && tick_broadcast_device.evtdev == dev);
}

요청한 클럭 이벤트 디바이스가 브로드캐스트 디바이스인지 여부를 반환한다.

 

틱 디바이스 이벤트 핸들러

틱 디바이스 핸들러 -1- (hz based, 저해상도 타이머)

tick_handle_periodic()

kernel/time/tick-common.c

/*
 * Event handler for periodic ticks
 */
void tick_handle_periodic(struct clock_event_device *dev)
{
        int cpu = smp_processor_id();
        ktime_t next = dev->next_event;

        tick_periodic(cpu);

        if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
                return;
        for (;;) {
                /*
                 * Setup the next period for devices, which do not have
                 * periodic mode:
                 */
                next = ktime_add(next, tick_period);

                if (!clockevents_program_event(dev, next, false))
                        return;
                /*
                 * Have to be careful here. If we're in oneshot mode,
                 * before we call tick_periodic() in a loop, we need
                 * to be sure we're using a real hardware clocksource.
                 * Otherwise we could get trapped in an infinite
                 * loop, as the tick_periodic() increments jiffies,
                 * which then will increment time, possibly causing
                 * the loop to trigger again and again.
                 */
                if (timekeeping_valid_for_hres())
                        tick_periodic(cpu);
        }
}

틱 디바이스가 periodic 모드로 동작하여 인터럽트가 발생하면 이 함수가 호출되며 이 때 tick_periodic() 함수를 호출한다. oneshot 모드를 사용하는 클럭 이벤트 디바이스인 경우 다음  tick을 프로그램한다.

  • 코드 라인 9에서 정규 틱 마다 할 일(스케줄, account process, rcu, irq work, posix cpu 타이머 등)을 처리한다. 추가로 현재 cpu가 do_timer() 호출 전담인 경우 jiffies를 1 증가시키고 wall time 등을 갱신한다.
  • 코드 라인 11~12에서 클럭 이벤트 디바이스가 이미 oneshot 모드가 아닌 경우 더 이상 처리를 할 필요 없으므로 핸들러를 빠져나간다.
    • 결국 periodic 모드에서는 tick 마다 인터럽트 발생하고 jiffies++하고 wall  time 만 갱신한다.
  • 코드 라인 13~ 33에서 tick에 대한 시간을 추가하여 oneshot 이벤트를 프로그램 한다. 프로그램에 실패한 경우 timekeeping용 클럭 소스에서 CLOCK_SOURCE_VALID_FOR_HRES 플래그를 사용했으면 다시 한 번 jiffies를 1 증가시키고 wall time 등을 갱신하게 한다. 그 후 다시 반복한다.

 

tick_periodic()

kernel/time/tick-common.c

/*
 * Periodic tick
 */
static void tick_periodic(int cpu)
{
        if (tick_do_timer_cpu == cpu) {
                write_seqlock(&jiffies_lock);

                /* Keep track of the next tick event */
                tick_next_period = ktime_add(tick_next_period, tick_period);

                do_timer(1);
                write_sequnlock(&jiffies_lock);
                update_wall_time();
        }                          

        update_process_times(user_mode(get_irq_regs()));
        profile_tick(CPU_PROFILING);
}

정규 틱 마다 할 일(스케줄, account process, rcu, irq work, posix cpu 타이머 등)을 처리한다. 추가로 현재 cpu가 do_timer() 호출 전담인 경우jiffies를 1 증가시키고 글로벌 로드를 계산하고 wall time 등을 갱신한다

  • 코드 라인 6~13에서 현재 cpu가 do_timer() 호출 전담인 경우 다음 틱 시간을 갱신하고 jiffies를 1 증가시키고 global  load를 계산한다.
    • 32bit arm은 jiffies 증가 시 64비트인 jiffies_64를 사용하므로 접근 시에는 반드시 시퀀스 락을 사용하여야 한다.
  • 코드 라인 14에서 wall time을 갱신한다.
  • 코드 라인 17에서 process 처리 시간을 갱신한다.
  • 코드 라인 18에서 cpu profile 정보를 남긴다

 

do_timer()

kernel/time/timekeeping.c

/*              
 * Must hold jiffies_lock
 */
void do_timer(unsigned long ticks)
{
        jiffies_64 += ticks;
        calc_global_load(ticks);
}

jiffies 값을 요청한 ticks 만큼 증가시키고 global load를 계산한다.

  • global load 산출
    • 계산 간격이 10 tick을 초과한 경우 1min, 5min, 15min 간격의 로드 평균 값을 avenrun[]에 갱신한다.
    • 참고: Linux kernel load accounting | HAPPY HACKING

 

profile_tick()

kernel/profile.c

void profile_tick(int type)
{
        struct pt_regs *regs = get_irq_regs();

        if (!user_mode(regs) && prof_cpu_mask != NULL &&
            cpumask_test_cpu(smp_processor_id(), prof_cpu_mask))
                profile_hit(type, (void *)profile_pc(regs));
}

cpu profile 정보를 남긴다.

  • 코드 라인 3에서 마지막 exception 프레임의 주소가 담긴 현재 cpu의 프레임 포인터를 알아온다.
  • 코드 라인 5~7에서 커널에서 exception되었으며 prof_cpu_mask에 현재 cpu가 포함된 경우 cpu profile 정보를 남긴다.

 

profile_hit()

include/linux/profile.h

/*
 * Single profiler hit:
 */
static inline void profile_hit(int type, void *ip)
{
        /*
         * Speedup for the common (no profiling enabled) case:
         */
        if (unlikely(prof_on == type))
                profile_hits(type, ip, 1);
}

커널이 요청 타입에 대한 profile 타입을 준비한 경우 ip 주소에 대해 profile 정보를 남긴다.

  • 요청 타입이 profiling 중인 타입이 아닌 거나 profile 버퍼가 설정되지 않은 경우 처리를 포기한다.
    • cpu, sched, sleep, kvm profiling 중 하나를 선택할 수 있다.
    • “profile=[schedule,]<number>”
      • 스케쥴 포인트에 대해 profile
      • <number>
        • step/bucket 사이즈를 2의 차수 값을 사용하며 통계 시간 기반 profile
    • “profile=sleep”
      • D-state sleeping (ms)에 대한 profile
    • “profile=kvm”
      • VM 종료에 대한 profile
  • 참고: Profiling the kernel
  • 참고: kerneltop | LWN.net

 

틱 디바이스 핸들러 -2- (nohz  기반, 저해상도 타이머)

tick_nohz_handler()

kernel/time/tick-sched.c

/*
 * The nohz low res interrupt handler
 */
static void tick_nohz_handler(struct clock_event_device *dev)
{
        struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
        struct pt_regs *regs = get_irq_regs();
        ktime_t now = ktime_get();

        dev->next_event.tv64 = KTIME_MAX;

        tick_sched_do_timer(now);
        tick_sched_handle(ts, regs);

        /* No need to reprogram if we are running tickless  */
        if (unlikely(ts->tick_stopped))
                return;

        while (tick_nohz_reprogram(ts, now)) {
                now = ktime_get();
                tick_do_update_jiffies64(now);
        }
}

nohz 기반의 저해상도 타이머를 사용한 틱 인터럽트 핸들러 루틴이다.

  • 코드 라인 12에서 jiffies를 update하고 wall time을 조정한다.
  • 코드 라인 13에서 process accounting 및 profile에 관련한 일들을 처리한다.
  • 코드 라인 16~17에서 낮은 확률로 tick이 정지된 경우 함수를 빠져나간다.
  • 코드 라인 19~22에서 tick을 재프로그램한다. 실패 시 반복한다.

 

틱 디바이스 핸들러 -3- (hz/nohz  기반, 고해상도 타이머)

hrtimer를 사용하여 tick을 프로그래밍한 인터럽트로 인해 hrtimer_interrupt()가 호출되면 등록된 tick 스케쥴 핸들러에 도달한다.

tick_sched_timer()

kernel/time/tick-sched.c

/*      
 * We rearm the timer until we get disabled by the idle code.
 * Called with interrupts disabled.
 */             
static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
{               
        struct tick_sched *ts =
                container_of(timer, struct tick_sched, sched_timer);
        struct pt_regs *regs = get_irq_regs();
        ktime_t now = ktime_get();

        tick_sched_do_timer(now);

        /*
         * Do not call, when we are not in irq context and have
         * no valid regs pointer
         */
        if (regs)
                tick_sched_handle(ts, regs);

        /* No need to reprogram if we are in idle or full dynticks mode */
        if (unlikely(ts->tick_stopped))
                return HRTIMER_NORESTART;

        hrtimer_forward(timer, now, tick_period);

        return HRTIMER_RESTART;
}

hz/nohz 기반의 고해상도 타이머를 사용한 틱 인터럽트 핸들러 루틴이다.

  • 코드 라인 12에서 jiffies를 update하고 wall time을 조정한다.
  • 코드 라인 18~19에서 process accounting 및 profile에 관련한 일들을 처리한다.
  • 코드 라인 22~23에서 틱 스케쥴러에서 틱을 멈춰달라는 요청이 있는 경우 HRTIMER_NORESTART를 결과로 함수를 빠져나간다.
  • 코드 라인 25~27에서 틱을 다시 프로그램하고 HRTIMER_RESTART를 결과로 함수를 빠져나간다.

 

Tick 디바이스의 Oneshot 모드 전환

타이머 Softirq에서 매번 준비 상태 확인 -1-

hrtimer가 초기화되고 준비된 경우 tick 디바이스의 모드를 oneshot으로 변경 가능한 경우 변경한다. (nohz 지원)

/*
 * Called from timer softirq every jiffy, expire hrtimers:
 *
 * For HRT its the fall back code to run the softirq in the timer
 * softirq context in case the hrtimer initialization failed or has
 * not been done yet.
 */
void hrtimer_run_pending(void) 
{
        if (hrtimer_hres_active())
                return;

        /*
         * This _is_ ugly: We have to check in the softirq context,
         * whether we can switch to highres and / or nohz mode. The
         * clocksource switch happens in the timer interrupt with
         * xtime_lock held. Notification from there only sets the
         * check bit in the tick_oneshot code, otherwise we might
         * deadlock vs. xtime_lock.
         */
        if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
                hrtimer_switch_to_hres();
}

매 jiffity 타이머 당 호출되는 softirq 핸들러에서 hrtimer가 준비되었는지 확인한다. 이 과정에서 hrtimer가 준비된 경우 high resolution 모드로 동작하게 하기 위해 틱 디바이스의 oneshot 모드로 전환한다.

  • 코드 라인 10~11에서 이미 hrtimer가 고해상도로 동작중인 경우 함수를 빠져나간다.
  • 코드 라인 21~22에서 high-resolution용 timer가 준비된 경우 hrtimer를 high-resolution 타이머를 사용하는 모드로 전환한다.
    • 내부에서는 high-resolution용 timer를 사용하지 못하는 경우 low-resolution용 nohz 핸들러를 준비한다.

 

hrtimer_hres_active()

kernel/time/hrtimer.c

/*
 * Is the high resolution mode active ?
 */
static inline int hrtimer_hres_active(void) 
{
        return __this_cpu_read(hrtimer_bases.hres_active); 
}

hrtimer가 고해상도 모드로 동작중인지 여부를 반환한다.

 

hrtimer_is_hres_enabled()

kernel/time/hrtimer.c

/*
 * hrtimer_high_res_enabled - query, if the highres mode is enabled
 */
static inline int hrtimer_is_hres_enabled(void) 
{
        return hrtimer_hres_enabled;
}

고해상도모드의 hrtimer가 enable되어 있는지 여부를 반환한다.

  • 디폴트 값으로 on되어 있고 “highres=off” 커널 파라메터를 사용하여 disable할 수 있다.

 

타이머 Softirq에서 매번 준비 상태 확인 -2-

tick_check_oneshot_change()

kernel/time/tick-sched.c

/**
 * Check, if a change happened, which makes oneshot possible.
 *
 * Called cyclic from the hrtimer softirq (driven by the timer
 * softirq) allow_nohz signals, that we can switch into low-res nohz
 * mode, because high resolution timers are disabled (either compile
 * or runtime).
 */
int tick_check_oneshot_change(int allow_nohz)
{
        struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);

        if (!test_and_clear_bit(0, &ts->check_clocks))
                return 0;

        if (ts->nohz_mode != NOHZ_MODE_INACTIVE) 
                return 0;

        if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
                return 0;

        if (!allow_nohz)
                return 1;

        tick_nohz_switch_to_nohz();
        return 0;
}

oneshot 모드로의 변경이 일어난 경우 체크한다.

  • 코드 라인 11~14에서 tick_sched의 check_clocks에서 0번 cpu에 해당하는 비트가 설정된 경우 클리어한다. 설정되지 않은 경우 0을 반환한다.
  • 코드 라인 16~17에서 nohz 모드가 활성화되지 않은 경우 0을 반환한다.
  • 코드 라인 19~20에서 timekeeping에 사용하는 클럭 이벤트 소스의 플래그에 valid_for_hres가 설정되지 않았거나 틱 이벤트 디바이스가 oneshot 모드가 아닌 경우 0을 반환한다.
  • 코드 라인 22~23에서 인수 allow_nohz가 0인 경우 1을 반환한다.
  • 코드 라인 25~26에서 nohz 모드로 전환하고 hrtimer를 사용하여 다음 스케쥴 틱을 프로그래밍 한다.

 

tick_nohz_switch_to_nohz()

kernel/time/tick-sched.c

/**
 * tick_nohz_switch_to_nohz - switch to nohz mode
 */
static void tick_nohz_switch_to_nohz(void)
{
        struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
        ktime_t next;

        if (!tick_nohz_enabled)
                return;

        local_irq_disable();
        if (tick_switch_to_oneshot(tick_nohz_handler)) {
                local_irq_enable();
                return;
        }
        tick_nohz_active = 1;
        ts->nohz_mode = NOHZ_MODE_LOWRES;

        /*
         * Recycle the hrtimer in ts, so we can share the
         * hrtimer_forward with the highres code.
         */
        hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
        /* Get the next period */
        next = tick_init_jiffy_update();

        for (;;) {
                hrtimer_set_expires(&ts->sched_timer, next);
                if (!tick_program_event(next, 0))
                        break;
                next = ktime_add(next, tick_period);
        }
        local_irq_enable();
}

nohz 모드로 전환하고 hrtimer를 사용하여 다음 스케쥴 틱을 프로그래밍 한다.

  • 코드 라인 9~10에서 nohz가 enable되어 있지 않은 경우 함수를 빠져나간다.
  • 코드 라인 13~16에서 틱 디바이스를 oneshot 모드로 전환시키고 처리할 핸들러를 준비한다. 만일 전환이 실패하는 경우 함수를 빠져나간다.
  • 코드 라인 17~18에서 nohz 모드가 저해상도 타이머 모드로 동작됨을 틱 sched에 표시한다.
  • 코드 라인 24~34에서 hrtimer를 사용하여 1 jiffies 틱을 프로그램한다.

 

 

tick_switch_to_oneshot()

kernel/time/tick-oneshot.c

/**
 * tick_switch_to_oneshot - switch to oneshot mode
 */             
int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
        struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
        struct clock_event_device *dev = td->evtdev;

        if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT) ||
                    !tick_device_is_functional(dev)) {

                printk(KERN_INFO "Clockevents: "
                       "could not switch to one-shot mode:");
                if (!dev) {
                        printk(" no tick device\n");
                } else {
                        if (!tick_device_is_functional(dev))
                                printk(" %s is not functional.\n", dev->name);
                        else
                                printk(" %s does not support one-shot mode.\n",
                                       dev->name);
                }
                return -EINVAL;
        }

        td->mode = TICKDEV_MODE_ONESHOT;
        dev->event_handler = handler;
        clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
        tick_broadcast_switch_to_oneshot();
        return 0;
}

틱 디바이스를 oneshot 모드로 전환시키고 처리할 핸들러를 준비한다.

  • 이 함수는 다음 두 군데에서 호출되어 사용된다.
    • 고해상도 타이머가 필요할 때 hrtimer_switch_to_hres() 함수 내부의 tick_init_hres() 함수에서 사용된다.
    • dynamic tick을 처리하기 위해 nohz 모드로 전환하기 위해  tick_nohz_switch_to_nohz() 함수에서 사용된다.

 

  • 코드 라인 9~24에서 clock_event_device가 준비되지 않았거나 oneshot 모드로 동작하지 않는 경우 에러 메시지를 출력하고 -EINVAL 에러를 반환한다.
  • 코드 라인 26~28에서 인수로 받은 이벤트 핸들러를 디바이스에 대입한다. 또한 clock_event_device의 set_mode() 후크를 통해 드라이버에서 oneshot 모드를 설정하게 한다.
    • rpi2: arch_timer_set_mode_virt() 호출되는데 shutdown 모드가 아닌 oneshot 모드에서는 특별히 설정하는 것이 없다.
  • 코드 라인 29에서 tick broadcast 디바이스를 위해 oneshot 모드로 변경한다.

 

tick_broadcast_switch_to_oneshot()

kernel/time/tick-broadcast.c

/*  
 * Select oneshot operating mode for the broadcast device
 */     
void tick_broadcast_switch_to_oneshot(void)
{       
        struct clock_event_device *bc;
        unsigned long flags;
        
        raw_spin_lock_irqsave(&tick_broadcast_lock, flags);

        tick_broadcast_device.mode = TICKDEV_MODE_ONESHOT;
        bc = tick_broadcast_device.evtdev;
        if (bc)
                tick_broadcast_setup_oneshot(bc);

        raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
}

tick broadcast 디바이스의 모드를 oneshot 모드로 바꾸고 tick broadcast 디바이스의 클럭 이벤트 디바이스가 준비된 경우 다음 tick을 설정한다.

 

hrtimer를 고해상도 타이머로 전환

hrtimer_switch_to_hres()

kernel/time/hrtimer.c

static int hrtimer_switch_to_hres(void)
{
        int i, cpu = smp_processor_id();
        struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
        unsigned long flags;

        if (base->hres_active)
                return 1;

        local_irq_save(flags);

        if (tick_init_highres()) {
                local_irq_restore(flags);
                printk(KERN_WARNING "Could not switch to high resolution "
                                    "mode on CPU %d\n", cpu);
                return 0;
        }
        base->hres_active = 1;
        for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
                base->clock_base[i].resolution = KTIME_HIGH_RES;

        tick_setup_sched_timer();
        /* "Retrigger" the interrupt to get things going */
        retrigger_next_event(NULL);
        local_irq_restore(flags);
        return 1;
}

hrtimer를 high resolution 모드로 설정하고 스케쥴러의 틱으로 동작시킨다.

  • 코드 라인 7~8에서 hrtimer의 high resolution 모드가  이미 동작 중이므로 결과 값 1로 함수를 빠져나간다.
  • 코드 라인 12~17에서 high resolution을 사용하는 oneshot 모드로 변경한다. 실패하는 경우 경고 메시지와 함께 결과 값 0으로 함수를 빠져나간다.
  • 코드 라인 18~20에서 hrtimer가 high resolution 상태임을 설정하고 4 개 클럭의 해상도에 1ns를 대입한다.
  • 코드 라인 22에서 틱을 스케쥴 타이머로 설정한다.
  • 코드 라인 24에서 hrtimer를 리프로그램하고 1을 반환한다.

 

tick_init_highres()

kernel/time/tick-oneshot.c

/**
 * tick_init_highres - switch to high resolution mode
 *
 * Called with interrupts disabled.
 */
int tick_init_highres(void)
{
        return tick_switch_to_oneshot(hrtimer_interrupt);
}

고해상도 모드의 oneshot 타이머를 동작시키고 인터럽트 발생 시 hrtimer 인터럽트 핸들러 콜백 함수인 hrtimer_interrupt() 함수를 호출하게 한다.

 

고해상도 hrtimer를 사용한 Sched Tick으로 전환

tick_setup_sched_timer()

kernel/time/tick-sched.c

/**
 * tick_setup_sched_timer - setup the tick emulation timer
 */
void tick_setup_sched_timer(void)
{
        struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
        ktime_t now = ktime_get();

        /*
         * Emulate tick processing via per-CPU hrtimers:
         */
        hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
        ts->sched_timer.function = tick_sched_timer;

        /* Get the next period (per cpu) */
        hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());

        /* Offset the tick to avert jiffies_lock contention. */
        if (sched_skew_tick) {
                u64 offset = ktime_to_ns(tick_period) >> 1;
                do_div(offset, num_possible_cpus());
                offset *= smp_processor_id();
                hrtimer_add_expires_ns(&ts->sched_timer, offset);
        }

        for (;;) {
                hrtimer_forward(&ts->sched_timer, now, tick_period);
                hrtimer_start_expires(&ts->sched_timer,
                                      HRTIMER_MODE_ABS_PINNED);
                /* Check, if the timer was already in the past */
                if (hrtimer_active(&ts->sched_timer))
                        break;
                now = ktime_get();
        }

#ifdef CONFIG_NO_HZ_COMMON
        if (tick_nohz_enabled) {
                ts->nohz_mode = NOHZ_MODE_HIGHRES;
                tick_nohz_active = 1;
        }
#endif
}

고해상도 hrtimer를 사용하여 틱 스케쥴 타이머가 nohz 모드로 타이머를 프로그래밍하고 핸들러를 설정한다.

  • 코드 라인 12~13에서 틱 스케쥴에서 사용할 hrtimer를 초기화하고 핸들러 함수를 지정한다.
  • 코드 라인 16에서 틱 스케쥴용 hrtimer의 만료 시간은 1 jiffies 기간으로 한다.
  • 코드 라인 19~24에서 jiffies lock 혼잡을 피하기 위해 cpu 마다 offset을 산출하고 추가하여 틱 만료시간을 재설정한다.
  • 코드 라인 26~34에서 hrtimer를 프로그램하고 hrtimer가 활성화된 것을 확인한 경우 루프를 빠져나간다.
  • 코드 라인 37~40에서 nohz가 enable된 경우 틱 스케쥴의 nohz 모드를 highres로 변경하고 active 되었음을 설정한다.

 

oneshot 틱 프로그램

tick_program_event()

kernel/time/tick-oneshot.c

/**
 * tick_program_event
 */
int tick_program_event(ktime_t expires, int force)
{
        struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);

        return clockevents_program_event(dev, expires, force);
}

요청한 만료 시간에 틱이 발생하도록 프로그램한다.

 

틱 브로드캐스트 디바이스 핸들러

tick_handle_periodic_broadcast()

kernel/time/tick-broadcast.c

/*
 * Event handler for periodic broadcast ticks
 */
static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
{
        ktime_t next;

        raw_spin_lock(&tick_broadcast_lock);

        tick_do_periodic_broadcast();

        /*
         * The device is in periodic mode. No reprogramming necessary:
         */
        if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
                goto unlock;

        /*
         * Setup the next period for devices, which do not have
         * periodic mode. We read dev->next_event first and add to it
         * when the event already expired. clockevents_program_event()
         * sets dev->next_event only when the event is really
         * programmed to the device.
         */
        for (next = dev->next_event; ; ) {
                next = ktime_add(next, tick_period);

                if (!clockevents_program_event(dev, next, false))
                        goto unlock;
                tick_do_periodic_broadcast();
        }
unlock:
        raw_spin_unlock(&tick_broadcast_lock);
}

브로드캐스트가 필요한 cpu를 대상으로 브로드캐스트하여 cpu를 idle 상태에서 깨운다. 클럭 이벤트 디바이스의 모드가 oneshot인 경우 틱을 프로그램한다.

  • 코드 라인 10에서 브로드캐스트가 필요한 cpu를 대상으로 브로드캐스트하여 cpu를 idle 상태에서 깨운다.
  • 코드 라인 15~16에서 클럭 이벤트 디바이스의 모드가 periodic인 경우 자동으로 틱이 발생하므로 함수를 빠져나간다.
  • 코드 라인 25~31에서 클럭 이벤트 디바이스의 틱을 프로그램한다. 실패한 경우 브로드캐스트를 다시 보내고 재시도한다.

 

다음 그림은 nohz idle 상태에 있는 cpu#1~#3 들을 깨우도록 브로드캐스트 하는 과정을 보여준다.

  • 틱 브로드캐스트 디바이스가 periodic 모드로 동작

 

tick_handle_oneshot_broadcast()

kernel/time/tick-broadcast.c

/*
 * Handle oneshot mode broadcasting
 */
static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)
{
        struct tick_device *td;
        ktime_t now, next_event;
        int cpu, next_cpu = 0;

        raw_spin_lock(&tick_broadcast_lock);
again:
        dev->next_event.tv64 = KTIME_MAX;
        next_event.tv64 = KTIME_MAX;
        cpumask_clear(tmpmask);
        now = ktime_get();
        /* Find all expired events */
        for_each_cpu(cpu, tick_broadcast_oneshot_mask) {
                td = &per_cpu(tick_cpu_device, cpu);
                if (td->evtdev->next_event.tv64 <= now.tv64) {
                        cpumask_set_cpu(cpu, tmpmask);
                        /*
                         * Mark the remote cpu in the pending mask, so
                         * it can avoid reprogramming the cpu local
                         * timer in tick_broadcast_oneshot_control().
                         */
                        cpumask_set_cpu(cpu, tick_broadcast_pending_mask);
                } else if (td->evtdev->next_event.tv64 < next_event.tv64) {
                        next_event.tv64 = td->evtdev->next_event.tv64;
                        next_cpu = cpu;
                }
        }

        /*
         * Remove the current cpu from the pending mask. The event is
         * delivered immediately in tick_do_broadcast() !
         */
        cpumask_clear_cpu(smp_processor_id(), tick_broadcast_pending_mask);
        
        /* Take care of enforced broadcast requests */
        cpumask_or(tmpmask, tmpmask, tick_broadcast_force_mask);
        cpumask_clear(tick_broadcast_force_mask);

        /*
         * Sanity check. Catch the case where we try to broadcast to
         * offline cpus.
         */
        if (WARN_ON_ONCE(!cpumask_subset(tmpmask, cpu_online_mask)))
                cpumask_and(tmpmask, tmpmask, cpu_online_mask);
                
        /*
         * Wakeup the cpus which have an expired event.
         */
        tick_do_broadcast(tmpmask);
        
        /*
         * Two reasons for reprogram:
         *
         * - The global event did not expire any CPU local
         * events. This happens in dyntick mode, as the maximum PIT
         * delta is quite small.
         *
         * - There are pending events on sleeping CPUs which were not
         * in the event mask
         */
        if (next_event.tv64 != KTIME_MAX) {
                /*
                 * Rearm the broadcast device. If event expired,
                 * repeat the above
                 */
                if (tick_broadcast_set_event(dev, next_cpu, next_event, 0))
                        goto again;
        }               
        raw_spin_unlock(&tick_broadcast_lock);
}

브로드캐스트가 필요한 cpu를 대상으로 브로드캐스트하여 cpu를 idle 상태에서 깨운다. 클럭 이벤트 디바이스의 모드가 oneshot인 경우 틱을 프로그램한다.

  • 코드 라인 17~31에서 브로드 캐스트가 필요한 cpu들에 대해 틱 만료된 cpu들을 tmp 마스크에 알아온다.
  • 코드 라인 37~41에서 현재 cpu는 pending 마스크에서 제거하고 tmp 마스크 대상에 force 마스크를 추가한다.
  • 코드 라인 47~48에서 tmp 마스크에서 online된 cpu들은 제거한다.
  • 코드 라인 53에서 tmp 마스크를 대상으로 브로드캐스트를 수행하여 idle 상태에 있는 cpu들을 모두 깨운다.
  • 코드 라인 65~72에서 남아있는 이벤트가 있는 경우 브로드캐스트 디바이스로 사용되는 클럭 이벤트 디바이스의 모드를 oneshot으로 변경하고 프로그램한다. 만일 실패하는 경우 irq set affinity를 수행하고 다시 시도해본다.

 

다음 그림은 nohz idle 상태에 있는 cpu#1~#3 들을 깨우도록 브로드캐스트 하는 과정을 보여준다.

  • 틱 브로드캐스트 디바이스가 oneshot 모드로 동작

 

다음 그림은 nohz를 사용하여 idle 상태에 있는 3개의 cpu가 브로드캐스트에 의해 각 만료시간마다 깨어나는 과정을 보여준다.

  • 별표와 같이 프로그램된 틱들이 브로드캐스트를 발송하는 cpu의 틱 타임에 맞춰 약간씩 지연되는 모습을 볼 수 있다.

 

tick_broadcast_set_event()

kernel/time/tick-broadcast.c

static int tick_broadcast_set_event(struct clock_event_device *bc, int cpu,
                                    ktime_t expires, int force)
{
        int ret;

        if (bc->mode != CLOCK_EVT_MODE_ONESHOT)
                clockevents_set_mode(bc, CLOCK_EVT_MODE_ONESHOT);

        ret = clockevents_program_event(bc, expires, force);
        if (!ret)
                tick_broadcast_set_affinity(bc, cpumask_of(cpu));
        return ret;
}

틱 브로드캐스트 디바이스에서 사용되는 클럭 이벤트 디바이스의 모드를 oneshot으로 변경하고 프로그램 한다. 만일 실패하는 경우 요청 cpu로 irq affinity를 설정한다.

 

tick_broadcast_set_affinity()

kernel/time/tick-broadcast.c

/*
 * Set broadcast interrupt affinity
 */
static void tick_broadcast_set_affinity(struct clock_event_device *bc,
                                        const struct cpumask *cpumask)
{
        if (!(bc->features & CLOCK_EVT_FEAT_DYNIRQ))
                return;

        if (cpumask_equal(bc->cpumask, cpumask))
                return;

        bc->cpumask = cpumask;
        irq_set_affinity(bc->irq, bc->cpumask);
}

클럭 이벤트 디바이스의 인터럽트를 요청한 cpu 들이 수신할 수 있도록 설정한다. 단 FEAT_DYNIRQ 기능이 없는 클럭 이벤트 디바이스는 수행할 수 없다.

 

C3Stop과 틱 브로드캐스트

타이머의 절전 기능이 구현된 SoC 즉, c3stop이 설정되었거나 per-cpu 타이머가 없는 경우 cpu가 deep idle 상태에 진입하고 나갈 때 마다 전역 비트맵에 알림 요청을 하고 다른 cpu에서 deep idle 상태의 cpu들을 broadcast 기능을 통해 깨울 수 있다.

 

cpuidle_setup_broadcast_timer()

drivers/cpuidle/driver.c

/**
 * cpuidle_setup_broadcast_timer - enable/disable the broadcast timer
 * @arg: a void pointer used to match the SMP cross call API
 *
 * @arg is used as a value of type 'long' with one of the two values:
 * - CLOCK_EVT_NOTIFY_BROADCAST_ON
 * - CLOCK_EVT_NOTIFY_BROADCAST_OFF
 *
 * Set the broadcast timer notification for the current CPU.  This function
 * is executed per CPU by an SMP cross call.  It not supposed to be called
 * directly.
 */
static void cpuidle_setup_broadcast_timer(void *arg)
{
        int cpu = smp_processor_id();
        clockevents_notify((long)(arg), &cpu);
}

 

clockevents_notify()

kernel/time/clockevents.c

/**
 * clockevents_notify - notification about relevant events
 * Returns 0 on success, any other value on error
 */
int clockevents_notify(unsigned long reason, void *arg)
{
        struct clock_event_device *dev, *tmp;
        unsigned long flags;
        int cpu, ret = 0;

        raw_spin_lock_irqsave(&clockevents_lock, flags);

        switch (reason) {
        case CLOCK_EVT_NOTIFY_BROADCAST_ON:
        case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
        case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
                tick_broadcast_on_off(reason, arg);
                break;

        case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
        case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
                ret = tick_broadcast_oneshot_control(reason);
                break;

        case CLOCK_EVT_NOTIFY_CPU_DYING:
                tick_handover_do_timer(arg);
                break;

        case CLOCK_EVT_NOTIFY_SUSPEND:
                tick_suspend();
                tick_suspend_broadcast();
                break;

        case CLOCK_EVT_NOTIFY_RESUME:
                tick_resume();
                break;

        case CLOCK_EVT_NOTIFY_CPU_DEAD:
                tick_shutdown_broadcast_oneshot(arg);
                tick_shutdown_broadcast(arg);
                tick_shutdown(arg);
                /*
                 * Unregister the clock event devices which were
                 * released from the users in the notify chain.
                 */
                list_for_each_entry_safe(dev, tmp, &clockevents_released, list)
                        list_del(&dev->list);
                /*
                 * Now check whether the CPU has left unused per cpu devices
                 */
                cpu = *((int *)arg);
                list_for_each_entry_safe(dev, tmp, &clockevent_devices, list) {
                        if (cpumask_test_cpu(cpu, dev->cpumask) &&
                            cpumask_weight(dev->cpumask) == 1 &&
                            !tick_is_broadcast_device(dev)) {
                                BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
                                list_del(&dev->list);
                        }
                }
                break;
        default:
                break;
        }
        raw_spin_unlock_irqrestore(&clockevents_lock, flags);
        return ret;
}
EXPORT_SYMBOL_GPL(clockevents_notify);

클럭 이벤트 통지를 받아 브로드캐스트 동작 또는 클럭의 전원 관리를 수행한다.

  • 코드 라인 13~18에서 브로드캐스트 on 요청을 받은 경우 현재 cpu의 틱 브로드캐스트 기능을 켜거나 끈다.
  • 코드 라인 20~23에서 브로드캐스트에 진입 시 브로드캐스트 대기 중인 cpu에 해당하는 클럭이벤트디바이스를 shutdown하고 브로드캐스트에서 빠져나오는 경우 다시 틱을 프로그램한다.
  • 코드 라인 25~27에서 cpu가 dying 상태에 진입하는 경우 timer를 다른 cpu로 migration 한다.
  • 코드 라인 29~32에서 suspend에 진입하는 경우 틱 디바이스 및 틱 브로드캐스트에 사용되는 클럭이벤트 디바이스를 shutdown 한다.
  • 코드 라인 34~36에서 resume 상태로 복귀하는 경우 틱 디바이스에 사용되는 클럭이벤트 디바이스를 resume 모드로 변경하고, 틱 브로드캐스트의 모드에 따라 periodic 또는 oneshot 모드로 프로그램한다.
  • 코드 라인 38~60에서 cpu가 dead 상태가 된 경우 관련 틱을 shutodown하고 클럭 이벤트 디바이스도 release 리스트로 옮긴다.

 

tick_do_broadcast_on_off()

kernel/time/tick-broadcast.c

/*
 * Powerstate information: The system enters/leaves a state, where
 * affected devices might stop
 */
static void tick_do_broadcast_on_off(unsigned long *reason)
{
        struct clock_event_device *bc, *dev;
        struct tick_device *td;
        unsigned long flags;
        int cpu, bc_stopped;

        raw_spin_lock_irqsave(&tick_broadcast_lock, flags);

        cpu = smp_processor_id();
        td = &per_cpu(tick_cpu_device, cpu);
        dev = td->evtdev;
        bc = tick_broadcast_device.evtdev;

        /*
         * Is the device not affected by the powerstate ?
         */
        if (!dev || !(dev->features & CLOCK_EVT_FEAT_C3STOP))
                goto out;

        if (!tick_device_is_functional(dev))
                goto out;

        bc_stopped = cpumask_empty(tick_broadcast_mask);

        switch (*reason) {
        case CLOCK_EVT_NOTIFY_BROADCAST_ON:
        case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
                cpumask_set_cpu(cpu, tick_broadcast_on);
                if (!cpumask_test_and_set_cpu(cpu, tick_broadcast_mask)) {
                        if (tick_broadcast_device.mode ==
                            TICKDEV_MODE_PERIODIC)
                                clockevents_shutdown(dev);
                }
                if (*reason == CLOCK_EVT_NOTIFY_BROADCAST_FORCE)
                        tick_broadcast_force = 1;
                break;
        case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
                if (tick_broadcast_force)
                        break;
                cpumask_clear_cpu(cpu, tick_broadcast_on);
                if (!tick_device_is_functional(dev))
                        break;
                if (cpumask_test_and_clear_cpu(cpu, tick_broadcast_mask)) {
                        if (tick_broadcast_device.mode ==
                            TICKDEV_MODE_PERIODIC)
                                tick_setup_periodic(dev, 0);
                }
                break;
        }

        if (cpumask_empty(tick_broadcast_mask)) {
                if (!bc_stopped)
                        clockevents_shutdown(bc);
        } else if (bc_stopped) {
                if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
                        tick_broadcast_start_periodic(bc);
                else
                        tick_broadcast_setup_oneshot(bc);
        }
out:
        raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
}

현재 cpu의 상태 변화에 따른 브로드캐스트 여부를 결정할 수 있는 비트마스크 설정을 한다.

  • 코드 라인 22~23에서 c3stop 기능이 없는 경우 함수를 빠져나간다.
  • 코드 라인 25~26에서 기능이 구현되지 않은 dummy 디바이스인 경우 함수를 빠져나간다.
  • 코드 라인 30~41에서 broadcast on 요청을 받은 경우 tick_broadcast_on 및 tick_broadcast_mask의 현재 cpu에 해당하는 비트를 설정한다. 만일 틱 브로드캐스트 모드가 periodic 이면서 tick이 발생중이면 tick을 정지시킨다.
  • 코드 라인 42~54에서 broadcast off 요청을 받은 경우 tick_broadcast_on 및 tick_broadcast_mask의 현재 cpu에 해당하는 비트를 클리어한다. 만일 틱 브토드캐스트 모드가 periodic이면서 tick이 발생하지 않는 상태인 경우 tick을 발생시키도록 요청한다.
  • 코드 라인 56~58에서 브로드캐스트할 cpu가 없는 경우 클럭이벤트 디바이스를 shutdown 한다.
  • 코드 라인 59~64에서 브로드캐스트할 cpu가 있는 경우 틱 브로드캐스트 모드에 따라 periodic 또는 oneshot 모드로 설정한다.

 

 

스케줄 틱 분석

먼저 타이머 인터럽트의 처리 경로를 다시 한 번 살펴보면 여러 개의 경로 중 rpi2의 경우 다음 타이머 인터럽트의 처리 경로가 있다.

  • tick_handle_periodic()
    • 루틴내에서 bottom-half 처리를 목적으로 softirq를 사용하여 타이머휠에 대한 처리를 수행한다. (run_timer_softirq())
  • hrtimer_interrupt() → __run_hrtimer() → tick_sched_timer()
    • hrtimer를 사용한 softirq는 커널 4.2.rc1부터 사용하지 않는다.

 

다음 그림은 스케줄틱이 처음 tick_handle_periodic() 함수로 처리되다가 hrtimer가 준비되면 tick_sched_timer() 함수로 이전되는 것을 보여준다.

 

SMP 시스템에서 타이머 인터럽트가 발생되는 상황에 대해 다음 컴포넌트들의 상태와 비교하여 살펴보자

  • 타이머 인터럽트를 발생시키는 틱 디바이스 상태 변화
    • SMP 코어 중 하나는 jiffies를 규칙적으로 발생하므로 nohz로 진입할 수 없다. 그 외의 cpu들은 nohz 상태에 진입가능하다.
  • 타이머를 프로그램하는 클럭이벤트디바이스 상태 변화
    • armv7 & armv8에 내장된 아키텍처 타이머는 oneshot으로만 프로그래밍 가능하므로 매번 CONFIG_HZ 단위로 틱을 프로그래밍해야 한다.

 

구조체

tick_device 구조체

kernel/time/tick-sched.h

struct tick_device {
        struct clock_event_device *evtdev;
        enum tick_device_mode mode;
};
  • *evtdev
    • 틱 디바이스로 사용하는 클럭 이벤트 디바이스
  • mode
    • TICKDEV_MODE_PERIODIC
      • nohz 모드를 사용하지 못하는 틱 디바이스 또는 부트업 과정
    • TICKDEV_MODE_ONESHOT
      • nohz 모드를 사용할 수 있는 틱 디바이스(possible)

 

tick_sched 구조체

kernel/time/tick-sched.h

/**
 * struct tick_sched - sched tick emulation and no idle tick control/stats
 * @sched_timer:        hrtimer to schedule the periodic tick in high
 *                      resolution mode
 * @last_tick:          Store the last tick expiry time when the tick
 *                      timer is modified for nohz sleeps. This is necessary
 *                      to resume the tick timer operation in the timeline
 *                      when the CPU returns from nohz sleep.
 * @tick_stopped:       Indicator that the idle tick has been stopped
 * @idle_jiffies:       jiffies at the entry to idle for idle time accounting
 * @idle_calls:         Total number of idle calls
 * @idle_sleeps:        Number of idle calls, where the sched tick was stopped
 * @idle_entrytime:     Time when the idle call was entered
 * @idle_waketime:      Time when the idle was interrupted
 * @idle_exittime:      Time when the idle state was left
 * @idle_sleeptime:     Sum of the time slept in idle with sched tick stopped
 * @iowait_sleeptime:   Sum of the time slept in idle with sched tick stopped, with IO outstanding
 * @sleep_length:       Duration of the current idle sleep
 * @do_timer_lst:       CPU was the last one doing do_timer before going idle
 */
struct tick_sched {
        struct hrtimer                  sched_timer;
        unsigned long                   check_clocks;
        enum tick_nohz_mode             nohz_mode;
        ktime_t                         last_tick;
        int                             inidle;
        int                             tick_stopped;
        unsigned long                   idle_jiffies;
        unsigned long                   idle_calls;
        unsigned long                   idle_sleeps;
        int                             idle_active;
        ktime_t                         idle_entrytime;
        ktime_t                         idle_waketime;
        ktime_t                         idle_exittime;
        ktime_t                         idle_sleeptime;
        ktime_t                         iowait_sleeptime;
        ktime_t                         sleep_length;
        unsigned long                   last_jiffies;
        u64                             next_timer;
        ktime_t                         idle_expires;
        int                             do_timer_last;
        atomic_t                        tick_dep_mask;
};
  • sched_timer
    • hrtimer가 활성된 이후 스케줄 틱을 발생하는 타이머
  • check_clocks
    • 클럭 소스의 변화가 있거나 틱 디바이스 모드가 변경될 때 1로 설정된다.
  • nohz_mode
    • NOHZ_MODE_INACTIVE
      • nohz 모드가 동작하지 않는 경우
    • NOHZ_MODE_LOWRES
      • low-resolution 타이머를 사용하여 nohz 모드 진입
    • NOHZ_MODE_HIGHRES
      • high-resolution 타이머를 사용하여 nohz 모드 진입
  • last_tick
    • 마지막 프로그래밍된 틱 시간(ns)
    • nohz를 멈추고 다시 periodic 틱을 발생 시킬 때 기억해둔 이 틱 시간 이후로 틱을 forward하여 사용한다.
  • inidle
    • nohz idle 진입 여부
    • tick_nohz_idle_enter() 함수에서 1로 설정하고, tick_nohz_idle_exit() 함수에서 0으로 클리어한다.
  • tick_stopped
    • nohz로 인해 tick이 멈춘 상태 여부 (nohz idle & nohz full)
  • idle_expires
    • nohz idle 진입 시 nohz 만료 예정 시각(ns)

 

참고

 

답글 남기기

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