Interrupts -5- (Softirq)

 

 

Softirq

리눅스 커널의 interrupt bottom-half 처리기 중 가장 큰 부분으로 동작한다. 기존의 많은 드라이버들이 interrupt bottom-half 처리기로 tasklet을 많이 사용해었는데 tasklet 인터페이스가 그대로 softirq의 한 부분으로 동작하면서 기존 tasklet을 사용하던 드라이버들을 흡수하였다. 향후 rt 커널에서 도입을 먼저하였던 threaded interrupt로 자리를 물려주기 전까지는 당분간 대세를 지속하고 있을 예정이다.

 

softirq는 두 가지 처리 특성을 가지고 있다. irq context 상태에서 직접 핸들러 함수를 호출하여 처리할 수도 있고 2ms 이상 처리가 길어지면 process context 인 ksoftirqd를 깨워서 ksoftirqd에 처리를 의뢰한다. ksoftirqd가 깨어나면 이후의 softirq 처리 요청들은 모두 ksoftirqd가 처리한다. 모든 softirq 처리 요청이 완료되면 다시 ksoftirqd는 잠든다. 이 후 softirq 요청 들은 다시 irq context에서 처리된다.

  • irq context에서 계속 처리하지 않고 ksoftirqd 같은 태스크에 처리를 의뢰하는 것은 대량의 softirq 처리를 하느라 cpu를 독식하여 일반 태스크들이 동작하지 못하는 기아(starvation) 현상을 없애기 위함이다.

 

다음 그림은 ksoftirqd가 각 cpu 마다 호출되는 과정을 보여준다.

 

ksoftirqd 스레드 생성

spawn_ksoftirqd()

kernel/softirq.c

static __init int spawn_ksoftirqd(void)
{
        register_cpu_notifier(&cpu_nfb);

        BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

        return 0;
}
early_initcall(spawn_ksoftirqd);

ksoftirqd를 생성한다.

  • 코드 라인 3에서 cpu notify 체인에 아래 cpu_nfb를 등록하여 cpu 상태가 변할 떄 마다 cpu_callback() 함수가 호출되도록 한다.

 

kernel/softirq.c

static struct notifier_block cpu_nfb = {
        .notifier_call = cpu_callback
};

 

  • 코드 라인 5에서 아래 softirq_threads 정보를 기준으로 per-cpu 스레드로 등록하고 모든 online cpu에서 동작하게 한다.

 

kernel/softirq.c

static struct smp_hotplug_thread softirq_threads = {
        .store                  = &ksoftirqd,
        .thread_should_run      = ksoftirqd_should_run,
        .thread_fn              = run_ksoftirqd,
        .thread_comm            = "ksoftirqd/%u",
};

 

cpu off에 따른 tasklet 이주

cpu_callback()

kernel/softirq.c

static int cpu_callback(struct notifier_block *nfb, unsigned long action,
                        void *hcpu)
{               
        switch (action) {
#ifdef CONFIG_HOTPLUG_CPU
        case CPU_DEAD:
        case CPU_DEAD_FROZEN:
                takeover_tasklets((unsigned long)hcpu);
                break;
#endif /* CONFIG_HOTPLUG_CPU */
        }
        return NOTIFY_OK;
}

특정 cpu 상태가 dead 상태로 변화하면 해당 cpu의 tasklets 들을 현재 cpu로 전환시킨다.

 

takeover_tasklets()

kernel/softirq.c

static void takeover_tasklets(unsigned int cpu)
{
        /* CPU is dead, so no lock needed. */
        local_irq_disable();

        /* Find end, append list for that CPU. */
        if (&per_cpu(tasklet_vec, cpu).head != per_cpu(tasklet_vec, cpu).tail) {
                *__this_cpu_read(tasklet_vec.tail) = per_cpu(tasklet_vec, cpu).head;
                this_cpu_write(tasklet_vec.tail, per_cpu(tasklet_vec, cpu).tail);
                per_cpu(tasklet_vec, cpu).head = NULL;
                per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head;
        }
        raise_softirq_irqoff(TASKLET_SOFTIRQ);
        
        if (&per_cpu(tasklet_hi_vec, cpu).head != per_cpu(tasklet_hi_vec, cpu).tail) {
                *__this_cpu_read(tasklet_hi_vec.tail) = per_cpu(tasklet_hi_vec, cpu).head;
                __this_cpu_write(tasklet_hi_vec.tail, per_cpu(tasklet_hi_vec, cpu).tail);
                per_cpu(tasklet_hi_vec, cpu).head = NULL;
                per_cpu(tasklet_hi_vec, cpu).tail = &per_cpu(tasklet_hi_vec, cpu).head;
        }
        raise_softirq_irqoff(HI_SOFTIRQ);
                
        local_irq_enable();
}

