Interrupts -6- (IPI cross-call)

 

IPI (Inter Processor Interrupts)

인터럽트의 특별한 케이스로 SMP 시스템에서 하나의 cpu에서 다른 cpu로 발생시킨다.

ARM 리눅스에서 처리하는 IPI 타입은 다음과 같다.

  • IPI_WAKEUP
    • 다른 cpu를 깨운다.
  • IPI_TIMER
    • broadcast 타이머
  • IPI_RESCHEDULE
    • 리스케쥴링 인터럽트
  • IPI_CALL_FUNC
    • function call 인터럽트
  • IPI_CALL_FUNC_SINGLE
    • single function call 인터럽트
  • IPI_CPU_STOP
    • cpu stop 인터럽트
  • IPI_IRQ_WORK
    • IRQ work 인터럽트
  • IPI_COMPLETION
    • 완료 인터럽트

 

 

smp_call_function_many()

/**
 * smp_call_function_many(): Run a function on a set of other CPUs.
 * @mask: The set of cpus to run on (only runs on online subset).
 * @func: The function to run. This must be fast and non-blocking.
 * @info: An arbitrary pointer to pass to the function.
 * @wait: If true, wait (atomically) until function has completed
 *        on other CPUs.
 *
 * If @wait is true, then returns once @func has returned.
 *
 * You must not call this function with disabled interrupts or from a
 * hardware interrupt handler or from a bottom half handler. Preemption
 * must be disabled when calling this function.
 */
void smp_call_function_many(const struct cpumask *mask,
                            smp_call_func_t func, void *info, bool wait)
{
        struct call_function_data *cfd;
        int cpu, next_cpu, this_cpu = smp_processor_id();

        /*
         * Can deadlock when called with interrupts disabled.
         * We allow cpu's that are not yet online though, as no one else can
         * send smp call function interrupt to this cpu and as such deadlocks
         * can't happen.
         */
        WARN_ON_ONCE(cpu_online(this_cpu) && irqs_disabled()
                     && !oops_in_progress && !early_boot_irqs_disabled);

        /* Try to fastpath.  So, what's a CPU they want? Ignoring this one. */
        cpu = cpumask_first_and(mask, cpu_online_mask);
        if (cpu == this_cpu)
                cpu = cpumask_next_and(cpu, mask, cpu_online_mask);

        /* No online cpus?  We're done. */
        if (cpu >= nr_cpu_ids)
                return;

        /* Do we have another CPU which isn't us? */
        next_cpu = cpumask_next_and(cpu, mask, cpu_online_mask);
        if (next_cpu == this_cpu)
                next_cpu = cpumask_next_and(next_cpu, mask, cpu_online_mask);

        /* Fastpath: do that cpu by itself. */
        if (next_cpu >= nr_cpu_ids) {
                smp_call_function_single(cpu, func, info, wait);
                return;
        }

        cfd = this_cpu_ptr(&cfd_data);

        cpumask_and(cfd->cpumask, mask, cpu_online_mask);
        cpumask_clear_cpu(this_cpu, cfd->cpumask);

        /* Some callers race with other cpus changing the passed mask */
        if (unlikely(!cpumask_weight(cfd->cpumask)))
                return;

        for_each_cpu(cpu, cfd->cpumask) {
                struct call_single_data *csd = per_cpu_ptr(cfd->csd, cpu);

                csd_lock(csd);
                csd->func = func;
                csd->info = info;
                llist_add(&csd->llist, &per_cpu(call_single_queue, cpu));
        }

        /* Send a message to all CPUs in the map */
        arch_send_call_function_ipi_mask(cfd->cpumask);

        if (wait) {
                for_each_cpu(cpu, cfd->cpumask) {
                        struct call_single_data *csd;

                        csd = per_cpu_ptr(cfd->csd, cpu);
                        csd_lock_wait(csd);
                }
        }
}
EXPORT_SYMBOL(smp_call_function_many);

