do_IPI()

 

do_IPI()

arch/arm/kernel/smp.c

/*
 * Main handler for inter-processor interrupts
 */
asmlinkage void __exception_irq_entry do_IPI(int ipinr, struct pt_regs *regs)
{
        handle_IPI(ipinr, regs);
}

ipinr 번호에 해당하는 IPI 소프트콜 서비스를 수행한다.

 

handle_IPI()

arch/arm/kernel/smp.c

void handle_IPI(int ipinr, struct pt_regs *regs)
{
        unsigned int cpu = smp_processor_id();
        struct pt_regs *old_regs = set_irq_regs(regs);

        if ((unsigned)ipinr < NR_IPI) {
                trace_ipi_entry(ipi_types[ipinr]);
                __inc_irq_stat(cpu, ipi_irqs[ipinr]);
        }

        switch (ipinr) {
        case IPI_WAKEUP:
                break;

#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
        case IPI_TIMER:
                irq_enter();
                tick_receive_broadcast();
                irq_exit();
                break;
#endif

        case IPI_RESCHEDULE:
                scheduler_ipi();
                break;

        case IPI_CALL_FUNC:
                irq_enter();
                generic_smp_call_function_interrupt();
                irq_exit();
                break;

        case IPI_CALL_FUNC_SINGLE:
                irq_enter();
                generic_smp_call_function_single_interrupt();
                irq_exit();
                break;

        case IPI_CPU_STOP:
                irq_enter();
                ipi_cpu_stop(cpu);
                irq_exit();
                break;

#ifdef CONFIG_IRQ_WORK
        case IPI_IRQ_WORK:
                irq_enter();
                irq_work_run();
                irq_exit();
                break;
#endif

        case IPI_COMPLETION:
                irq_enter();
                ipi_complete(cpu);
                irq_exit();
                break;

        default:
                pr_crit("CPU%u: Unknown IPI message 0x%x\n",
                        cpu, ipinr);
                break;
        }

        if ((unsigned)ipinr < NR_IPI)
                trace_ipi_exit(ipi_types[ipinr]);
        set_irq_regs(old_regs);
}

ipinr 번호에 해당하는 IPI 소프트콜 서비스를 수행한다.

 

  • 코드 라인 3에서 현재 cpu 번호를 알아온다.
  • 코드 라인 4에서 기존 *pt_regs 포인터 주소를 가져오고, per-cpu *pt_regs 포인터 주소에 인수로 받은 새 regs를 대입한다.
  • 코드 라인 6~9에서 ipinr이 범위내에 있는 경우 trace 출력을 하고 해당 ipinr에 해당하는 ipi 카운터를 증가시킨다.
  • 코드 라인 11~13에서 IPI_WAKEUP(0) 요청을 받은 경우 아무것도 처리하지 않는다.
    • 이미 깨어나서 돌고 있고 아울러 트래킹을 위해 이미 해당 통계 카운터도 증가시켰다.
    • 호출 방법 1: arch_send_wakeup_ipi_mask(cpumask) 함수를 사용하여 cpumask에 해당하는 각 cpu들에 대해 WFI에 의해 잠들어 있는 경우 프로세서를 깨울 수 있다.
    • 호출 방법 2: 빅/리틀 hot plug cpu 시스템에서 gic_raise_softirq() 함수를 사용하여 WFI에 의해 대기하고 있는 cpu들을 깨운다. 이 때 인수로 1대신 0을 사용하여 호출하면 불필요한 printk 메시지(“CPU%u: Unknown IPI message 0x00000001”)를 출력하지 않고 트래킹을 위해 해당 카운터 수도 추적할 수 있다.
      • PM으로 전력 기능까지 콘트롤하는 GIC(Generic Interrupt Controller) #0을 사용하는 방법으로 지정한 cpu를 wakeup 시킨다.
    • 참고: ARM: 7536/1: smp: Formalize an IPI for wakeup
  • 코드 라인 15~21에서 IPI_TIMER(1) 요청을 받은 경우 tick 디바이스에 등록된 이벤트 디바이스의 핸들러 함수를 호출한다.
  • 코드 라인 23~25에서 IPI_RESCHEDULE 요청을 받은 경우 현재 프로세서에 대해 리스케쥴한다.
  • 코드 라인 27~31에서 IPI_CALL_FUNC 요청을 받은 경우 미리 등록된 함수들을 호출한다.
  • 코드 라인 33~37에서 IPI_CALL_FUNC_SINGLE 요청을 받은 경우 미리 등록된 함수를 호출한다.
  • 코드 라인 39~43에서 IPI_CPU_STOP 요청을 받은 경우 현재  cpu가 부팅 중 또는 동작 중인 경우 “CPU%u: stopping” 메시지 출력 및 스택 덤프를 하고 해당 cpu의 irq, fiq를 모두 정지 시키고 offline 상태로 바꾼 후 정지(spin)한다.
  • 코드 라인 45~51에서 IPI_IRQ_WORK 요청을 받은 경우 현재의 모든 irq 작업들을 즉시 수행하게 한다.
  • 코드 라인 53~57에서 IPI_COMPLETION 요청을 받은 경우 register_ipi_completion() 함수로 등록해 놓은 per-cpu cpu_completion 에서 wait_for_completion()등으로 대기하고 있는 스레드를 깨운다.
  • 코드 라인 65~66에서  ipinr이 범위내에 있는 경우 trace 출력을 한다.
  • 코드 라인 67에서 per-cpu *pt_regs 포인터 주소에 백업해 둔 old_regs를 대입한다.

 

