개요
_tx_thread_schedule 함수는 최초의 스케줄을 발생시키는 동작을 수행한다. 필요한 것들을 초기화하고 PendSV인터럽트를 발생시키는 식이다. 따라서 해당 글에서는 PendSV_Handler를 중점으로 알아볼 것이다.
PendSV_Handler
PROFILE을 활성화 한 경우
#if (defined(TX_ENABLE_EXECUTION_CHANGE_NOTIFY) || defined(TX_EXECUTION_PROFILE_ENABLE))
/* Call the thread exit function to indicate the thread is no longer executing. */
#ifdef TX_PORT_USE_BASEPRI
LDR r1, =TX_PORT_BASEPRI // Mask interrupt priorities =< TX_PORT_BASEPRI
MSR BASEPRI, r1
#else
CPSID i // Disable interrupts
#endif /* TX_PORT_USE_BASEPRI */
PUSH {r0, lr} // Save LR (and r0 just for alignment)
BL _tx_execution_thread_exit // Call the thread exit function
POP {r0, lr} // Recover LR
#ifdef TX_PORT_USE_BASEPRI
MOV r0, 0 // Disable BASEPRI masking (enable interrupts)
MSR BASEPRI, r0
#else
CPSIE i // Enable interrupts
#endif /* TX_PORT_USE_BASEPRI */
#endif /* EXECUTION PROFILE */#ifdef TX_PORT_USE_BASEPRI
LDR r1, =TX_PORT_BASEPRI // Mask interrupt priorities =< TX_PORT_BASEPRI
MSR BASEPRI, r1
#else
CPSID i // Disable interrupts
#endif /* TX_PORT_USE_BASEPRI *이 부분은 컨텍스트 스위칭 과정을 위해, TX_PORT_USE_BASEPRI에 따라서 BASEPRI를 마스킹하여 우선순위 보다 낮거나 같은 인터럽트를 비활성화 하나거나 CPSID명령으로 모든 인터럽트를 비활성화 시킨다.
CPSID Instruction Meaning
- CPSID stands for “Change Processor State, Interrupt Disable.”
- The suffix
ispecifies that it targets the interrupt (IRQ) system.
PUSH {r0, lr} // Save LR (and r0 just for alignment)
BL _tx_execution_thread_exit // Call the thread exit function
POP {r0, lr} // Recover LR여기서 lr을 스택에 PUSH하고 _tx_execution_thread_exit 함수를 Call한다.
#ifdef TX_PORT_USE_BASEPRI
MOV r0, 0 // Disable BASEPRI masking (enable interrupts)
MSR BASEPRI, r0
#else
CPSIE i // Enable interrupts
#endif /* TX_PORT_USE_BASEPRI */다시 interrupt를 활성화한다.
이후 동작
LDR r0, =_tx_thread_current_ptr // 현재 스레드 포인터 주소를 로드
LDR r2, =_tx_thread_execute_ptr // 다음 실행할 스레드 포인터 주소를 로드
MOV r3, #0 // NULL 값을 생성
LDR r1, [r0] // 현재 스레드 포인터를 가져옴
CBZ r1, __tx_ts_new // 만약 현재 스레드 포인터가 NULL이면, 컨텍스트 보존 과정을 건너뛰고 새로운 스레드 선택으로 이동CBZ (Compare and Branch on Zero) 명령어는 ARM의 Thumb 명령어 집합에 속하는 명령어로, 지정된 레지스터의 값이 0인지 비교한 후, 0일 경우 지정된 오프셋으로 분기(branch)하는 기능을 수행한다.
STR r3, [r0] // _tx_thread_current_ptr를 NULL로 설정 (현재 스레드 제거)
MRS r12, PSP // 현재 스레드의 스택 포인터(PSP)를 r12에 저장
STMDB r12!, {r4-r11} // 레지스터 R4~R11을 스택에 저장하고, PSP를 업데이트Multiple Registers Store:
- 여러 개의 레지스터를 한 번에 저장할 수 있다.
- 예를 들어,
STMDB r12!, {r4-r11}는 레지스터 r4부터 r11까지의 값을 메모리에 저장한다.- Decrement Before:
- “Decrement Before”는 기본 주소 레지스터의 값을 저장하기 전에 먼저 감소시킨다는 의미다.
- 즉, 지정한 레지스터(r12)의 값을 기준으로 메모리의 주소를 감소시킨 후, 그 위치에 데이터를 저장한다.
- 이 방식은 스택이 보통 높은 주소에서 낮은 주소로 내려가는 “내림차순 스택”에서 자연스럽게 사용된다.
- Write-back 기능:
!기호가 붙으면, 기본 주소 레지스터가 업데이트(Write-back)된다.- 예를 들어,
STMDB r12!, {r4-r11}에서 r12는 저장 후에 최종 저장된 메모리 주소(스택 포인터의 새로운 값)로 갱신된다.
LDR r4, =_tx_timer_time_slice // 전역 타임 슬라이스 변수의 주소를 r4에 로드
STMDB r12!, {LR} // LR(링크 레지스터)를 스택(현재 스레드의 스택, 즉 PSP) 에 저장하고, PSP(r12)를 업데이트함
STR r12, [r1, #8] // 업데이트된 PSP 값을 현재 스레드의 제어 블록(스레드 구조체) 내 오프셋 8에 저장