Process / Syscall · Linux v6.6 · source + diagram note

syscall entry

arm64 EL0 SVC 진입, 번호 검사와 디스패치, 사용자 모드 복귀.

전체 흐름

범위는 AArch64 사용자 태스크가 svc #0로 일으킨 EL0 동기 예외다. entry.S가 레지스터 문맥을 저장하고 entry-common.c가 ESR의 예외 종류를 판별한다. SVC인 경우에만 do_el0_svc()로 넘어간다. 데이터 중단·명령 중단·IRQ는 시스템 호출 디스패치가 아니다.

그림 1. AArch64 EL0 SVC에서 사용자 복귀까지
  1. EL0 · SVC인자는 x0–x5, native 호출 번호는 x8.
  2. entry.S · kernel_entrypt_regs를 만들고 기본 syscallno를 NO_SYSCALL로 둔다.
  3. entry-common.c · SVC64 판별el0t_64_sync_handler()가 ESR을 분기해 el0_svc()로 보낸다.
  4. syscall.c · el0_svc_common번호·원래 x0를 기록하고 진입 추적·스킵 조건을 처리한다.
  5. invoke_syscall테이블 범위 안이면 핸들러를 호출하고 결과를 regs에 저장한다.
  6. exit_to_user_mode → kernel_exit복귀 작업 뒤 문맥을 복원하고 eret으로 EL0에 돌아간다.

갈림길: SVC가 아닌 예외는 별도 핸들러로 간다. 추적·seccomp·ptrace가 호출을 건너뛰거나 비정상 번호가 미구현 경로로 갈 수도 있다.

v6.6의 el0_svc()는 fp_user_discard() 뒤 exit_to_user_mode()를 호출한다. 버전별 복귀 코드를 같은 것으로 간주하지 않는다.

소스 코드 위치

entry.S의 예외 프레임 저장·복원, entry-common.c의 SVC 판별, syscall.c의 번호 검사·테이블 선택, kernel/entry/common.c의 사용자 복귀 작업을 대조했다.

Linux v6.6 원본: arch/arm64/kernel/entry.S arch/arm64/kernel/entry-common.c arch/arm64/kernel/syscall.c kernel/entry/common.c include/uapi/asm-generic/unistd.h

파일연결
entry.Skernel_entry → C handler → ret_to_user → kernel_exit 0
entry-common.cESR_ELx_EC_SVC64일 때 el0_svc로 분기하고 복귀 준비를 호출한다.
syscall.cdo_el0_svc, el0_svc_common, invoke_syscall이 번호와 결과를 처리한다.
kernel/entry/common.c조건에 따라 사용자 복귀 전 작업을 처리한다.

대표 코드

kernel_entry 끝 / kernel_exit 시작

