init_timers()

Lowres 타이머 초기화 – jiffies 기반

init_timers()

kernel/time/timer.c

void __init init_timers(void)
{
        int err;

        /* ensure there are enough low bits for flags in timer->base pointer */
        BUILD_BUG_ON(__alignof__(struct tvec_base) & TIMER_FLAG_MASK);

        err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
                               (void *)(long)smp_processor_id());
        BUG_ON(err != NOTIFY_OK);

        init_timer_stats();
        register_cpu_notifier(&timers_nb);
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

현재 cpu에 대한 타이머 벡터를 초기화하고 타이머 softirq를 준비한다. 또한 다른 cpu들 상태(up, dead, …)가 바뀌는 경우 타이머 설정(init or migrate) 등이 호출되도록 cpu notify chain에 해당 동작 함수를 추가한다.

  • 코드 라인 8~9에서 현재 cpu에 대한 타이머 벡터를 초기화한다.
    • 5개의 타이머 벡터 리스트 등의 초기화
  • 코드 라인 12에서 타이머 통계를 위해 관련 lock을 초기화한다.
  • 코드 라인 13에서 cpu 상태 변화에 따라 notify 되도록 cpu notify chain에 timers_nb를 추가한다.
    • 호출되는 함수: timer_cpu_notify()
  • 코드 라인 14에서 타이머 softirq를 준비한다.
    • 호출되는 함수: run_timer_softirq()

 

cpu notify chain에 추가할 타이머용 notifier block

kernel/time/timer.c

static struct notifier_block timers_nb = {
        .notifier_call  = timer_cpu_notify,
};

 

timer_cpu_notify()

kernel/time/timer.c

static int timer_cpu_notify(struct notifier_block *self,
                                unsigned long action, void *hcpu)
{
        long cpu = (long)hcpu;
        int err;

        switch(action) {
        case CPU_UP_PREPARE:
        case CPU_UP_PREPARE_FROZEN:
                err = init_timers_cpu(cpu);
                if (err < 0)
                        return notifier_from_errno(err);
                break;
#ifdef CONFIG_HOTPLUG_CPU
        case CPU_DEAD:
        case CPU_DEAD_FROZEN:
                migrate_timers(cpu);
                break;
#endif
        default:
                break;
        }
        return NOTIFY_OK;
}

cpu 상태가 변화될 때 타이머를 초기화하거나 migration 한다.

 

init_timers_cpu()

kernel/time/timer.c

static int init_timers_cpu(int cpu)
{
        int j;
        struct tvec_base *base;
        static char tvec_base_done[NR_CPUS];

        if (!tvec_base_done[cpu]) {
                static char boot_done;

                if (boot_done) {
                        /*
                         * The APs use this path later in boot
                         */
                        base = kzalloc_node(sizeof(*base), GFP_KERNEL,
                                            cpu_to_node(cpu));
                        if (!base)
                                return -ENOMEM;

                        /* Make sure tvec_base has TIMER_FLAG_MASK bits free */
                        if (WARN_ON(base != tbase_get_base(base))) {
                                kfree(base);
                                return -ENOMEM;
                        }
                        per_cpu(tvec_bases, cpu) = base;
                } else {
                        /*
                         * This is for the boot CPU - we use compile-time
                         * static initialisation because per-cpu memory isn't
                         * ready yet and because the memory allocators are not
                         * initialised either.
                         */
                        boot_done = 1;
                        base = &boot_tvec_bases;
                }
                spin_lock_init(&base->lock);
                tvec_base_done[cpu] = 1;
                base->cpu = cpu;
        } else {
                base = per_cpu(tvec_bases, cpu);
        }


        for (j = 0; j < TVN_SIZE; j++) {
                INIT_LIST_HEAD(base->tv5.vec + j);
                INIT_LIST_HEAD(base->tv4.vec + j);
                INIT_LIST_HEAD(base->tv3.vec + j);
                INIT_LIST_HEAD(base->tv2.vec + j);
        }
        for (j = 0; j < TVR_SIZE; j++)
                INIT_LIST_HEAD(base->tv1.vec + j);

        base->timer_jiffies = jiffies;
        base->next_timer = base->timer_jiffies;
        base->active_timers = 0;
        base->all_timers = 0;
        return 0;
}

요청 cpu에 대한 타이머 벡터를 초기화한다.

  • 코드 라인 5에서 static 배열로 cpu별 tvec_base의 초기화 여부를 구분한다.
    • 1=초기화 완료, 0=미초기화
  • 코드 라인 7에서 요청 cpu에 대한 tvec_base가 아직 초기화되지 않은 경우
  • 코드 라인 8~24 부트 타임이 아닌 경우 tvec_base를 동적 할당받아 per-cpu tvec_bases의 현재 cpu에 설정한다.
  • 코드 라인 25~34에서 부트 타임의 경우 tvec_base는 컴파일 타임에 빌드된 boot_tvec_bases를 사용한다.
    • 추후 커널에서 boot_tvec_bases가 없어지면서 부트 타임과 이원화된 할당 방식을 사용하지 않고 항상 동적할당한다.
  • 코드 라인 35~37에서 tvec_base가 초기화되었음을 알린다.
  • 코드 라인 38~40에서 이미 tvec_base가 설정된 경우요청 cpu에 대한 tvec_base를 구한다.
  • 코드 라인 43~48에서 tv2 ~ tv5까지의 tvec 리스트 배열을 초기화한다.
  • 코드 라인 49~50에서 tv1의 tvec 리스트 배열을 초기화한다.
    • tv1의 리스트 배열 크기는 tv2 ~ tv5 리스트 배열 크기와 같다.
  • 코드 라인 52~55에서 tvec_base의 나머지 멤버들도 초기화한다.

 

init_timer_stats()

kernel/time/timer_stats.c

void __init init_timer_stats(void)
{
        int cpu;

        for_each_possible_cpu(cpu)
                raw_spin_lock_init(&per_cpu(tstats_lookup_lock, cpu));
}

타이머 통계용으로 사용되는 tstats_lookup_lock을 초기화한다.

 

참고

답글 남기기

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