현재 cpu를 제외한 다른 cpu 모두에서 함수가 실행되게 한다. wait 인수를 true로 지정하는 경우 각 cpu에서 함수가 실행이 완료될 때 까지 기다린다.

fastpath: 실행해야 할 다른 cpu가 1개만 있는 경우

  • cpu = cpumask_first_and(mask, cpu_online_mask);
    • 첫 cpu를 알아온다.
    • 인수로 받은 비트맵 mask와 cpu_online _mask에 대해 가장 처음의 1로 설정된 cpu를 알아온다.
  • if (cpu == this_cpu) cpu = cpumask_next_and(cpu, mask, cpu_online_mask);
    • 알아온 첫 cpu가 현재 동작중인 cpu인 경우 다음 cpu를 알아온다.
  • if (cpu >= nr_cpu_ids) return;
    • 없으면 함수를 빠져나간다.
  • next_cpu = cpumask_next_and(cpu, mask, cpu_online_mask);
    • 두 번째 cpu를 알아온다.
  • if (next_cpu == this_cpu) next_cpu = cpumask_next_and(next_cpu, mask, cpu_online_mask);
    • 두 번째 cpu가 현재 cpu인 경우 두 번째 cpu를 다시 다음 cpu로 지정한다.
  • if (next_cpu >= nr_cpu_ids) { smp_call_function_single(cpu, func, info, wait); return; }
    • 두 번째 cpu가 없는 경우 즉 다른 cpu를 고려할 필요가 없는 경우

