인프런 강의 링크
홍영기(가일스쿨) 교수님 블로그 링크
졸업프로젝트로 FreeRTOS 기반 상용 RTOS인 'ESP-IDF'를 활용한 프로젝트를 진행한 뒤, 인프런의 FreeRTOS 강의 수강을 통해 내용을 확실히 정리했습니다. 이해한 내용을 최종적으로 정리하며 기록을 남기고자 글을 올립니다.
저작권을 최대한 존중하기 위해 홍영기 교수님의 강의자료와 실습자료는 일부라도 절대 공유하지 않으며, FreeRTOS 공식 레퍼런스 문서를 기반으로 작성합니다.
5. 문맥전환과 인터럽트 (Context switching & Interrupt)
5.1. Context Switching
Context switching이 무엇인지는 간략하게 알고있지만, 정확한 정의는 잘 모르는 경우가 많다. 이번 기회에 context switching이 무엇인지, 무엇 때문에 오래 걸려서 잦은 수행을 피해야하는지 알아보자.
1. Context의 정의
- Context는 CPU의 모든 register 데이터를 말한다. 즉, 현재 task를 실행하는 도중의 CPU register의 모든 정보가 하나의 context를 의미한다.
- 또한 현재 실행중인 task 또는 process의 모든 정보도 포함한다. Task의 stack pointer, 이름, 우선순위, 시작 주소 등 모든 정보를 TCB (Task Control Block)이라는 곳에 구조체 리스트로 저장한다. (우리는 운영체제에서 프로세스의 정보를 PCB(Process Control Block)에 저장한다고 이미 배웠다.)
2. Context Switching의 과정
5.2. Interrupt
오해하기 쉽지만, 인터럽트는 반드시 빠르게 처리해야 하는 것이 아니다. 인터럽트는 단순히 HW가 SW 적인 작업을 수행하기 위해 CPU에게 비동기적으로 기능을 요청하는 신호일 뿐이고, ISR은 우선순위가 다른 task보다 높은 하나의 task일 뿐이다. ISR은 다른 task와 마찬가지로 제 시간 안에만 처리하면 되는 메커니즘일 뿐이다. 그렇기 때문에 다른 인터럽트를 놓치지 않도록 ISR을 경량으로 유지하는 것이 중요하다.
- 인터럽트가 발생하면, 현재 실행중인 task 및 context 정보를 TCB에 저장한다.
- IVT (Interrupt Vector Table)에서 발생한 인터럽트의 종류를 파악하고, 수행한 ISR의 포인터를 따라간 뒤 PC를 포함한 CPU register를 수정한다.
- ISR을 수행한다.
- ISR이 끝난 뒤 다른 highest priority task (HPT)가 있을 경우 그쪽으로 context를 restore하면 되고, 없다면 방금 저장했던 context를 restore 하면 된다.
5.3. Deferred Interrupt Processing (DFI)
FreeRTOS는 ISR를 최대한 경량화할 것을 권장하며 다음과 같은 네 가지 근거를 든다.
- ISR은 task보다 우선순위가 높기 때문에 언제나 먼저 실행된다. 따라서 ISR의 수행시간이 길어지면, task의 수행 시작시간과 종료시간에 악영향을 줄 수 있다.
- ISR 수행시간이 길어지면 새로운 인터럽트를 accept할 수 없을 가능성이 증가한다. ISR 수행 도중에는 다른 ISR을 수행할 수 없기 때문이다.
- 물론 ARM의 NVIC (Nested Vectored Interrrupt Controller) 덕분에 인터럽트 간에도 우선순위를 적용할 수 있지만, FreeRTOS를 포함해 많은 전문가가 nested interrupt 사용을 지양한다. 왜냐하면 ① 프로그램의 복잡도를 기하급수적으로 높히고 ② 예측가능성을 낮추며 ③ 개발자가 인터럽트 간 우선순위를 배정하는 기준이 애매모호하기 때문이다.
- ISR이 커지고 복잡해지면 공유자원을 사용할 가능성이 커진다. Task-인터럽트 간 공유자원 사용에 대한 책임은 오롯히 개발자가 져야하며 이는 매우 위험하다.
FreeRTOS는 꼭 ISR이 크고 복잡한 기능을 수행해야 한다면, 그 기능을 대신 수행할 높은 우선순위의 task를 생성한 뒤 위임할 것을 강력히 권장한다. 이 task가 바로 deferred interrupt processing task다.
5.4 실습 05 - Deferred Interrupt Processing
이번 실습에서는 수 MB의 데이터를 처리하는 매우 무거운 연산은 버튼 인터럽트 ISR에서 처리하는 경우와 ISR()이 semaphore give()
해서 깨운 deferred processing task에서 처리하는 경우로 나눠 결과를 살펴본다. 차이를 알기위해 xTaskGetTickCount()
함수로 현재 SYSTICK의 값을 출력한다.
ISR을 최대한 경량화해야하는 이유에 대해 잘 알 수 있었던 실습이었다.