특정 cpu 상태가 dead 상태로 변화하면 해당 cpu의 tasklets 들을 현재 cpu로 전환시킨다.

  • 코드 라인 7~13에서 요청 cpu의 tasklet_vec 리스트의 엔트리들을 현재 cpu로 옮기고 tasklet softirq를 요청한다.
  • 코드 라인 15~21에서 요청 cpu의 tasklet_hi_vec 리스트의 엔트리들을 현재 cpu로 옮기고 hi softirq를 요청한다.

 

SMP 핫플러그 스레드 등록

모든 online cpu 마다 동작할 커널 스레드들을 등록한다. 다음 커널 스레드들이 이러한 smp 핫플러그 등록을 사용한다.

  • ksoftirqd
  • migration
  • watchdog
  • rcuc

 

smpboot_register_percpu_thread()

kernel/smpboot.c

/**
 * smpboot_register_percpu_thread - Register a per_cpu thread related to hotplug
 * @plug_thread:        Hotplug thread descriptor
 *
 * Creates and starts the threads on all online cpus.
 */
int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
{
        unsigned int cpu;               
        int ret = 0;

        get_online_cpus();
        mutex_lock(&smpboot_threads_lock);
        for_each_online_cpu(cpu) {
                ret = __smpboot_create_thread(plug_thread, cpu);
                if (ret) {
                        smpboot_destroy_threads(plug_thread);
                        goto out;       
                }
                smpboot_unpark_thread(plug_thread, cpu);
        }
        list_add(&plug_thread->list, &hotplug_threads);
out:
        mutex_unlock(&smpboot_threads_lock);
        put_online_cpus();
        return ret;
}
EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread);

인수로 받은 plug_thread 정보를 기준으로 per-cpu 스레드로 등록하고 모든 online cpu에서 동작하게 한다.

  • 코드 라인 14~19에서 online cpu 수 만큼 루프를 돌며 smpboot_thread_fn() 함수를 fork하고 ht->thread_comm 이름으로 등록한다.
  • 코드 라인 20에서 스레드를 unpark 한다.
  • 코드 라인 22에서 인수로 받은 스레드를 전역 hotplug_threads 리스트에 등록한다.

 

__smpboot_create_thread()

kernel/smpboot.c

static int
__smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{
        struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
        struct smpboot_thread_data *td;

        if (tsk)
                return 0;

        td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));
        if (!td)
                return -ENOMEM;
        td->cpu = cpu;
        td->ht = ht;

        tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
                                    ht->thread_comm);
        if (IS_ERR(tsk)) {
                kfree(td);
                return PTR_ERR(tsk);
        }
        get_task_struct(tsk);
        *per_cpu_ptr(ht->store, cpu) = tsk;
        if (ht->create) {
                /*
                 * Make sure that the task has actually scheduled out
                 * into park position, before calling the create
                 * callback. At least the migration thread callback
                 * requires that the task is off the runqueue.
                 */
                if (!wait_task_inactive(tsk, TASK_PARKED))
                        WARN_ON(1);
                else
                        ht->create(cpu);
        }
        return 0;
}

online cpu 수 만큼 루프를 돌며 smpboot_thread_fn() 함수를 fork 하여 동작하게 하고 생성된 태스크명은 ht->thread_comm으로 한다. 성공 시엔 0을 반환한다.

  • 코드 라인 4~8에서 요청한 cpu에 해당하는 ht->store 값을 tsk로 가져온다. 이 값이 이미 설정된 경우 함수를 빠져나간다.
  • 코드 라인 10~14에서 smpboot_thread_data 구조체르 할당받고 인수로 받은 cpu와 ht 정보를 대입한다.
  • 코드 라인 16~21에서 smpboot_thread_fn() 함수를 fork 하여 동작하게 하고 생성된 태스크명으로 ht->thread_comm을 사용한다.
  • 코드 라인 22~23에서 생성된 태스크를 ht->store에 대입한다.
  • 코드 라인 24~35에서 ht->create에 함수가 등록된 경우 함수를 호출한다.
    • migration 커널 스레드에서 cpu_stop_create() 함수가 등록되어 사용된다.

 

