← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 10/21

IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC

한 CPU가 다른 CPU에 reschedule, call-function, TLB flush, stop 요청을 보내고 완료를 기다리는 과정을 세 architecture의 mailbox와 interrupt mechanism으로 분석합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 219 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

01 · QUESTION

무엇을 확인할 것인가

IPI payload는 hardware가 운반하는가, shared memory에 놓이는가? target CPU가 요청을 보았다고 판단하는 completion point는 어디인가?

대부분 IPI hardware는 작은 vector/reason만 전달하고 실제 function pointer와 argument는 per-CPU call_single_data queue에 저장된다. sender는 queue publication 뒤 interrupt를 보내고 receiver는 acquire 조건으로 queue를 비운다.

shared queue publication, hardware doorbell, receiver hardirq, callback 실행과 completion ack를 분리한다. synchronous smp_call_function은 callback이 끝날 때까지 기다릴 수 있으므로 atomic context와 deadlock 조건을 점검해야 한다.

지연 시간 관점Tipi = Tqueue + Tdoorbell + Tinterrupt_off_target + Thandler + Tcallback. target이 IRQ를 오래 막으면 sender의 synchronous wait도 같은 시간만큼 늘어난다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64GIC SGI와 logical CPU affinity encodingMPIDR affinity encoding이나 cpu logical map이 틀리면 다른 PE에 SGI가 가거나 target이 영원히 응답하지 않는다.SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다.
x86-64local APIC ICR 또는 x2APIC MSR vector deliveryoffline CPU나 stale APIC ID를 target으로 삼으면 sender가 completion을 기다리며 멈춘다. vector 충돌은 잘못된 system handler 호출로 이어진다.APIC ID, ICR destination/vector, vector statistics, target irq-off duration과 call queue head를 본다.
RISC-VSBI send_ipi, supervisor software interrupt 또는 IMSIC IPIhart mask 변환 오류, SSIP clear와 새 bit set의 race 또는 firmware latency가 IPI loss와 긴 stall을 만든다.logical CPU-hart map, SBI return, SIP.SSIP, ipi_data.bits, IMSIC EIID와 target callback completion을 확인한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

arm64

mechanism
GIC SGI와 logical CPU affinity encoding
state
sender가 target affinity를 ICC_SGI1R_EL1에 써 SGI를 보내고 do_handle_IPI()가 IPI_RESCHEDULE, CALL_FUNC, CPU_STOP, TIMER 등 reason을 분기한다. payload queue는 cache-coherent memory에 있다.
checkpoint
SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다.

x86-64

mechanism
local APIC ICR 또는 x2APIC MSR vector delivery
state
sender는 destination APIC ID와 vector를 ICR에 기록한다. target의 system-vector IDT handler가 call-function 또는 reschedule 코드를 실행하고 per-CPU queue를 비운다.
checkpoint
APIC ID, ICR destination/vector, vector statistics, target irq-off duration과 call queue head를 본다.

RISC-V

