# x86-64 IDT·syscall 진입과 secondary CPU 기동

v6.18.37 / arch/x86/entry/entry_64.S

x86 SYSCALL은 일반 IDT 진입과 다르게 사용자 RSP에서 커널 스택으로 자동 전환하지 않습니다. v6.18.37 entry_SYSCALL_64의 시작 부분을 읽으며 Linux가 빠진 프레임을 직접 만드는 이유를 설명합니다. 이 발췌는 함수 전체가 아니라 커널 C 호출 전의 프레임 구성 구간입니다.

## entry_SYSCALL_64의 프레임 구성 구간

```c

SYM_CODE_START(entry_SYSCALL_64)
	UNWIND_HINT_ENTRY
	ENDBR

	swapgs
	/* tss.sp2 is scratch space. */
	movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp

SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
	ANNOTATE_NOENDBR

	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs->ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */
	pushq	%r11					/* pt_regs->flags */
	pushq	$__USER_CS				/* pt_regs->cs */
	pushq	%rcx					/* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
	pushq	%rax					/* pt_regs->orig_ax */

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS

	/* IRQs are off. */
	movq	%rsp, %rdi
	/* Sign extend the lower 32bit as syscall numbers are treated as int */
	movslq	%eax, %rsi

	/* clobbers %rax, make sure it is after saving the syscall nr */
	IBRS_ENTER
	UNTRAIN_RET
	CLEAR_BRANCH_HISTORY

	call	do_syscall_64		/* returns with IRQs disabled */

```

### 87행

```c

SYM_CODE_START(entry_SYSCALL_64)

```

SYSCALL 진입 코드의 전역 기호 시작을 표시합니다. 이 발췌는 전체 함수 중 C 호출 전 구간입니다.

### 88행

```c

	UNWIND_HINT_ENTRY

```

스택 추적 도구가 진입 시점의 unwind 상태를 알도록 힌트를 남깁니다. 레지스터를 저장하는 CPU 명령이 아닙니다.

### 89행

```c

	ENDBR

```

IBT 구성에 맞는 간접 분기 도착점 표시를 둡니다. 실제 생성 형태는 빌드 설정과 매크로 정의에 따릅니다.

### 91행

```c

	swapgs

```

GS 기준을 커널 per-CPU 자료 접근에 맞게 전환합니다. 뒤의 PER_CPU_VAR 접근을 위한 준비입니다.

### 93행

```c

	movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)

```

사용자 RSP를 per-CPU TSS의 scratch 필드 sp2에 보관합니다. 아직 커널 스택을 선택하기 전의 사용자 스택 위치입니다.

### 94행

```c

	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp

```

필요한 구성에서 커널 CR3로 바꾸며 RSP를 임시 레지스터로 허용합니다. 그래서 사용자 RSP를 앞에서 먼저 저장했습니다.

### 95행

```c

	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp

```

현재 task의 커널 스택 상단을 실제 RSP에 넣습니다. 이후 push는 이 스택에 기록됩니다.

### 97행

```c

SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)

```

안전한 커널 스택이 마련된 내부 위치에 전역 라벨을 붙입니다. 독립 함수 호출은 아닙니다.

### 98행

```c

	ANNOTATE_NOENDBR

```

이 내부 지점에 ENDBR이 없음을 분석 도구에 표시합니다.

### 101행

```c

	pushq	$__USER_DS				/* pt_regs->ss */

```

사용자 SS 값을 푸시하여 pt_regs의 하드웨어 프레임 모양을 소프트웨어로 구성합니다.

### 102행

```c

	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */

```

TSS scratch에 보관한 원래 사용자 RSP를 프레임에 넣습니다.

### 103행

```c

	pushq	%r11					/* pt_regs->flags */

```

SYSCALL이 R11에 남긴 사용자 RFLAGS를 보존합니다.

### 104행

```c

	pushq	$__USER_CS				/* pt_regs->cs */

```

사용자 CS 선택자를 프레임에 기록합니다.

### 105행

```c

	pushq	%rcx					/* pt_regs->ip */

```

SYSCALL이 RCX에 남긴 사용자 복귀 RIP를 기록합니다.

### 106행

```c

SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)

```

하드웨어 프레임 모양의 항목을 완성한 내부 지점에 이름을 붙입니다. 앞의 push가 실제로 하드웨어에서 자동 실행됐다는 뜻은 아닙니다.

### 107행

```c

	pushq	%rax					/* pt_regs->orig_ax */

```

사용자 RAX에 있던 syscall 번호를 orig_ax로 남깁니다. 뒤에서 RAX를 반환값 용도로 바꿔도 원래 번호를 추적할 수 있습니다.

### 109행

```c

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS

```

나머지 레지스터를 저장하고 작업용 값을 정리하는 매크로입니다. RAX 초기 결과를 -ENOSYS로 두어 미구현 호출의 기본 오류값을 마련합니다.

### 112행

```c

	movq	%rsp, %rdi

```

현재 RSP, 즉 pt_regs의 주소를 C 첫 인수 RDI에 넣습니다.

### 114행

```c

	movslq	%eax, %rsi

```

시스템 호출 번호는 int로 해석하므로 EAX의 32비트 값을 부호 확장하여 두 번째 C 인수 RSI에 넣습니다.

### 117행

```c

	IBRS_ENTER

```

커널 진입에 필요한 분기 예측 완화 처리를 적용합니다. RAX를 사용할 수 있으므로 원래 번호 저장 뒤에 위치합니다.

### 118행

```c

	UNTRAIN_RET

```

지원·설정 조건에 맞는 복귀 예측 완화 매크로를 실행합니다.

### 119행

```c

	CLEAR_BRANCH_HISTORY

```

분기 이력 관련 완화를 적용합니다. 매크로가 모든 시스템에서 동일한 긴 명령열을 만든다고 가정하지 않습니다.

### 121행

```c

	call	do_syscall_64		/* returns with IRQs disabled */

```

준비된 regs와 syscall 번호로 do_syscall_64를 호출합니다. 주석처럼 돌아올 때 IRQ는 차단된 상태이며 사용자 복귀 선택은 다음 구간에서 계속됩니다.