任務的休眠與喚醒

1、問題linux

任務的基本狀態就是可運行與不可運行,這是一個任務的基本狀態,正是運行的任務完成了真正的內核功能,而非運行的任務實現了任務的同步。因此任務的運行與非運行的轉換是內核調度的一個基本功能。ide

2、設置的時機和方式函數

一、任務的去活躍優化

從調度的代碼中看,一個線程設置爲活躍與不活躍的兩個最基本的操做分別爲activate_task何deactivate_task,這兩個函數完成了線程從可運行隊列到不可運行隊列之間的一個實質性轉換。這個實質性的轉換有別於經過set_current_state這種表面的標誌性操做。例如,當經過set_current_state設置當前線程爲TASK_INTERRUPTABLE以後,這個線程還會繼續運行,直到在這個線程中運行了schedule函數位置。this

如今假設有一個任務以爲本身離開某個條件或者環境就沒法運行了,那麼它能夠簡單的經過set_current_state設置本身爲非RUNNING狀態,而後執行schedule函數,該函數將會對當前執行schedule的任務狀態進行特殊處理和實質性判斷,這個能夠說是set_current_state設置以後最重要的生效時機了。idea

咱們看一下這個函數對於調用線程狀態的判斷spa

 switch_count = &prev->nivcsw;
 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {其中#define TASK_RUNNING  0,其它全部的非零值表示這個任務處於不可運行狀態,因此可能就要將他真正的從可運行隊列中剔除了。其中的PREEMPT_ACTIVE表示這次搶佔是在內核態執行的一次任務搶佔,也就是說這個被搶佔的任務並無直接調用這個schedule函數,而是在異常或者中斷髮生的時候被動調用這個從新調度函數的。這個判斷對系統的統計有用。若是不是搶佔,那麼久表示自願調度
  switch_count = &prev->nvcsw;
  if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
    unlikely(signal_pending(prev))))這裏判斷若是一個任務時能夠被信號喚醒的,而且此時它已經有信號到來,則立刻喚醒,不然可能會丟失信號。這個狀況可能發生在多核中,由於另個CPU中的任務向這個任務發送一個信號,此時因爲判斷該任務是運行狀態,因此不會作喚醒操做。而這個任務以後將本身設置爲可中斷,而後準備睡眠,此時要再次進行判斷,不然可能會丟失信號,形成信號沒法喚醒。(在單核下不知道在什麼狀況下出現這種狀況,可能某些中斷或者異常中會向當前任務發送信號,例如SIGSEGV,固然這個在正常的內核裏是不會出現的)。
   prev->state = TASK_RUNNING; 這裏並不會將任務真正睡眠,而是讓它繼續運行。
  else {
   if (prev->state == TASK_UNINTERRUPTIBLE)
    rq->nr_uninterruptible++;
   deactivate_task(prev, rq); 這裏就是真正的要將線程從調度的運行隊列中刪除了,這個是實質性操做
  }
 }線程

deactivate_task--->>>dequeue_taskrest

static void dequeue_task(struct task_struct *p, struct prio_array *array)
{
 array->nr_active--;
 list_del(&p->run_list);這裏是一個實質性的刪除操做,全部的任務的選擇都是經過這個結構來完成的。這個就是將任務p從本身的run_list中刪除。此時當遍歷run_list的時候就不會找到這個任務。
 if (list_empty(array->queue + p->prio))
  __clear_bit(p->prio, array->bitmap);
}orm

順便看優先級隊列

struct prio_array {
 unsigned int nr_active;
 DECLARE_BITMAP(bitmap, MAX_PRIO+1); /* include 1 bit for delimiter */
 struct list_head queue[MAX_PRIO];
};

#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO  MAX_USER_RT_PRIO

#define MAX_PRIO  (MAX_RT_PRIO + 40)

也就是說系統中共140個優先級,雖然前100個是實時任務,非實時任務通常經過CFS中的紅黑樹來實現(因此不須要優先級隊列),可是它們一樣有本身對應的優先級鏈表頭。