mechanism
SBI send_ipi, supervisor software interrupt 또는 IMSIC IPI
state
sender는 hart mask를 firmware SBI에 넘기거나 IMSIC interrupt file에 doorbell을 쓴다. receiver는 supervisor software interrupt pending을 clear한 뒤 per-CPU ipi_data.bits를 읽어 여러 reason을 한 번에 처리한다.
checkpoint
logical CPU-hart map, SBI return, SIP.SSIP, ipi_data.bits, IMSIC EIID와 target callback completion을 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contract대부분 IPI hardware는 작은 vector/reason만 전달하고 실제 function pointer와 argument는 per-CPU call_single_data queue에 저장된다. sender는 queue publication 뒤 interrupt를 보내고 receiver는 acquire 조건으로 queue를 비운다.
arm64GIC SGI와 logical CPU affinity encodingqueue write가 SGI MMIO/system-register write보다 먼저 보이도록 barrier가 필요하고 receiver는 interrupt entry 뒤 queue를 읽는다.
x86-64local APIC ICR 또는 x2APIC MSR vector deliveryICR delivery status, queue store ordering과 APIC EOI가 맞아야 다음 IPI가 정상 전달된다. NMI-based stop은 일반 maskable IPI와 별도 경계다.
RISC-VSBI send_ipi, supervisor software interrupt 또는 IMSIC IPIatomic bit set이 doorbell보다 먼저 보이고 receiver의 atomic exchange가 reason을 잃지 않아야 한다.
lifetime boundarystack에 둔 call_single_data와 argument는 target이 callback을 끝내고 ack할 때까지 살아 있어야 한다. async request는 embedded node나 refcounted object를 사용한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비queue write가 SGI MMIO/system-register write보다 먼저 보이도록 barrier가 필요하고 receiver는 interrupt entry 뒤 queue를 읽는다.관찰: SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다.
x86-64state 준비ICR delivery status, queue store ordering과 APIC EOI가 맞아야 다음 IPI가 정상 전달된다. NMI-based stop은 일반 maskable IPI와 별도 경계다.관찰: APIC ID, ICR destination/vector, vector statistics, target irq-off duration과 call queue head를 본다.
RISC-Vstate 준비atomic bit set이 doorbell보다 먼저 보이고 receiver의 atomic exchange가 reason을 잃지 않아야 한다.관찰: logical CPU-hart map, SBI return, SIP.SSIP, ipi_data.bits, IMSIC EIID와 target callback completion을 확인한다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.

arm64 · Linux 6.18.37

GIC SGI와 logical CPU affinity encoding

sender가 target affinity를 ICC_SGI1R_EL1에 써 SGI를 보내고 do_handle_IPI()가 IPI_RESCHEDULE, CALL_FUNC, CPU_STOP, TIMER 등 reason을 분기한다. payload queue는 cache-coherent memory에 있다.

원본 코드: arch/arm64/kernel/smp.c:950-1040

