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 | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | GIC SGI와 logical CPU affinity encoding | MPIDR affinity encoding이나 cpu logical map이 틀리면 다른 PE에 SGI가 가거나 target이 영원히 응답하지 않는다. | SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다. |
| x86-64 | local APIC ICR 또는 x2APIC MSR vector delivery | offline 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-V | SBI send_ipi, supervisor software interrupt 또는 IMSIC IPI | hart 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
세 그림으로 먼저 읽기
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을 확인한다.
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개 줄에 각각 설명을 붙였습니다.
__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를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Main handler for inter-processor interruptsLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
static void do_handle_IPI(int ipinr)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if ((unsigned)ipinr < NR_IPI)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
trace_ipi_entry(ipi_types[ipinr]);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (ipinr) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case IPI_RESCHEDULE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
scheduler_ipi();need_resched, nohz wakeup 같은 scheduler 요청을 target CPU에서 처리한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case IPI_CALL_FUNC:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
generic_smp_call_function_interrupt();shared call-function queue를 꺼내 등록된 callback과 argument를 실행한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case IPI_CPU_STOP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case IPI_CPU_STOP_NMI:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
if (IS_ENABLED(CONFIG_KEXEC_CORE) && crash_stop) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
ipi_cpu_crash_stop(cpu, get_irq_regs());helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
unreachable();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
} else {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
local_cpu_stop(cpu);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCASTKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
case IPI_TIMER:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
tick_receive_broadcast();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_IRQ_WORKKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
case IPI_IRQ_WORK:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
irq_work_run();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case IPI_CPU_BACKTRACE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* NOTE: in some cases this _won't_ be NMI context. See theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* comment in arch_trigger_cpumask_backtrace().Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
nmi_cpu_backtrace(get_irq_regs());helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case IPI_KGDB_ROUNDUP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
kgdb_nmicallback(cpu, get_irq_regs());helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
default:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if ((unsigned)ipinr < NR_IPI)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
trace_ipi_exit(ipi_types[ipinr]);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static irqreturn_t ipi_handler(int irq, void *data)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int ipi = (irq - ipi_irq_base) % nr_ipi;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
do_handle_IPI(ipi);최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.
return IRQ_HANDLED;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
trace_ipi_raise(target, ipi_types[ipinr]);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
arm64_send_ipi(target, ipinr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static bool ipi_should_be_nmi(enum ipi_msg_type ipi)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (!system_uses_irq_prio_masking())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (ipi) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case IPI_CPU_STOP_NMI:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case IPI_CPU_BACKTRACE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case IPI_KGDB_ROUNDUP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
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개 줄에 각각 설명을 붙였습니다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
apic_eoi();local APIC의 in-service bit를 내려 같은 또는 낮은 priority vector가 다시 전달될 수 있게 한다.
trace_reschedule_entry(RESCHEDULE_VECTOR);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
inc_irq_stat(irq_resched_count);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
scheduler_ipi();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
trace_reschedule_exit(RESCHEDULE_VECTOR);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
DEFINE_IDTENTRY_SYSVEC(sysvec_call_function)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
apic_eoi();local APIC의 in-service bit를 내려 같은 또는 낮은 priority vector가 다시 전달될 수 있게 한다.
trace_call_function_entry(CALL_FUNCTION_VECTOR);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
inc_irq_stat(irq_call_count);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
generic_smp_call_function_interrupt();target CPU의 shared call queue를 처리하고 각 callback 완료를 publish한다.
trace_call_function_exit(CALL_FUNCTION_VECTOR);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
DEFINE_IDTENTRY_SYSVEC(sysvec_call_function_single)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
apic_eoi();local APIC의 in-service bit를 내려 같은 또는 낮은 priority vector가 다시 전달될 수 있게 한다.
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를 연결해 본다.
inc_irq_stat(irq_call_count);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
generic_smp_call_function_single_interrupt();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
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를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int __init nonmi_ipi_setup(char *str)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
smp_no_nmi_ipi = true;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
return 1;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
__setup("nonmi_ipi", nonmi_ipi_setup);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
struct smp_ops smp_ops = {계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.smp_prepare_boot_cpu = native_smp_prepare_boot_cpu,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.smp_prepare_cpus = native_smp_prepare_cpus,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.smp_cpus_done = native_smp_cpus_done,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.stop_other_cpus = native_stop_other_cpus,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#if defined(CONFIG_CRASH_DUMP)Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
.crash_stop_other_cpus = kdump_nmi_shootdown_cpus,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
.smp_send_reschedule = native_smp_send_reschedule,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.kick_ap_alive = native_kick_ap,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.cpu_disable = native_cpu_disable,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.play_dead = native_play_dead,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.send_call_func_ipi = native_send_call_func_ipi,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.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개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_IRQ_WORKKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
void arch_irq_work_raise(void)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
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를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static irqreturn_t handle_IPI(int irq, void *data)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
int ipi = irq - ipi_virq_base;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (ipi) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case IPI_RESCHEDULE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
scheduler_ipi();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case IPI_CALL_FUNC:공통 SMP call queue를 target hart에서 실행하는 reason이다.
generic_smp_call_function_interrupt();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case IPI_CPU_STOP:target hart를 멈추고 crash/stop synchronization에 참여시킨다.
ipi_stop();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case IPI_CPU_CRASH_STOP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
ipi_cpu_crash_stop(cpu, get_irq_regs());helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case IPI_IRQ_WORK:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
irq_work_run();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCASTKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
case IPI_TIMER:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
tick_receive_broadcast();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
case IPI_CPU_BACKTRACE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
nmi_cpu_backtrace(get_irq_regs());helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case IPI_KGDB_ROUNDUP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
kgdb_nmicallback(cpu, get_irq_regs());helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
default:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return IRQ_HANDLED;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void riscv_ipi_enable(void)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int i;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (WARN_ON_ONCE(!ipi_virq_base))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < nr_ipi; i++)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
enable_percpu_irq(ipi_virq_base + i, 0);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void riscv_ipi_disable(void)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int i;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (WARN_ON_ONCE(!ipi_virq_base))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < nr_ipi; i++)range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
disable_percpu_irq(ipi_virq_base + i);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
bool riscv_ipi_have_virq_range(void)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return (ipi_virq_base) ? true : false;이 함수가 IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V IPI와 SMP call: SGI, APIC ICR와 SBI/IMSIC 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void riscv_ipi_set_virq_range(int virq, int nr)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
05 · WORKED EXAMPLE
숫자로 검산하기
동기식 8-CPU call-function의 upper bound
7개 target에 callback을 보내며 6개는 3us 안에 처리하지만 한 CPU가 700us 동안 IRQ를 막는다고 가정한다.
- publicationsender가 7개 queue node를 연결하고 target mask를 만든 뒤 architecture doorbell을 보낸다.
- parallelism빠른 6개 target의 완료는 전체 대기 시간을 18us로 합산시키지 않는다. 병렬 처리이므로 가장 느린 target이 지배한다.
- tail마지막 CPU의 700us irq-off 구간 뒤 IPI entry와 callback 3us가 더해져 약 703us 이상이 된다.
- deadlocksender가 target이 잡으려는 lock을 쥔 채 wait하면 703us가 아니라 무한 대기가 된다.
결론동기 SMP 호출의 upper bound는 평균 IPI latency가 아니라 가장 느린 target의 interrupt-off 시간과 lock dependency로 계산한다.
06 · DEEP DIVE
경계별 상세 분석
공통 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 조건을 점검해야 한다.
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를 확인한다.
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를 본다.
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을 확인한다.
객체 수명과 소유권을 먼저 고정한다
stack에 둔 call_single_data와 argument는 target이 callback을 끝내고 ack할 때까지 살아 있어야 한다. async request는 embedded node나 refcounted object를 사용한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
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
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | MPIDR affinity encoding이나 cpu logical map이 틀리면 다른 PE에 SGI가 가거나 target이 영원히 응답하지 않는다. | SGI INTID, target MPIDR, ipi reason counter, call_single queue와 completion cpumask를 확인한다. |
| x86-64 | offline 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-V | hart 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
재현과 계측 절차
- 각 CPU에 의도적으로 다른 irq-off 시간을 넣고
smp_call_function_many()completion 분포를 측정한다. - CPU hotplug와 동시에 IPI를 보내 logical mask에서 offline CPU가 제거되는 지점을 추적한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/smp.c:950-1040
- x86-64arch/x86/kernel/smp.c:249-299
- RISC-Varch/riscv/kernel/smp.c:124-200
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.