二、激活一個任務

激活一個任務經過activate_task接口來完成,這個接口

static void enqueue_task(struct task_struct *p, struct prio_array *array)
{
 sched_info_queued(p);
 list_add_tail(&p->run_list, array->queue + p->prio);全部的運行隊列中的任務經過任務中的run_list鏈接在一塊兒
 __set_bit(p->prio, array->bitmap);設置位圖
 array->nr_active++;
 p->array = array;這裏對任務的array進行了賦值,這個值將會在deactivate_task中用到:dequeue_task(p, p->array);。
}

大部分的激活動做都是在try_to_wake_up函數中完成的,因此這裏的參數sync的意義不是很清楚,從註釋上看是,若是sync爲1,標誌新喚醒的線程不用搶佔當前線程,在《深刻理解LInux內核》中也是如此說明的

A flag (sync) that forbids the awakened process to preempt the process currently running on the local CPU

。由於大部分狀況下sync都是0,因此新喚醒的任務通常都會進行搶佔判斷

 if (!sync || cpu != this_cpu) {
  if (TASK_PREEMPTS_CURR(p, rq))
   resched_task(rq->curr);
 }

在resched_task中,事實上沒有作什麼實質性操做,而只是設置了一個標誌,標誌着在某個時間以後須要搶佔,那麼具體在何時搶佔呢?一樣是不肯定的。大部分發生在中斷或者異常返回以後,若是中斷或者異常返回以後沒有執行,那說明極可能執行了preempt_disable,可是既然執行了disable,就必定會執行enable,在enable的時候會在此判斷這個標誌,若是線程標誌位須要調度,就會執行調度

#define preempt_enable() \
do { \
 preempt_enable_no_resched(); \
 barrier(); \
 preempt_check_resched(); \
} while (0)
#define preempt_check_resched() \
do { \
 if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \
  preempt_schedule(); \
} while (0)

若是一直沒有發生中斷或者異常,那麼不要忘記,從用戶態進入內核態就是異常或者中斷的一種,因此在返回用戶態的時候一樣會進行這個判斷,從而進行調度。linux-2.6.21\arch\i386\kernel\entry.S

ENTRY(resume_userspace)
  DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
     # setting need_resched or sigpending
     # between sampling and the iret
 movl TI_flags(%ebp), %ecx
 andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
     # int/exception return?
 jne work_pending
 jmp restore_all
END(ret_from_exception)

/* work to do on interrupt/exception return */
#define _TIF_WORK_MASK \
  (0x0000FFFF & ~(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
    _TIF_SECCOMP | _TIF_SYSCALL_EMU))

也就是說,處理上面列出的標誌以外,其它的全部的都會致使在返回用戶態以前跳轉到work_pending中

work_pending:
 testb $_TIF_NEED_RESCHED, %cl
 jz work_notifysig
work_resched:
 call schedule

這裏進行再次調度,因此若是設置了從新調度,那麼可能在內核中發生異常或者中斷以後,或者在內核preempt_enable的時候,最遲在返回用戶態的時候進行調度。

三、調度的選擇

array = rq->active;
 if (unlikely(!array->nr_active)) {若是說active隊列已空,那麼切換active和expire隊列,這主要是爲了知足分時系統中,例如SCHED_RR和CFS調度。
  /*
   * Switch the active and expired arrays.
   */
  schedstat_inc(rq, sched_switch);
  rq->active = rq->expired;
  rq->expired = array;
  array = rq->active;
  rq->expired_timestamp = 0;
  rq->best_expired_prio = MAX_PRIO;
 }

 idx = sched_find_first_bit(array->bitmap);從隊列中找到最高優先級的任務,
 queue = array->queue + idx;隊列頭。
 next = list_entry(queue->next, struct task_struct, run_list);隊列的第一個元素,能夠看到是經過run_list遍歷鏈表。

 

四、2.6.37的調度器

/*
 * Pick up the highest-prio task:
 */