smpboot_thread_fn()

kernel/smpboot.c

/**
 * smpboot_thread_fn - percpu hotplug thread loop function
 * @data:       thread data pointer
 *
 * Checks for thread stop and park conditions. Calls the necessary
 * setup, cleanup, park and unpark functions for the registered
 * thread.
 *
 * Returns 1 when the thread should exit, 0 otherwise.
 */
static int smpboot_thread_fn(void *data)
{
        struct smpboot_thread_data *td = data;
        struct smp_hotplug_thread *ht = td->ht;

        while (1) {
                set_current_state(TASK_INTERRUPTIBLE);
                preempt_disable();
                if (kthread_should_stop()) {
                        __set_current_state(TASK_RUNNING);
                        preempt_enable();
                        if (ht->cleanup)
                                ht->cleanup(td->cpu, cpu_online(td->cpu));
                        kfree(td);
                        return 0;
                }

                if (kthread_should_park()) {
                        __set_current_state(TASK_RUNNING);
                        preempt_enable();
                        if (ht->park && td->status == HP_THREAD_ACTIVE) {
                                BUG_ON(td->cpu != smp_processor_id());
                                ht->park(td->cpu);
                                td->status = HP_THREAD_PARKED;
                        }
                        kthread_parkme();
                        /* We might have been woken for stop */
                        continue;
                }

                BUG_ON(td->cpu != smp_processor_id());

                /* Check for state change setup */
                switch (td->status) {
                case HP_THREAD_NONE:
                        __set_current_state(TASK_RUNNING);
                        preempt_enable();
                        if (ht->setup)
                                ht->setup(td->cpu);
                        td->status = HP_THREAD_ACTIVE;
                        continue;

                case HP_THREAD_PARKED:
                        __set_current_state(TASK_RUNNING);
                        preempt_enable();
                        if (ht->unpark)
                                ht->unpark(td->cpu);
                        td->status = HP_THREAD_ACTIVE;
                        continue;
                }

                if (!ht->thread_should_run(td->cpu)) {
                        preempt_enable_no_resched();
                        schedule();
                } else {
                        __set_current_state(TASK_RUNNING);
                        preempt_enable();
                        ht->thread_fn(td->cpu);
                }
        }
}

per-cpu 핫플러그 스레드 루프 함수로 무한 루프를 돌며 요청 시 마다 등록된 함수를 호출한다.

  • 코드 라인 17~18에서 현재 태스크를 인터럽트 허용상태로 두고 커널 선점은 막는다.
  • 코드 라인 19~26에서 현재 태스크에 KTHREAD_SHOULD_STOP 플래그가 설정된 경우 스레드의 종료 처리를 한다.
  • 코드 라인 28~39에서 현재 태스크에 KTHREAD_SHOULD_PARK 플래그가 설정된 경우 스레드를 park 상태로 변경시켜 sleep한다. 깨어나는 경우 계속 루프를 돈다.
  • 코드 라인 44~51에서 smp 핫플러그 스레드 상태가 HP_THREAD_NONE인 경우 태스크를 running 상태로 변경하고 커널 선점을 오픈하며 ht->setup 함수를 동작시키고 smp 핫플러그 상태를 active로 변경한다.
  • 코드 라인 53~60에서 smp 핫플러그 스레드 상태가 HP_THREAD_PARKED 상태로 요청한 경우 태스크를 running 상태로 변경하고 커널 선점을 오픈하며 ht->unpark 함수를 동작시키고 smp 핫플러그 상태를 active로 변경한다.
  • 코드 라인 62~64에서 ht->should_run() 함수가 false인 경우 리스케쥴한다. (sleep)
    • ksoftirqd 커널 스레드에서는 ksoftirqd_should_run() 함수를 호출하여 처리할 softirq의 여부를 확인한다.
  • 코드 라인 65~69에서 태스크를 TASK_RUNNING 상태로 바꾸고 선점 enable 한 후 해당 스레드의 처리 함수를 호출한다.
    • ksoftirqd 커널 스레드에서는 run_ksoftirqd() 함수를 호출한다.

 