원본 코드: arch/arm64/kernel/entry.S, 공통 진입·복귀 매크로의 중간 발췌
alternative_else_nop_endif
#endif

	stp	x22, x23, [sp, #S_PC]

	/* Not in a syscall by default (el0_svc overwrites for real syscall) */
	.if	\el == 0
	mov	w21, #NO_SYSCALL
	str	w21, [sp, #S_SYSCALLNO]
	.endif

#ifdef CONFIG_ARM64_PSEUDO_NMI
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
	b	.Lskip_pmr_save\@
alternative_else_nop_endif

	mrs_s	x20, SYS_ICC_PMR_EL1
	str	x20, [sp, #S_PMR_SAVE]
	mov	x20, #GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET
	msr_s	SYS_ICC_PMR_EL1, x20

.Lskip_pmr_save\@:
#endif

	/*
	 * Registers that may be useful after this macro is invoked:
	 *
	 * x20 - ICC_PMR_EL1
	 * x21 - aborted SP
	 * x22 - aborted PC
	 * x23 - aborted PSTATE
	*/
	.endm

	.macro	kernel_exit, el
	.if	\el != 0
	disable_daif
	.endif

#ifdef CONFIG_ARM64_PSEUDO_NMI

읽는 법: 이 발췌 자체는 el0_svc() 함수가 아니다. NO_SYSCALL은 EL0 예외의 기본값이며 실제 SVC 번호는 이후 syscall.c에서 기록한다. 끝부분은 kernel_exit의 시작이므로 전체 반환 어셈블리는 포함하지 않는다.

invoke_syscall

원본 코드: arch/arm64/kernel/syscall.c, 30행 부근
#endif

	return sys_ni_syscall();
}

static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)
{
	return syscall_fn(regs);
}

static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
			   unsigned int sc_nr,
			   const syscall_fn_t syscall_table[])
{
	long ret;

	add_random_kstack_offset();

	if (scno < sc_nr) {
		syscall_fn_t syscall_fn;
		syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
		ret = __invoke_syscall(regs, syscall_fn);
	} else {
		ret = do_ni_syscall(regs, scno);
	}

	syscall_set_return_value(current, regs, 0, ret);

	/*
	 * Ultimately, this value will get limited by KSTACK_OFFSET_MAX(),
	 * but not enough for arm64 stack utilization comfort. To keep
	 * reasonable stack head room, reduce the maximum offset to 9 bits.
	 *
	 * The actual entropy will be further reduced by the compiler when
	 * applying stack alignment constraints: the AAPCS mandates a
	 * 16-byte (i.e. 4-bit) aligned SP at function boundaries.
	 *
	 * The resulting 5 bits of entropy is seen in SP[8:4].
	 */
	choose_random_kstack_offset(get_random_u16() & 0x1FF);

읽는 법: 앞 함수의 마지막 줄에서 시작하는 발췌다. 번호 범위를 검사하고 array_index_nospec으로 인덱스를 제한한 다음 함수 포인터를 호출한다. 범위 밖이면 do_ni_syscall()로 가며 결과는 사용자 복귀용 regs에 기록된다.

함수별 분석

예외 진입, 호출 선택, 사용자 복귀는 서로 다른 층이다. el0_svc()만으로 번호 검사와 복귀 작업 전체를 설명할 수 없다.

순서함수·매크로역할
1kernel_entry 0,64사용자 레지스터·PC·PSTATE를 저장하고 syscallno=NO_SYSCALL로 둔다.
2el0t_64_sync_handlerESR exception class가 SVC64인 경우에만 el0_svc를 선택한다.
3el0_svc → do_el0_svc사용자 모드 진입 상태를 전환하고 x8 번호를 넘긴다. fp_user_discard 처리가 이 주변에 있다.
4el0_svc_commonorig_x0와 번호를 저장한다. MTE fault·syscall-work 플래그는 실제 호출 전 분기를 만든다.
5invoke_syscall테이블 함수를 호출하거나 미구현 경로로 가고 반환값을 저장한다.
6exit_to_user_mode → ret_to_user사용자 복귀 작업과 문맥 복원 뒤 eret한다.

구조체와 필드

호출 인자와 반환값은 저장된 pt_regs와 선택된 테이블을 통해 해석된다.

값생산·소비주의
regs->regs[8]native 호출 번호. do_el0_svc가 읽는다.일반 C 함수의 여덟 번째 인자가 아니다.
regs->regs[0] / orig_x0진입 x0를 보관하고 결과도 복귀용 x0에 쓴다.입력과 반환 시점을 구분한다.
regs->syscallno진입 기본값은 NO_SYSCALL, SVC에서 번호 갱신.모든 EL0 예외가 시스템 호출은 아니다.
sys_call_table[]native 범위 검사 뒤 핸들러 선택.범위 밖 번호는 테이블을 인덱싱하지 않는다.
compat_sys_call_table[]CONFIG_COMPAT의 AArch32 SVC는 x7과 __NR_compat_syscalls를 사용.native ABI와 크기를 섞지 않는다.
thread flags진입 추적·사용자 복귀 작업의 조건.핸들러 반환이 즉시 eret은 아니다.

실행 단계와 분기

실제 핸들러를 실행하지 않는 경우도 pt_regs와 사용자 복귀 절차를 통과할 수 있다.

조건분기확인할 결과
ESR이 SVC64가 아님다른 EL0 예외 핸들러페이지 폴트 등을 invoke_syscall로 그리지 않는다.
비동기 MTE fault 플래그재시작용 반환값을 설정하고 디스패치를 미룸복귀 쪽 시그널 전달을 살핀다.
ptrace·seccomp 등 syscall-work번호 변경 또는 NO_SYSCALL 스킵 가능x0와 종료 추적을 함께 본다.
번호가 테이블 범위 밖do_ni_syscall 처리미구현 결과 또는 compat 처리 여부.
핸들러가 반환함필요 시 추적 종료, 복귀 작업, 레지스터 복원즉시 eret한다고 가정하지 않는다.

불변 조건과 실패 사례

예외 종류

SVC64 판별 전에 EL0 동기 예외 전체를 호출로 취급하면 데이터 중단 경로를 잘못 설명한다.

번호 경계

범위 검사 뒤 함수 포인터를 선택한다. 추적 경로에서는 호출 번호가 바뀌거나 스킵될 수 있다.

반환 문맥

orig_x0와 사용자에게 반환할 x0를 구분한다. 저장된 문맥은 복귀 어셈블리까지 이어진다.

복귀 작업

시그널·재스케줄·추적은 사용자 복귀 준비에 포함될 수 있다. 핸들러 뒤 바로 eret으로 건너뛰는 그림은 틀리다.

계측과 검증

tracepoint 가용성은 빌드 설정에 따라 다르다. sys_enter/sys_exit만으로 ESR 분기, 스킵 이유, 모든 복귀 작업을 식별할 수 없으므로 코드 조건과 함께 읽는다.

관찰알 수 있는 것한계
raw_syscalls:sys_enter관측된 호출 번호·인자모든 EL0 예외를 나타내지 않는다.
raw_syscalls:sys_exit관측된 반환값최종 eret 시점은 아니다.
코드 경로 대조스킵·범위 밖 조건정적 함수의 ftrace 가능 여부는 빌드·최적화에 좌우된다.
tracefs 이벤트가 켜진 환경에서 확인:
ls /sys/kernel/tracing/events/raw_syscalls/
cat /sys/kernel/tracing/events/raw_syscalls/sys_enter/format
cat /sys/kernel/tracing/events/raw_syscalls/sys_exit/format

추가 확인 사항

  • 같은 EL0 동기 예외라도 SVC64와 DABT_LOW는 어디서 갈라지는가?
  • 추적이 NO_SYSCALL로 실행을 건너뛰면 사용자 x0와 종료 추적은 어떻게 처리되는가?
  • 핸들러 반환과 최종 eret 사이에 어떤 사용자 복귀 작업이 가능한가?
  • 두 버전의 FP 상태 처리와 스택 오프셋 선택은 어떻게 달라졌는가?