static inline struct task_struct *
pick_next_task(struct rq *rq)
{
 const struct sched_class *class;
 struct task_struct *p;

 /*
  * Optimization: we know that if all tasks are in
  * the fair class we can call that function directly:
  */
 if (likely(rq->nr_running == rq->cfs.nr_running)) {簡單優化,若是全部的都是CFS任務,則直接調用fair_sched_calse的調度,這在桌面系統中是比較常見的狀況。
  p = fair_sched_class.pick_next_task(rq);
  if (likely(p))
   return p;
 }

 for_each_class(class) {不然從不一樣的調度器開始選擇,這樣就保證了實時任務老是最先的獲得調度。
  p = class->pick_next_task(rq);
  if (p)
   return p;
 }

 BUG(); /* the idle class will always have a runnable task */
}

各個優先級的遍歷,注意,這裏是一個循環,也就是說,若是第一個調度器返回爲空,那麼第二個調度器會被調用,因此高優先級的調度器沒有必要來本身調用低優先級的調度器。

#define sched_class_highest (&stop_sched_class)
#define for_each_class(class) \
   for (class = sched_class_highest; class; class = class->next)
也就是stop_sched_class是最高優先級的調度器

static const struct sched_class stop_sched_class = {
 .next   = &rt_sched_class,

static const struct sched_class rt_sched_class = {
 .next   = &fair_sched_class,

static const struct sched_class fair_sched_class = {
 .next   = &idle_sched_class,

static const struct sched_class idle_sched_class = {
 /* .next is NULL */
 /* no enqueue/yield_task for idle tasks */

這是一個靜態的鏈表。

 五、時間片實時任務


static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
 update_curr_rt(rq);

 watchdog(rq, p);

 /*
  * RR tasks need a special form of timeslice management.
  * FIFO tasks have no timeslices.
  */
 if (p->policy != SCHED_RR)
  return;

 if (--p->rt.time_slice)
  return;

 p->rt.time_slice = DEF_TIMESLICE;

 /*
  * Requeue to the end of queue if we are not the only element
  * on the queue:
  */
 if (p->rt.run_list.prev != p->rt.run_list.next) {
  requeue_task_rt(rq, p, 0);
  set_tsk_need_resched(p);
 }
}

因爲fair是按照時間來分配的,因此在時鐘中斷來臨的時候,它是以事件爲單位判斷的

static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
 unsigned long ideal_runtime, delta_exec;

 ideal_runtime = sched_slice(cfs_rq, curr);
 delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;這裏累加了一個任務的真實運行時間。
 if (delta_exec > ideal_runtime) {
  resched_task(rq_of(cfs_rq)->curr);
  /*
   * The current task ran long enough, ensure it doesn't get
   * re-elected due to buddy favours.
   */
  clear_buddies(cfs_rq, curr);
  return;
 }

而對於實時任務來講,它因爲是不能被搶佔的,因此它的循環是經過時鐘切換次數來判斷的,每次時鐘中斷到來的時候認爲已經完成了一個時間片。並且這個RR只是相同優先級之間的RR,當一個實時線程用完了本身的時間片以後,纔會給其餘實時任務是用,包括相同優先級的非RR實時任務


static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
 update_curr_rt(rq);

 watchdog(rq, p);

 /*
  * RR tasks need a special form of timeslice management.
  * FIFO tasks have no timeslices.
  */
 if (p->policy != SCHED_RR)
  return;

 if (--p->rt.time_slice)
  return;

 p->rt.time_slice = DEF_TIMESLICE;

 /*
  * Requeue to the end of queue if we are not the only element
  * on the queue:
  */
 if (p->rt.run_list.prev != p->rt.run_list.next) {
  requeue_task_rt(rq, p, 0);
  set_tsk_need_resched(p);
 }
}

 * default timeslice is 100 msecs (used only for SCHED_RR tasks). * Timeslices get refilled after they expire. */#define DEF_TIMESLICE  (100 * HZ / 1000)這是一個頻率單位。

相關文章
相關標籤/搜索