kernel/smpboot.c

enum {
        HP_THREAD_NONE = 0,
        HP_THREAD_ACTIVE,
        HP_THREAD_PARKED,
};

 

다음 그림은 SMP 핫플러그 스레드 상태별 처리 흐름을 보여준다.

ksoftirqd_should_run()

kernel/softirq.c

static int ksoftirqd_should_run(unsigned int cpu)
{
        return local_softirq_pending();
}

처리할 softirq가 있는지 여부를 알아온다.

 

pending softirq 처리

 

tasklet을 감싼 softirq는 매우 빠르게 처리해야 할 인터럽트에 대해서만 제한적으로 메인 라인 커미터에 의해 하드 코딩되어 유지 관리되고 있다. softirq는 최고 우선 순위의 ksoftirqd 스레드가 각각의 cpu에서 동작된다. 그러나 특정 커널 옵션 및 커널 파라메터를 사용한 경우 특정 조건을 만족하는 경우에 한해 irq context가 끝나자 마자 인터럽트를 복귀하지 않고 곧장 softirq 서비스 루틴을 호출할 수 있다.

  • irq context(top-half)에서 직접 호출 조건
    •  디폴트로 호출가능
      • CONFIG_IRQ_FORCED_THREADING 커널 옵션을 사용한 상태에서 “thredirqs” 커널 파라메터 사용한 경우를 제외
  • 그 외 사용 조건
    • 호출 시 최대 2ms를 벗어나는 경우 ksoftirqd에서 나머지 pending softirq를 처리

다음 그림은 softirq 호출 시 각 softirq의 처리 루틴으로 분기되는 과정을 보여준다. 특정 softirq를 호출하기 위해서는 raise_softirq()를 사용한다.

 

run_ksoftirqd()

kernel/softirq.c

static void run_ksoftirqd(unsigned int cpu)
{
        local_irq_disable();
        if (local_softirq_pending()) {
                /*
                 * We can safely run softirq on inline stack, as we are not deep
                 * in the task stack here.
                 */
                __do_softirq();
                local_irq_enable();
                cond_resched_rcu_qs();
                return;
        }
        local_irq_enable();
}

local 인터럽트를 막아둔채로 처리할 softirq가 있으면 해당 softirq 핸들러 함수를 호출한다.

 

local_softirq_pending()

include/linux/irq_cpustat.h

  /* arch independent irq_stat fields */
#define local_softirq_pending() \
        __IRQ_STAT(smp_processor_id(), __softirq_pending)

현재 cpu에 처리할 softirq가 있는지 유무를 알아온다.

 

include/linux/irq_cpustat.h

#ifndef __ARCH_IRQ_STAT
extern irq_cpustat_t irq_stat[];                /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#endif

 

kernel/softirq.c

#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);
#endif

 

do_softirq()

kernel/softirq.c

asmlinkage __visible void do_softirq(void)
{
        __u32 pending;
        unsigned long flags;

        if (in_interrupt())
                return;

        local_irq_save(flags);

        pending = local_softirq_pending();

        if (pending)
                do_softirq_own_stack();

        local_irq_restore(flags);
}


local 인터럽트를 막아둔채로 처리할 softirq가 있으면 해당 softirq 핸들러 함수를 호출한다.

 

do_softirq_own_stack()

include/linux/interrupt.h

static inline void do_softirq_own_stack(void)
{
        __do_softirq();
}

처리할 softirq가 있으면 해당 softirq 핸들러 함수를 호출한다.

 

__do_softirq()

kernel/softirq.c

