null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
☆ Star
협력형 스케줄러 — 타임 슬롯 기반 직접 구현
#scheduler
#cooperative
#time-slot
#stm
#tc37x
@devpc
|
2026-04-02 06:39:39
|
GET /api/v1/nodes/224?nv=1
History:
v1 (2026-04-02) (Latest)
0
Views
3
Calls
# 협력형 스케줄러 — 타임 슬롯 기반 직접 구현 ## 협력형 스케줄러란? 협력형(Cooperative) 스케줄러는 RTOS 없이 **각 태스크가 자발적으로 CPU를 양보**하는 방식으로 동작하는 단순 스케줄러입니다. ``` 선점형(RTOS): 협력형: ISR이 언제든 태스크 중단 태스크가 실행 완료 후 다음 태스크로 복잡한 동기화 필요 단순, 예측 가능한 실행 순서 실시간 보장 강함 각 태스크가 빨리 끝나야 함 ``` --- ## 타임 슬롯 기반 설계 가장 일반적인 협력형 스케줄러는 **주기 타이머**를 기반으로 각 태스크에 실행 주기(슬롯)를 할당합니다. ``` 1ms 타이머 틱 기준 태스크 주기: 틱: 0 1 2 3 4 5 6 7 8 9 10 ... ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 1ms: [A] [A] [A] [A] [A] [A] [A] [A] [A] [A] [A] 5ms: [B] [B] [B] 10ms:[C] [C] 50ms:[D] A: 센서 읽기 (1ms마다) B: 필터 계산 (5ms마다) C: 제어 출력 (10ms마다) D: 통신 패킷 처리 (50ms마다) ``` --- ## 구현 ### 태스크 테이블 정의 ```c typedef void (*TaskFunc)(void); typedef struct { TaskFunc func; // 태스크 함수 포인터 uint16_t period; // 실행 주기 (ms 단위) uint16_t counter; // 카운트다운 카운터 boolean ready; // 실행 준비 플래그 } Task; ``` ### 태스크 등록 ```c // 태스크 함수 선언 void task_SensorRead(void); void task_Filter(void); void task_ControlOutput(void); void task_CommProcess(void); // 태스크 테이블 static Task g_tasks[] = { // 함수 주기 카운터 ready { task_SensorRead, 1, 1, FALSE }, { task_Filter, 5, 5, FALSE }, { task_ControlOutput, 10, 10, FALSE }, { task_CommProcess, 50, 50, FALSE }, }; #define TASK_COUNT (sizeof(g_tasks) / sizeof(g_tasks[0])) ``` ### ISR — 틱 카운터 갱신 ```c // STM 1ms 인터럽트에서 태스크 ready 플래그 세트 IFX_INTERRUPT(stm0Sr0ISR, 0, ISR_PRIORITY_STM0_SR0) { IfxStm_clearCompareFlag(&MODULE_STM0, IfxStm_Comparator_0); IfxStm_increaseCompare(&MODULE_STM0, IfxStm_Comparator_0, STM_TICKS_1MS); // 카운터 감소, 0에 도달하면 ready 세트 for (uint8_t i = 0; i < TASK_COUNT; i++) { if (--g_tasks[i].counter == 0) { g_tasks[i].counter = g_tasks[i].period; // 재장전 g_tasks[i].ready = TRUE; } } } ``` ### 메인 루프 — 스케줄러 디스패처 ```c int main(void) { initHardware(); initScheduler(); while (TRUE) { for (uint8_t i = 0; i < TASK_COUNT; i++) { if (g_tasks[i].ready) { g_tasks[i].ready = FALSE; // 플래그 클리어 g_tasks[i].func(); // 태스크 실행 } } // 모든 태스크가 ready 아니면 저전력 대기 (선택) // IfxCpu_enableInterrupts(); __asm("wait"); } } ``` --- ## 태스크 오버런 감지 태스크 실행 시간이 주기를 초과하면 다음 실행이 밀립니다. 오버런을 감지하여 디버그할 수 있습니다. ```c // 개선된 태스크 구조 typedef struct { TaskFunc func; uint16_t period; uint16_t counter; volatile boolean ready; uint32_t overrunCount; // 오버런 카운터 추가 } Task; // ISR에서 오버런 감지 for (uint8_t i = 0; i < TASK_COUNT; i++) { if (--g_tasks[i].counter == 0) { g_tasks[i].counter = g_tasks[i].period; if (g_tasks[i].ready) { g_tasks[i].overrunCount++; // 이전 실행이 아직 안 끝남 } g_tasks[i].ready = TRUE; } } ``` --- ## 실행 시간 측정 (TC37x STM) ```c void schedulerRun(void) { for (uint8_t i = 0; i < TASK_COUNT; i++) { if (g_tasks[i].ready) { g_tasks[i].ready = FALSE; uint32_t start = STM0_TIM0.U; g_tasks[i].func(); uint32_t elapsed = STM0_TIM0.U - start; // 주기 내에 실행 완료했는지 확인 uint32_t budget = g_tasks[i].period * STM_TICKS_1MS; if (elapsed > budget) { g_tasks[i].overrunCount++; } } } } ``` --- ## 전체 구조 다이어그램 ``` STM0 (1ms ISR) │ ▼ counter-- (0 도달?) │ YES ▼ ready = TRUE counter 재장전 ↓↓↓ (ISR 종료) 메인 루프 (스케줄러) │ ▼ for each task: ready == TRUE? │ YES ▼ ready = FALSE task->func() │ ▼ (다음 태스크) │ └──────────────────▶ (루프 반복) ``` --- ## 협력형 스케줄러의 한계 ``` 주의해야 할 상황: 1. 긴 태스크 하나가 전체 지연시킴 → 모든 태스크를 짧게 설계해야 함 (수백 μs 이내 권장) 2. 선점 불가 → 빠른 반응이 필요한 처리는 ISR에서 해야 함 3. 태스크 실행 순서가 고정 → 우선순위 변동 불가 (RTOS 필요 시점의 신호) ``` --- ## TC37x에서의 활용 TC37x의 멀티코어 특성을 활용하면 코어별로 독립적인 협력형 스케줄러를 운영할 수 있습니다. ``` CPU0 (TC0): 제어 루프 스케줄러 (1ms, 10ms 태스크) CPU1 (TC1): 통신 스케줄러 (10ms, 100ms 태스크) CPU2 (TC2): 모니터링 스케줄러 (100ms, 1000ms 태스크) 각 코어가 독립 STM 비교기로 자체 틱 생성 ``` --- ## 정리 | 항목 | 설명 | |------|------| | 협력형 스케줄러 | 태스크가 자발적으로 CPU 양보, RTOS 불필요 | | 타임 슬롯 | STM 인터럽트로 주기적 ready 플래그 세트 | | 디스패처 | 메인 루프에서 ready 태스크 순서대로 실행 | | 오버런 | 태스크 실행 시간이 주기를 초과한 상황 | | 멀티코어 확장 | TC37x에서 코어별 독립 스케줄러 운영 가능 |
// COMMENTS
Newest First
ON THIS PAGE