Exception -7- (ARM64 Vector)

<kernel v6.0>

ARM64 Exception

ARM64 Exception 종류

ARM64 Exception 벡터에 포함된 exception 종류는 다음과 같다.  Synchronous를 제외하고 나머지 3개는 비동기이다.

  • Synchronous
    • Instruction Abort
      • MMU를 통해 명령 fetch 에러
        • 예) Execute Never로 마킹된 매핑 주소로 접근하는 경우 발생한다.
    • Data Abort
      • MMU를 통해 데이터 fetch 에러
        • 예) 권한 실패 및 SP 또는 PC alignment 체크에 의해 발생한다.
    • Exception Generating 명령들
      • SVC(Supervisor Call)
        • 유저 모드에서 OS 서비스 요청 (syscall, 동기 exception)
      • HVC
        • Guest OS에서 Hypervisor 요청 (동기 exception)
      • SMC
        • normal(non-secure) 월드에서 secure 월드 서비스 요청 (동기 exception)
  • IRQ
    • 디바이스에서 인터럽트 서비스를 요청하는 경우 발생한다.
    • irq 처리 중 fiq 요청에 대한 preemption은 가능하지만 irq 재진입(irq preemption)은 허용하지 않는다.
    • ARM32 아키텍처는 irq preemption을 허용하지만 ARM 리눅스 커널이 이를 허용하지 않는 설계로 구현되어 있다.
    • Pesudo-NMI를 지원하는 ARM64 아키텍처와 ARM64 커널이 사용될 때 nmi 관련 디바이스에 한해 irq preemption을 허용한다.
  • FIQ
    • IRQ보다 더 빠른 인터럽트 서비스를 요청하는 경우 발생한다.
    • 리눅스 커널의 fiq 핸들러 함수인 handle_fiq_as_nmi()에 처리 코드를 추가하여 사용하여야 한다.
      • 디폴트 처리로 아무것도 하지 않는다. (nop)
      • 예) rpi2의 경우 usb 호스트 드라이버(dwc_otg)에서 사용한다.
    • irq 보다 높은 우선 순위를 갖고, irq 처리 중에도 preemption될 수 있다.
    • 일반적으로 secure 펌웨어에서 fiq를 처리하고, 리눅스 커널에서는 fiq를 처리하지 않는다.
  • SError (System Error)
    • 비동기 Data Abort
      • 예) 캐시 라인을 시스템 메모리에 writeback하느 과정에서 중단되는 경우 발생한다.

 

Reset

최고 레벨의 특별한 exception 이다. 리셋되는 주소는 IMPLEMENTATION DEFINED이고, RVBAR_ELn 레지스터로 지정할 수 있다.

 

Exception 레벨별 벡터 테이블

다음 그림은 VHE 확장이 없는 ARMv8 아키텍처에서 운영되는 각 Exception 레벨에서 운영중인 벡터들을 보여준다.

 

다음 그림은 VHE 확장이 있는 ARMv8 아키텍처에서 운영되는 각 Exception 레벨에서 운영중인 벡터들을 보여준다.

 

리눅스 커널의 EL2 부팅

예) rpi4가 부팅하면 Hyper 모드로 호스트 OS가 동작하도록 EL2로 부팅 후 EL2용 벡터와 핸들러들을 설치한다. 그 후 EL1용 벡터와 핸들러들을 설치한다.  ARMv8.1의 VHE 지원 여부에 따라 호스트 커널은 각각 EL2 또는 EL1에서 동작한다. 즉 처음 부팅된 리눅스 커널이 하이퍼바이저 역할을 수행하는 것이다. 그 후 Qemu/KVM을 사용하여 동작시키는 Guest OS들은 EL1에 벡터와 핸들러들을 설치하고 EL1에서 수행한다.

 

Exception 벡터 테이블 주소

벡터 테이블의 주소는 AArch64 및 AArch32에 대해 다음과 같은 레지스터를 사용하여 지정한다.  리눅스 커널의 경우 boot cpu는 __primary_switched: 레이블에서 EL1용 벡터 위치를 VBAR_EL1에 지정하고, 나머지 cpu들은 __secondary_switched: 레이블에서 지정하였다. 하이퍼모드로 운영되는 경우 install_el2_stub: 레이블에서 EL2용 벡터 위치를 VBAR_EL2에 지정한다.

  • for AArch64
    • VBAR_EL3,  VBAR_EL2,  VBAR_EL1
  • for AArch32
    •  HVBAR

 