tick_receive_broadcast()

kernel/time/tick-broadcast.c

#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
int tick_receive_broadcast(void)
{
        struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
        struct clock_event_device *evt = td->evtdev;

        if (!evt)
                return -ENODEV;

        if (!evt->event_handler)
                return -EINVAL;

        evt->event_handler(evt);
        return 0;
}
#endif

tick 디바이스에 등록된 이벤트 디바이스의 핸들러 함수를 호출한다.

  • CONFIG_GENERIC_CLOCKEVENTS_BROADCAST 커널 옵션을 사용하는 경우에만 동작한다.
  • 호출 방법: tick_do_broadcast() 함수를 사용하여 해당 cpu들에 tick을 전달한다.
  • 참고: clockevents: Add generic timer broadcast receiver

 

  • 코드 라인 4~8에서 tick 디바이스에 등록된 clock 이벤트 디바이스를 알아온다. 없으면 -ENODEV 에러를 반환한다.
  • 코드 라인 10~11에서 clock 이벤트 디바이스에 이벤트 핸들러 함수가 등록되어 있지 않으면 -EINVAL 에러를 반환한다.
  • 코드 라인 13에서 등록된 이벤트 핸들러 함수를 호출한다.

 

scheduler_ipi()

kernel/sched/core.c

void scheduler_ipi(void)
{
        /*            
         * Fold TIF_NEED_RESCHED into the preempt_count; anybody setting
         * TIF_NEED_RESCHED remotely (for the first time) will also send
         * this IPI.
         */
        preempt_fold_need_resched();

        if (llist_empty(&this_rq()->wake_list) && !got_nohz_idle_kick())
                return;

        /*
         * Not all reschedule IPI handlers call irq_enter/irq_exit, since
         * traditionally all their work was done from the interrupt return
         * path. Now that we actually do some work, we need to make sure
         * we do call them.
         *
         * Some archs already do call them, luckily irq_enter/exit nest
         * properly.
         *
         * Arguably we should visit all archs and update all handlers,
         * however a fair share of IPIs are still resched only so this would
         * somewhat pessimize the simple resched case.
         */
        irq_enter();
        sched_ttwu_pending();

        /*
         * Check if someone kicked us for doing the nohz idle load balance.
         */
        if (unlikely(got_nohz_idle_kick())) {
                this_rq()->idle_balance = 1;
                raise_softirq_irqoff(SCHED_SOFTIRQ);
        }
        irq_exit();
}