950		__ipi_send_single(get_ipi_desc(cpu, IPI_KGDB_ROUNDUP), cpu);
951	}
952}
953#endif
954 
955/*
956 * Main handler for inter-processor interrupts
957 */
958static void do_handle_IPI(int ipinr)
959{
960	unsigned int cpu = smp_processor_id();
961 
962	if ((unsigned)ipinr < NR_IPI)
963		trace_ipi_entry(ipi_types[ipinr]);
964 
965	switch (ipinr) {
966	case IPI_RESCHEDULE:
967		scheduler_ipi();
968		break;
969 
970	case IPI_CALL_FUNC:
971		generic_smp_call_function_interrupt();
972		break;
973 
974	case IPI_CPU_STOP:
975	case IPI_CPU_STOP_NMI:
976		if (IS_ENABLED(CONFIG_KEXEC_CORE) && crash_stop) {
977			ipi_cpu_crash_stop(cpu, get_irq_regs());
978			unreachable();
979		} else {
980			local_cpu_stop(cpu);
981		}
982		break;
983 
984#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
985	case IPI_TIMER:
986		tick_receive_broadcast();
987		break;
988#endif
989 
990#ifdef CONFIG_IRQ_WORK
991	case IPI_IRQ_WORK:
992		irq_work_run();
993		break;
994#endif
995 
996	case IPI_CPU_BACKTRACE:
997		/*
998		 * NOTE: in some cases this _won't_ be NMI context. See the
999		 * comment in arch_trigger_cpumask_backtrace().
1000		 */
1001		nmi_cpu_backtrace(get_irq_regs());
1002		break;
1003 
1004	case IPI_KGDB_ROUNDUP:
1005		kgdb_nmicallback(cpu, get_irq_regs());
1006		break;
1007 
1008	default:
1009		pr_crit("CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
1010		break;
1011	}
1012 
1013	if ((unsigned)ipinr < NR_IPI)
1014		trace_ipi_exit(ipi_types[ipinr]);
1015}
1016 
1017static irqreturn_t ipi_handler(int irq, void *data)
1018{
1019	unsigned int ipi = (irq - ipi_irq_base) % nr_ipi;
1020 
1021	do_handle_IPI(ipi);
1022	return IRQ_HANDLED;
1023}
1024 
1025static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
1026{
1027	trace_ipi_raise(target, ipi_types[ipinr]);
1028	arm64_send_ipi(target, ipinr);
1029}
1030 
1031static bool ipi_should_be_nmi(enum ipi_msg_type ipi)
1032{
1033	if (!system_uses_irq_prio_masking())
1034		return false;
1035 
1036	switch (ipi) {
1037	case IPI_CPU_STOP_NMI:
1038	case IPI_CPU_BACKTRACE:
1039	case IPI_KGDB_ROUNDUP:
1040		return true;

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 91개 줄에 각각 설명을 붙였습니다.

L950 __ipi_send_single(get_ipi_desc(cpu, IPI_KGDB_ROUNDUP), cpu);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L951 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L952}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L953#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L954(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L955/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L956 * Main handler for inter-processor interrupts

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L957 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L958static void do_handle_IPI(int ipinr)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L959{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L960 unsigned int cpu = smp_processor_id();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L961(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L962 if ((unsigned)ipinr < NR_IPI)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L963 trace_ipi_entry(ipi_types[ipinr]);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L964(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L965 switch (ipinr) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L966 case IPI_RESCHEDULE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L967 scheduler_ipi();

need_resched, nohz wakeup 같은 scheduler 요청을 target CPU에서 처리한다.

L968 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L969(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L970 case IPI_CALL_FUNC:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L971 generic_smp_call_function_interrupt();

shared call-function queue를 꺼내 등록된 callback과 argument를 실행한다.

L972 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L973(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L974 case IPI_CPU_STOP:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L975 case IPI_CPU_STOP_NMI:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L976 if (IS_ENABLED(CONFIG_KEXEC_CORE) && crash_stop) {

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L977 ipi_cpu_crash_stop(cpu, get_irq_regs());

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L978 unreachable();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L979 } else {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L980 local_cpu_stop(cpu);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L981 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L982 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L983(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L984#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L985 case IPI_TIMER:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L986 tick_receive_broadcast();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L987 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L988#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L989(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L990#ifdef CONFIG_IRQ_WORK

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L991 case IPI_IRQ_WORK:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L992 irq_work_run();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L993 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L994#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L995(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L996 case IPI_CPU_BACKTRACE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L997 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L998 * NOTE: in some cases this _won't_ be NMI context. See the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L999 * comment in arch_trigger_cpumask_backtrace().

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1000 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1001 nmi_cpu_backtrace(get_irq_regs());

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1002 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1003(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1004 case IPI_KGDB_ROUNDUP:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1005 kgdb_nmicallback(cpu, get_irq_regs());

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1006 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1007(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1008 default:

분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.

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

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1010 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L1011 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1012(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1013 if ((unsigned)ipinr < NR_IPI)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1014 trace_ipi_exit(ipi_types[ipinr]);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1015}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1016(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1017static irqreturn_t ipi_handler(int irq, void *data)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1018{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1019 unsigned int ipi = (irq - ipi_irq_base) % nr_ipi;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1020(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1021 do_handle_IPI(ipi);

최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.

L1022 return IRQ_HANDLED;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1023}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1024(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1025static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1026{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1027 trace_ipi_raise(target, ipi_types[ipinr]);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1028 arm64_send_ipi(target, ipinr);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1029}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1030(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1031static bool ipi_should_be_nmi(enum ipi_msg_type ipi)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1032{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1033 if (!system_uses_irq_prio_masking())

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1034 return false;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1035(blank)

빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1036 switch (ipi) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1037 case IPI_CPU_STOP_NMI:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1038 case IPI_CPU_BACKTRACE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1039 case IPI_KGDB_ROUNDUP:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L1040 return true;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

x86-64 · Linux 6.18.37

local APIC ICR 또는 x2APIC MSR vector delivery

sender는 destination APIC ID와 vector를 ICR에 기록한다. target의 system-vector IDT handler가 call-function 또는 reschedule 코드를 실행하고 per-CPU queue를 비운다.

원본 코드: arch/x86/kernel/smp.c:249-299

249{
250	apic_eoi();
251	trace_reschedule_entry(RESCHEDULE_VECTOR);
252	inc_irq_stat(irq_resched_count);
253	scheduler_ipi();
254	trace_reschedule_exit(RESCHEDULE_VECTOR);
255}
256 
257DEFINE_IDTENTRY_SYSVEC(sysvec_call_function)
258{
259	apic_eoi();
260	trace_call_function_entry(CALL_FUNCTION_VECTOR);
261	inc_irq_stat(irq_call_count);
262	generic_smp_call_function_interrupt();
263	trace_call_function_exit(CALL_FUNCTION_VECTOR);
264}
265 
266DEFINE_IDTENTRY_SYSVEC(sysvec_call_function_single)
267{
268	apic_eoi();
269	trace_call_function_single_entry(CALL_FUNCTION_SINGLE_VECTOR);
270	inc_irq_stat(irq_call_count);
271	generic_smp_call_function_single_interrupt();
272	trace_call_function_single_exit(CALL_FUNCTION_SINGLE_VECTOR);
273}
274 
275static int __init nonmi_ipi_setup(char *str)
276{
277	smp_no_nmi_ipi = true;
278	return 1;
279}
280 
281__setup("nonmi_ipi", nonmi_ipi_setup);
282 
283struct smp_ops smp_ops = {
284	.smp_prepare_boot_cpu	= native_smp_prepare_boot_cpu,
285	.smp_prepare_cpus	= native_smp_prepare_cpus,
286	.smp_cpus_done		= native_smp_cpus_done,
287 
288	.stop_other_cpus	= native_stop_other_cpus,
289#if defined(CONFIG_CRASH_DUMP)
290	.crash_stop_other_cpus	= kdump_nmi_shootdown_cpus,
291#endif
292	.smp_send_reschedule	= native_smp_send_reschedule,
293 
294	.kick_ap_alive		= native_kick_ap,
295	.cpu_disable		= native_cpu_disable,
296	.play_dead		= native_play_dead,
297 
298	.send_call_func_ipi	= native_send_call_func_ipi,
299	.send_call_func_single_ipi = native_send_call_func_single_ipi,

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 51개 줄에 각각 설명을 붙였습니다.

L249{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L250 apic_eoi();

local APIC의 in-service bit를 내려 같은 또는 낮은 priority vector가 다시 전달될 수 있게 한다.

L251 trace_reschedule_entry(RESCHEDULE_VECTOR);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L252 inc_irq_stat(irq_resched_count);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L253 scheduler_ipi();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L254 trace_reschedule_exit(RESCHEDULE_VECTOR);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L255}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L256(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L257DEFINE_IDTENTRY_SYSVEC(sysvec_call_function)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L258{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L259 apic_eoi();

local APIC의 in-service bit를 내려 같은 또는 낮은 priority vector가 다시 전달될 수 있게 한다.

L260 trace_call_function_entry(CALL_FUNCTION_VECTOR);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L261 inc_irq_stat(irq_call_count);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L262 generic_smp_call_function_interrupt();

target CPU의 shared call queue를 처리하고 각 callback 완료를 publish한다.

L263 trace_call_function_exit(CALL_FUNCTION_VECTOR);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L264}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L265(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L266DEFINE_IDTENTRY_SYSVEC(sysvec_call_function_single)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L267{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L268 apic_eoi();

local APIC의 in-service bit를 내려 같은 또는 낮은 priority vector가 다시 전달될 수 있게 한다.

L269 trace_call_function_single_entry(CALL_FUNCTION_SINGLE_VECTOR);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L270 inc_irq_stat(irq_call_count);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L271 generic_smp_call_function_single_interrupt();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L272 trace_call_function_single_exit(CALL_FUNCTION_SINGLE_VECTOR);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L273}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L274(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L275static int __init nonmi_ipi_setup(char *str)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L276{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L277 smp_no_nmi_ipi = true;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L278 return 1;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L279}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L280(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L281__setup("nonmi_ipi", nonmi_ipi_setup);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L282(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L283struct smp_ops smp_ops = {

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L284 .smp_prepare_boot_cpu = native_smp_prepare_boot_cpu,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L285 .smp_prepare_cpus = native_smp_prepare_cpus,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L286 .smp_cpus_done = native_smp_cpus_done,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L287(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L288 .stop_other_cpus = native_stop_other_cpus,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L289#if defined(CONFIG_CRASH_DUMP)

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L290 .crash_stop_other_cpus = kdump_nmi_shootdown_cpus,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L291#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L292 .smp_send_reschedule = native_smp_send_reschedule,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L293(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L294 .kick_ap_alive = native_kick_ap,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L295 .cpu_disable = native_cpu_disable,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L296 .play_dead = native_play_dead,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L297(blank)

빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L298 .send_call_func_ipi = native_send_call_func_ipi,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L299 .send_call_func_single_ipi = native_send_call_func_single_ipi,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

RISC-V · Linux 6.18.37

SBI send_ipi, supervisor software interrupt 또는 IMSIC IPI

sender는 hart mask를 firmware SBI에 넘기거나 IMSIC interrupt file에 doorbell을 쓴다. receiver는 supervisor software interrupt pending을 clear한 뒤 per-CPU ipi_data.bits를 읽어 여러 reason을 한 번에 처리한다.

원본 코드: arch/riscv/kernel/smp.c:124-200

124 
125#ifdef CONFIG_IRQ_WORK
126void arch_irq_work_raise(void)
127{
128	send_ipi_single(smp_processor_id(), IPI_IRQ_WORK);
129}
130#endif
131 
132static irqreturn_t handle_IPI(int irq, void *data)
133{
134	unsigned int cpu = smp_processor_id();
135	int ipi = irq - ipi_virq_base;
136 
137	switch (ipi) {
138	case IPI_RESCHEDULE:
139		scheduler_ipi();
140		break;
141	case IPI_CALL_FUNC:
142		generic_smp_call_function_interrupt();
143		break;
144	case IPI_CPU_STOP:
145		ipi_stop();
146		break;
147	case IPI_CPU_CRASH_STOP:
148		ipi_cpu_crash_stop(cpu, get_irq_regs());
149		break;
150	case IPI_IRQ_WORK:
151		irq_work_run();
152		break;
153#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
154	case IPI_TIMER:
155		tick_receive_broadcast();
156		break;
157#endif
158	case IPI_CPU_BACKTRACE:
159		nmi_cpu_backtrace(get_irq_regs());
160		break;
161	case IPI_KGDB_ROUNDUP:
162		kgdb_nmicallback(cpu, get_irq_regs());
163		break;
164	default:
165		pr_warn("CPU%d: unhandled IPI%d\n", cpu, ipi);
166		break;
167	}
168 
169	return IRQ_HANDLED;
170}
171 
172void riscv_ipi_enable(void)
173{
174	int i;
175 
176	if (WARN_ON_ONCE(!ipi_virq_base))
177		return;
178 
179	for (i = 0; i < nr_ipi; i++)
180		enable_percpu_irq(ipi_virq_base + i, 0);
181}
182 
183void riscv_ipi_disable(void)
184{
185	int i;
186 
187	if (WARN_ON_ONCE(!ipi_virq_base))
188		return;
189 
190	for (i = 0; i < nr_ipi; i++)
191		disable_percpu_irq(ipi_virq_base + i);
192}
193 
194bool riscv_ipi_have_virq_range(void)
195{
196	return (ipi_virq_base) ? true : false;
197}
198 
199void riscv_ipi_set_virq_range(int virq, int nr)
200{

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 77개 줄에 각각 설명을 붙였습니다.

L124(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L125#ifdef CONFIG_IRQ_WORK

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L126void arch_irq_work_raise(void)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L127{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L128 send_ipi_single(smp_processor_id(), IPI_IRQ_WORK);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L129}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L130#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L131(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L132static irqreturn_t handle_IPI(int irq, void *data)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L133{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L134 unsigned int cpu = smp_processor_id();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L135 int ipi = irq - ipi_virq_base;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L136(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L137 switch (ipi) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L138 case IPI_RESCHEDULE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L139 scheduler_ipi();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L140 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L141 case IPI_CALL_FUNC:

공통 SMP call queue를 target hart에서 실행하는 reason이다.

L142 generic_smp_call_function_interrupt();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L143 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L144 case IPI_CPU_STOP:

target hart를 멈추고 crash/stop synchronization에 참여시킨다.

L145 ipi_stop();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L146 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L147 case IPI_CPU_CRASH_STOP:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L148 ipi_cpu_crash_stop(cpu, get_irq_regs());

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L149 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L150 case IPI_IRQ_WORK:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L151 irq_work_run();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L152 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L153#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L154 case IPI_TIMER:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L155 tick_receive_broadcast();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L156 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L157#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L158 case IPI_CPU_BACKTRACE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L159 nmi_cpu_backtrace(get_irq_regs());

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L160 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L161 case IPI_KGDB_ROUNDUP:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L162 kgdb_nmicallback(cpu, get_irq_regs());

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L163 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L164 default:

분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.

L165 pr_warn("CPU%d: unhandled IPI%d\n", cpu, ipi);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L166 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L167 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L168(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L169 return IRQ_HANDLED;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L170}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L171(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L172void riscv_ipi_enable(void)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L173{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L174 int i;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L175(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L176 if (WARN_ON_ONCE(!ipi_virq_base))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L177 return;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L178(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L179 for (i = 0; i < nr_ipi; i++)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L180 enable_percpu_irq(ipi_virq_base + i, 0);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L181}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L182(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L183void riscv_ipi_disable(void)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L184{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L185 int i;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L186(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L187 if (WARN_ON_ONCE(!ipi_virq_base))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L188 return;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L189(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L190 for (i = 0; i < nr_ipi; i++)

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L191 disable_percpu_irq(ipi_virq_base + i);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L192}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L193(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L194bool riscv_ipi_have_virq_range(void)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L195{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L196 return (ipi_virq_base) ? true : false;

이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L197}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L198(blank)

빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L199void riscv_ipi_set_virq_range(int virq, int nr)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L200{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

동기식 8-CPU call-function의 upper bound

7개 target에 callback을 보내며 6개는 3us 안에 처리하지만 한 CPU가 700us 동안 IRQ를 막는다고 가정한다.

  1. publicationsender가 7개 queue node를 연결하고 target mask를 만든 뒤 architecture doorbell을 보낸다.
  2. parallelism빠른 6개 target의 완료는 전체 대기 시간을 18us로 합산시키지 않는다. 병렬 처리이므로 가장 느린 target이 지배한다.
  3. tail마지막 CPU의 700us irq-off 구간 뒤 IPI entry와 callback 3us가 더해져 약 703us 이상이 된다.
  4. deadlocksender가 target이 잡으려는 lock을 쥔 채 wait하면 703us가 아니라 무한 대기가 된다.

결론동기 SMP 호출의 upper bound는 평균 IPI latency가 아니라 가장 느린 target의 interrupt-off 시간과 lock dependency로 계산한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

대부분 IPI hardware는 작은 vector/reason만 전달하고 실제 function pointer와 argument는 per-CPU call_single_data queue에 저장된다. sender는 queue publication 뒤 interrupt를 보내고 receiver는 acquire 조건으로 queue를 비운다.

shared queue publication, hardware doorbell, receiver hardirq, callback 실행과 completion ack를 분리한다. synchronous smp_call_function은 callback이 끝날 때까지 기다릴 수 있으므로 atomic context와 deadlock 조건을 점검해야 한다.

02

arm64: GIC SGI와 logical CPU affinity encoding

sender가 target affinity를 ICC_SGI1R_EL1에 써 SGI를 보내고 do_handle_IPI()가 IPI_RESCHEDULE, CALL_FUNC, CPU_STOP, TIMER 등 reason을 분기한다. payload queue는 cache-coherent memory에 있다.

queue write가 SGI MMIO/system-register write보다 먼저 보이도록 barrier가 필요하고 receiver는 interrupt entry 뒤 queue를 읽는다. 디버깅할 때는 SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다.

03

x86-64: local APIC ICR 또는 x2APIC MSR vector delivery

sender는 destination APIC ID와 vector를 ICR에 기록한다. target의 system-vector IDT handler가 call-function 또는 reschedule 코드를 실행하고 per-CPU queue를 비운다.

ICR delivery status, queue store ordering과 APIC EOI가 맞아야 다음 IPI가 정상 전달된다. NMI-based stop은 일반 maskable IPI와 별도 경계다. 디버깅할 때는 APIC ID, ICR destination/vector, vector statistics, target irq-off duration과 call queue head를 본다.

04

RISC-V: SBI send_ipi, supervisor software interrupt 또는 IMSIC IPI

sender는 hart mask를 firmware SBI에 넘기거나 IMSIC interrupt file에 doorbell을 쓴다. receiver는 supervisor software interrupt pending을 clear한 뒤 per-CPU ipi_data.bits를 읽어 여러 reason을 한 번에 처리한다.

atomic bit set이 doorbell보다 먼저 보이고 receiver의 atomic exchange가 reason을 잃지 않아야 한다. 디버깅할 때는 logical CPU-hart map, SBI return, SIP.SSIP, ipi_data.bits, IMSIC EIID와 target callback completion을 확인한다.

05

객체 수명과 소유권을 먼저 고정한다

stack에 둔 call_single_data와 argument는 target이 callback을 끝내고 ack할 때까지 살아 있어야 한다. async request는 embedded node나 refcounted object를 사용한다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

latency upper bound는 hardware instruction 하나가 아니다

Tipi = Tqueue + Tdoorbell + Tinterrupt_off_target + Thandler + Tcallback. target이 IRQ를 오래 막으면 sender의 synchronous wait도 같은 시간만큼 늘어난다.

평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.

07 · FAILURE

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64MPIDR affinity encoding이나 cpu logical map이 틀리면 다른 PE에 SGI가 가거나 target이 영원히 응답하지 않는다.SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다.
x86-64offline CPU나 stale APIC ID를 target으로 삼으면 sender가 completion을 기다리며 멈춘다. vector 충돌은 잘못된 system handler 호출로 이어진다.APIC ID, ICR destination/vector, vector statistics, target irq-off duration과 call queue head를 본다.
RISC-Vhart mask 변환 오류, SSIP clear와 새 bit set의 race 또는 firmware latency가 IPI loss와 긴 stall을 만든다.logical CPU-hart map, SBI return, SIP.SSIP, ipi_data.bits, IMSIC EIID와 target callback completion을 확인한다.

08 · LAB

재현과 계측 절차

  1. 각 CPU에 의도적으로 다른 irq-off 시간을 넣고 smp_call_function_many() completion 분포를 측정한다.
  2. CPU hotplug와 동시에 IPI를 보내 logical mask에서 offline CPU가 제거되는 지점을 추적한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.