Vector 선언

EL1 벡터 (or EL2 VHE)

vectors

arch/arm64/kernel/entry.S

/*
 * Exception vectors for spectre mitigations on entry from EL1 when
 * kpti is not in use.
 */
        .macro generate_el1_vector, bhb
.Lvector_start\@:
        kernel_ventry   1, t, 64, sync          // Synchronous EL1t
        kernel_ventry   1, t, 64, irq           // IRQ EL1t
        kernel_ventry   1, t, 64, fiq           // FIQ EL1h
        kernel_ventry   1, t, 64, error         // Error EL1t

        kernel_ventry   1, h, 64, sync          // Synchronous EL1h
        kernel_ventry   1, h, 64, irq           // IRQ EL1h
        kernel_ventry   1, h, 64, fiq           // FIQ EL1h
        kernel_ventry   1, h, 64, error         // Error EL1h

        .rept   4
        tramp_ventry    .Lvector_start\@, 64, 0, \bhb
        .endr
        .rept 4
        tramp_ventry    .Lvector_start\@, 32, 0, \bhb
        .endr
        .endm
  • 총 16개의 벡터 엔트리들로 구성되며, 각각의 엔트리에 0x80 바이트를 제공한다.
    • ARMv7이 엔트리당 4바이트의 공간만을 제공한 것에 비해 ARMv8은 충분한 크기를 제공하는 것을 알 수 있다.
  • 16개의 벡터 엔트리들을 4개씩 전체 4 세트로 묶는 경우 각 세트들은 다음과  같은 특징을 가지고있다.
    • t와 h는 thread와 handler의 약자이다.
      • 예) ‘el1t
        • el1에서 동작 중인 커널이 SP_EL1을 선택하여 사용할 때 exception이 발생한것이다.
      • 예) ‘el1h
        • el1에서 동작 중인 커널이 SP_EL0을 선택하여 사용할 때 exception이 발생한것이다.
    • 첫 번째 4개 벡터
      • 현재 코드가 수행 중인 exception 레벨과 동일한 exception 레벨에서 excetion이 발생하였고, SP_EL0를 사용하고 있었다.
      • 리눅스 커널은 이에 해당하는 핸들러들을 처리하지 않도록 invalid 핸들러들로 연결되어 있다.
    • 두 번째, 현재 코드가 수행 중인 exception 레벨과 동일한 exception 레벨에서 excetion이 발생하였고, SPn(n>0)을 사용하고 있었다.
      • 커널(EL1)에서 exception이 발생하였고, 이 때 SP_EL1을 사용 중인 경우이다.
      • 예) sync exception이 발생하는 경우 el1_sync: 레이블로 이동하여 sync 관련 루틴을 수행한다.
    • 세 번째, 현재 코드가 수행 중인 exception 레벨보다 하위 exception 레벨이며 AArch64 수행 중이다.
      • AArch64 유저(EL0)에서 exception이 발생한 경우이다.
    • 네 번째, 현재 코드가 수행 중인 exception 레벨보다 하위 exception 레벨이며 AArch32 수행 중이다.
      • AArch32 유저(EL0)에서 exception이 발생한 경우이다.

 

EL2 벡터

KVM 성능을 높여주는 ARMv8.1의 VHE(Virtualization Host Extension) 기능 지원 여부에 따라 다음과 같이 두 개의 el2 벡터를 지원한다.

  • 커널이 el2에서 처음 부팅한 경우엔 hvc 명령만 대응하기 위해 다음 벡터를 사용한다. 커널은 hvc를 제외한 실제 운영을 el1에서수행한다.
    • arch/arm64/kernel/hyp-stub.S에 위치한 __hyp_stub_vectors를 사용한다.
  • 커널이 VHE를 지원하고 el2에서 동작하는 경우 커널이 hvc 및 el2 인터럽트를 처리하기 위해 el1에서 사용하던 vectors를 사용한다.

 

__kvm_hyp_vector – with nVHE

