2010年3月1日 星期一

Linux Synchronization - 4

"scheduling while atomic: x/y/z"

其中 x 為行程的名稱 (current->comm)
   y 為16進制的 preempt_count
   z 為 PID

出現此訊息時,表示目前的 kernel task 是一個不可被中斷的行程,但卻被排程進入休眠。
以下為核心列印該錯誤訊息的程式片段:

/*
* schedule() is the main scheduler function.
*/
asmlinkage void __sched schedule(void)
{
  long *switch_count;
  task_t *prev, *next;
  runqueue_t *rq;
  prio_array_t *array;
  struct list_head *queue;
  unsigned long long now;
  unsigned long run_time;
  int cpu, idx;

  /*
  * Test if we are atomic. Since do_exit() needs to call into
  * schedule() atomically, we ignore that path for now.
  * Otherwise, whine if we are scheduling when we should not be.
  */
  if (likely(!current->exit_state)) {
    if (unlikely(in_atomic())) {
      printk(KERN_ERR "scheduling while atomic: "
        "%s/0x%08x/%d\n",
        current->comm, preempt_count(), current->pid);
      dump_stack();
    }
  }
  profile_hit(SCHED_PROFILING, __builtin_return_address(0));

  ... /* 以下略 */
}

定義於 kernel/sched.c

#if defined(CONFIG_PREEMPT) && !defined(CONFIG_PREEMPT_BKL)
# define in_atomic()  ((preempt_count() & ~PREEMPT_ACTIVE) != kernel_locked())
#else
# define in_atomic()  ((preempt_count() & ~PREEMPT_ACTIVE) != 0)
#endif

定義於 include/linux/hardirq.h

#define preempt_count()  (current_thread_info()->preempt_count)

定義於 include/linux/preempt.h

當核心要進行 schedule 時,會先檢查目前是否為不可中斷的行程(in_atomic)
若為真,則印出 "scheduling while atomic" 的訊息,表示此時不應該做排程

而 in_atomic 則是判斷 kernel preemption 是否為關閉的狀態
也就是關閉 preemption 是為了讓行程能夠不被打斷的執行,直到完成其工作
換言之,開啟 preemption 就允許其他行程取代目前行程,故可進行排程


以下的程式片段在定義 CONFIG_PREEMPT 的核心會造成 "scheduling while atomic" 錯誤:
(若未定義 CONFIG_PREEMPT 就不是 in_atomic 的程序)

 struct semaphore _sem;
 spinlock_t _lock;

 spin_lock_init( &_lock );
 sema_init(&_sem, 0);

 spin_lock( &_lock );
 down_interruptible( &_sem ); /* 在 Spin lock 中休眠 */
 spin_unlock( &_lock );