RTOS - PendSV handler 와 Context switching

오늘은 Context Switching을 하기 위한 PendSV Handler 구현을 다뤄보도록 하겠습니다.

 

대부분의 RTOS들은 PendSV Handler를 내에서 Context Switching을 하게 되는데 이는 Cortex-M core의 권장사항입니다. PendSV Interrupt 핸들러는 아래 명령어로 쉽게 발생하게 됩니다.

#define schedule()  SCB_ICSR |= (1 << 28)
 

schedule()로 define되어 있기 때문에 해당 함수를 호출하면 자동적으로 PendSV Handler가 실행됩니다. 호출하는 법은 알았지만 Interrupt 설정은 어떻게 해야 할까요? 보통 다른 Interrupt 핸들러에 중첩되어 Stack frame을 다루는 PendSV Interrupt가 실행되면 Hardfault가 발생하기에 우선 순위를 가장 낮게 하는 것을 권장합니다.

HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0);
 

STM32G4(Cortex-M4) 기준으로 가장 낮은 우선순위로 하였습니다. PendSV Interrupt Handler의 코드를 보기전에 현재 Context를 저장(store)하고 바뀔 Context로 복원(restore)하는 함수를 보겠습니다.

static void __attribute__((naked)) __attribute__((used)) store_context(void)
{
  asm volatile("mrs r0, msp");
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  asm volatile ("vstmdb r0!, {s16-s31}");
#endif
  asm volatile("stmdb r0!, {r4-r11}");
  asm volatile("msr msp, r0");
  asm volatile("bx lr");
}

static void __attribute__((naked)) __attribute__((used)) restore_context(void)
{
  asm volatile("mrs r0, msp");
  asm volatile("ldmfd r0!, {r4-r11}");
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  asm volatile ("vldmia r0!, {s16-s31}");
#endif
  asm volatile("msr msp, r0");
  asm volatile("bx lr");
}
 

__attribute__((naked)) __attribute__((used)) 컴파일러 키워드는 gcc 컴파일러를 쓸 경우 반드시 포함하여야 하는 키워드입니다. 이 키워드를 넣지 않을 경우 Interrupt Handler 내의 실제 코드를 실행하기 전에 몇 개의 instruction(프롤로그, 에필로그)이 gcc에 의해 포함됩니다. 그 외 함수내의 코드들은 이전 포스트에서 소개했던 asm 코드들입니다.

 

다음으로 PendSV Handler내의 코드입니다.

task_curr는 현재 테스트를 나타내는 전역 포인터 변수입니다.

void __attribute__((naked)) __attribute__((used)) PendSV_Handler(void)
{
  store_context();

  /* save msp to task'sp */
  asm volatile("mrs %0, msp" : "=r"(task_curr->sp));

  if (task_curr->state == TASK_RUNNING)
  {
    task_curr->state = TASK_READY;
  }

  task_set_current(task_list_next_ready(task_curr));
  task_curr->state = TASK_RUNNING;

  /* restore msp from task's sp */
  asm volatile("msr msp, %0" ::"r"(task_curr->sp));

  /* restore context */
  restore_context();

  asm volatile("mov lr, %0" ::"r"(0xFFFFFFF9));
  asm volatile("bx lr");
  /* USER CODE END PendSV_IRQn 0 */
  /* USER CODE BEGIN PendSV_IRQn 1 */

  /* USER CODE END PendSV_IRQn 1 */
}
 

현재 task의 context를 저장하고 현재 task의 상태를 ready로 바꾼 후, 중요한 아래의 두 개의 함수를 호출합니다.

 

  • task_list_next_ready : 현재 ready 상태인 task의 포인터를 가져온다.
  • task_set_current : 가져온 task 포인터를 넘겨 현재 task로 설정한다.

 

마지막에 running 상태로 바뀐 task가 동작하도록 설정하고 Interrupt 핸들러를 빠져나오게 됩니다. 다음 포스팅에서는 실제 task 자료형을 다루는 task.c 를 알아보도록 하겠습니다.