IPI(Inter Processor Interrupt)의 약자로 지정한 CPU 코어를 타겟으로 인터럽트(소프트웨어적)를 유발하는 매커니즘입니다. 이번 포스트에서는 리눅스 커널에서 IPI Call을 요청하는 루틴을 리뷰하겠습니다.
 
IPI Call은 아키텍처에 의존적으로 처리되므로 하이 레벨과 로우 레벨로 코드가 구현돼 있습니다.
 
* High Level 코드 위치
 
   kernel/sched/core.c
   kernel/smp.c
   kernel/irq/ipi.c
 
* Low Level (Arm 아키텍처) 코드 위치
 
   arch/arm64/kernel/smp.c
   drivers/irqchip/irq-gic-v3.c
 
IPI Call 요청 루틴(하이 레벨)
 
아래 함수는 Qualcomm Watchdog 드라이버에 존재하는 함수인데요.
 
static void ping_other_cpus(struct msm_watchdog_data *wdog_dd)
{
int cpu;
 
cpumask_clear(&wdog_dd->alive_mask);
/* Make sure alive mask is cleared and set in order */
smp_mb();
for_each_cpu(cpu, cpu_online_mask) {
if (!cpu_idle_pc_state[cpu] && !cpu_isolated(cpu)) {
wdog_dd->ping_start[cpu] = sched_clock();
smp_call_function_single(cpu, keep_alive_response,
 wdog_dd, 1);
}
}
}
 
온라인 상태에 있는 CPU 코어에게 smp_call_function_single() 함수를 호출해 IPI Call을 요청하는 루틴입니다. 여기서 지정된 CPU 코어가 IPI Call을 받으면 콜백으로 지정된 keep_alive_response() 함수가 호출되는데요. keep_alive_response() 함수에서 "난 지금 정상 동작한다"라는 정보를 써줍니다.
 
smp_call_function_single() 함수의 구현부는 다음과 같습니다.
 
int smp_call_function_single(int cpu, smp_call_func_t func, void *info,
     int wait)
{
call_single_data_t *csd;
call_single_data_t csd_stack = {
.node = { .u_flags = CSD_FLAG_LOCK | CSD_TYPE_SYNC, },
};
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);
 
/*
 * When @wait we can deadlock when we interrupt between llist_add() and
 * arch_send_call_function_ipi*(); when !@wait we can deadlock due to
 * csd_lock() on because the interrupt context uses the same csd
 * storage.
 */
WARN_ON_ONCE(!in_task());
 
csd = &csd_stack;
if (!wait) {
csd = this_cpu_ptr(&csd_data);
csd_lock(csd);
}
 
csd->func = func;
csd->info = info;
#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG
csd->node.src = smp_processor_id();
csd->node.dst = cpu;
#endif
 
err = generic_exec_single(cpu, csd);
 
if (wait)
csd_lock_wait(csd);
 
put_cpu();
 
return err;
}
 
call_single_data_t 자료구조에 IPI Call과 관련된 정보를 써주고 generic_exec_single() 함수를 호출합니다.
 
generic_exec_single() 함수의 구현부는 다음과 같습니다.
 
static int generic_exec_single(int cpu, struct __call_single_data *csd)
{
if (cpu == smp_processor_id()) {
smp_call_func_t func = csd->func;
void *info = csd->info;
unsigned long flags;
 
/*
 * We can unlock early even for the synchronous on-stack case,
 * since we're doing this from the same CPU..
 */
csd_lock_record(csd);
csd_unlock(csd);
local_irq_save(flags);
func(info);
csd_lock_record(NULL);
local_irq_restore(flags);
return 0;
}
 
if ((unsigned)cpu >= nr_cpu_ids || !cpu_online(cpu)) {
csd_unlock(csd);
return -ENXIO;
}
 
__smp_call_single_queue(cpu, &csd->node.llist);
 
return 0;
}
 
코드 앞 부분에 자신(CPU)에게 IPI Call을 전달하는 경우의 예외 처리 루틴이 보입니다.
이 루틴을 제외하면 __smp_call_single_queue() 함수를 호출합니다.
 
__smp_call_single_queue() 함수의 구현부는 다음과 같습니다.
 
