커널 메모리의 코드 및 데이터 일부를 유저가 직접 호출 또는 접근할 수 있도록 유일하게 허용한 페이지이다.
- Posix syscall을 사용하지 않고 직접 유저 공간에서 호출하므로 매우 빠른 속도가 요구되는 코드를 수행할 수 있다.
- 하이 벡터를 사용하는 프로세서에서 CONFIG_KUSER_HELPERS 커널 옵션을 사용하여 제공한다.
- 로우 벡터에서는 kuser helper 코드를 지원하지 않는다.
- arm에서 가상 주소 0xffff_0000로 시작하는 페이지를 사용하는 하이 벡터 페이지의 사용하지 않는 윗 부분을 이용한다.
- 현재 0xffff_0f60 ~ 0xffff0fff 까지 범위에서 사용하고 있다.
코드 위치
현재 커널은 하이벡터 페이지의 가장 윗 부분을 이용하여 다음 4개의 함수와 1개의 상수 값을 제공한다.
- __kuser_cmpxchg64()
- __kuser_memory_barrier()
- __kuser_cmpxchg()
- __kuser_get_tls()
- __kuser_helper_version 상수
코드가 위치한 주소는 다음 그림과 같다.
Kernel provided User Helper 버전 확인
가상 주소: 0xffff_0ffc
사용 방법
#define __kuser_helper_version (*(int32_t *)0xffff0ffc) void check_kuser_version(void) { if (__kuser_helper_version < 2) { fprintf(stderr, "can't do atomic operations, kernel too old\n"); abort(); } }
버전 값이 2보다 작은 경우 커널이 너무 오래되어서 atomic operation을 지원하지 않는 것을 알 수 있다.
__kuser_cmpxchg64() 함수
가상 주소: 0xffff_0f60
사용방법
typedef int (__kuser_cmpxchg64_t)(const int64_t *oldval, const int64_t *newval, volatile int64_t *ptr); #define __kuser_cmpxchg64 (*(__kuser_cmpxchg64_t *)0xffff0f60) int64_t atomic_add64(volatile int64_t *ptr, int64_t val) { int64_t old, new; do { old = *ptr; new = old + val; } while(__kuser_cmpxchg64(&old, &new, ptr)); return new; }
64bit 값이 담긴 ptr 포인터 주소에 val 값을 atomic하게 더한다.
- __kuser_cmpxchg64() 함수는 64bit 현재 값이 oldval과 같은 경우 newval을 atomic 하게 저장하고 성공리에 0을 반환한다. 그 외의 값은 oldval과 newval이 달라 저장하지 않은 경우이다.
3가지 구현 방법
Kernel-provided User Helper code 중 kuser_cmpxchg64() 함수는 시스템에 따라 3 가지의 구현 중 하나를 사용한다.
- fastpath
- ARMv6K를 포함하여 이후 버전의 ARM 아키텍처는 user space에서 직접 cmpxchg 및 cmpxchg64 atomic operation을 수행할 수 있다.
- ARM SMP 시스템에서 사용하는 atomic operation
- ARMv6: swp
- ARMv7: ldrex/strex이 권장 사용되며, swp는 호환목적으로 s/w emulation 방법을 사용하여 구현되어 있다.(비권장)
- ARM SMP 시스템에서 사용하는 atomic operation
- ARMv6K를 포함하여 이후 버전의 ARM 아키텍처는 user space에서 직접 cmpxchg 및 cmpxchg64 atomic operation을 수행할 수 있다.
- slowpath for SMP
- fastpath를 지원하지 않는 SMP 아키텍처를 사용하는 경우 CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG 커널 옵션을 사용하여 arm syscall을 사용하여 커널로 진입하여 64bit cmpxchg atomic operation을 수행하는 방법이다.
- slowpath for UP
- UP 시스템에서 atomic을 포기하고 순서대로 진행하는 방법으로 인터럽트가 커널에서 호출되는 경우에 한하여 fixup을 수행하여 보강한다.
NEEDS_SYSCALL_FOR_CMPXCHG
- arm 아키텍처로 집중해서 이 커널 옵션을 보면 ARMv6 이전 SMP 프로세서(아직은 이렇게 만든 SoC가 없는 것 같다. 하지만 미래에 어떤 회사가 만들지 모르는 법이므로…)는 직접 user space에서 atomic하게 cmpxchg를 수행할 수 없다. 따라서 느리더라도 POSIX call을 사용하여 커널에 진입한 후 처리하도록 이 방법을 사용하여야 한다.
__kuser_cmpxchg64:
user space에서 64 bit atomic operation을 처리하기 위해 이러한 기능에 대한 아키텍처의 지원 여부에 따라 구현 방법을 3가지로 달리 구현되었다.
1) 커널에 위탁 처리하는 방법
arch/arm/kernel/entry-armv.S
/* * Due to the length of some sequences, __kuser_cmpxchg64 spans 2 regular * kuser "slots", therefore 0xffff0f80 is not used as a valid entry point. */ __kuser_cmpxchg64: @ 0xffff0f60 #if defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) /* * Poor you. No fast solution possible... * The kernel itself must perform the operation. * A special ghost syscall is used for that (see traps.c). */ stmfd sp!, {r7, lr} ldr r7, 1f @ it's 20 bits swi __ARM_NR_cmpxchg64 ldmfd sp!, {r7, pc} 1: .word __ARM_NR_cmpxchg64
CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG 커널 옵션은 주로 user space에서 atomic operation을 지원하지 못하는 SMP 아키텍처에서 사용한다. atomic operation이 필요한 경우 커널은 인터럽트되지 않도록 블럭한 후 처리할 수 있기 때문에 user space에서의 kuser_cmpxchg64 요청은 arm syscall을 사용하여 커널에 위탁하여 처리하고 그 결과를 내려보내주게 구현되었다.
- 코드 라인 15에서 r7, lr 레지스터 사용해야 하므로 잠시 스택에 보관한다.
- 코드 라인 16~17에서 r7에 __ARM_NR_cmpxchg64에 해당하는 arm syscall 넘버를 대입한 후 swi swi 명령을 사용하여 arm syscall 호출을 수행한다.
- 코드 라인 18에서 스택으로 부터 r7을 복구하고, lr 주소로 jump 한다.
2) atomic이 지원되는 아키텍처에서 직접 수행하는 방법
#elif defined(CONFIG_CPU_32v6K) stmfd sp!, {r4, r5, r6, r7} ldrd r4, r5, [r0] @ load old val ldrd r6, r7, [r1] @ load new val smp_dmb arm 1: ldrexd r0, r1, [r2] @ load current val eors r3, r0, r4 @ compare with oldval (1) eoreqs r3, r1, r5 @ compare with oldval (2) strexdeq r3, r6, r7, [r2] @ store newval if eq teqeq r3, #1 @ success? beq 1b @ if no then retry smp_dmb arm rsbs r0, r3, #0 @ set returned val and C flag ldmfd sp!, {r4, r5, r6, r7} usr_ret lr
CONFIG_CPU_32v6K는 user space에서도 atomic operation을 사용할 수 있는 armv6 이상의 SMP 아키텍처에서 사용하는 커널 옵션이다. 이러한 경우 user space에서 직접 ldrex 및 strex를 사용하여 atomic operation을 수행할 수 있다.
- 코드 라인 3에서 r4~r7까지 레지스터를 스택에 백업한다.
- 코드 라인 4에서 r0(old)가 가리키는 주소에서 double word(64bit) 값을 읽어서 r4와 r5 레지스터에 저장한다.
- ldr r4, [r0]; ldr r5, [r0, #4]와 동일한 결과다.
- 코드 라인 5에서 r1(new)가 가리키는 주소에서 double word(64bit) 값을 읽어서 r6와 r7 레지스터에 저장한다.
- 코드 라인 6에서 다음 ldrexd를 호출하기 전에 dmb를 사용하여 메모리에 대해 order 문제가 생기지 않도록 한다.
- 코드 라인 7에서 r2(ptr)가 가리키는 주소에서 double word(64bit) 값을 읽어서 r0와 r1 레지스터에 저장한다.
- 코드 라인 8~10에서 읽은 ptr 값(r0, r1)과 old 값(r4, r5)가 비교를 하여 같은 경우 r2 주소에 new(r6, r7) 값을 저장하되 결과를 r3에 저장한다.
- 코드 라인 11~12에서 저장 시 이 캐시 라인이 exclusive되지 않아 결과(r3)이 실패(#1)한 경우 다시 레이블 1로 이동하여 성공할 때까지 반복한다.
- 다른 cpu에서 이 exclusive한 캐시라인에 접근하는 경우 open되어 strex 명령을 수행 시 실패를 얻게된다.
- 코드 라인 13에서 다시 메모리 배리어를 사용하여 order 문제가 생기지 않도록 한다.
- 코드 라인 14에서 0에서 r3(0=기록한 경우, 그 외=oldval과 newval이 달라 기록하지 않은 경우)와 캐리(C)까지 뺀 후 r0 레지스터에 담는다.
- 코드 라인 15~16에서 스택에 백업해둔 레지스터들을 복구하고 lr 주소로 복귀한다.
3) UP 시스템에서 atomic 구현
#elif !defined(CONFIG_SMP) #ifdef CONFIG_MMU /* * The only thing that can break atomicity in this cmpxchg64 * implementation is either an IRQ or a data abort exception * causing another process/thread to be scheduled in the middle of * the critical sequence. The same strategy as for cmpxchg is used. */ stmfd sp!, {r4, r5, r6, lr} ldmia r0, {r4, r5} @ load old val ldmia r1, {r6, lr} @ load new val 1: ldmia r2, {r0, r1} @ load current val eors r3, r0, r4 @ compare with oldval (1) eoreqs r3, r1, r5 @ compare with oldval (2) 2: stmeqia r2, {r6, lr} @ store newval if eq rsbs r0, r3, #0 @ set return val and C flag ldmfd sp!, {r4, r5, r6, pc} .text kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @ sp = saved regs. r7 and r8 are clobbered. @ 1b = first critical insn, 2b = last critical insn. @ If r4 >= 1b and r4 <= 2b then saved pc_usr is set to 1b. mov r7, #0xffff0fff sub r7, r7, #(0xffff0fff - (0xffff0f60 + (1b - __kuser_cmpxchg64))) subs r8, r4, r7 rsbcss r8, r8, #(2b - 1b) strcs r7, [sp, #S_PC] #if __LINUX_ARM_ARCH__ < 6 bcc kuser_cmpxchg32_fixup #endif ret lr .previous #endif kuser_pad __kuser_cmpxchg64, 64
user space에서 atomic이 구현되지 않는 UP 아키텍처에서 __kuser_cmpxchg64 함수의 기능 구현은 atomic과 관련 없이 단순하게 구현되어 있다. 그 구현 루틴 아래에 위치한 kuser_cmpxchg64_fixup 루틴을 살펴보기 전에는 그 어떠한 atomic 관련한 부가 루틴도 찾아 볼 수가 없다. atomic에 대한 보장을 위해 자세한 것은 kuser_cmpxchg64_fixup 레이블에서 설명하기로 한다.
- 코드 라인 11에서 r4, r5, r6, lr 레지스터를 스택에 백업해둔다.
- 코드 라인 12에서 r0위치에 있는 64bit old 값을 r4와 r5 레지스터에 로드한다.
- 코드 라인 13에서 r1 위치에 있는 64bit new 값을 r6와 lr 레지스터에 로드한다.
- 코드 라인 14에서 r2 위치에 있는 64bit ptr 값을 r0와 r1 레지스터에 로드한다.
- 코드 라인 15~17에서 old 값과 ptr 값을 비교하여 같은 경우 new 값을 ptr에 저장한다.
- 코드 라인 18에서 0에서 r3(0=기록한 경우, 그 외=old 값과 new 값이 달라 기록하지 않은 경우)와 캐리(C)까지 뺀 후 r0 레지스터에 담는다.
- 코드 라인 19에서 스택에 백업해둔 레지스터들을 복구하고 lr 주소로 복귀한다.
kuser_cmpxchg64_fixup:
user space에서 atomic이 구현되지 않는 UP 아키텍처에서 __kuser_cmpxchg64 루틴을 수행 시 atomic하게 처리해야 하는 구간 즉, 레이블 1과 레이블 2 사이를 수행하는 도중에 irq, fiq, dabt 등을 만나게 되는 경우 해당 exception을 처리한 후 되돌아갈 주소를 atomic 구간의 가장 윗 부분으로 바꾸기 위해 스택의 pt_regs의 pc를 조작하는 방법을 사용한다.
- 코드 라인 28~29에서 atomic operation이 시작되는 레이블 1:의 가상 주소 값을 알아온다.
- r7 <- ffff0f60 + (1b – __kuser_cmpxchg64)를 대입하고 싶지만 operand에 사용하는 값의 크기 제한으로 인해 두 개의 명령을 사용하였다.
- 코드 라인 30~32에서 인터럽트 되었을 때의 pc 주소가 담긴 r4 레지스터 값이 atomic 하게 처리할 구간 범위(레이블 1: ~ 레이블 2:)인 경우 스택에 저장해 둔 pt_regs 구조체 중 pc 위치에 레이블 1 주소를 저장하여 다시 인터럽트 복귀 시 atomic operation을 다시 시도하게 변경한다.
- 코드 라인 33~35에서 arm 아키텍처가 버전 6보다 이전인 경우에는 위의 atomic opeation 수행 도중 인터럽트 된 것이 아니라면 kuser_cmpxchg32 명령 수행 도중에 발생한 일인지 확인하여 역시 같은 방식으로 복귀 주소를 변경하게 한다.
__kuser_memory_barrier:
arch/arm/kernel/entry-armv.S
__kuser_memory_barrier: @ 0xffff0fa0 smp_dmb arm usr_ret lr kuser_pad __kuser_memory_barrier, 32
SMP 시스템에서 메모리 배리어를 사용하여 이전 로직의 메모리 액세스와 연관하여 order 문제가 생기지 않도록 막는다.
- 참고:
- Barriers | 문c
- Barriers of ARMv7 | 문c
__kuser_cmpxchg:
user space에서 32 bit atomic operation을 처리하기 위해 이러한 기능에 대한 아키텍처의 지원 여부에 따라 구현 방법을 3가지로 달리 구현되었다.
1) 커널에 위탁 처리하는 방법
arch/arm/kernel/entry-armv.S
__kuser_cmpxchg: @ 0xffff0fc0 #if defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) /* * Poor you. No fast solution possible... * The kernel itself must perform the operation. * A special ghost syscall is used for that (see traps.c). */ stmfd sp!, {r7, lr} ldr r7, 1f @ it's 20 bits swi __ARM_NR_cmpxchg ldmfd sp!, {r7, pc} 1: .word __ARM_NR_cmpxchg
CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG 커널 옵션은 주로 user space에서 atomic operation을 지원하지 못하는 SMP 아키텍처에서 사용한다. atomic operation이 필요한 경우 커널은 인터럽트되지 않도록 블럭한 후 처리할 수 있기 때문에 user space에서의 kuser_cmpxchg 요청은 arm syscall을 사용하여 커널에 위탁하여 처리하고 그 결과를 내려보내주게 구현되었다.
- 코드 라인 10에서 r7, lr 레지스터 사용해야 하므로 잠시 스택에 보관한다.
- 코드 라인 11~12에서 r7에 __ARM_NR_cmpxchg64에 해당하는 arm syscall 넘버를 대입한 후 swi swi 명령을 사용하여 arm syscall 호출을 수행한다.
- 코드 라인 13에서 스택으로 부터 r7을 복구하고, lr 주소로 jump 한다.
2) UP 시스템에서 atomic 구현
#elif __LINUX_ARM_ARCH__ < 6 #ifdef CONFIG_MMU /* * The only thing that can break atomicity in this cmpxchg * implementation is either an IRQ or a data abort exception * causing another process/thread to be scheduled in the middle * of the critical sequence. To prevent this, code is added to * the IRQ and data abort exception handlers to set the pc back * to the beginning of the critical section if it is found to be * within that critical section (see kuser_cmpxchg_fixup). */ 1: ldr r3, [r2] @ load current val subs r3, r3, r0 @ compare with oldval 2: streq r1, [r2] @ store newval if eq rsbs r0, r3, #0 @ set return val and C flag usr_ret lr .text kuser_cmpxchg32_fixup: @ Called from kuser_cmpxchg_check macro. @ r4 = address of interrupted insn (must be preserved). @ sp = saved regs. r7 and r8 are clobbered. @ 1b = first critical insn, 2b = last critical insn. @ If r4 >= 1b and r4 <= 2b then saved pc_usr is set to 1b. mov r7, #0xffff0fff sub r7, r7, #(0xffff0fff - (0xffff0fc0 + (1b - __kuser_cmpxchg))) subs r8, r4, r7 rsbcss r8, r8, #(2b - 1b) strcs r7, [sp, #S_PC] ret lr .previous #else #warning "NPTL on non MMU needs fixing" mov r0, #-1 adds r0, r0, #0 usr_ret lr #endif
user space에서 atomic이 구현되지 않는 UP 아키텍처에서 __kuser_cmpxchg 함수의 기능 구현은 atomic과 관련 없이 단순하게 구현되어 있다. 그 구현 루틴 아래에 위치한 kuser_cmpxchg_fixup 루틴을 살펴보기 전에는 그 어떠한 atomic 관련한 부가 루틴도 찾아 볼 수가 없다. atomic에 대한 보장을 위해 자세한 것은 kuser_cmpxchg_fixup 레이블에서 설명하기로 한다.
- 코드 라인 14에서 r2(ptr)위치에 있는 값을 r3 레지스터에 로드한다.
- 코드 라인 15~16에서 r0(old) 레지스터 값과 r3 레지스터 값을 비교하여 같은 경우 r1(new) 레지스터 값을 r2(ptr) 레지스터가 가리키는 주소에 저장한다.
- 코드 라인 17~18은 0에서 r3(0=기록한 경우, 그 외=old 값과 new 값이 달라 기록하지 않은 경우)와 캐리(C)까지 뺀 후 r0 레지스터에 담고 lr 주소로 복귀한다.
kuser_cmpxchg_fixup:
user space에서 atomic이 구현되지 않는 UP 아키텍처에서 __kuser_cmpxchg 루틴을 수행 시 atomic하게 처리해야 하는 구간 즉, 레이블 1과 레이블 2 사이를 수행하는 도중에 irq, fiq, dabt 등을 만나게 되는 경우 해당 exception을 처리한 후 되돌아갈 주소를 atomic 구간의 가장 윗 부분으로 바꾸기 위해 스택의 pt_regs의 pc를 조작하는 방법을 사용한다.
- 코드 라인 27~28에서 atomic operation이 시작되는 레이블 1:의 가상 주소 값을 알아온다.
- r7 <- ffff0fc0 + (1b – __kuser_cmpxchg)를 대입하고 싶지만 operand에 사용하는 값의 크기 제한으로 인해 두 개의 명령을 사용하였다.
- 코드 라인 29~32에서 인터럽트 되었을 때의 pc 주소가 담긴 r4 레지스터 값이 atomic 하게 처리할 구간 범위(레이블 1: ~ 레이블 2:)인 경우 스택에 저장해 둔 pt_regs 구조체 중 pc 위치에 레이블 1 주소를 저장하여 다시 인터럽트 복귀 시 atomic operation을 다시 처음 부터 시도하게 변경하고 lr 주소로 복귀한다.
3) atomic이 지원되는 아키텍처에서 직접 수행하는 방법
#else smp_dmb arm 1: ldrex r3, [r2] subs r3, r3, r0 strexeq r3, r1, [r2] teqeq r3, #1 beq 1b rsbs r0, r3, #0 /* beware -- each __kuser slot must be 8 instructions max */ ALT_SMP(b __kuser_memory_barrier) ALT_UP(usr_ret lr) #endif kuser_pad __kuser_cmpxchg, 32
user space에서도 atomic operation을 사용할 수 있는 armv6 이상의 SMP 아키텍처에서 사용하는 커널 옵션이다. 이러한 경우 user space에서 직접 ldrex 및 strex를 사용하여 atomic operation을 수행할 수 있다.
- 참고
- Atomic Operation | 문c
- Exclusive loads and store | 문c
- 코드 라인 3에서 메모리 배리어를 사용하여 이전 로직의 메모리 액세스와 연관하여 order 문제가 생기지 않도록 막는다.
- 코드 라인 4~6에서 r2(ptr)가 가리키는 주소에서 값을 읽은 값을 r3 레지스터에 저장한다. 이 값과 r0(old) 값을 비교하여 같은 경우 r1(new) 값을 r2(ptr)가 가리키는 주소에 저장한다. 저장 결과는 r3에 담는다. (성공=0, 실패=1)
- 코드 라인 7~8에서 저장 시 이 캐시 라인이 exclusive되지 않아 결과(r3)이 실패(#1)한 경우 다시 레이블 1로 이동하여 성공할 때까지 반복한다.
- 다른 cpu에서 이 exclusive한 캐시라인에 접근하는 경우 open되어 strex 명령을 수행 시 실패를 얻게된다.
- 코드 라인 9는 0에서 r3(0=기록한 경우, 그 외=oldval과 newval이 달라 기록하지 않은 경우)와 캐리(C)까지 뺀 후 r0 레지스터에 담는다.
- 코드 라인 11~12에서 복귀를 하되 SMP 시스템인 경우 다시 메모리 배리어를 사용하여 order 문제가 생기지 않도록 한다.
__kuser_get_tls:
arch/arm/kernel/entry-armv.S
__kuser_get_tls: @ 0xffff0fe0 ldr r0, [pc, #(16 - 8)] @ read TLS, set in kuser_get_tls_init usr_ret lr mrc p15, 0, r0, c13, c0, 3 @ 0xffff0fe8 hardware TLS code kuser_pad __kuser_get_tls, 16 .rep 3 .word 0 @ 0xffff0ff0 software TLS value, then .endr
TLS(Thread Local Storage) 값을 알아온다. 2가지의 구현을 사용한다.
- S/W TLS
- 0xffff_0ff0 주소에 TLS 값을 보관해두고 그 값을 읽어 사용한다.
- H/W TLS
- user 에서 읽기만 가능한 TPIDRURO 레지스터에서 값을 읽어 사용한다.
- 코드 라인 2~3에서 0xfff_0ff0 위치에서 TLS 값을 가져와 r0 레지스터에 담고 lr 주소로 복귀한다.
- 코드 라인 4에는 TPIDRURO 레지스터에서 값을 읽어 r0 레지스터에 담는 코드를 두었다.
- H/W TLS 레지스터를 지원하는 경우 이 코드를 복사하여 코드 라인 2의 주소인 0xffff_0fe0에 복사하기 위해 사용된다.
- setup_arch() -> paging_init() -> devicemaps_init() -> early_trap_init() -> kuser_init() 함수 내부에 다음 코드를 찾을 수 있다.
- memcpy(vectors + 0xfe0, vectors + 0xfe8, 4);
- setup_arch() -> paging_init() -> devicemaps_init() -> early_trap_init() -> kuser_init() 함수 내부에 다음 코드를 찾을 수 있다.
- H/W TLS 레지스터를 지원하는 경우 이 코드를 복사하여 코드 라인 2의 주소인 0xffff_0fe0에 복사하기 위해 사용된다.
매크로 함수
usr_ret 매크로
arch/arm/kernel/entry-armv.S
.macro usr_ret, reg #ifdef CONFIG_ARM_THUMB bx \reg #else ret \reg #endif .endm
\reg 주소로 복귀한다.
kuser_pad 매크로
arch/arm/kernel/entry-armv.S
.macro kuser_pad, sym, size .if (. - \sym) & 3 .rept 4 - (. - \sym) & 3 .byte 0 .endr .endif .rept (\size - (. - \sym)) / 4 .word 0xe7fddef1 .endr .endm
현재 주소 위치 – sym 주소가 4바이트 단위로 정렬되지 않은 경우 정렬을 위해 0으로 채운다.(0~3 바이트) 그 이후 sym 주소부터 size 만큼의 공간 중 현재 위치부터 0xe7fddef1 값으로 채운다.
- 예) kuser_pad __kuser_memory_barrier, 32
- __kuser_memory_barrier 함수 위치 부터 32 바이트 공간내에서 빈 자리를 0xe7fddef1 값으로 채운다.
- 0xffff0fa0: 0xf57ff05b 0xe12fff1e 0xe7fddef1 0xe7fddef1
- 0xffff0fb0: 0xe7fddef1 0xe7fddef1 0xe7fddef1 0xe7fddef1
- __kuser_memory_barrier 함수 위치 부터 32 바이트 공간내에서 빈 자리를 0xe7fddef1 값으로 채운다.
참고
- Kernel-provided User Helpers | kernel.org
- devicemaps_init() | 문c
- ARM Exception Vector | 문c
- ARM Exception Handler -1- | 문c