might_sleep()

기능

  • CONFIG_PREEMPT_VOLUNTARY 옵션이 동작할 때에만 동작하는 Preemption 포인트
  • 현재 태스크보다 더 높은 우선 순위의 태스크를 빨리 처리할 수 있도록 중간에 리스케쥴링을 허용한다.
  • 리스케쥴링하는 경우 현재 태스크가 preemption되며 이 때 sleep이 일어나게 된다.
  • Preemption Points
    • 리눅스커널은 이렇게 preemption 포인트를 커널의 여러 곳에 뿌려(?) 놓았다.
    • preemption 포인트의 도움을 받아 급한 태스크의 가동에 필요한 latency가 100us 이내로 줄어드는 성과가 있었다.
    • preemption 포인트는 보통 1ms(100us) 이상 소요되는 루틴에는 보통 추가하여 놓는다.

소스 분석

might_sleep()

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());
}

참고

댓글 남기기