기능
- CONFIG_PREEMPT_VOLUNTARY 옵션이 동작할 때에만 동작하는 Preemption 포인트
- 현재 태스크보다 더 높은 우선 순위의 태스크를 빨리 처리할 수 있도록 중간에 리스케쥴링을 허용한다.
- 리스케쥴링하는 경우 현재 태스크가 preemption되며 이 때 sleep이 일어나게 된다.
- Preemption Points
- 리눅스커널은 이렇게 preemption 포인트를 커널의 여러 곳에 뿌려(?) 놓았다.
- preemption 포인트의 도움을 받아 급한 태스크의 가동에 필요한 latency가 100us 이내로 줄어드는 성과가 있었다.
- preemption 포인트는 보통 1ms(100us) 이상 소요되는 루틴에는 보통 추가하여 놓는다.
소스 분석
might_sleep()
아래 소스에서 CONFIG_DEBUG_ATOMIC_SLEEP 옵션이 있는 경우에만 디버그를 위해 __might_sleep()루틴을 호출한다.
#ifdef CONFIG_DEBUG_ATOMIC_SLEEP /** * might_sleep - annotation for functions that can sleep * * this macro will print a stack trace if it is executed in an atomic * context (spinlock, irq-handler, ...). * * This is a useful debugging help to be able to catch problems early and not * be bitten later when the calling function happens to sleep when it is not * supposed to. */ # define might_sleep() \ do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0) #else # define might_sleep() do { might_resched(); } while (0) #endif
might_resched()는 CONFIG_PREEMPT_VOLUNTARY 옵션이 있는 경우에만 _cond_resched()루틴을 호출하고 없는 경우 아무것도 하지 않는다.
include/linux/kernel.h
#ifdef CONFIG_PREEMPT_VOLUNTARY # define might_resched() _cond_resched() #else # define might_resched() do { } while (0) #endif
_cond_resched() 함수에서는 preemption이 필요한 상황인 경우 preemption 되며 sleep 한다.
- should_resched()함수는 preemption 가능한 상태(preempt count가 0)이면서 현재 태스크보다 더 높은 우선 순위를 갖은 태스크가 있는 경우에 true
- preempt_schedule_commoon() 함수는 태스크의 스케쥴링을 다시 한다.
include/linux/kernel.h
int __sched _cond_resched(void) { if (should_resched()) { preempt_schedule_common(); return 1; } return 0; }
should_resched()
should_resched 함수는 리스케쥴이 필요한 상황에서 true로 리턴한다.
- preemption이 enable(preempt_count()가 0) 이면서
- 리스케쥴 요청이 있는 경우(tif_need_resched()가 true)
include/asm-generic/preempt.h
/* * Returns true when we need to resched and can (barring IRQ state). */ static __always_inline bool should_resched(void) { return unlikely(!preempt_count() && tif_need_resched()); }
preempt_count()는 현재 스레드 정보(thread_info 구조체)에서 preempt_count를 리턴.
- preempt_count 값은 preempt_enable() 호출 시 감소되며 preempt_disable() 호출 시 증가된다.
- preempt_count 값이 0인 경우 preemption이 가능한 상태가 된다.
include/asm-generic/preempt.h
static __always_inline int preempt_count(void) { return current_thread_info()->preempt_count; }
tif_need_resched() 매크로는 -> test_thread_flag() 매크로를 호출하고 -> 다시 test_ti_thread_flag() 함수를 호출한다.
- test_bit() 함수는 비트 operation에 사용되는 함수로 해당 비트가 설정되어 있는지를 체크하여 리턴한다.
- 스레드 플래그
- 여러 개의 많은 비트로 구성되어 있는데 그 중 TIF_NEED_RESCHED 비트는 리스케쥴이 필요한 경우 세트된다.
include/linux/thread_info.h
static inline int test_ti_thread_flag(struct thread_info *ti, int flag) { return test_bit(flag, (unsigned long *)&ti->flags); } #define test_thread_flag(flag) \ test_ti_thread_flag(current_thread_info(), flag) #define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
preempt_schedule_common()
preempt_schedule_common() 함수는 실제 리스케쥴을 수행하는 함수이므로 preemption되는 경우 sleep이 일어나는 장소다.
이 함수는 리눅스 커널의 scheduler에 해당하는 주요 함수이므로 scheduler 부분을 분석해야 하므로 이 파트에서는 자세한 분석은 생략한다.
kernel/sched/core.c
static void __sched notrace preempt_schedule_common(void) { do { __preempt_count_add(PREEMPT_ACTIVE); __schedule(); __preempt_count_sub(PREEMPT_ACTIVE); /* * Check again in case we missed a preemption opportunity * between schedule and now. */ barrier(); } while (need_resched()); }
참고
- Optimizing preemption | LWN.net
- Real-Time Preemption PatchSet | Timesys – 다운로드