arch/arm64/kernel/hyp-stub.S

       ventry  el2_sync_invalid                // Synchronous EL2t
        ventry  el2_irq_invalid                 // IRQ EL2t
        ventry  el2_fiq_invalid                 // FIQ EL2t
        ventry  el2_error_invalid               // Error EL2t

        ventry  elx_sync                        // Synchronous EL2h
        ventry  el2_irq_invalid                 // IRQ EL2h
        ventry  el2_fiq_invalid                 // FIQ EL2h
        ventry  el2_error_invalid               // Error EL2h

        ventry  elx_sync                        // Synchronous 64-bit EL1
        ventry  el1_irq_invalid                 // IRQ 64-bit EL1
        ventry  el1_fiq_invalid                 // FIQ 64-bit EL1
        ventry  el1_error_invalid               // Error 64-bit EL1

        ventry  el1_sync_invalid                // Synchronous 32-bit EL1
        ventry  el1_irq_invalid                 // IRQ 32-bit EL1
        ventry  el1_fiq_invalid                 // FIQ 32-bit EL1
        ventry  el1_error_invalid               // Error 32-bit EL1
SYM_CODE_END(__hyp_stub_vectors)

 

Vector가 저장되는 섹션 위치

ENTRY_TEXT

include/asm-generic/vmlinux.lds.h

#define ENTRY_TEXT                                                      \
                ALIGN_FUNCTION();                                       \
                __entry_text_start = .;                                 \
                *(.entry.text)                                          \
                __entry_text_end = .;

.extry.text 섹션에 벡터코드가 포함되며 ENTRY_TEXT로 정의되어 있다.

 

arch/arm64/kernel/vmlinux.lds.S

.       .head.text : {
                _text = .;
                HEAD_TEXT
        }
       .text : ALIGN(SEGMENT_ALIGN) {  /* Real text segment            */
                _stext = .;             /* Text and read-only data      */
                        IRQENTRY_TEXT
                        SOFTIRQENTRY_TEXT
                        ENTRY_TEXT
                        TEXT_TEXT
                        SCHED_TEXT
                        CPUIDLE_TEXT
                        LOCK_TEXT
                        KPROBES_TEXT
                        HYPERVISOR_TEXT
                        IDMAP_TEXT
                        *(.gnu.warning)
                . = ALIGN(16);
                *(.got)                 /* Global offset table          */
        }

벡터코드가 포함된 ENTRY_TEXT는 .text 영역에 포함되어 있다.

 

kernel_ventry 매크로

arch/arm64/kernel/entry.S

        .macro kernel_ventry, el:req, ht:req, regsize:req, label:req
        .align 7
.Lventry_start\@:
        .if     \el == 0
        /*
         * This must be the first instruction of the EL0 vector entries. It is
         * skipped by the trampoline vectors, to trigger the cleanup.
         */
        b       .Lskip_tramp_vectors_cleanup\@
        .if     \regsize == 64
        mrs     x30, tpidrro_el0
        msr     tpidrro_el0, xzr
        .else
        mov     x30, xzr
        .endif
.Lskip_tramp_vectors_cleanup\@:
        .endif

        sub     sp, sp, #PT_REGS_SIZE
#ifdef CONFIG_VMAP_STACK
        /*
         * Test whether the SP has overflowed, without corrupting a GPR.
         * Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
         * should always be zero.
         */
        add     sp, sp, x0                      // sp' = sp + x0
        sub     x0, sp, x0                      // x0' = sp' - x0 = (sp + x0) - x0 = sp
        tbnz    x0, #THREAD_SHIFT, 0f
        sub     x0, sp, x0                      // x0'' = sp' - x0' = (sp + x0) - sp = x0
        sub     sp, sp, x0                      // sp'' = sp' - x0 = (sp + x0) - x0 = sp
        b       el\el\ht\()_\regsize\()_\label

0:
        /*
         * Either we've just detected an overflow, or we've taken an exception
         * while on the overflow stack. Either way, we won't return to
         * userspace, and can clobber EL0 registers to free up GPRs.
         */

        /* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */
        msr     tpidr_el0, x0

        /* Recover the original x0 value and stash it in tpidrro_el0 */
        sub     x0, sp, x0
        msr     tpidrro_el0, x0

        /* Switch to the overflow stack */
        adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0

        /*
         * Check whether we were already on the overflow stack. This may happen
         * after panic() re-enables interrupts.
         */
        mrs     x0, tpidr_el0                   // sp of interrupted context
        sub     x0, sp, x0                      // delta with top of overflow stack
        tst     x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?
        b.ne    __bad_stack                     // no? -> bad stack pointer

        /* We were already on the overflow stack. Restore sp/x0 and carry on. */
        sub     sp, sp, x0
        mrs     x0, tpidrro_el0
#endif
        b       el\el\ht\()_\regsize\()_\label
.org .Lventry_start\@ + 128     // Did we overflow the ventry slot?
        .endm

