RTOS 맛보기를 마치고, 기본 개념을 정리하고자 한다.
태스크(Task)
태스크의 개념에 대해 알아보자.
그림 1을 보면, 3개의 태스크가 순차적으로 실행되고 있다.
정해진 시간(보통 1[ms]) 안에 선언된 태스크가 모두 실행된다.
더 자세한 설명은 이따가 설명한다.
특징은 다음과 같다.
그림 2의 코드 예시를 보자.
main.c에 작성했다.
함수는 리턴이 없기에 void 형으로 선언했으며, 받아오는 인수는 (void *) 타입이다.
그 바로 밑에 지역 변수를 선언하고, while 문을 사용하여 무한 루프로 동작한다.
마지막으로 스스로 태스크 정보를 지우는 코드를 작성했다.
조건에 따라 while이 false가 될 수 있는 코드를 만든다면, 태스크 정보를 지우도록 한다.
태스크 생성 코드를 보자.
CubeMX를 사용하면 그림 3.1와 같이 태스크의 속성이 자동으로 생성된다.
그림 3.1의 속성을 바탕으로 그림 3.2와 같이 코드를 작성해주면 태스크 생성은 완료된다.
이렇게 작성하면 생성된 태스크로 인해 적색 LED가 깜빡이게 된다.
우선 순위
태스크가 1개 있을 때는 문제가 되지 않지만, 여러개 있다면 어떻게 될까?
매 [ms] 주기 마다 여러 개의 태스크가 서로 먼저 시작하려고 난리를 칠 것이다.
그럴 때 각 태스크가 보유하고 있는 우선 순위 속성에 따라서 "우선 순위 값이 낮은" 태스크부터 먼저 실행된다.
그 값은 cmsis_os.h에 정의되어 있다.
값이 낮을수록 우선 순위가 높은 것이다.
Idle 값인 -3보다 낮게 설정하지 않도록 주의하자.
이제 다시 그림 1을 보자.
이 그림을 본다면, TaskC > TaskB > TaskA 순으로 우선순위가 높은 것을 알 수 있다.
이렇게 순차적으로 실행되는 경우도 있지만, 다른 경우를 생각해보자.
우선 순위가 낮은 태스크가 동작 중일 때, 인터럽트에 의해 우선 순위가 높은 태스크에 동작 신호가 전달된다면?
실행 중인 태스크의 우선순위가 낮으므로 잠시 태스크 실행이 중단되고, 높은 우선 순위의 태스크가 실행된다.
그리고 코드가 전부 실행되고 나서 중단 되었던 태스크가 연이어 실행되게끔 운영체제가 동작한다.
그림으로 이해해보자.
평소엔 Idle Task만 동작하다가, 키가 눌리거나, Timer 이벤트가 발생하면 각각 vKetHandlerTask와 vControlTask가 동작하는 방식이다.
파란색의 vKeyHandlerTask의 실행 시간은 빨간색의 vControlTask 보다 더 길다.
t4-t5의 간격과 t3-t4의 간격을 비교하면 알 수 있다.
여기서 t7을 주목하자.
파란색의 태스크가 t6 시점에서 실행되고 있되고 도중에, t7 시점에서 빨간색의 태스크가 실행된다.
그리고 t8 시점에서 빨간색의 태스크가 실행 종료되면, 파란색의 태스크가 마저 실행된다.
이 것이 바로 우선순위 때문이다.
빨간색의 태스크의 우선 순위가 파란색의 태스크보다 더 높기 때문이다.
이렇게 중간에 코드 실행이 중단되는 것은 마냥 좋은 것은 아니다.
우선 순위가 높은 태스크의 경우, 가급적이면 코드 실행을 최소화 하는 게 중요하다.
강제로 코드 실행을 중단할 경우, 호출해오는 변수나 실행 코드에 주의하지 않으면 의도치 않은 연산이 발생할 수 있기 때문이다.
태스크 상태
마지막으로 태스크의 상태에 대해 알아보자.
4가지 상태가 있다.
그림 6에서 "Ready" - "Running" - "Blocked" 의 3가지 상태를 운영체제가 관장하는데, 이를 스케쥴링(Scheduling)이라 한다.
설명은 어렵지만, 차차 알아가보자.
이 때, Idle Task를 Blocked/Suspended 상태로 보내지 않는 것이 중요하다.
그렇게 하면 OS가 뻗을 수 있으므로 Ready 상태로만 둔다.
tasks.c를 열어보면, vTaskStartScheduler() 함수 안에 정의되어 있다.
가장 큰 특징으로는, CPU 사용량을 측정하여 내부에서 전원관리를 진행할 수 있다.
CPU 사용량?
CPU 사용량이라 해야 할지 모르겠지만, MCU를 다루다보면 여러개의 인터럽트, 타이머 이벤트가 발생한다.
내가 사용했던 EtherCAT MCU의 경우, PDI와 Sync0 인터럽트가 발생하고 내부 타이머 인터럽트도 발생한다.
총 3개의 인터럽트가 발생한다.
이 3개의 인터럽트는 1[ms] 안에 전부 실행되어야 했다.
그래서 시스템 클락을 호출하는 함수를 통해 각 인터럽트로 인해 실행되는 함수의 최대 실행 시간을 계산한다.
그리하여 3개의 인터럽트가 1[ms] 안에 전부 실행 가능한지 판단했었다.
만약 불가능했다면, 2[ms], 4[ms] 등 이더캣 DC 주기를 늘려야 한다.
윈도우에서는 작업 관리자에서 명시적으로 보여주지만, MCU에서는 이런 기능이 없기(?) 때문에 코드 실행 시간(클럭)을 기록해서 최대 실행 시간을 기록하는 것이 중요하다.
기록된 데이터를 바탕으로 실시간성이 보장되는지 검증한다.
예시로 설명을 해보면 다음과 같다.
마지막으로 태스크는 TCB(Task Control Block)이란 자료구조를 가지고 있다.
태스크 이름, 스택 정보, 큐 정보 등을 포함한다.
tasks.c에서 구조체 선언을 검색하면 내용을 볼 수 있다.
필자도 활용법을 아직은 잘 모르기에 넘어가도록 한다.
컴퓨터 구조나 자료 구조에 대해 아직 미흡한 점이 있어서 나중에 공부하고자 하는 내용을 적어보았다.
펌웨어 개발을 할 때 어느정도 알고 있으면 좋을 것 같아서 기록해두고 나중에 다시 봐야겠다.
각 태스크는 별도의 스택이 존재하는데, 이 스택은 운영체제에서 관리하는 힙 영역에 위치한다.
이게 무슨 말인지 나도 아직 이해하지 못 했다.
복잡한 설명이 나오니 참고할만한 블로그를 연결하니 같이 공부해보자.
링크 바로가기
출처)
그림 1 => https://www.ddiinnxx.com/intro-real-time-operating-systems-rtos/
그림 5 => https://www.freertos.org/implementation/a00008.html
RTOS 개념 정리 (3) - 세마포어 (1) | 2023.01.28 |
---|---|
RTOS 개념 정리 (2) - 임계 영역 (1) | 2023.01.28 |
RTOS 맛보기 (5) - Task 생성하기(최종 실습) (0) | 2023.01.27 |
RTOS 맛보기 (4) - 인터럽트로 문자열 출력하기 (0) | 2023.01.26 |
RTOS 맛보기 (3) - 인터럽트 발생하기 (0) | 2023.01.26 |