3 분 소요

이 글은 ‘uC/OS-III: The Real-Time Kernel For the STM32 ARM Cortex-M3, Jean J. Labrosse, Micrium, 2009’를 번역한 글입니다. 오역이 있을 수 있으며, 발견하시면 github에 issue나 댓글 남겨주시기 바랍니다.

critical region이라고 불리는 코드의 임계 영역은 나눠서 실행할 수 없는 코드이다. μC/OS-III에는 많은 임계 영역이 있다. ISR과 task가 한 임계영역에 접근할 수 있다면, 인터럽트를 비활성화하는 것이 필요하다. 만약 task level 코드만 임계영역에 접근할 수 있으면, preemption lock사용을 통해 임계 구역을 보호할 수 있다.

μC/OS-III에서, 임계구역 접근 방법은 인터럽트가 어떤 ISR post 방법을 사용하느냐에 따라 달라진다. 만약 OS_CFG_ISR_POST_DEFERRED_EN이 0이면(os_cfg.h 참조) 내부 임계구역에 접근할 때 인터럽트를 비활성화한다. 만약 OS_CFG_ISR_POST_DEFERRED_EN이 1이면 내부 임계구역에 접근할 때 스케쥴러를 잠근다.

μC/OS-III에는 임계구역에 들어가는 함수 하나와 나오는 함수 2개가 있다.

OS_CRITICAL_ENTER()
OS_CRITICAL_EXIT()
OS_CRITICAL_EXIT_NO_SCHED()

이 함수는 μC/OS-III 내부 함수이고, application code가 호출하면 안된다.

4-1 Disabling interrupts

OS_CFG_ISR_POST_DEFERRED_EN이 0이면, μC/OS-III는 임계구역에 들어가기 전에 인터럽트를 비활성화하고, 임계구역에서 나올 때 인터럽트를 다시 활성화한다.

OS_CRITICAL_ENTER()는 μC/CPU 함수 CPU_CRITICAL_ENTER()를 호출하고, 이는 다시 CPU_SR_Save()를 호출한다. CPU_SR_Save()는 현재 인터럽트 활성화/비활성화 상태를 저장한 다음 인터럽트를 비활성화하는, 어셈블리어로 쓰여진 함수이다. 저장은 호출자의 스택에 cpu_sr이라는 변수명으로 저장된다.

OS_CRITICAL_EXIT()과 OS_CRITICAL_EXIT_NO_SCHED()는 둘다 μC/CPU 함수인 CPU_CRITICAL_EXIT()를 호출하고, 이는 CPU_SR_Restore()와 연결된다. CPU_SR_Restore()는 인터럽트를 재활성화하기위해 저장된 cpu_sr변수를 전달받고 OS_CRITICAL_ENTER()를 호출하기 전과 같이 인터럽트 활성화여부를 설정한다.

//typical code for critical section code - disabling interrupt
#define OS_CRITICAL_ENTER() { CPU_CRITICAL_ENTER(); }
#define OS_CRITICAL_EXIT() { CPU_CRITICAL_EXIT(); }
#define OS_CRITICAL_EXIT_NO_SCHED() { CPU_CRITICAL_EXIT(); }
//CPU critical functions invoke the following μC/CPU macros
#define CPU_CRITICAL_ENTER() do { CPU_INT_DIS(); } while (0)
#define CPU_CRITICAL_EXIT() do { CPU_INT_EN(); } while (0)

//μC/CPU macros store interrupt status onto the caller’s stack in a variable called “cpu_sr”
#define CPU_INT_DIS() do { cpu_sr = CPU_SR_Save(); } while (0) 
#define CPU_INT_EN() do { CPU_SR_Restore(cpu_sr); } while (0)

4-1-1 Measuring interrupt disable time

μC/CPU는 인터럽트가 비활성화되는 시간을 측정할 수 있는 기능을 제공한다. 이는 cpu_cfg.h에 있는 상수 CPU_CFG_INT_DIS_MEAS_EN을 1로 설정하여 기능을 켤 수 있다.

측정은 인터럽트가 비활성화될때 마다 시작되고 인터럽트가 활성화될때 종료된다. 측정은 2개의 변수를 계속 추적하는데, 전체 인터럽트 비활성화 시간과 각 task별 인터럽트 비활성화 시간이다. 따라서, 각 task가 인터럽트를 비활성화하는 시간을 알 수 있고, 코드를 최적화할 수 있게 해준다.

task별 인터럽트 비활성화 시간은 문맥교환(context-switch) 동안 task의 OS_TCB에 저장된다.

시간 측정 단위는 CPU_TS(timestamp)이다. 따라서 타임스탬프를 이용하여 시간을 측정할 때, 타이머의 해상도를 알 필요가 있다. 예를 들어, 타임스탬프가 1MHz마다 올라간다면 CPU_TS의 해상도는 1 마이크로초이다.

인터럽트 비활성화 시간을 측정하는 것은 오버헤드를 증가시키고, 이는 인터럽트 비활성화 시간을 늘린다. 그러나 측정 오버헤드를 고려하여 측정하기 때문에 측정된 값은 실제 인터럽트 비활성화 시간을 나타낸다.

인터럽트 비활성화 시간은 프로세서가 명령어에 엑세스하는 속도 (따라서 메모리 엑세스 속도)에 크게 영향을 받음. 이 경우 하드웨어 설계자는 메모리 엑세스에 wait state를 추가했을 수 있고, 이는 시스템의 전반적인 성능에 영향을 미친다. 이는 비정상적으로 긴 인터럽트 비활성화 시간으로 나타날 수 있음