Exception이 발생되면 16개의 exception vector 엔트리 중 해당하는 exception 위치로 jump 되어 수행한다.

  • 코드 라인 1에서 인자 들은 다음과 같은 의미를 가지고 있고 이를 이용하여 총 16개의 점프할 C 함수를 구성한다.
    • @el 은 exception 레벨(0, 1, 2)
    • @ht는 h(handle) 또는 t(thread)
    • @regsize는 시스템 레지스터의 사이즈 32 또는 64
    • @label은 4가지 sync, irq, fiq, error로 점프할 함수명을 조합할 때 사용된다.
  • 코드 라인 2에서 .align 7을 지정하여 벡터간 128바이트 단위로 정렬하도록 한다.
  • 코드 라인 4~15에서 64비트 EL0(유저)에서 exception이 발생하여 진입한 경우 EL0에서 커널을 감추기 위한 보안 목적으로 사용되는 trampoline 벡터를 운영한다. 이러한 경우 trampoline 페이지 테이블 주소를 x30에 백업하고, 0으로 기록한다. 단 32비트 EL0에서 exception이 발생한 경우엔 x30에만 0을 대입한다.
    •  trampoline 벡터를 운영하지 않는 경우를 위해 b  .Lskip_tramp_vectors_cleanup\@  명령을 처음 코드에 사용하였고, 이 코드는 trampoline 벡터를 운영할 때엔 trampoline 벡터의 첫 명령어로 교체된다.
    • 고성능 아키텍처에서 문제가 되었던 추측 공격(Speculation 공격, side 채널 공격)을 통해 유저 영역에서 MMU 권한 체크를 우회하여 커널 액세스가 가능해지는 버그가 있다. 이러한 커널 액세스를 방지하기 위해 유저 영역에서 커널 영역을 완전히 분리하여 액세스를 방지하는 CONFIG_UNMAP_KERNEL_AT_EL0 커널 옵션이며 이를 설정하여 사용하는 경우 성능을 약간 희생한다.
    • 참고로 커널 영역 이외에 벡터 테이블 조차 감추기 위해 trampoline 페이지 테이블을 운영하며 exception이 발생할 경우에만 커널 페이지 테이블로 전환하여 사용한다.
    • 위 보안 기능을 사용하기 위해 fake cpu 기능(capability) 하나를 추가하였다. 이 ARM64_UNMAP_KERNEL_AT_EL0 capability를 지원하는 시스템에서만 코드가 동작하게 제한한다.
  • 코드 라인 19에서 context 전환 목적으로 현재 레지스터들을 백업하기 위해 pt_regs 구조체 사이즈(S_FRAME_SIZE)만큼  스택을 증가시킨다. (growth down)
  • 코드 라인 20에서 CONFIG_VMAP_STACK 커널 옵션을 사용하면 스택을 생성할 때 vmalloc 공간을 이용하여 생성한다. 스택은 물리적으로 연속된 공간이 필요 없고, 가상 주소 공간만 연속되면 문제 없으므로 vmalloc 공간에 리니어 매핑하여 사용하는 것이 메모리의 파편화 방지에 도움이 된다.
  • 코드 라인 26~27에서 sp 레지스터와 x0 레지스터 값을 교환할 목적으로먼저 x0 <- sp를 수행한다.이때 sp에는 sp + x0 값이 잠시 담겨있다.
    • 두 레지스터 간의 교환이 필요할 때 DRAM 메모리를 사용할 수 있다. 그러나 exception 루틴에서 DRAM에 접근하면 수백 사이클의 대기 시간이 소요되어 매우 느려지므로 수학적으로 가감 연산만을 사용하여 두 레지스터를 교환할 수 있다.
  • 코드 라인 28에서 sp 레지스터를 범용 레지스터인 x0 레지스터로 옮겨야 tbnz 명령을 사용하여 특정 비트가 설정되었는지 여부를 확인할 수 있다. 여기서 스택 사이즈 또는 태스크 사이즈로 정렬되었는지 여부를 테스트하여 정렬되지 않은 경우 0: 레이블로 이동한다.
    • 스택 레지스터의 THREAD_SHIFT 위치의 비트가 설정된 경우 스택 정렬되지 않은 것으로 판단한다.
  • 코드 라인 29~30에서 x0 레지스터와 sp 레지스터 값을 다시 원래대로 복귀시킨다.
  • 코드 라인 31에서 이 매크로에 전달된 @el , @ht, @regsize 및 @label을 연결하여 만든 레이블로 점프한다.
    • 문자열과 매크로 인자 값을 연결하는 \() 문자는 접착재 같이 사용되므로 제거한다.
    • 예) \el=1, \ht=h, \regsize=64, \label=irq
      • b el\el\ht\()_\regsize\()_\label -> “b el1h_64_irq
    • 위에서 만들어진 점프 레이블은 총 16가지이며, 이들은 entry_handler 매크로에서 생성된다.
  • 코드 라인 33에서 스택이 정렬되지 않은채로 호출되어 이동해온 0 레이블이다. 이 레이블에서는 깨진 스택 대신 static하게 만들어진 per-cpu 오버플로우용 스택을 사용하는 코드로 진행된다.
  • 코드 라인 41에서 x0 레지스터에는 sp 레지스터 값과 바뀐채로 이동해왔다. 이 값을 잠시 tpidr_el0에 저장해둔다.
    • tpidr_el0 <- sp
  • 코드 라인 44~45에서 원래 x0 레지스터 값으로 복원하여 잠시 tpidrro_el0에 저장해둔다.
    • tpidrro_el0 <- x0
  • 코드 라인 48에서 현재 cpu에 해당하는 오버 플로우용 스택을 지정한다.
    • 스택은 growth down으로 동작하므로 스택의 높은 주소를 지정한다.
    • 정적 per-cpu 선언된 overflow_stack은 4K 크기로 사용된다.
  • 코드 라인 54~57에서 백업해둔 스택 값을 x0 레지스터로 읽어와서 현재 스택 범위 이내에 있는지 비교하여 범위 밖인 경우 __bad_stack 레이블로 이동한다.
  • 코드 라인 60~61에서 x0 레지스터와 스택을 다시 복구하고 이 매크로에 전달된 @el 값과 @label을 연결하여 만든 레이블로 점프한다.
  • 코드 라인 63에서 이 매크로에 전달된 @el , @ht, @regsize 및 @label을 연결하여 만든 레이블로 점프한다.

 

