요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
==============
Kernel Entries
==============
This file documents some of the kernel entries in
arch/x86/entry/entry_64.S. A lot of this explanation is adapted from
an email from Ingo Molnar:
https://lore.kernel.org/r/20110529191055.GC9835%40elte.hu
The x86 architecture has quite a few different ways to jump into
kernel code. Most of these entry points are registered in
arch/x86/kernel/traps.c and implemented in arch/x86/entry/entry_64.S
for 64-bit, arch/x86/entry/entry_32.S for 32-bit and finally
arch/x86/entry/entry_64_compat.S which implements the 32-bit compatibility
syscall entry points and thus provides for 32-bit processes the
ability to execute syscalls when running on 64-bit kernels.
The IDT vector assignments are listed in arch/x86/include/asm/irq_vectors.h.
Some of these entries are:
- system_call: syscall instruction from 64-bit code.
- entry_INT80_compat: int 0x80 from 32-bit or 64-bit code; compat syscall
either way.
- entry_INT80_compat, ia32_sysenter: syscall and sysenter from 32-bit
code
- interrupt: An array of entries. Every IDT vector that doesn't
explicitly point somewhere else gets set to the corresponding
value in interrupts. These point to a whole array of
magically-generated functions that make their way to common_interrupt()
with the interrupt number as a parameter.
- APIC interrupts: Various special-purpose interrupts for things
like TLB shootdown.
- Architecturally-defined exceptions like divide_error.
There are a few complexities here. The different x86-64 entries
have different calling conventions. The syscall and sysenter
instructions have their own peculiar calling conventions. Some of
the IDT entries push an error code onto the stack; others don't.
IDT entries using the IST alternative stack mechanism need their own
magic to get the stack frames right. (You can find some
documentation in the AMD APM, Volume 2, Chapter 8 and the Intel SDM,
Volume 3, Chapter 6.)
Dealing with the swapgs instruction is especially tricky. Swapgs
toggles whether gs is the kernel gs or the user gs. The swapgs
instruction is rather fragile: it must nest perfectly and only in
single depth, it should only be used if entering from user mode to
kernel mode and then when returning to user-space, and precisely
so. If we mess that up even slightly, we crash.
So when we have a secondary entry, already in kernel mode, we *must
not* use SWAPGS blindly - nor must we forget doing a SWAPGS when it's
not switched/swapped yet.
Now, there's a secondary complication: there's a cheap way to test
which mode the CPU is in and an expensive way.
The cheap way is to pick this info off the entry frame on the kernel
stack, from the CS of the ptregs area of the kernel stack::
xorl %ebx,%ebx
testl $3,CS+8(%rsp)
je error_kernelspace
SWAPGS
The expensive (paranoid) way is to read back the MSR_GS_BASE value
(which is what SWAPGS modifies)::
movl $1,%ebx
movl $MSR_GS_BASE,%ecx
rdmsr
testl %edx,%edx
js 1f /* negative -> in kernel */
SWAPGS
xorl %ebx,%ebx
1: ret
If we are at an interrupt or user-trap/gate-alike boundary then we can
use the faster check: the stack will be a reliable indicator of
whether SWAPGS was already done: if we see that we are a secondary
entry interrupting kernel mode execution, then we know that the GS
base has already been switched. If it says that we interrupted
user-space execution then we must do the SWAPGS.
But if we are in an NMI/MCE/DEBUG/whatever super-atomic entry context,
which might have triggered right after a normal entry wrote CS to the
stack but before we executed SWAPGS, then the only safe way to check
for GS is the slower method: the RDMSR.
Therefore, super-atomic entries (except NMI, which is handled separately)
must use idtentry with paranoid=1 to handle gsbase correctly. This
triggers three main behavior changes:
- Interrupt entry will use the slower gsbase check.
- Interrupt entry from user mode will switch off the IST stack.
- Interrupt exit to kernel mode will not attempt to reschedule.
We try to only use IST entries and the paranoid entry code for vectors
that absolutely need the more expensive check for the GS base - and we
generate all 'normal' entry points with the regular (faster) paranoid=0
variant.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
entry source와 architecture 범위
1-22이 `GPL-2.0` 문서는 `arch/x86/entry/entry_64.S`의 kernel entry 일부를 설명하며 Ingo Molnar의 email `https://lore.kernel.org/r/20110529191055.GC9835%40elte.hu`를 바탕으로 합니다.
x86에는 kernel code로 들어가는 경로가 많습니다. 대부분 `arch/x86/kernel/traps.c`에 등록되고 64-bit는 `arch/x86/entry/entry_64.S`, 32-bit는 `arch/x86/entry/entry_32.S`가 구현합니다. `arch/x86/entry/entry_64_compat.S`는 64-bit kernel에서 32-bit process가 syscall을 실행하는 compatibility entry를 구현합니다.
IDT vector assignment는 `arch/x86/include/asm/irq_vectors.h`에 있습니다.
syscall, interrupt와 exception entry
23-51| entry | source와 역할 |
|---|---|
| `system_call` | 64-bit code의 `syscall` instruction |
| `entry_INT80_compat` | 32-bit 또는 64-bit code의 `int 0x80`; 어느 쪽이든 compat syscall |
| `entry_INT80_compat`, `ia32_sysenter` | 32-bit code의 syscall과 `sysenter` |
| `interrupt` array | 다른 곳을 명시적으로 가리키지 않는 모든 IDT vector가 대응 entry를 사용합니다. generated function array가 interrupt number를 argument로 `common_interrupt()`에 도달합니다. |
| APIC interrupts | TLB shootdown 같은 special-purpose interrupt |
| architecture exception | `divide_error` 같은 architecture-defined exception |
x86-64 entry마다 calling convention이 다릅니다. `syscall`, `sysenter`는 고유 convention을 사용하고 일부 IDT entry는 stack에 error code를 push하지만 다른 entry는 그렇지 않습니다. IST alternative-stack을 쓰는 entry도 stack frame을 맞추기 위한 별도 처리가 필요합니다.
관련 architecture 설명은 AMD APM Volume 2 Chapter 8과 Intel SDM Volume 3 Chapter 6을 참조합니다.
SWAPGS의 fast와 paranoid check
52-86`SWAPGS`는 `gs`가 kernel GS인지 user GS인지 전환합니다. 정확히 한 depth로 완벽히 nesting해야 하고 user mode에서 kernel mode로 들어갈 때와 userspace로 돌아갈 때만 정확히 사용해야 합니다. 조금만 어긋나도 crash합니다.
이미 kernel mode인 secondary entry에서는 `SWAPGS`를 무조건 실행해서도 안 되고 아직 switch하지 않았는데 빠뜨려서도 안 됩니다. 현재 mode를 확인하는 fast 방식과 expensive paranoid 방식이 있습니다.
fast 방식은 kernel stack의 entry frame, 즉 `ptregs` area의 CS에서 privilege level을 확인합니다.
xorl %ebx,%ebx
testl $3,CS+8(%rsp)
je error_kernelspace
SWAPGS
paranoid 방식은 `SWAPGS`가 바꾸는 `MSR_GS_BASE`를 `RDMSR`로 읽습니다. 상위 값이 negative이면 kernel GS이므로 swap하지 않고, 그렇지 않으면 `SWAPGS`를 실행합니다.
movl $1,%ebx
movl $MSR_GS_BASE,%ecx
rdmsr
testl %edx,%edx
js 1f /* negative -> in kernel */
SWAPGS
xorl %ebx,%ebx
1: ret
entry boundary별 gsbase 판별
87-110일반 interrupt 또는 user trap/gate boundary에서는 stack이 이미 `SWAPGS`했는지 신뢰할 수 있어 fast check를 사용합니다. kernel-mode 실행을 interrupt한 secondary entry라면 GS base가 이미 switch됐고, userspace를 interrupt했다면 `SWAPGS`가 필요합니다.
그러나 NMI/MCE/DEBUG 같은 super-atomic entry가 normal entry가 CS를 stack에 쓴 직후, 아직 `SWAPGS` 전인 순간에 발생할 수 있습니다. 이때 GS state를 안전하게 확인하는 유일한 방법은 느린 `RDMSR`입니다.
따라서 별도로 처리되는 NMI를 제외한 super-atomic entry는 `gsbase`를 올바르게 다루기 위해 `idtentry`의 `paranoid=1`을 사용합니다. 이 선택은 다음 세 behavior를 바꿉니다.
- interrupt entry가 느린 gsbase check를 사용합니다.
- user mode에서 온 interrupt entry가 IST stack을 switch off합니다.
- kernel mode로 돌아가는 interrupt exit가 reschedule을 시도하지 않습니다.
expensive GS-base check가 꼭 필요한 vector에만 IST entry와 paranoid code를 사용하고 모든 normal entry point는 더 빠른 `paranoid=0` variant로 생성합니다.
요약과 해설
entry_64.rst:1-110x86-64 kernel entry는 syscall, compat syscall, IDT interrupt, APIC interrupt, architecture exception마다 calling convention과 stack frame이 다릅니다. 특히 `SWAPGS`는 user/kernel GS 전환을 정확히 한 번씩 맞춰야 합니다.
normal boundary는 saved CS로 fast check를 하지만 super-atomic entry는 CS를 저장하고 `SWAPGS`하기 전 틈에 끼어들 수 있어 `RDMSR` 기반 `paranoid=1` check가 필요합니다. 비용이 큰 경로는 꼭 필요한 vector에만 사용합니다.