asmlinkage __visible void __do_softirq(void)
{
        unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
        unsigned long old_flags = current->flags;
        int max_restart = MAX_SOFTIRQ_RESTART;
        struct softirq_action *h;
        bool in_hardirq;
        __u32 pending;
        int softirq_bit;

        /*
         * Mask out PF_MEMALLOC s current task context is borrowed for the
         * softirq. A softirq handled such as network RX might set PF_MEMALLOC
         * again if the socket is related to swap
         */
        current->flags &= ~PF_MEMALLOC;

        pending = local_softirq_pending();
        account_irq_enter_time(current);
                
        __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
        in_hardirq = lockdep_softirq_start();

restart:
        /* Reset the pending bitmask before enabling irqs */
        set_softirq_pending(0);

        local_irq_enable();

        h = softirq_vec;

        while ((softirq_bit = ffs(pending))) {
                unsigned int vec_nr;
                int prev_count;

                h += softirq_bit - 1;

                vec_nr = h - softirq_vec;
                prev_count = preempt_count();

                kstat_incr_softirqs_this_cpu(vec_nr);

                trace_softirq_entry(vec_nr);
                h->action(h);
                trace_softirq_exit(vec_nr);
                if (unlikely(prev_count != preempt_count())) {
                        pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
                               vec_nr, softirq_to_name[vec_nr], h->action,
                               prev_count, preempt_count());
                        preempt_count_set(prev_count);
                }
                h++;
                pending >>= softirq_bit;
        }

        rcu_bh_qs();
        local_irq_disable();

        pending = local_softirq_pending();
        if (pending) {
                if (time_before(jiffies, end) && !need_resched() &&
                    --max_restart)
                        goto restart;

                wakeup_softirqd();
        }

        lockdep_softirq_end(in_hardirq);
        account_irq_exit_time(current);
        __local_bh_enable(SOFTIRQ_OFFSET);
        WARN_ON_ONCE(in_interrupt());
        tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}

처리할 softirq가 있으면 해당 softirq 핸들러 함수를 호출한다.

  • 코드 라인 3에서 최대 softirq 처리 시간으로 현재 시간으로 부터 2ms 후에 해당하는 jiffies 값을 산출한다.
  • 코드 라인 4에서 현재 태스크의 플래그를 백업해둔다.
  • 코드 라인 5에서 최대 재시도 수를 10번으로 제한한다.
  • 코드 라인 16에서 현재 태스크 플래그에서 PF_MEMALLOC을 제거하여 softirq 처리 핸들러 루틴에서 응급 메모리를 사용하지 못하도록 한다. 단 네트워크 swap을 이용하여야 하는 NET_RX_SOFTIRQ는 예외적으로 해당 핸들러에서 PF_MEMALLOC을 설정한다.
  • 코드 라인 18에서 처리할 softirq를 알아온다.
  • 코드 라인 19에서 CONFIG_IRQ_TIME_ACCOUNTING 커널 옵션을 사용한 경우 트레이스 목적으로 irq 진입 시간을 기록한다.
  • 코드 라인 21에서 커널 선점되지 않도록 preempt_count를 SOFTIRQ_OFFSET 만큼 더한다.
  • 코드 라인 26~28에서 irq를 enable하기 전에 __softirq_pending 플래그를 클리어한 후 irq를 enable 한다.
  • 코드 라인 32~38에서 처리할 softirq 중 가장 우선 순위가 높은 softirq의 벡터번호를 알아온다.
    • vec_nr=0    <- softirq_vec[HI_SOFTIRQ]
  • 코드 라인 41에서 처리할 softirq 통계 카운터를 1 증가 시킨다.
  • 코드 라인 44에서 softirq 핸들러 함수를 호출한다.
  • 코드 라인 46~51에서 softirq 핸들러 함수를 수행하기 전에 읽은 preempt_count 값에 변화가 발생하면 에러 메시지를 출력하고 preempt_count 값을 처음 읽었던 값으로 되돌린다.
  • 코드 라인 52~54에서 다음 순위의 softirq를 처리할 준비를 한다.
  • 코드 라인 56~57에서 rcu의 bottom half 처리를 한 후 irq를 disable 한다.
  • 코드 라인 59~63에서 여전히 pending된 softirq가 있는지 확인하여 여전히 존재하면 다음 조건을 만족하면 다시 restart: 레이블로 이동하여 softirq 처리를 수행하게 한다.
    • 최대 반복 횟수: 10회 이내
    • 최대 처리 시간: 2ms 이내
    • 리스케쥴 요청이 없어야 한다.
  • 코드 라인 64에서 ksoftirqd에서 호출되지 않고 irq context에서 직접 호출한 경우ksoftirqd가 나머지 pending 된 softirq를 처리하도록 깨운다. 다음과 같은 특정 커널 조건에서만 유효하다.
    • CONFIG_IRQ_FORCED_THREADING 커널 옵션 사용
    • “thredirqs” 커널 파라메터 사용
  • 코드 라인 69에서 CONFIG_IRQ_TIME_ACCOUNTING 커널 옵션을 사용한 경우 트레이스 목적으로 irq 퇴출 시간을 기록한다.
  • 코드 라인 70에서 softirq에서 막은 커널 선점을 다시 가능하도록 preempt_count를 SOFTIRQ_OFFSET 만큼 감소시킨다.
  • 코드 라인 71에서 아직도 interrupt 처리중인 경우 경고 메시지를 출력한다.
  • 코드 라인 72에서 현재 태스크 플래그를 원래대로 다시 복구한다.

 