adr_this_cpu 매크로

arch/arm64/include/asm/assembler.h

.       /*
         * @dst: Result of per_cpu(sym, smp_processor_id()) (can be SP)
         * @sym: The name of the per-cpu variable
         * @tmp: scratch register
         */
.       .macro adr_this_cpu, dst, sym, tmp
        adrp    \tmp, \sym
        add     \dst, \tmp, #:lo12:\sym
        get_this_cpu_offset \tmp
        add     \dst, \dst, \tmp
        .endm

현재 cpu가 접근할 per-cpu 변수 @sym의 주소를 @dst에 담아 반환한다. (예: @dst <- 현재 cpu로 접근할 per-cpu @sym) 이 매크로는 스크래치 레지스터 @tmp가 하나 소모된다.

  • 코드 라인 2에서 인자로 전달받은 per-cpu 변수 @sym에 대한 페이지 단위로 절삭된 주소를 @tmp에 담는다.
    • 현재 cpu의 pc 주소로 부터 4G 이내의 상대 주소(relative addressing)를 사용하여 알아온다.
    • 예) @sym(overflow_stack 주소 0x1234_5678)이 현재 cpu의 주소로부터 +-4G 이내인 조건에서 페이지 단위로 절삭된 0x1234_5000 값을 @tmp에 대입한다.
  • 코드 라인 3에서 @sym의 하위 12비트를 @tmp에 더해 @dst에 산출한다.
    • 예) @dst(0x1234_5678) = @tmp(0x1234_5000) + 하위 12비트 @sym(0x678)
  • 코드 라인 4에서 현재 cpu에 대한 per-cpu offset 값이 담겨있는 값을 @tmp에 읽어온다.
  • 코드 라인 5에서 per-cpu offset 값이 적용된 per-cpu 변수 @tmp와 심볼 주소 @dst를 더해 반환한다.

 

adr vs adrp

  • adr 명령은 심볼의 주소를 상대 주소(relative addressing)를 사용하여 알아온다. 20비트(추가 1비트는 부호) 상대 주소 기법을 사용하므로 현재 cpu pc 주소로부터 심볼 위치까지 지원하는 범위가 +-1M 주소로 제한된다.
  • adrp 명령은 adr과 유사하지만 더 넓은 범위를 지원하기 위해 심볼 주소의 하위 12비트를 사용하지 않는 20비트(추가 1비트는 부호, 12비트 좌쉬프트) 상대 주소 기법을 사용하므로 더욱 넓은 범위인 +-4G 주소 범위에 한하여 페이지 단위로 절삭된 주소를 알아온다.

 