현재 프로세서에 대해 리스케쥴한다.

  • 호출 방법: smp_send_reschedule() 명령을 사용하여 해당 cpu에서 리스케쥴링하도록 요청한다.

 

  • 코드 라인 8에서 현재 스레드에 리스케쥴 요청이 있는 경우 preempt count에 있는 리스쥴 요청중 비트를 제거한다.
  • 코드 라인 10~11애서 런큐의 wake_list가 비어 있으면서 현재 cpu의 런큐에 NOHZ_BALANCE_KICK 요청이 없거나, 현재 cpu가 idle 상태가 아니거나 리스케쥴 요청이 있는 경우 리스케쥴할 태스크가 없어서 함수를 빠져나간다.
  • 코드 라인 26에서 hard irq preecmption 카운터를 증가시켜 preemption을 막고 irq 소요 시간 및 latency를 측정할 수 있도록 한다.
  • 코드 라인 27에서 런큐의 wake_list에서 모두 꺼내서 다시 enqueue 하여 리스케쥴 한다.
  • 코드 라인 32~35에서 낮은 확률로 현재 cpu의 런큐에 NOHZ_BALANCE_KICK 요청이 있고 현재 cpu가 idle 상태이면서 리스케쥴 요청이 없는 경우 런큐의 idle_balance 를 1로 설정하고 SCHED_SOFTIRQ 를 정지시킨다.
  • 코드 라인 36에서  hard irq preecmption 카운터를 감소시켜 preemption을 다시 열어주고  irq 소요 시간 및 latency를 측정할 수 있도록 후처리 작업을 수행한다.

 

generic_smp_call_function_interrupt()

include/linux/smp.h

#define generic_smp_call_function_interrupt \
        generic_smp_call_function_single_interrupt

IPI에 의해 인터럽트 된 후 미리 등록된 함수를 호출한다.

  • 호출 방법: arch_send_call_function_ipi_mask() 명령을 사용하여 해당 cpu들에서 미리 등록된 함수들을 호출한다.

 

generic_smp_call_function_single_interrupt()

kernel/smp.c

/**
 * generic_smp_call_function_single_interrupt - Execute SMP IPI callbacks
 *
 * Invoked by arch to handle an IPI for call function single.
 * Must be called with interrupts disabled.
 */
void generic_smp_call_function_single_interrupt(void)
{
        flush_smp_call_function_queue(true);
}

IPI에 의해 인터럽트 된 후 미리 등록된 함수를 호출한다.

  • 호출 방법: arch_send_call_function_single_ipi() 명령을 사용하여 요청 cpu에서 미리 등록된 함수를 호출한다.

 

flush_smp_call_function_queue()

kernel/smp.c

/**
 * flush_smp_call_function_queue - Flush pending smp-call-function callbacks
 *
 * @warn_cpu_offline: If set to 'true', warn if callbacks were queued on an
 *                    offline CPU. Skip this check if set to 'false'.
 *
 * Flush any pending smp-call-function callbacks queued on this CPU. This is
 * invoked by the generic IPI handler, as well as by a CPU about to go offline,
 * to ensure that all pending IPI callbacks are run before it goes completely
 * offline.
 *
 * Loop through the call_single_queue and run all the queued callbacks.
 * Must be called with interrupts disabled.
 */
static void flush_smp_call_function_queue(bool warn_cpu_offline)
{
        struct llist_head *head;
        struct llist_node *entry;
        struct call_single_data *csd, *csd_next;
        static bool warned;

        WARN_ON(!irqs_disabled());

        head = this_cpu_ptr(&call_single_queue);
        entry = llist_del_all(head);
        entry = llist_reverse_order(entry);

        /* There shouldn't be any pending callbacks on an offline CPU. */
        if (unlikely(warn_cpu_offline && !cpu_online(smp_processor_id()) &&
                     !warned && !llist_empty(head))) {
                warned = true; 
                WARN(1, "IPI on offline CPU %d\n", smp_processor_id());

                /*
                 * We don't have to use the _safe() variant here
                 * because we are not invoking the IPI handlers yet.
                 */
                llist_for_each_entry(csd, entry, llist)
                        pr_warn("IPI callback %pS sent to offline CPU\n",
                                csd->func);
        }

        llist_for_each_entry_safe(csd, csd_next, entry, llist) {
                csd->func(csd->info);
                csd_unlock(csd);
        }

        /*
         * Handle irq works queued remotely by irq_work_queue_on().
         * Smp functions above are typically synchronous so they
         * better run first since some other CPUs may be busy waiting
         * for them.
         */
        irq_work_run();
}