slowpath: 실행해야 할 다른 cpu가 2개 이상인 경우

  • cfd = this_cpu_ptr(&cfd_data)
    • cfd_data라는 이름의 전역 per-cpu 객체
  • cpumask_and(cfd->cpumask, mask, cpu_online_mask);
    • cfd->cpumask <- mask 비트맵과 cpu_online_mask 비트맵을 and 한 결과를 담는다.
  • cpumask_clear_cpu(this_cpu, cfd->cpumask);
    • cfd->cpumask에서 현재 cpu 비트를 clear 한다.
  • if (unlikely(!cpumask_weight(cfd->cpumask))) return;
    • 1로 설정된 비트가 하나도 없는 경우 함수를 빠져나간다.
  • for_each_cpu(cpu, cfd->cpumask) {
    • cfd->cpumask 에서 1로 설정된 cpu들을 대상으로 루프를 돈다.
  • struct call_single_data *csd = per_cpu_ptr(cfd->csd, cpu);
    • 현재 cpu의 cfd->csd
  • csd_lock(csd);
    • csd->flags에 lock 비트가 설정되어 있는 동안 sleep하고 설정되어 있지 않으면 lock 비트를 설정한다.
  • csd->func = func; csd->info = info;
    • csd 정보를 update한다.
  • llist_add(&csd->llist, &per_cpu(call_single_queue, cpu));
    • 현재 cpu의 call_single_queue의 선두에 추가한다.
  • arch_send_call_function_ipi_mask(cfd->cpumask);
    • cpu들에 대해 IPI single function call interrupt를 발생시킨다.

인수 wait이 true인 경우 다른 cpu에서 func이 모두 실행되어 complete될 때까지 기다린다.

  • if (wait) {
    • 인수 wait  옵션이 true인 경우
  • for_each_cpu(cpu, cfd->cpumask) {
    • cfd->cpumask 에서 1로 설정된 cpu들을 대상으로 루프를 돈다.
  • csd_lock_wait(csd);
    • csd->flags에 lock 비트가 설정되어 있는 동안 sleep 한다.

 

kernel/smp.c

static DEFINE_PER_CPU_SHARED_ALIGNED(struct call_function_data, cfd_data);

 

다음 그림은 other cpu(요청 cpu 중 현재 cpu가 아닌 online된 cpu)가 fastpath와 slowpath로 나뉘어 처리되는 과정을 보여주며 큐에 들어간 구조체들은 해당 cpu에 소프트 인터럽트가 호출되어 처리되는 과정을 보여준다.

smp_call_function_many-1b

 

smp_call_function_single()

kernel/smp.c

/*
 * smp_call_function_single - Run a function on a specific CPU
 * @func: The function to run. This must be fast and non-blocking.
 * @info: An arbitrary pointer to pass to the function.
 * @wait: If true, wait until function has completed on other CPUs.
 *
 * Returns 0 on success, else a negative status code.
 */
int smp_call_function_single(int cpu, smp_call_func_t func, void *info,
                             int wait)
{
        int this_cpu;
        int err;

        /*
         * prevent preemption and reschedule on another processor,
         * as well as CPU removal
         */
        this_cpu = get_cpu();

        /*
         * Can deadlock when called with interrupts disabled.
         * We allow cpu's that are not yet online though, as no one else can
         * send smp call function interrupt to this cpu and as such deadlocks
         * can't happen.
         */
        WARN_ON_ONCE(cpu_online(this_cpu) && irqs_disabled()
                     && !oops_in_progress);

        err = generic_exec_single(cpu, NULL, func, info, wait);

        put_cpu();

        return err;
}
EXPORT_SYMBOL(smp_call_function_single);

요청한 cpu에서 함수를 수행시킨다. 인수 wait이 true인 경우 실행이 완료될 때까지 기다린다.

 

generic_exec_single()

kernel/smp.c

/*
 * Insert a previously allocated call_single_data element
 * for execution on the given CPU. data must already have
 * ->func, ->info, and ->flags set.
 */
static int generic_exec_single(int cpu, struct call_single_data *csd,
                               smp_call_func_t func, void *info, int wait)
{
        struct call_single_data csd_stack = { .flags = 0 };
        unsigned long flags;


        if (cpu == smp_processor_id()) {
                local_irq_save(flags);
                func(info);
                local_irq_restore(flags);
                return 0;
        }


        if ((unsigned)cpu >= nr_cpu_ids || !cpu_online(cpu))
                return -ENXIO;


        if (!csd) {
                csd = &csd_stack;
                if (!wait)
                        csd = this_cpu_ptr(&csd_data);
        }

        csd_lock(csd);

        csd->func = func;
        csd->info = info;

        if (wait)
                csd->flags |= CSD_FLAG_WAIT;

        /*
         * The list addition should be visible before sending the IPI
         * handler locks the list to pull the entry off it because of
         * normal cache coherency rules implied by spinlocks.
         *
         * If IPIs can go out of order to the cache coherency protocol
         * in an architecture, sufficient synchronisation should be added
         * to arch code to make it appear to obey cache coherency WRT
         * locking and barrier primitives. Generic code isn't really
         * equipped to do the right thing...
         */
        if (llist_add(&csd->llist, &per_cpu(call_single_queue, cpu)))
                arch_send_call_function_single_ipi(cpu);

        if (wait)
                csd_lock_wait(csd);

        return 0;
}

지정된 cpu에서 함수가 수행되게 한다. 인수 wait이 true인 경우 수행이 완료될 때 까지 기다린다.

  • if (cpu == smp_processor_id()) { local_irq_save(flags); func(info); local_irq_restore(flags); return 0; }
    • 요청 받은 cpu가 현재 cpu인 경우그냥 함수를 호출하여 수행한다.
  • csd_lock(csd);
    • csd->flags에 lock 비트가 설정되어 있는 동안 sleep하고 설정되어 있지 않으면 lock 비트를 설정한다.
  • csd->func = func; csd->info = info;
    • csd에 호출할 함수와 인수 정보를 대입한다.
  • if (wait) csd->flags |= CSD_FLAG_WAIT;
    • wait이 true인 경우 csd->flags에 wait 비트를 설정한다. (대기중 표시)
  • if (llist_add(&csd->llist, &per_cpu(call_single_queue, cpu))) arch_send_call_function_single_ipi(cpu);
    • csd를 call_single_queue에 추가한 후 IPI cross call 소프트 인터럽트를 호출한다.
  • if (wait) csd_lock_wait(csd);
    • csd->flags에서 lock이 풀릴 때까지 대기한다.
      • 다른 cpu가 해당 함수를 수행하고 완료된 후 lock 비트를 clear 시켜 종료를 알린다.

 

arch_send_call_function_single_ipi()

arch/arm/kernel/smp.c

void arch_send_call_function_single_ipi(int cpu)
{
        smp_cross_call(cpumask_of(cpu), IPI_CALL_FUNC_SINGLE);
}

지정된 cpu에 대해 IPI cross call 소프트 인터럽트를 호출한다.

  • 호출된 cpu는 call_single_queue에 추가된 함수를 수행한다.

 

smp_cross_call()

arch/arm/kernel/smp.c

static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
{
        trace_ipi_raise(target, ipi_types[ipinr]);
        __smp_cross_call(target, ipinr);
}

전역 __smp_cross_call에 등록된 핸들러 함수를 호출한다.

  • 지정된 cpu에 대해 IPI cross call 소프트 인터럽트를 호출한다.
    • 호출된 cpu는 call_single_queue에 추가된 함수를 수행한다.

 

rpi2에서 __smp_cross_call에 호출함수를 등록하는 곳

bcm2709_smp_init_cpus()

void __init bcm2709_smp_init_cpus(void)
{
        void secondary_startup(void);
        unsigned int i, ncores;

        ncores = 4; // xxx scu_get_core_count(NULL);
        printk("[%s] enter (%x->%x)\n", __FUNCTION__, (unsigned)virt_to_phys((void *)secondary_startup), (unsigned)__io_address(ST_BASE + 0x10));
        printk("[%s] ncores=%d\n", __FUNCTION__, ncores);

        for (i = 0; i < ncores; i++) {
                set_cpu_possible(i, true);
                /* enable IRQ (not FIQ) */
                writel(0x1, __io_address(ARM_LOCAL_MAILBOX_INT_CONTROL0 + 0x4 * i));
                //writel(0xf, __io_address(ARM_LOCAL_TIMER_INT_CONTROL0   + 0x4 * i));
        }
        set_smp_cross_call(bcm2835_send_doorbell);
}

 

set_smp_cross_call()

arch/arm/kernel/smp.c

/*
 * Provide a function to raise an IPI cross call on CPUs in callmap.
 */ 
void __init set_smp_cross_call(void (*fn)(const struct cpumask *, unsigned int))
{
        if (!__smp_cross_call)
                __smp_cross_call = fn;
}

 

bcm2835_send_doorbell()

arch/arm/mach-bcm2709/bcm2709.c

static void bcm2835_send_doorbell(const struct cpumask *mask, unsigned int irq)
{
        int cpu;
        /*
         * Ensure that stores to Normal memory are visible to the
         * other CPUs before issuing the IPI.
         */
        dsb();

        /* Convert our logical CPU mask into a physical one. */
        for_each_cpu(cpu, mask)
        {
                /* submit softirq */
                writel(1<<irq, __io_address(ARM_LOCAL_MAILBOX0_SET0 + 0x10 * MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 0)));
        }
}

요청 받은 cpu들에 대해 소프트 인터럽트를 호출한다.

 

구조체

call_function_data 구조체

kernel/smp.c

struct call_function_data {
        struct call_single_data __percpu *csd;
        cpumask_var_t           cpumask;
};

 

call_single_data 구조체

include/linux/smp.h

struct call_single_data {
        struct llist_node llist;
        smp_call_func_t func;
        void *info;
        u16 flags;
};
  • llist
    • csd 리스트
  • func
    • 수행할 함수
  • *info
    • 함수에 인수로 전달할 정보
  • flags
    • 아래 비트를 저장한다.

kernel/smp.c

enum {
        CSD_FLAG_LOCK           = 0x01,
        CSD_FLAG_WAIT           = 0x02,
};
  • SMP 시스템에서 call single data를 위한 cpu의 처리 상태

 

참고

 

답글 남기기

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