get_this_cpu_cpu_offset 매크로

arch/arm64/include/asm/assembler.h

#if defined(__KVM_NVHE_HYPERVISOR__) || defined(__KVM_VHE_HYPERVISOR__)
        .macro  get_this_cpu_offset, dst
        mrs     \dst, tpidr_el2
        .endm
#else
        .macro  get_this_cpu_offset, dst
alternative_if_not ARM64_HAS_VIRT_HOST_EXTN
        mrs     \dst, tpidr_el1
alternative_else
        mrs     \dst, tpidr_el2
alternative_endif
        .endm

각 cpu의 tpidr 레지스터에 per-cpu offset 값이 담겨 있고, 이 매크로에선 현재 cpu의 per-cpu offset 값을 @dst에 반환한다.

  • EL2로 부팅하여 VHE(Virtual Host Extension) 기능이 사용되고 있는 시스템에서는 tpidr_el1 레지스터를 지정해도 tpidr_el2 레지스터를 액세스한다. EL2로 부팅하였지만 EL1에서 운영하는 nVHE 시스템에서는 정확히 tpidr_el2 레지스터를 지정해서 사용해야 한다.

 

16개의 entry_handler 들

arch/arm64/kernel/entry.S

/*
 * Early exception handlers
 */
        entry_handler   1, t, 64, sync
        entry_handler   1, t, 64, irq
        entry_handler   1, t, 64, fiq
        entry_handler   1, t, 64, error

        entry_handler   1, h, 64, sync
        entry_handler   1, h, 64, irq
        entry_handler   1, h, 64, fiq
        entry_handler   1, h, 64, error

        entry_handler   0, t, 64, sync
        entry_handler   0, t, 64, irq
        entry_handler   0, t, 64, fiq
        entry_handler   0, t, 64, error

        entry_handler   0, t, 32, sync
        entry_handler   0, t, 32, irq
        entry_handler   0, t, 32, fiq
        entry_handler   0, t, 32, error

총 16개의 entry_handler는 kernel_ventry 엔트리에서 호출된다.

  • 예) \el=1, \ht=h, \regsize=64, \label=irq
    • bl el\el\ht\()_\regsize\()_\label_handler -> “b el1h_64_irq_handler

entry_handler 매크로

arch/arm64/kernel/entry.S

        .macro entry_handler el:req, ht:req, regsize:req, label:req
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
        kernel_entry \el, \regsize
        mov     x0, sp
        bl      el\el\ht\()_\regsize\()_\label\()_handler
        .if \el == 0
        b       ret_to_user
        .else
        b       ret_to_kernel
        .endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
        .endm

이 매크로는 C 언어로 작성한 엔트리 핸들러를 호출하고, 유저 또는 커널로 되돌아가는 레이블을 호출한다.

 

TPID 레지스터 관련

TPIDR_EL0 (Thread ID Register EL0)
  • 유저(EL0) 영역에서 TLS(Thread Location Storage)를 사용하기 위해 스레드 데이터가 위치한 베이스 주소를 저장한다.
  • Exception이 발생할 때 vmap 스택에서 overflow가 발생하여 overflow용 스택을 사용하는 경우 이 레지스터를 임시 저장 영역으로 사용된다.
  • TPIDR_EL0의 경우 커널 영역(EL1) 및 유저 영역(EL0)에서 읽고 쓰기가 가능하다.

 

TPIDRRO_EL0 (Thread ID Register EL0 with User Read Only)
  • TPIDR_EL0와 같은 목적으로 유저(EL0) 영역에서 TLS(Thread Location Storage)를 사용하기 위해 스레드 데이터가 위치한 베이스 주소를 저장한다.
  • Exception이 발생할 때 vmap 스택에서 overflow가 발생하여 overflow용 스택을 사용하는 경우 이 레지스터도 임시 저장 영역으로 사용된다.
  • 커널 영역(EL1)에서 읽고 쓰기가 가능하며, 유저 영역(EL0)에서는 읽기만 가능하다.

 

TPIDR_EL1 (Thread ID Register EL1)
  • per-cpu 베이스 주소를 기억하기 위해 사용한다.
  • 커널(EL1) 영역에서 읽고 쓸 수 있다.

 

참고

 

 

댓글 남기기