IPI에 의해 인터럽트 된 후 미리 등록된 함수를 호출한다.

 

  • 코드 라인 24~26에서 per-cpu call_single_queue에 등록된 엔트리들을 모두 제거하고 entry로 가져오는데 가장 처음에 추가한 call_single_data 엔트리가 앞으로 가도록 순서를 거꾸로 바꾼다.
  • 코드 라인 29~41에서 인수 warn_cpu_offline가 설정된 경우 현재 cpu가 offline된 cpu인 경우 한 번만 “IPI on offline CPU %d” 및 “IPI callback %pS sent to offline CPU”라는 경고 메시지를 출력하게 한다.
  • 코드 라인 43~46에서 등록되어 있는 함수들을 모두 호출하여 수행한다.
  • 코드 라인 54에서 현재의 모든 irq 작업들을 즉시 수행하게 한다.

 

ipi_cpu_stop()

arch/arm/kernel/smp.c

/*
 * ipi_cpu_stop - handle IPI from smp_send_stop()
 */
static void ipi_cpu_stop(unsigned int cpu)
{
        if (system_state == SYSTEM_BOOTING ||
            system_state == SYSTEM_RUNNING) {
                raw_spin_lock(&stop_lock);
                pr_crit("CPU%u: stopping\n", cpu);
                dump_stack();
                raw_spin_unlock(&stop_lock);
        }

        set_cpu_online(cpu, false);

        local_fiq_disable();
        local_irq_disable();

        while (1)
                cpu_relax();
}

현재  cpu가 부팅 중 또는 동작 중인 경우 “CPU%u: stopping” 메시지 출력 및 스택 덤프를 하고 해당 cpu의 irq, fiq를 모두 정지 시키고 offline 상태로 바꾼 후 정지(spin)한다.

  • 호출 방법: smp_send_stop() 함수를 사용하여 현재 cpu를 제외한 online cpu를 stop 시킨다.

 

irq_work_run()

kernel/irq_work.c

/*
 * hotplug calls this through:
 *  hotplug_cfd() -> flush_smp_call_function_queue()
 */
void irq_work_run(void)
{
        irq_work_run_list(this_cpu_ptr(&raised_list));
        irq_work_run_list(this_cpu_ptr(&lazy_list));
}       
EXPORT_SYMBOL_GPL(irq_work_run);

현재의 모든 irq 작업들을 즉시 수행하게 한다.

  • CONFIG_IRQ_WORK 커널 옵션을 사용하는 경우에만 동작한다.
  • 호출 방법: arch_irq_work_raise() 요청을 받은 경우 현재 자신의 cpu에서 모든 irq 작업들을 즉시 수행하게 한다.
  • 참고: ARM: 7872/1: Support arch_irq_work_raise() via self IPIs

 

  • 코드 라인 7에서 &raised_list에 있는 모든 irq 작업들을 수행하게 한다.
  • 코드 라인 8에서 &lazy_list에 있는 모든 irq 작업들을 수행하게 한다.

 

ipi_complete()

arch/arm/kernel/smp.c

static void ipi_complete(unsigned int cpu)
{
        complete(per_cpu(cpu_completion, cpu));
}

register_ipi_completion() 함수로 등록해 놓은 per-cpu cpu_completion 에서 wait_for_completion()등으로 대기하고 있는 스레드를 깨운다.

 

참고

답글 남기기

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