특정 softirq 호출

raise_softirq()

kernel/softirq.c

void raise_softirq(unsigned int nr)
{
        unsigned long flags;

        local_irq_save(flags);
        raise_softirq_irqoff(nr);
        local_irq_restore(flags);
}

요청한 번호의 softirq 서비스를 호출한다.

 

raise_softirq_irqoff()

kernel/softirq.c

/*
 * This function must run with irqs disabled!
 */
inline void raise_softirq_irqoff(unsigned int nr)
{
        __raise_softirq_irqoff(nr);

        /*
         * If we're in an interrupt or softirq, we're done
         * (this also catches softirq-disabled code). We will
         * actually run the softirq once we return from
         * the irq or softirq.
         *
         * Otherwise we wake up ksoftirqd to make sure we
         * schedule the softirq soon.
         */
        if (!in_interrupt())
                wakeup_softirqd();
}

요청한 번호의 softirq 서비스를 호출한다.

  • 코드 라인 6에서 요청한 softirq 번호에 해당하는 펜딩 비트플래그를 설정한다.
  • 코드 라인 17에서 인터럽트 서비중이 아닌 경우 만일 softirqd 스레드가 잠들어있는 경우 깨운다.

 

__raise_softirq_irqoff()

kernel/softirq.c

void __raise_softirq_irqoff(unsigned int nr)
{
        trace_softirq_raise(nr);
        or_softirq_pending(1UL << nr);
}

요청한 softirq 번호에 해당하는 펜딩 비트플래그를 설정한다.

 

#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

 

wakeup_softirqd()

kernel/softirq.c

/*
 * we cannot loop indefinitely here to avoid userspace starvation,
 * but we also don't want to introduce a worst case 1/HZ latency
 * to the pending events, so lets the scheduler to balance
 * the softirq load for us.
 */
static void wakeup_softirqd(void)
{
        /* Interrupts are disabled: no need to stop preemption */
        struct task_struct *tsk = __this_cpu_read(ksoftirqd);

        if (tsk && tsk->state != TASK_RUNNING)
                wake_up_process(tsk);
}

softirqd 스레드가 잠들어있는 경우 깨운다.

 

특정 softirq 핸들러 준비

open_softirq()

kernel/softirq.c

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
        softirq_vec[nr].action = action;
}

요청한 softirq 벡터 번호에 action 핸들러 함수를 대입한다.

 

Timer Softirq

run_timer_softirq()

kernel/time/timer.c

/*
 * This function runs timers and the timer-tq in bottom half context.
 */
static void run_timer_softirq(struct softirq_action *h)
{
        struct tvec_base *base = __this_cpu_read(tvec_bases);

        hrtimer_run_pending();

        if (time_after_eq(jiffies, base->timer_jiffies))
                __run_timers(base);
}

타이머 softirq 처리 루틴이다.

  • 코드 라인 8에서 hrtimer가 가동되었는지 확인하고 틱 oneshot 모드 전환 여부를 확인한 후에 oneshot 모드로 전환한다.
  • 코드 라인 10~11에서 lowres 타이머 휠에서 만료된 타이머가 있는 경우 등록된 콜백 함수를 호출한다.

 

참고

답글 남기기

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