void __smp_call_single_queue(int cpu, struct llist_node *node)
{
#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG
if (static_branch_unlikely(&csdlock_debug_extended)) {
unsigned int type;
 
type = CSD_TYPE(container_of(node, call_single_data_t,
     node.llist));
if (type == CSD_TYPE_SYNC || type == CSD_TYPE_ASYNC) {
__smp_call_single_queue_debug(cpu, node);
return;
}
}
#endif
 
/*
 * 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(node, &per_cpu(call_single_queue, cpu)))
send_call_function_single_ipi(cpu);
}
 
IPI Call을 관리하는 call_single_queue에 Enqueue을 한 다음에 send_call_function_single_ipi() 함수를 호출합니다.
 
이어서 send_call_function_single_ipi() 함수의 구현부를 보겠습니다.
 
void send_call_function_single_ipi(int cpu)
{
struct rq *rq = cpu_rq(cpu);
 
if (!set_nr_if_polling(rq->idle))
arch_send_call_function_single_ipi(cpu);
else
trace_sched_wake_idle_without_ipi(cpu);
}
 
아이들 프로세스를 처리하는 상황이 아니면 arch_send_call_function_single_ipi() 함수를 호출합니다.
 
arch_send_call_function_single_ipi() 함수의 구현부를 보겠습니다.
 
void arch_send_call_function_single_ipi(int cpu)
{
smp_cross_call(cpumask_of(cpu), IPI_CALL_FUNC);
}
 
코드의 위치가 'arch/arm64/kernel/smp.c'이므로 Arm64 아키텍처에 의존적인 루틴이라는
점을 알 수 있습니다.
 
smp_cross_call() 함수를 보겠습니다.
 
01 static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
02 {
03 trace_ipi_raise(target, ipi_types[ipinr]);
04 __ipi_send_mask(ipi_desc[ipinr], target);
05 }
 
03번째 줄에서는 ftrace로 ipi_raise 이벤트 메시지를 출력합니다.
이어서 04번째 줄에서 __ipi_send_mask() 함수를 호출합니다.
 
__ipi_send_mask() 함수의 구현부를 보겠습니다.
 
int __ipi_send_mask(struct irq_desc *desc, const struct cpumask *dest)
{
struct irq_data *data = irq_desc_get_irq_data(desc);
struct irq_chip *chip = irq_data_get_irq_chip(data);
unsigned int cpu;
...
if (chip->ipi_send_mask) {
chip->ipi_send_mask(data, dest);
return 0;
}
 
if (irq_domain_is_ipi_per_cpu(data->domain)) {
unsigned int base = data->irq;
 
for_each_cpu(cpu, dest) {
unsigned irq = base + cpu - data->common->ipi_offset;
 
data = irq_get_irq_data(irq);
chip->ipi_send_single(data, cpu);
}
} else {
for_each_cpu(cpu, dest)
chip->ipi_send_single(data, cpu);
}
return 0;
}
 
'chip->ipi_send_mask' 구문을 실행해 함수 포인터를 실행합니다.
여기서 함수를 분석하다가 포기하는 경우가 많은데요. 
 
조금 더 면밀히 코드를 분석하거나 램덤프를 분석하면 다음과 같은 사실을 알 수 있습니다.
 
    "chip->ipi_send_mask를 통해 gic_ipi_send_mask() 함수가 호출된다"
 
GICv3으로 커널 컨피그를 설정하면 아래와 같이 정의된 gic_chip 전역 변수가 
인터럽트 디스크립터로 등록됩니다.
 
static struct irq_chip gic_chip = {
    .name           = "GICv3",
    .irq_mask       = gic_mask_irq,
    .irq_unmask     = gic_unmask_irq,
    .irq_eoi        = gic_eoi_irq,
    .irq_set_type       = gic_set_type,
    .irq_set_affinity   = gic_set_affinity,
    .irq_retrigger          = gic_retrigger,
    .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
    .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
    .irq_nmi_setup      = gic_irq_nmi_setup,
    .irq_nmi_teardown   = gic_irq_nmi_teardown,
    .ipi_send_mask      = gic_ipi_send_mask,
    .flags          = IRQCHIP_SET_TYPE_MASKED |
                  IRQCHIP_SKIP_SET_WAKE |
                  IRQCHIP_MASK_ON_SUSPEND,
};

+ Recent posts