4-2 Locking the scheduler

OS_CFG_ISR_POST_DEFERRED_EN이 1로 설정됬을 경우, μC/OS-III는 임계구역(critical section)에 들어가기 전에 스케쥴러를 잠그고, 임계구역에서 나올 때 스케쥴러를 활성화한다.

OS_CRITICAL_ENTER()은 스케쥴러를 잠그기 위해 OSSchedLockNestingCtr을 하나 증가시킨다. 이 변수는 스케쥴러가 자기자신이 잠겼는지 풀렸는지 확인하는데 쓰인다. 이 변수가 0이 아닐때 잠긴다.

OS_CRITICAL_EXIT()은 OSSchedLockNestingCtr을 1 감소시키고, 이 변수가 0이 되었을때 스케쥴러를 호출한다.

OS_CRITICAL_EXIT_NO_SCHED()는 OSSchedLockNestingCtr을 1 감소시키지만, 이 변수가 0이 되어도 스케쥴러를 호출하지 않는다.

매크로 코드가 아래 L4-2에 나와있다.

#define OS_CRITICAL_ENTER() {                           \
                                CPU_CRITICAL_ENTER(); \
                                OSSchedLockNestingCtr++; \
                                CPU_CRITICAL_EXIT(); \
                            }
#define OS_CRITICAL_EXIT() {
                            CPU_CRITICAL_ENTER(); \
                            OSSchedLockNestingCtr--; \
                            if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) { \
                                CPU_CRITICAL_EXIT(); \
                                OSSched(); \
                            } 
                            else { \
                                CPU_CRITICAL_EXIT(); \
                            } \
                            }
#define OS_CRITICAL_EXIT_NO_SCHED() {
                                        CPU_CRITICAL_ENTER(); \
                                        OSSchedLockNestingCtr--; \
                                        CPU_CRITICAL_EXIT(); \
                                    }

4-2-1 Measuring scheduler lock time

μC/OS-III는 스케쥴러가 얼마동안 잠겼는지 측정할 수 있는 기능을 제공한다. os_cfg.h에 있는 OS_CFG_SCHED_LOCK_TIME_MEAS_EN을 1로 설정하여 사용할 수 있다.

측정은 스케쥴러가 잠길 때마다 시작되고 스케쥴러가 잠금 해제되면 종료된다. 측정은 전체 스케쥴러 잠금 시간과 task별 스케쥴러 잠금 시간 두가지 값을 측정한다. 따라서 각 task가 스케쥴러를 잠근 시간을 알 수 있으므로, 사용자가 코드를 보다 효율적으로 최적화 할 수 있음.

task별 스케쥴러 잠금 시간은 문맥교환(context-switch)동안 task의 OS_TCB에 저장된다.

시간 측정 단위는 CPU_TS(timestamp)이다. 따라서 타임스탬프를 이용하여 시간을 측정할 때, 타이머의 해상도를 알 필요가 있다. 예를 들어, 타임스탬프가 1MHz마다 올라간다면 CPU_TS의 해상도는 1 마이크로초이다.

스케쥴러 잠금 시간을 측정하는 것은 오버헤드를 증가시키고, 이는 스케쥴러 잠금 시간을 늘린다. 그러나 측정 오버헤드를 고려하여 측정하기 때문에 측정된 값은 실제 스케쥴러 잠금 시간을 나타낸다.

4-3 μC/OS-III FEATURES WITH LONGER CRITICAL SECTIONS

표 4-1은 잠재적으로 더 긴 임계구역(critical section)을 갖는 μC/OS-III 기능을 보여준다. 이러한 지식은 어떤 임계구역을 사용할지 결정하게 해준다.

기능 이유
같은 우선순위의 task 여러개 같은 우선순위의 task 여러개는 더 긴 critical section을 만든다. 그러나 같은 우선순위의 테스크가 몇개 없으면, 인터럽트 지연이 상대적으로 작을 것이다. 만약 여러개의 task가 같은 우선순위로 만들어지지 않았으면, interrupt disable 모드를 써라
Event Flags(Chapter14, “Synchronization” 참조 p.273) 만약 여러 작업이 다른 이벤트를 기다리고 있으면 모든 작업이 수행되기까지 상당한 시간이 필요하므로 critical section이 길어짐. event flag 그룹에서 몇가지 작업만 대기 중인 경우에는 인터럽트 비활성화 방법을 사용할 수 있을 정도로 critical section이 짧음
Pend on multiple objects(Chapter 16,”Pending on Multiple Objects” p.333 참조) 여러 object가 보류 중인 경우, 인터럽트 비활성화 를 선택할 경우 오랜 시간 동안 인터럽트를 비활성화 해야한다. 따라서 이때는 스케쥴러 잠금 방법을 쓰는 것이 좋다.
Broadcast on Post calls(Appendix A에 있는 “μC/OS-III API Reference” p.443에 OSSemPost(), OSQPost() 참조) μC/OS-III는 여러 task에 post를 보낼 때 인터럽트를 비활성화합니다.

(표 4.1)

Reference

  • uC/OS-III: The Real-Time Kernel For the STM32 ARM Cortex-M3, Jean J. Labrosse, Micrium, 2009

  • https://inasie.github.io/it%EC%9D%BC%EB%B0%98/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4-%ED%91%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0/

책 링크

댓글남기기