QUESTION
같은 CPU 진입 코드처럼 보이는 exception, hardware IRQ, syscall과 secondary CPU startup은 어떤 frame과 동기화 규칙을 사용하는가?
x86의 exception과 hardware interrupt는 IDT gate를 통해 들어오지만 모든 vector가 같은 stack frame을 만들지는 않는다. CPU가 error code를 push하는 vector와 Linux stub가 가짜 error code 또는 vector를 push하는 경우를 맞춰야 공통 pt_regs layout을 만들 수 있다.
SYSCALL은 IDT gate가 아니다. CPU가 RCX와 R11에 복귀 RIP와 RFLAGS를 남기고 kernel stack으로 자동 전환하지 않으므로, entry_SYSCALL_64가 user RSP를 per-CPU TSS scratch에 보관하고 kernel CR3와 stack을 직접 선택한다.
secondary CPU는 runtime interrupt entry와도 다르다. BSP가 APIC ID, trampoline, idle stack, initial_code를 준비하고 INIT/SIPI 또는 64-bit wakeup을 보낸 뒤 AP가 secondary_startup_64와 start_secondary()를 통과한다. online flag는 이 준비가 끝난 뒤 게시된다.
ENTRY CONTRACT
들어오는 상태와 내보내는 상태
| 경계 | 입력 | 이 경계가 완성하는 상태 |
|---|---|---|
| IDT IRQ | vector와 hardware frame | common interrupt C handler |
| IDT exception | error-code 유무와 fault registers | exception-specific C handler |
| SYSCALL | RAX number, RCX RIP, R11 flags, user RSP | do_syscall_64()와 SYSRET/IRET |
| AP startup | APIC ID, trampoline, idle task stack | online secondary CPU |
ARCHITECTURE FIGURES
주소와 register를 먼저 그려 본다
SYSCALL instruction이 자동으로 만들지 않는 SS, RSP, RFLAGS, CS, RIP를 entry code가 kernel stack에 구성한다.
IDT exception / IRQ
SYSCALL
공통점은 C에 pt_regs *를 넘기는 것이지만 frame 생산자와 복귀 명령이 다르다.
wakeup signal을 보낸 시점과 scheduler가 CPU를 online으로 보는 시점 사이에는 여러 동기화 단계가 있다.
SOURCE 01
SYSCALL entry가 user context를 pt_regs로 바꾸는 구간
SYSCALL hardware contract의 빈 부분을 assembly가 채운다. 특히 user RSP 보관, kernel CR3, per-CPU stack 선택 순서는 바꿀 수 없다.
원본: arch/x86/entry/entry_64.S 87-150줄
87SYM_CODE_START(entry_SYSCALL_64)
88 UNWIND_HINT_ENTRY
89 ENDBR
90
91 swapgs
92 /* tss.sp2 is scratch space. */
93 movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
94 SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
95 movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
96
97SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
98 ANNOTATE_NOENDBR
99
100 /* Construct struct pt_regs on stack */
101 pushq $__USER_DS /* pt_regs->ss */
102 pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
103 pushq %r11 /* pt_regs->flags */
104 pushq $__USER_CS /* pt_regs->cs */
105 pushq %rcx /* pt_regs->ip */
106SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
107 pushq %rax /* pt_regs->orig_ax */
108
109 PUSH_AND_CLEAR_REGS rax=$-ENOSYS
110
111 /* IRQs are off. */
112 movq %rsp, %rdi
113 /* Sign extend the lower 32bit as syscall numbers are treated as int */
114 movslq %eax, %rsi
115
116 /* clobbers %rax, make sure it is after saving the syscall nr */
117 IBRS_ENTER
118 UNTRAIN_RET
119 CLEAR_BRANCH_HISTORY
120
121 call do_syscall_64 /* returns with IRQs disabled */
122
123 /*
124 * Try to use SYSRET instead of IRET if we're returning to
125 * a completely clean 64-bit userspace context. If we're not,
126 * go to the slow exit path.
127 * In the Xen PV case we must use iret anyway.
128 */
129
130 ALTERNATIVE "testb %al, %al; jz swapgs_restore_regs_and_return_to_usermode", \
131 "jmp swapgs_restore_regs_and_return_to_usermode", X86_FEATURE_XENPV
132
133 /*
134 * We win! This label is here just for ease of understanding
135 * perf profiles. Nothing jumps here.
136 */
137syscall_return_via_sysret:
138 IBRS_EXIT
139 POP_REGS pop_rdi=0
140
141 /*
142 * Now all regs are restored except RSP and RDI.
143 * Save old stack pointer and switch to trampoline stack.
144 */
145 movq %rsp, %rdi
146 movq PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
147 UNWIND_HINT_END_OF_STACK
148
149 pushq RSP-RDI(%rdi) /* RSP */
150 pushq (%rdi) /* RDI */87-150줄 주석
SYM_CODE_START(entry_SYSCALL_64)linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.
UNWIND_HINT_ENTRY이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
ENDBR이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
swapgsuser GS base와 kernel per-CPU GS base를 교환한다. 이후 PER_CPU_VAR 접근의 전제다.
/* tss.sp2 is scratch space. */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)SYSCALL이 kernel stack으로 자동 전환하지 않으므로 user RSP를 per-CPU scratch에 먼저 보관한다.
SWITCH_TO_KERNEL_CR3 scratch_reg=%rspKPTI가 활성화된 경우 user page table에서 kernel page table로 이동한다.
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.
ANNOTATE_NOENDBR이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* Construct struct pt_regs on stack */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
pushq $__USER_DS /* pt_regs->ss */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */SYSCALL이 kernel stack으로 자동 전환하지 않으므로 user RSP를 per-CPU scratch에 먼저 보관한다.
pushq %r11 /* pt_regs->flags */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
pushq $__USER_CS /* pt_regs->cs */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
pushq %rcx /* pt_regs->ip */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.
pushq %rax /* pt_regs->orig_ax */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
PUSH_AND_CLEAR_REGS rax=$-ENOSYS나머지 general-purpose register를 pt_regs 순서로 쌓고 지정한 register를 정리한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* IRQs are off. */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
movq %rsp, %rdi주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.
/* Sign extend the lower 32bit as syscall numbers are treated as int */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
movslq %eax, %rsi이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* clobbers %rax, make sure it is after saving the syscall nr */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
IBRS_ENTER이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
UNTRAIN_RET이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
CLEAR_BRANCH_HISTORY이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
call do_syscall_64 /* returns with IRQs disabled */C dispatcher에 pt_regs *와 sign-extended syscall number를 넘긴다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Try to use SYSRET instead of IRET if we're returning to복귀 context가 canonical address와 flag 제약을 만족할 때만 빠른 SYSRET 경로를 쓴다. 아니면 IRET 경로로 간다.
* a completely clean 64-bit userspace context. If we're not,원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* go to the slow exit path.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* In the Xen PV case we must use iret anyway.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
ALTERNATIVE "testb %al, %al; jz swapgs_restore_regs_and_return_to_usermode", \user GS base와 kernel per-CPU GS base를 교환한다. 이후 PER_CPU_VAR 접근의 전제다.
"jmp swapgs_restore_regs_and_return_to_usermode", X86_FEATURE_XENPVuser GS base와 kernel per-CPU GS base를 교환한다. 이후 PER_CPU_VAR 접근의 전제다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* We win! This label is here just for ease of understanding원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* perf profiles. Nothing jumps here.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
syscall_return_via_sysret:분기 대상 label이다. 이 지점에 들어올 수 있는 모든 선행 경로가 같은 register와 stack 조건을 만족하는지 비교한다.
IBRS_EXIT이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
POP_REGS pop_rdi=0이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Now all regs are restored except RSP and RDI.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Save old stack pointer and switch to trampoline stack.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
movq %rsp, %rdi주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.
movq PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.
UNWIND_HINT_END_OF_STACK이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
pushq RSP-RDI(%rdi) /* RSP */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
pushq (%rdi) /* RDI */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
SOURCE 02
IDT IRQ와 특수 exception entry macro
vector stub가 error-code slot을 정규화한 뒤 idtentry body로 들어간다. #MC와 #DB는 kernel의 임의 상태를 끊을 수 있어 paranoid entry를 사용한다.
원본: arch/x86/entry/entry_64.S 367-430줄
367/*
368 * Interrupt entry/exit.
369 *
370 + The interrupt stubs push (vector) onto the stack, which is the error_code
371 * position of idtentry exceptions, and jump to one of the two idtentry points
372 * (common/spurious).
373 *
374 * common_interrupt is a hotpath, align it to a cache line
375 */
376.macro idtentry_irq vector cfunc
377 .p2align CONFIG_X86_L1_CACHE_SHIFT
378 idtentry \vector asm_\cfunc \cfunc has_error_code=1
379.endm
380
381/**
382 * idtentry_mce_db - Macro to generate entry stubs for #MC and #DB
383 * @vector: Vector number
384 * @asmsym: ASM symbol for the entry point
385 * @cfunc: C function to be called
386 *
387 * The macro emits code to set up the kernel context for #MC and #DB
388 *
389 * If the entry comes from user space it uses the normal entry path
390 * including the return to user space work and preemption checks on
391 * exit.
392 *
393 * If hits in kernel mode then it needs to go through the paranoid
394 * entry as the exception can hit any random state. No preemption
395 * check on exit to keep the paranoid path simple.
396 */
397.macro idtentry_mce_db vector asmsym cfunc
398SYM_CODE_START(\asmsym)
399 UNWIND_HINT_IRET_ENTRY
400 ENDBR
401 ASM_CLAC
402 cld
403
404 pushq $-1 /* ORIG_RAX: no syscall to restart */
405
406 /*
407 * If the entry is from userspace, switch stacks and treat it as
408 * a normal entry.
409 */
410 testb $3, CS-ORIG_RAX(%rsp)
411 jnz .Lfrom_usermode_switch_stack_\@
412
413 /* paranoid_entry returns GS information for paranoid_exit in EBX. */
414 call paranoid_entry
415
416 UNWIND_HINT_REGS
417
418 movq %rsp, %rdi /* pt_regs pointer */
419
420 call \cfunc
421
422 jmp paranoid_exit
423
424 /* Switch to the regular task stack and use the noist entry point */
425.Lfrom_usermode_switch_stack_\@:
426 idtentry_body noist_\cfunc, has_error_code=0
427
428_ASM_NOKPROBE(\asmsym)
429SYM_CODE_END(\asmsym)
430.endm367-430줄 주석
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Interrupt entry/exit.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
+ The interrupt stubs push (vector) onto the stack, which is the error_code현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
* position of idtentry exceptions, and jump to one of the two idtentry points원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* (common/spurious).원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* common_interrupt is a hotpath, align it to a cache line원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
.macro idtentry_irq vector cfunc각 IRQ vector용 stub를 cache-line 정렬하고 공통 idtentry body로 연결하는 macro다.
.p2align CONFIG_X86_L1_CACHE_SHIFTassembler가 section, symbol 크기 또는 반복 생성을 제어하는 지시자다. 최종 ELF와 disassembly에서 결과를 확인한다.
idtentry \vector asm_\cfunc \cfunc has_error_code=1IRQ stub가 vector를 error-code 위치에 push했으므로 공통 코드에는 slot이 이미 있는 것으로 알린다.
.endmassembler가 section, symbol 크기 또는 반복 생성을 제어하는 지시자다. 최종 ELF와 disassembly에서 결과를 확인한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/**원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* idtentry_mce_db - Macro to generate entry stubs for #MC and #DB#MC와 #DB처럼 NMI에 가까운 조건을 처리하는 별도 진입 macro다.
* @vector: Vector number원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* @asmsym: ASM symbol for the entry point원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* @cfunc: C function to be called원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* The macro emits code to set up the kernel context for #MC and #DB원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* If the entry comes from user space it uses the normal entry path원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* including the return to user space work and preemption checks on원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* exit.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* If hits in kernel mode then it needs to go through the paranoid원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* entry as the exception can hit any random state. No preemption원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* check on exit to keep the paranoid path simple.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
.macro idtentry_mce_db vector asmsym cfunc#MC와 #DB처럼 NMI에 가까운 조건을 처리하는 별도 진입 macro다.
SYM_CODE_START(\asmsym)linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.
UNWIND_HINT_IRET_ENTRY이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
ENDBR이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
ASM_CLAC이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
cld문자열 명령의 방향을 증가 방향으로 고정한다. 펌웨어가 남긴 DF 상태를 C ABI가 기대하는 값으로 되돌리는 줄이다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
pushq $-1 /* ORIG_RAX: no syscall to restart */현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* If the entry is from userspace, switch stacks and treat it as원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* a normal entry.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
testb $3, CS-ORIG_RAX(%rsp)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
jnz .Lfrom_usermode_switch_stack_\@이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* paranoid_entry returns GS information for paranoid_exit in EBX. */GS와 context가 정상 kernel/user 경계에 있다는 가정을 버리고 안전하게 현재 상태를 판별한다.
call paranoid_entryGS와 context가 정상 kernel/user 경계에 있다는 가정을 버리고 안전하게 현재 상태를 판별한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
UNWIND_HINT_REGS이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
movq %rsp, %rdi /* pt_regs pointer */완성된 pt_regs frame pointer를 첫 C argument로 넘긴다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
call \cfunc하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
jmp paranoid_exit현재 함수로 돌아오지 않는 제어 이전이다. 다음 진입점이 요구하는 register와 주소 공간을 이 줄 직전에 모두 완성해야 한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* Switch to the regular task stack and use the noist entry point */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
.Lfrom_usermode_switch_stack_\@:assembler가 section, symbol 크기 또는 반복 생성을 제어하는 지시자다. 최종 ELF와 disassembly에서 결과를 확인한다.
idtentry_body noist_\cfunc, has_error_code=0이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
_ASM_NOKPROBE(\asmsym)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
SYM_CODE_END(\asmsym)linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.
.endmassembler가 section, symbol 크기 또는 반복 생성을 제어하는 지시자다. 최종 ELF와 disassembly에서 결과를 확인한다.
SOURCE 03
BSP의 do_boot_cpu와 AP의 start_secondary
control CPU가 AP가 실행할 trampoline과 C entry, idle stack을 게시하고 architecture wakeup callback을 호출한다.
원본: arch/x86/kernel/smpboot.c 842-900줄
842static int do_boot_cpu(u32 apicid, unsigned int cpu, struct task_struct *idle)
843{
844 unsigned long start_ip = real_mode_header->trampoline_start;
845 int ret;
846
847#ifdef CONFIG_X86_64
848 /* If 64-bit wakeup method exists, use the 64-bit mode trampoline IP */
849 if (apic->wakeup_secondary_cpu_64)
850 start_ip = real_mode_header->trampoline_start64;
851#endif
852 idle->thread.sp = (unsigned long)task_pt_regs(idle);
853 initial_code = (unsigned long)start_secondary;
854
855 if (IS_ENABLED(CONFIG_X86_32)) {
856 early_gdt_descr.address = (unsigned long)get_cpu_gdt_rw(cpu);
857 initial_stack = idle->thread.sp;
858 } else if (!(smpboot_control & STARTUP_PARALLEL_MASK)) {
859 smpboot_control = cpu;
860 }
861
862 /* Enable the espfix hack for this CPU */
863 init_espfix_ap(cpu);
864
865 /* So we see what's up */
866 announce_cpu(cpu, apicid);
867
868 /*
869 * This grunge runs the startup process for
870 * the targeted processor.
871 */
872 if (x86_platform.legacy.warm_reset) {
873
874 pr_debug("Setting warm reset code and vector.\n");
875
876 smpboot_setup_warm_reset_vector(start_ip);
877 /*
878 * Be paranoid about clearing APIC errors.
879 */
880 if (APIC_INTEGRATED(boot_cpu_apic_version)) {
881 apic_write(APIC_ESR, 0);
882 apic_read(APIC_ESR);
883 }
884 }
885
886 smp_mb();
887
888 /*
889 * Wake up a CPU in difference cases:
890 * - Use a method from the APIC driver if one defined, with wakeup
891 * straight to 64-bit mode preferred over wakeup to RM.
892 * Otherwise,
893 * - Use an INIT boot APIC message
894 */
895 if (apic->wakeup_secondary_cpu_64)
896 ret = apic->wakeup_secondary_cpu_64(apicid, start_ip, cpu);
897 else if (apic->wakeup_secondary_cpu)
898 ret = apic->wakeup_secondary_cpu(apicid, start_ip, cpu);
899 else
900 ret = wakeup_secondary_cpu_via_init(apicid, start_ip, cpu);842-900줄 주석
static int do_boot_cpu(u32 apicid, unsigned int cpu, struct task_struct *idle)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
{이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
unsigned long start_ip = real_mode_header->trampoline_start;real-mode 또는 64-bit AP wakeup에서 처음 실행할 low-memory entry를 선택한다.
int ret;이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
#ifdef CONFIG_X86_64빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.
/* If 64-bit wakeup method exists, use the 64-bit mode trampoline IP */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (apic->wakeup_secondary_cpu_64)platform/APIC implementation이 64-bit direct wakeup을 지원하면 별도 trampoline을 사용한다.
start_ip = real_mode_header->trampoline_start64;real-mode 또는 64-bit AP wakeup에서 처음 실행할 low-memory entry를 선택한다.
#endif빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.
idle->thread.sp = (unsigned long)task_pt_regs(idle);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
initial_code = (unsigned long)start_secondary;AP의 assembly startup 끝에서 간접 호출할 C entry를 start_secondary로 지정한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
if (IS_ENABLED(CONFIG_X86_32)) {이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
early_gdt_descr.address = (unsigned long)get_cpu_gdt_rw(cpu);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
initial_stack = idle->thread.sp;32비트 경로가 사용할 AP별 idle task stack을 게시한다.
} else if (!(smpboot_control & STARTUP_PARALLEL_MASK)) {이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
smpboot_control = cpu;이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
}이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* Enable the espfix hack for this CPU */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
init_espfix_ap(cpu);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* So we see what's up */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
announce_cpu(cpu, apicid);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* This grunge runs the startup process for원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* the targeted processor.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (x86_platform.legacy.warm_reset) {이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
pr_debug("Setting warm reset code and vector.\n");주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
smpboot_setup_warm_reset_vector(start_ip);legacy wakeup이 요구하는 warm-reset vector를 firmware-visible 위치에 기록한다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Be paranoid about clearing APIC errors.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (APIC_INTEGRATED(boot_cpu_apic_version)) {이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
apic_write(APIC_ESR, 0);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
apic_read(APIC_ESR);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
}이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
}이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
smp_mb();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Wake up a CPU in difference cases:원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* - Use a method from the APIC driver if one defined, with wakeup원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* straight to 64-bit mode preferred over wakeup to RM.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Otherwise,원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* - Use an INIT boot APIC message원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (apic->wakeup_secondary_cpu_64)platform/APIC implementation이 64-bit direct wakeup을 지원하면 별도 trampoline을 사용한다.
ret = apic->wakeup_secondary_cpu_64(apicid, start_ip, cpu);platform/APIC implementation이 64-bit direct wakeup을 지원하면 별도 trampoline을 사용한다.
else if (apic->wakeup_secondary_cpu)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
ret = apic->wakeup_secondary_cpu(apicid, start_ip, cpu);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
else이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
ret = wakeup_secondary_cpu_via_init(apicid, start_ip, cpu);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
SOURCE 04
AP가 ALIVE를 게시하기 전 수행하는 최소 초기화
AP는 exception handling과 microcode를 준비한 뒤 CPU hotplug 동기화 지점에 도착한다. 이 전에 일반 subsystem 작업을 넣지 말라는 원본 주석이 핵심 계약이다.
원본: arch/x86/kernel/smpboot.c 232-294줄
232static void notrace __noendbr start_secondary(void *unused)
233{
234 /*
235 * Don't put *anything* except direct CPU state initialization
236 * before cpu_init(), SMP booting is too fragile that we want to
237 * limit the things done here to the most necessary things.
238 */
239 cr4_init();
240
241 /*
242 * 32-bit specific. 64-bit reaches this code with the correct page
243 * table established. Yet another historical divergence.
244 */
245 if (IS_ENABLED(CONFIG_X86_32)) {
246 /* switch away from the initial page table */
247 load_cr3(swapper_pg_dir);
248 __flush_tlb_all();
249 }
250
251 cpu_init_exception_handling(false);
252
253 /*
254 * Load the microcode before reaching the AP alive synchronization
255 * point below so it is not part of the full per CPU serialized
256 * bringup part when "parallel" bringup is enabled.
257 *
258 * That's even safe when hyperthreading is enabled in the CPU as
259 * the core code starts the primary threads first and leaves the
260 * secondary threads waiting for SIPI. Loading microcode on
261 * physical cores concurrently is a safe operation.
262 *
263 * This covers both the Intel specific issue that concurrent
264 * microcode loading on SMT siblings must be prohibited and the
265 * vendor independent issue`that microcode loading which changes
266 * CPUID, MSRs etc. must be strictly serialized to maintain
267 * software state correctness.
268 */
269 load_ucode_ap();
270
271 /*
272 * Synchronization point with the hotplug core. Sets this CPUs
273 * synchronization state to ALIVE and spin-waits for the control CPU to
274 * release this CPU for further bringup.
275 */
276 cpuhp_ap_sync_alive();
277
278 cpu_init();
279 fpu__init_cpu();
280 rcutree_report_cpu_starting(raw_smp_processor_id());
281 x86_cpuinit.early_percpu_clock_init();
282
283 ap_starting();
284
285 /* Check TSC synchronization with the control CPU. */
286 check_tsc_sync_target();
287
288 /*
289 * Calibrate the delay loop after the TSC synchronization check.
290 * This allows to skip the calibration when TSC is synchronized
291 * across sockets.
292 */
293 ap_calibrate_delay();
294 232-294줄 주석
static void notrace __noendbr start_secondary(void *unused)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
{이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Don't put *anything* except direct CPU state initialization원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* before cpu_init(), SMP booting is too fragile that we want toper-CPU GDT/TSS와 일반 CPU 상태를 runtime 사용 가능하게 만든다.
* limit the things done here to the most necessary things.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
cr4_init();AP의 CR4 feature bit를 boot CPU 정책과 맞춘다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* 32-bit specific. 64-bit reaches this code with the correct page원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* table established. Yet another historical divergence.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (IS_ENABLED(CONFIG_X86_32)) {이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
/* switch away from the initial page table */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
load_cr3(swapper_pg_dir);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
__flush_tlb_all();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
}이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
cpu_init_exception_handling(false);AP별 TSS, IDT 관련 exception 처리 기반을 준비한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Load the microcode before reaching the AP alive synchronization원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* point below so it is not part of the full per CPU serialized원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* bringup part when "parallel" bringup is enabled.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* That's even safe when hyperthreading is enabled in the CPU as원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* the core code starts the primary threads first and leaves the원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* secondary threads waiting for SIPI. Loading microcode on원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* physical cores concurrently is a safe operation.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* This covers both the Intel specific issue that concurrent원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* microcode loading on SMT siblings must be prohibited and the원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* vendor independent issue`that microcode loading which changes원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* CPUID, MSRs etc. must be strictly serialized to maintain원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* software state correctness.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
load_ucode_ap();CPUID와 MSR 의미가 이후 software state와 일치하도록 ALIVE 게시 전에 microcode를 올린다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Synchronization point with the hotplug core. Sets this CPUs원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* synchronization state to ALIVE and spin-waits for the control CPU to원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* release this CPU for further bringup.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
cpuhp_ap_sync_alive();AP가 최소 architecture 준비를 마쳤음을 control CPU에 알리고 다음 단계 허가를 기다린다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
cpu_init();per-CPU GDT/TSS와 일반 CPU 상태를 runtime 사용 가능하게 만든다.
fpu__init_cpu();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
rcutree_report_cpu_starting(raw_smp_processor_id());이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
x86_cpuinit.early_percpu_clock_init();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
ap_starting();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* Check TSC synchronization with the control CPU. */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
check_tsc_sync_target();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Calibrate the delay loop after the TSC synchronization check.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* This allows to skip the calibration when TSC is synchronized원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* across sockets.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
ap_calibrate_delay();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
SOURCE 05
INIT deassert와 두 번의 SIPI가 AP의 첫 instruction 주소를 만드는 구간
BSP가 start_eip를 그대로 전송하는 것이 아니라 4KiB page number로 바꿔 STARTUP IPI vector에 넣는 코드다. AP는 vector << 12를 real-mode 시작 물리 주소로 복원한다.
원본: arch/x86/kernel/smpboot.c 695-757줄
695static int wakeup_secondary_cpu_via_init(u32 phys_apicid, unsigned long start_eip, unsigned int cpu)
696{
697 unsigned long send_status = 0, accept_status = 0;
698 int num_starts, j, maxlvt;
699
700 preempt_disable();
701 maxlvt = lapic_get_maxlvt();
702 send_init_sequence(phys_apicid);
703
704 mb();
705
706 /*
707 * Should we send STARTUP IPIs ?
708 *
709 * Determine this based on the APIC version.
710 * If we don't have an integrated APIC, don't send the STARTUP IPIs.
711 */
712 if (APIC_INTEGRATED(boot_cpu_apic_version))
713 num_starts = 2;
714 else
715 num_starts = 0;
716
717 /*
718 * Run STARTUP IPI loop.
719 */
720 pr_debug("#startup loops: %d\n", num_starts);
721
722 for (j = 1; j <= num_starts; j++) {
723 pr_debug("Sending STARTUP #%d\n", j);
724 if (maxlvt > 3) /* Due to the Pentium erratum 3AP. */
725 apic_write(APIC_ESR, 0);
726 apic_read(APIC_ESR);
727 pr_debug("After apic_write\n");
728
729 /*
730 * STARTUP IPI
731 */
732
733 /* Target chip */
734 /* Boot on the stack */
735 /* Kick the second */
736 apic_icr_write(APIC_DM_STARTUP | (start_eip >> 12),
737 phys_apicid);
738
739 /*
740 * Give the other CPU some time to accept the IPI.
741 */
742 if (init_udelay == 0)
743 udelay(10);
744 else
745 udelay(300);
746
747 pr_debug("Startup point 1\n");
748
749 pr_debug("Waiting for send to finish...\n");
750 send_status = safe_apic_wait_icr_idle();
751
752 /*
753 * Give the other CPU some time to accept the IPI.
754 */
755 if (init_udelay == 0)
756 udelay(10);
757 else695-757줄 주석
static int wakeup_secondary_cpu_via_init(u32 phys_apicid, unsigned long start_eip, unsigned int cpu)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
{이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
unsigned long send_status = 0, accept_status = 0;이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
int num_starts, j, maxlvt;이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
preempt_disable();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
maxlvt = lapic_get_maxlvt();이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
send_init_sequence(phys_apicid);대상 APIC ID에 INIT assert/deassert를 보내 AP를 초기 상태로 되돌린다. 이 과정은 AP가 Linux trampoline을 실행했다는 증거가 아니다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
mb();trampoline data와 AP별 stack/code pointer 게시가 INIT/SIPI보다 앞서 관찰되도록 ordering 경계를 둔다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Should we send STARTUP IPIs ?원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Determine this based on the APIC version.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* If we don't have an integrated APIC, don't send the STARTUP IPIs.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (APIC_INTEGRATED(boot_cpu_apic_version))이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
num_starts = 2;integrated APIC에는 Pentium 계열 호환성을 위해 SIPI를 두 번 보낼 수 있다. AP가 두 번 부팅된다는 뜻이 아니라 첫 SIPI 수신 실패를 견디는 protocol이다.
else이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
num_starts = 0;이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Run STARTUP IPI loop.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
pr_debug("#startup loops: %d\n", num_starts);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
for (j = 1; j <= num_starts; j++) {이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
pr_debug("Sending STARTUP #%d\n", j);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
if (maxlvt > 3) /* Due to the Pentium erratum 3AP. */이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
apic_write(APIC_ESR, 0);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
apic_read(APIC_ESR);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
pr_debug("After apic_write\n");이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* STARTUP IPI원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/* Target chip */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
/* Boot on the stack */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
/* Kick the second */원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
apic_icr_write(APIC_DM_STARTUP | (start_eip >> 12),STARTUP vector field는 물리 주소의 12비트 오른쪽 이동값이다. 따라서 trampoline은 4KiB 정렬이어야 하고 1MiB 아래에서 표현 가능해야 한다.
phys_apicid);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Give the other CPU some time to accept the IPI.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (init_udelay == 0)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
udelay(10);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
else이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
udelay(300);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
pr_debug("Startup point 1\n");이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
pr_debug("Waiting for send to finish...\n");이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
send_status = safe_apic_wait_icr_idle();ICR send pending이 내려가는 것만 확인한다. AP의 C entry 도달은 뒤의 ALIVE/completion handshake로 별도 확인한다.
(빈 줄)빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.
/*원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
* Give the other CPU some time to accept the IPI.원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
*/원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.
if (init_udelay == 0)이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
udelay(10);이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
else이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.
WORKED EXAMPLES
숫자를 넣어 끝까지 계산한다
SYSCALL 직후 CPU가 보존한 값과 Linux가 추가하는 frame
user RIP=0x00007f0012345678, user RSP=0x00007fffffffdc00, RFLAGS=0x246, RAX=60인 exit syscall을 예로 든다.
- hardware resultSYSCALL은 RCX에 user RIP, R11에 RFLAGS를 저장하지만 SS:RSP frame과 kernel stack 전환은 만들지 않는다.
- entry scratch
swapgs뒤 user RSP를 per-CPUTSS_sp2에 저장하고 kernel CR3·kernel stack을 선택한다. - synthetic frameSS, 저장된 user RSP, R11, CS, RCX,
orig_ax=60을pt_regs순서로 push한다. - return choiceRCX가 canonical이고 R11에 SYSRET 금지 flag가 없을 때만 빠른 SYSRET를 사용한다.
판정SYSCALL을 IDT interrupt처럼 설명하면 자동 stack switch와 error-code slot을 잘못 가정하게 된다. frame의 생산자를 hardware와 entry assembly로 나눠 적는다.
SIPI vector와 AP 시작 주소 계산
trampoline 물리 주소가 0x00090000, 대상 APIC ID가 0x04라고 가정한다.
- vector
0x00090000 >> 12 = 0x90; STARTUP IPI vector field에는0x90이 들어간다. - AP reconstructionAP는 vector를 12bit 왼쪽 이동해 첫 physical PC
0x00090000을 얻는다. - 표현 범위8bit vector이므로 시작 주소는 1MiB 아래의 4KiB 경계여야 한다.
- 완료 판정ICR idle은 전송 완료일 뿐이다.
secondary_startup_64checkpoint와 ALIVE completion을 별도로 본다.
판정AP 정지 분석에서는 start_eip, ICR low/high, target APIC ID, trampoline data의 initial stack/code를 한 시점에 덤프한다.
DEEP DIVE
코드에서 놓치기 쉬운 경계
hardware frame과 pt_regs를 같은 것으로 부르지 않는다
CPU는 privilege 전환 여부와 vector 종류에 따라 서로 다른 최소 frame을 만든다. Linux entry stub는 vector/error slot과 general registers를 추가해 C가 공통 구조로 읽을 수 있는 pt_regs를 완성한다.
crash dump에서 stack을 해석할 때 현재 RSP가 hardware frame 시작인지 완성된 pt_regs 시작인지 먼저 판별해야 offset을 틀리지 않는다.
error code가 있는 exception과 없는 exception을 정규화한다
#PF, #GP처럼 CPU가 error code를 push하는 exception과 #DE처럼 push하지 않는 exception이 있다. idtentry macro의 has_error_code와 generated stub가 이 차이를 흡수한다.
vector table만 보고 stack slot을 가정하지 말고 생성 macro가 넘긴 parameter와 disassembly를 확인한다.
KPTI와 swapgs는 보안 경계이자 ordering 경계다
user page table에서는 kernel mapping이 제한될 수 있으므로 per-CPU data와 kernel stack을 쓰기 전에 GS와 CR3를 순서대로 바꾼다. speculation mitigation macro도 register frame을 만든 뒤 C 호출 전에 배치된다.
entry 코드의 한 줄을 이동할 때는 functional correctness뿐 아니라 transient execution에서 어떤 주소가 노출되는지도 함께 본다.
SYSRET은 무조건 빠른 복귀가 아니다
SYSRET은 canonical RIP/RSP와 flag에 제약이 있고 일부 CPU errata도 있다. ptrace, signal, modified registers처럼 context가 깨끗하지 않으면 IRET 기반 slow path가 필요하다.
성능 측정에서는 syscall entry 비용과 return-to-user work, IRET fallback 비율을 분리해야 한다.
local APIC vector와 Linux IRQ number는 같은 번호가 아니다
CPU가 받은 vector는 IDT slot이고 irqdomain/core가 관리하는 Linux IRQ number와 직접 일치하지 않을 수 있다. IOAPIC/MSI routing, vector allocation, descriptor lookup을 분리해 추적한다.
장치 /proc/interrupts 번호만으로 IDT vector를 추정하지 말고 APIC/irq tracepoint와 handler entry를 같이 본다.
AP startup은 lock 하나가 아니라 handshake들의 연쇄다
BSP는 trampoline data와 stack을 먼저 게시하고 wakeup을 보낸다. AP는 microcode와 exception state를 준비한 뒤 ALIVE를 알리고, control CPU가 다음 hotplug state를 열어야 scheduler와 interrupt를 사용할 수 있다.
timeout이 나면 SIPI 전송, trampoline 도달, secondary_startup_64, start_secondary, ALIVE completion 중 마지막으로 관찰된 지점을 나눠야 한다.
FAILURE PATH
멈춘 위치보다 먼저 볼 값
| 조건 | 관찰되는 증상 | 첫 확인 값 |
|---|---|---|
| swapgs/CR3 순서 오류 | 첫 per-CPU 접근이나 kernel stack 전환에서 page fault 또는 잘못된 CPU data를 읽는다. | GS base, CR3, TSS_sp2 |
| frame layout 불일치 | C handler가 vector, RIP, error code를 잘못 해석해 2차 fault가 난다. | stub push와 pt_regs offset |
| EOI/IRQ routing 오류 | 한 vector가 반복 진입하거나 반대로 다시 발생하지 않는다. | APIC ISR/IRR와 handler return |
| AP startup 게시 순서 오류 | SIPI는 보냈지만 AP가 초기 stack 또는 initial_code를 0으로 읽는다. | trampoline data와 memory visibility |
| AP ALIVE timeout | CPU online 실패 로그가 나고 hotplug state가 진행되지 않는다. | secondary_startup_64와 cpuhp sync |
LAB
직접 확인할 실험
- 01
perf record -e irq:irq_handler_entry,irq:irq_handler_exit로 Linux IRQ와 handler 시간을 기록한다.명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.
- 02ftrace function graph에서
do_syscall_64,common_interrupt,start_secondary를 서로 다른 trace로 수집한다.명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.
- 03QEMU
-d int와 GDB로 IDT vector, CS, error code, CR2를 page fault 한 건에 대해 대조한다.명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.
- 04
dmesg -w의 CPU bring-up 로그와 APIC ID,/sys/devices/system/cpu/online을 함께 기록한다.명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.
PRIMARY REFERENCES