Linux進程核心調度器之主調度器schedule--Linux進程的管理與調度(十九)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/10/29/9871694.html
-Advertisement-
Play Games

主調度器 在內核中的許多地方, 如果要將CPU分配給與當前活動進程不同的另一個進程, 都會直接調用主調度器函數schedule, 從系統調用返回後, 內核也會檢查當前進程是否設置了重調度標誌 例如, 前述的周期性調度器的scheduler_tick就會設置該標誌, 如果是這樣則內核會調用schedu ...


主調度器

在內核中的許多地方, 如果要將CPU分配給與當前活動進程不同的另一個進程, 都會直接調用主調度器函數schedule, 從系統調用返回後, 內核也會檢查當前進程是否設置了重調度標誌TLF_NEDD_RESCHED

例如, 前述的周期性調度器的scheduler_tick就會設置該標誌, 如果是這樣則內核會調用schedule, 該函數假定當前活動進程一定會被另一個進程取代.

1.1 調度函數的__sched首碼
在詳細論述schedule之前, 需要說明一下__sched首碼, 該首碼可能用於調用schedule的函數, 包括schedule本身.

__sched首碼的聲明, 在include/linux/sched.h, L416, 如下所示

/* Attach to any functions which should be ignored in wchan output. */
#define __sched         __attribute__((__section__(".sched.text")))

attribute((_section(“…”)))是一個gcc的編譯屬性, 其目的在於將相關的函數的代碼編譯之後, 放到目標文件的以惡搞特定的段內, 即.sched.text中. 該信息使得內核在顯示棧轉儲活類似信息時, 忽略所有與調度相關的調用. 由於調度哈書調用不是普通代碼流程的一部分, 因此在這種情況下是沒有意義的.

用它修飾函數的方式如下

void __sched some_function(args, ...)
{
    ......
    schedule();
    ......
}

1.2 schedule函數

schedule就是主調度器的函數, 在內核中的許多地方, 如果要將CPU分配給與當前活動進程不同的另一個進程, 都會直接調用主調度器函數schedule.

該函數完成如下工作

  1. 確定當前就緒隊列, 併在保存一個指向當前(仍然)活動進程的task_struct指針
  2. 檢查死鎖, 關閉內核搶占後調用__schedule完成內核調度
  3. 恢復內核搶占, 然後檢查當前進程是否設置了重調度標誌TLF_NEDD_RESCHED, 如果該進程被其他進程設置了TIF_NEED_RESCHED標誌, 則函數重新執行進行調度

該函數定義在kernel/sched/core.c, L3243, 如下所示

asmlinkage __visible void __sched schedule(void)
{

    /*  獲取當前的進程  */
    struct task_struct *tsk = current;

    /*  避免死鎖 */
    sched_submit_work(tsk);
    do {
        preempt_disable();                                  /*  關閉內核搶占  */
        __schedule(false);                                  /*  完成調度  */
        sched_preempt_enable_no_resched();                  /*  開啟內核搶占  */
    } while (need_resched());   /*  如果該進程被其他進程設置了TIF_NEED_RESCHED標誌,則函數重新執行進行調度    */
}
EXPORT_SYMBOL(schedule);

1.2.2 sched_submit_work避免死鎖

該函數定義在kernel/sched/core.c, L3231, 如下所示

static inline void sched_submit_work(struct task_struct *tsk)
{
    /*  檢測tsk->state是否為0 (runnable), 若為運行態時則返回,
     *   tsk_is_pi_blocked(tsk),檢測tsk的死鎖檢測器是否為空,若非空的話就return
    if (!tsk->state || tsk_is_pi_blocked(tsk))
        return;
    /*
     * If we are going to sleep and we have plugged IO queued,
     * make sure to submit it to avoid deadlocks.
     */
    if (blk_needs_flush_plug(tsk))  /*  然後檢測是否需要刷新plug隊列,用來避免死鎖  */
        blk_schedule_flush_plug(tsk);
}

1.2.3 preempt_disable和sched_preempt_enable_no_resched開關內核搶占

內核搶占

Linux除了內核態外還有用戶態。用戶程式的上下文屬於用戶態,系統調用和中斷處理常式上下文屬於內核態. 如果一個進程在用戶態時被其他進程搶占了COU則成發生了用戶態搶占, 而如果此時進程進入了內核態, 則內核此時代替進程執行, 如果此時發了搶占, 我們就說發生了內核搶占.

內核搶占是Linux 2.6以後引入的一個重要的概念

我們說:如果進程正執行內核函數時,即它在內核態運行時,允許發生內核切換(被替換的進程是正執行內核函數的進程),這個內核就是搶占的。

搶占內核的主要特點是:一個在內核態運行的進程,當且僅當在執行內核函數期間被另外一個進程取代。

這與用戶態的搶占有本質區別.

內核為了支撐內核搶占, 提供了很多機制和結構, 必要時候開關內核搶占也是必須的, 這些函數定義在include/linux/preempt.h, L145

#define preempt_disable() \
do { \
    preempt_count_inc(); \
    barrier(); \
} while (0)

#define sched_preempt_enable_no_resched() \
do { \
    barrier(); \
    preempt_count_dec(); \
} while (0)

1.3 __schedule開始進程調度

__schedule完成了真正的調度工作, 其定義在kernel/sched/core.c, L3103, 如下所示

1.3.1 __schedule函數主框架

static void __sched notrace __schedule(bool preempt)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq *rq;
    int cpu;

    /*  ==1==  
        找到當前cpu上的就緒隊列rq
        並將正在運行的進程curr保存到prev中  */
    cpu = smp_processor_id();
    rq = cpu_rq(cpu);
    prev = rq->curr;

    /*
     * do_exit() calls schedule() with preemption disabled as an exception;
     * however we must fix that up, otherwise the next task will see an
     * inconsistent (higher) preempt count.
     *
     * It also avoids the below schedule_debug() test from complaining
     * about this.
     */
    if (unlikely(prev->state == TASK_DEAD))
        preempt_enable_no_resched_notrace();

    /*  如果禁止內核搶占,而又調用了cond_resched就會出錯
     *  這裡就是用來捕獲該錯誤的  */
    schedule_debug(prev);

    if (sched_feat(HRTICK))
        hrtick_clear(rq);

    /*  關閉本地中斷  */
    local_irq_disable();

    /*  更新全局狀態,
     *  標識當前CPU發生上下文的切換  */
    rcu_note_context_switch();

    /*
     * Make sure that signal_pending_state()->signal_pending() below
     * can't be reordered with __set_current_state(TASK_INTERRUPTIBLE)
     * done by the caller to avoid the race with signal_wake_up().
     */
    smp_mb__before_spinlock();
    /*  鎖住該隊列  */
    raw_spin_lock(&rq->lock);
    lockdep_pin_lock(&rq->lock);

    rq->clock_skip_update <<= 1; /* promote REQ to ACT */

    /*  切換次數記錄, 預設認為非主動調度計數(搶占)  */
    switch_count = &prev->nivcsw;

    /*
     *  scheduler檢查prev的狀態state和內核搶占表示
     *  如果prev是不可運行的, 並且在內核態沒有被搶占
     *  
     *  此時當前進程不是處於運行態, 並且不是被搶占
     *  此時不能只檢查搶占計數
     *  因為可能某個進程(如網卡輪詢)直接調用了schedule
     *  如果不判斷prev->stat就可能誤認為task進程為RUNNING狀態
     *  到達這裡,有兩種可能,一種是主動schedule, 另外一種是被搶占
     *  被搶占有兩種情況, 一種是時間片到點, 一種是時間片沒到點
     *  時間片到點後, 主要是置當前進程的need_resched標誌
     *  接下來在時鐘中斷結束後, 會preempt_schedule_irq搶占調度
     *  
     *  那麼我們正常應該做的是應該將進程prev從就緒隊列rq中刪除, 
     *  但是如果當前進程prev有非阻塞等待信號, 
     *  並且它的狀態是TASK_INTERRUPTIBLE
     *  我們就不應該從就緒隊列總刪除它 
     *  而是配置其狀態為TASK_RUNNING, 並且把他留在rq中

    /*  如果內核態沒有被搶占, 並且內核搶占有效
        即是否同時滿足以下條件:
        1  該進程處於停止狀態
        2  該進程沒有在內核態被搶占 */
    if (!preempt && prev->state)
    {

        /*  如果當前進程有非阻塞等待信號,並且它的狀態是TASK_INTERRUPTIBLE  */
        if (unlikely(signal_pending_state(prev->state, prev)))
        {
            /*  將當前進程的狀態設為:TASK_RUNNING  */
            prev->state = TASK_RUNNING;
        }
        else   /*  否則需要將prev進程從就緒隊列中刪除*/
        {
            /*  將當前進程從runqueue(運行隊列)中刪除  */
            deactivate_task(rq, prev, DEQUEUE_SLEEP);

            /*  標識當前進程不在runqueue中  */
            prev->on_rq = 0;

            /*
             * If a worker went to sleep, notify and ask workqueue
             * whether it wants to wake up a task to maintain
             * concurrency.
             */
            if (prev->flags & PF_WQ_WORKER) {
                struct task_struct *to_wakeup;

                to_wakeup = wq_worker_sleeping(prev);
                if (to_wakeup)
                    try_to_wake_up_local(to_wakeup);
            }
        }
        /*  如果不是被搶占的,就累加主動切換次數  */
        switch_count = &prev->nvcsw;
    }

    /*  如果prev進程仍然在就緒隊列上沒有被刪除  */
    if (task_on_rq_queued(prev))
        update_rq_clock(rq);  /*  跟新就緒隊列的時鐘  */

    /*  挑選一個優先順序最高的任務將其排進隊列  */
    next = pick_next_task(rq, prev);
    /*  清除pre的TIF_NEED_RESCHED標誌  */
    clear_tsk_need_resched(prev);
    /*  清楚內核搶占標識  */
    clear_preempt_need_resched();

    rq->clock_skip_update = 0;

    /*  如果prev和next非同一個進程  */
    if (likely(prev != next))
    {
        rq->nr_switches++;  /*  隊列切換次數更新  */
        rq->curr = next;    /*  將next標記為隊列的curr進程  */
        ++*switch_count;    /* 進程切換次數更新  */

        trace_sched_switch(preempt, prev, next);
        /*  進程之間上下文切換    */
        rq = context_switch(rq, prev, next); /* unlocks the rq */
    }
    else    /*  如果prev和next為同一進程,則不進行進程切換  */
    {
        lockdep_unpin_lock(&rq->lock);
        raw_spin_unlock_irq(&rq->lock);
    }

    balance_callback(rq);
}
STACK_FRAME_NON_STANDARD(__schedule); /* switch_to() */

1.3.2 pick_next_task選擇搶占的進程

內核從cpu的就緒隊列中選擇一個最合適的進程來搶占CPU

next = pick_next_task(rq);

全局的pick_next_task函數會從按照優先順序遍歷所有調度器類的pick_next_task函數, 去查找最優的那個進程, 當然因為大多數情況下, 系統中全是CFS調度的非實時進程, 因而linux內核也有一些優化的策略

其執行流程如下

  • 如果當前cpu上所有的進程都是cfs調度的普通非實時進程, 則直接用cfs調度, 如果無程式可調度則調度idle進程

其定義在kernel/sched/core.c, line 3068, 如下所示

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

    /*
     * Optimization: we know that if all tasks are in
     * the fair class we can call that function directly:
     *
     * 如果待被調度的進程prev是隸屬於CFS的普通非實時進程
     * 而當前cpu的全局就緒隊列rq中的進程數與cfs_rq的進程數相等
     * 則說明當前cpu上的所有進程都是由cfs調度的普通非實時進程
     *
     * 那麼我們選擇最優進程的時候
     * 就只需要調用cfs調度器類fair_sched_class的選擇函數pick_next_task
     * 就可以找到最優的那個進程p
     */
    /*  如果當前所有的進程都被cfs調度, 沒有實時進程  */
    if (likely(prev->sched_class == class &&
           rq->nr_running == rq->cfs.h_nr_running))
    {
        /*  調用cfs的選擇函數pick_next_task找到最優的那個進程p*/
        p = fair_sched_class.pick_next_task(rq, prev);
        /*  #define RETRY_TASK ((void *)-1UL)有被其他調度氣找到合適的進程  */
        if (unlikely(p == RETRY_TASK))
            goto again; /*  則遍歷所有的調度器類找到最優的進程 */

        /* assumes fair_sched_class->next == idle_sched_class */
        if (unlikely(!p))   /*  如果沒有進程可被調度  */
            p = idle_sched_class.pick_next_task(rq, prev); /*  則調度idle進程  */

        return p;
    }

/*  進程中所有的調度器類, 是通過next域鏈接域鏈接在一起的
 *  調度的順序為stop -> dl -> rt -> fair -> idle 
 *  again出的迴圈代碼會遍歷他們找到一個最優的進程  */
again:
    for_each_class(class)
    {
        p = class->pick_next_task(rq, prev);
        if (p)
        {
            if (unlikely(p == RETRY_TASK))
                goto again;
            return p;
        }
    }

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

其中for_each_class遍歷所有的調度器類, 依次執行pick_next_task操作選擇最優的進程

它會從優先順序最高的sched_class_highest(目前是stop_sched_class)查起, 依次按照調度器類的優先順序從高到低的順序調用調度器類對應的pick_next_task_fair函數直到查找到一個能夠被調度的進程

for_each_class定義在kernel/sched/sched.h, 如下所示

#define sched_class_highest (&stop_sched_class)
#define for_each_class(class) \
   for (class = sched_class_highest; class; class = class->next)

extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;

除了全局的pick_next_task函數, 每個調度器類都提供了pick_next_task函數用以查找對應調度器下的最優進程, 其定義如下所示

調度器類 pick_next策略 pick_next_task_fair函數
stop_sched_class - kernel/sched/stop_task.c, line 121, pick_next_task_stop
dl_sched_class - kernel/sched/deadline.c, line 1782, pick_next_task_dl
rt_sched_class 取出合適的進程後, dequeue_pushable_task從pushable隊列里取出來 kernel/sched/rt.c, line 1508, pick_next_task_rt
fail_sched_class pick_next_task_fair,從紅黑樹里,選出vtime最小的那個進程,調用set_next_entity將其出隊 kernel/sched/fair.c, line 5441, pick_next_task_fail
idle_sched_class 直接調度idle進程 kernel/sched/idle_task.c, line 26, pick_next_task_idle

實際上,對於RT進程,put和pick並不操作運行隊列

對於FIFO和RR的區別,在scheduler_tick中通過curr->sched_class->task_tick進入到task_tick_rt的處理, 如果是非RR的進程則直接返回,否則遞減時間片,如果時間片耗完,則需要將當前進程放到運行隊列的末尾, 這個時候才操作運行隊列(FIFO和RR進程,是否位於同一個plist隊列?),時間片到點,會重新移動當前進程requeue_task_rt,進程會被加到隊列尾,接下來set_tsk_need_resched觸發調度,進程被搶占進入schedule

問題1 : 為什麼要多此一舉判斷所有的進程是否全是cfs調度的普通非實時進程?

加快經常性事件, 是程式開發中一個優化的準則, 那麼linux系統中最普遍的進程是什麼呢? 肯定是非實時進程啊, 其調度器必然是cfs, 因此

rev->sched_class == class && rq->nr_running == rq->cfs.h_nr_running

這種情形發生的概率是很大的, 也就是說多數情形下, 我們的linux中進程全是cfs調度的

而likely這個巨集業表明瞭這點, 這也是gcc內建的一個編譯選項, 它其實就是告訴編譯器表達式很大的情況下為真, 編譯器可以對此做出優化

//  http://lxr.free-electrons.com/source/tools/virtio/linux/kernel.h?v=4.6#L91
 #ifndef likely
 # define likely(x)     (__builtin_expect(!!(x), 1))
 #endif

 #ifndef unlikely
 # define unlikely(x)   (__builtin_expect(!!(x), 0))
 #endif

1.4 context_switch進程上下文切換

進程上下文的切換其實是一個很複雜的過程, 我們在這裡不能詳述, 但是我會儘可能說明白

具體的內容請參照

1.4.1 進程上下文切換

上下文切換(有時也稱做進程切換或任務切換)是指CPU從一個進程或線程切換到另一個進程或線程

稍微詳細描述一下,上下文切換可以認為是內核(操作系統的核心)在 CPU 上對於進程(包括線程)進行以下的活動:

  1. 掛起一個進程,將這個進程在 CPU 中的狀態(上下文)存儲於記憶體中的某處,
  2. 在記憶體中檢索下一個進程的上下文並將其在 CPU 的寄存器中恢復
  3. 跳轉到程式計數器所指向的位置(即跳轉到進程被中斷時的代碼行),以恢復該進程

因此上下文是指某一時間點CPU寄存器和程式計數器的內容, 廣義上還包括記憶體中進程的虛擬地址映射信息.

上下文切換隻能發生在內核態中, 上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統來說意味著消耗大量的 CPU 時間,事實上,可能是操作系統中時間消耗最大的操作。

Linux相比與其他操作系統(包括其他類 Unix 系統)有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少.

1.4.2 context_switch流程

context_switch函數完成了進程上下文的切換, 其定義在kernel/sched/core.c#L2711

context_switch( )函數建立next進程的地址空間。進程描述符的active_mm欄位指向進程所使用的記憶體描述符,而mm欄位指向進程所擁有的記憶體描述符。對於一般的進程,這兩個欄位有相同的地址,但是,內核線程沒有它自己的地址空間而且它的 mm欄位總是被設置為 NULL

context_switch( )函數保證:如果next是一個內核線程, 它使用prev所使用的地址空間

它主要執行如下操作

  • 調用switch_mm(), 把虛擬記憶體從一個進程映射切換到新進程中
  • 調用switch_to(),從上一個進程的處理器狀態切換到新進程的處理器狀態。這包括保存、恢復棧信息和寄存器信息

由於不同架構下地址映射的機制有所區別, 而寄存器等信息弊病也是依賴於架構的, 因此switch_mm和switch_to兩個函數均是體繫結構相關的

1.4.3 switch_mm切換進程虛擬地址空間

switch_mm主要完成了進程prev到next虛擬地址空間的映射, 由於內核虛擬地址空間是不許切換的, 因此切換的主要是用戶態的虛擬地址空間

這個是一個體繫結構相關的函數, 其實現在對應體繫結構下的arch/對應體繫結構/include/asm/mmu_context.h文件中, 我們下麵列出了幾個常見體繫結構的實現

體繫結構 switch_mm實現
x86 arch/x86/include/asm/mmu_context.h, line 118
arm arch/arm/include/asm/mmu_context.h, line 126
arm64 arch/arm64/include/asm/mmu_context.h, line 183

其主要工作就是切換了進程的CR3

控制寄存器(CR0~CR3)用於控制和確定處理器的操作模式以及當前執行任務的特性

CR0中含有控制處理器操作模式和狀態的系統控制標誌;

CR1保留不用;

CR2含有導致頁錯誤的線性地址;

CR3中含有頁目錄表物理記憶體基地址,因此該寄存器也被稱為頁目錄基地址寄存器PDBR(Page-Directory Base address Register)。

1.4.4 switch_to切換進程堆棧和寄存器

執行環境的切換是在switch_to()中完成的, switch_to完成最終的進程切換,它保存原進程的所有寄存器信息,恢復新進程的所有寄存器信息,並執行新的進程

調度過程可能選擇了一個新的進程, 而清理工作則是針對此前的活動進程, 請註意, 這不是發起上下文切換的那個進程, 而是系統中隨機的某個其他進程, 內核必須想辦法使得進程能夠與context_switch常式通信, 這就可以通過switch_to巨集實現. 因此switch_to函數通過3個參數提供2個變數,

在新進程被選中時, 底層的進程切換冽程必須將此前執行的進程提供給context_switch, 由於控制流會回到陔函數的中間, 這無法用普通的函數返回值來做到, 因此提供了3個參數的巨集

/*
 * Saving eflags is important. It switches not only IOPL between tasks,
 * it also protects other tasks from NT leaking through sysenter etc.
*/
#define switch_to(prev, next, last)
體繫結構 switch_to實現
x86 arch/x86/include/asm/switch_to.h中兩種實現
定義CONFIG_X86_32巨集
未定義CONFIG_X86_32巨集
arm arch/arm/include/asm/switch_to.h, line 25
通用 include/asm-generic/switch_to.h, line 25

內核在switch_to中執行如下操作

  1. 進程切換, 即esp的切換, 由於從esp可以找到進程的描述符
  2. 硬體上下文切換, 設置ip寄存器的值, 並jmp到__switch_to函數

1.5 need_resched, TIF_NEED_RESCHED標識與用戶搶占

1.5.1 need_resched標識TIF_NEED_RESCHED

內核在即將返回用戶空間時檢查進程是否需要重新調度,如果設置了,就會發生調度, 這被稱為用戶搶占, 因此內核在thread_info的flag中設置了一個標識來標誌進程是否需要重新調度, 即重新調度need_resched標識TIF_NEED_RESCHED

並提供了一些設置可檢測的函數

函數 描述 定義
set_tsk_need_resched 設置指定進程中的need_resched標誌 include/linux/sched.h, L2920
clear_tsk_need_resched 清除指定進程中的need_resched標誌 include/linux/sched.h, L2926
test_tsk_need_resched 檢查指定進程need_resched標誌 include/linux/sched.h, L2931
// http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L3093
static __always_inline bool need_resched(void)
{
    return unlikely(tif_need_resched());
}

// http://lxr.free-electrons.com/source/include/linux/thread_info.h?v=4.6#L106
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)

1.5.2 用戶搶占和內核搶占

當內核即將返回用戶空間時, 內核會檢查need_resched是否設置,如果設置,則調用schedule(),此時,發生用戶搶占。

一般來說,用戶搶占發生幾下情況

  1. 從系統調用返回用戶空間
  2. 從中斷(異常)處理程式返回用戶空間

當kerne(系統調用或者中斷都在kernel中)返回用戶態時,系統可以安全的執行當前的任務,或者切換到另外一個任務.

搶占時伴隨著schedule()的執行, 因此內核提供了一個TIF_NEED_RESCHED標誌來表明是否要用schedule()調度一次

根據搶占發生的時機分為用戶搶占和內核搶占。

用戶搶占發生在內核即將返回到用戶空間的時候。內核搶占發生在返回內核空間的時候。

搶占類型 描述 搶占發生時機
用戶搶占 內核在即將返回用戶空間時檢查進程是否設置了TIF_NEED_RESCHED標誌,如果設置了,就會發生用戶搶占. 從系統調用或中斷處理程式返回用戶空間的時候
內核搶占 在不支持內核搶占的內核中,內核進程如果自己不主動停止,就會一直的運行下去。無法響應實時進程. 搶占內核雖然犧牲了上下文切換的開銷, 但獲得 了更大的吞吐量和響應時間
1. 2.6的內核添加了內核搶占,同時為了某些地方不被搶占,又添加了自旋鎖. 在進程的thread_info結構中添加了preempt_count該數值為0,當進程使用一個自旋鎖時就加 1,釋放一個自旋鎖時就減1. 為0時表示內核可以搶占.
1. 從中斷處理程式返回內核空間時,內核會檢查preempt_count和TIF_NEED_RESCHED標誌,如果進程設置了 TIF_NEED_RESCHED標誌,並且preempt_count為0,發生內核搶占
2.當內核再次用於可搶占性的時候,當進程所有的自旋鎖都釋放了,釋放程式會檢查TIF_NEED_RESCHED標誌,如果設置了就會調用schedule
3. 顯示調用schedule時
4. 內核中的進程被堵塞的時候

2 總結

2.1 schedule調度流程

schedule就是主調度器的函數, 在內核中的許多地方, 如果要將CPU分配給與當前活動進程不同的另一個進程, 都會直接調用主調度器函數schedule, 該函數定義在kernel/sched/core.c, L3243, 如下所示

該函數完成如下工作

  1. 確定當前就緒隊列, 併在保存一個指向當前(仍然)活動進程的task_struct指針
  2. 檢查死鎖, 關閉內核搶占後調用__schedule完成內核調度
  3. 恢復內核搶占, 然後檢查當前進程是否設置了重調度標誌TLF_NEDD_RESCHED, 如果該進程被其他進程設置了TIF_NEED_RESCHED標誌, 則函數重新執行進行調度
do {
        preempt_disable();                                  /*  關閉內核搶占  */
        __schedule(false);                                  /*  完成調度  */
        sched_preempt_enable_no_resched();                  /*  開啟內核搶占  */
    } while (need_resched());   /*  如果該進程被其他進程設置了TIF_NEED_RESCHED標誌,則函數重新執行進行調度    */

2.2 __schedule如何完成內核搶占

  1. 完成一些必要的檢查, 並設置進程狀態, 處理進程所在的就緒隊列
  2. 調度全局的pick_next_task選擇搶占的進程
    1. 如果當前cpu上所有的進程都是cfs調度的普通非實時進程, 則直接用cfs調度, 如果無程式可調度則調度idle進程
    2. 否則從優先順序最高的調度器類sched_class_highest(目前是stop_sched_class)開始依次遍歷所有調度器類的pick_next_task函數, 選擇最優的那個進程執行
  3. context_switch完成進程上下文切換
    1. 調用switch_mm(), 把虛擬記憶體從一個進程映射切換到新進程中
    2. 調用switch_to(),從上一個進程的處理器狀態切換到新進程的處理器狀態。這包括保存、恢復棧信息和寄存器信息

2.3 調度的內核搶占和用戶搶占

內核在完成調度的過程中總是先關閉內核搶占, 等待內核完成調度的工作後, 再把內核搶占開啟, 如果在內核完成調度器過程中, 這時候如果發生了內核搶占, 我們的調度會被中斷, 而調度卻還沒有完成, 這樣會丟失我們調度的信息.

而同樣我們可以看到, 在調度完成後, 內核會去判斷need_resched條件, 如果這個時候為真, 內核會重新進程一次調度, 此次調度由於發生在內核態因此仍然是一次內核搶占

need_resched條件其實是判斷need_resched標識TIF_NEED_RESCHED的值, 內核在thread_info的flag中設置了一個標識來標誌進程是否需要重新調度, 即重新調度need_resched標識TIF_NEED_RESCHED, 內核在即將返回用戶空間時會檢查標識TIF_NEED_RESCHED標誌進程是否需要重新調度,如果設置了,就會發生調度, 這被稱為用戶搶占,

而內核搶占是通過自旋鎖preempt_count實現的, 同樣當內核可以進行內核搶占的時候(比如從中斷處理程式返回內核空間或內核中的進程被堵塞的時候),內核會檢查preempt_count和TIF_NEED_RESCHED標誌,如果進程設置了 TIF_NEED_RESCHED標誌,並且preempt_count為0,發生內核搶占


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 通過後臺,想刪除C盤下”C:\\Windows\\winsxs\\Backup“的緩存文件。 然後提示對路徑“C:\\Windows\\winsxs\\Backup\\amd64_hid-user.resources_31bf3856ad364e35_10.0.17134.1_zh-cn_aa ...
  • C# -- 隨機數產生的字母金字塔 1. 代碼實現: 2. 運行結果: ...
  • 摘要: 寫在前面:此隨筆僅僅是作為個人學習總結,有不對的地方,請各位前輩指正O(∩_∩)O........ 一: 引入 在學習集合之前我們都學習過數組.可以知道數組的長度在聲明的時候就已經被固定了,不可以增加或者修改.這個時候我們想要有數組的效果,又可以隨意的改變長度,那怎麼辦呢?此時我們的集合就出 ...
  • 以前一直搞的centos配置開機啟動腳本,但是相同方法用在ubuntu系統上就不管用了,非常傷腦筋. 非常感謝 https://www.linuxidc.com/Linux/2017-09/147178.htm http://www.linuxdiyf.com/linux/26896.html 這兩 ...
  • mkswap 在Linux設備或者文件中創建交換分區,創建完成之後必須使用swapon來使用它。一般在“/etc/fstab”中有一個交換分區列表,這樣開機的時候就可以使用它。 此命令的適用範圍:RedHat、RHEL、Ubuntu、CentOS、SUSE、openSUSE、Fedora。 1、語法 ...
  • 入門總結 Awk簡介 awk不僅僅時linux系統中的一個命令,而且是一種編程語言,可以用來處理數據和生成報告。處理的數據可以是一個或多個文件,可以是來自標準輸入,也可以通過管道獲取標準輸入,awk可以在命令行上直接編輯命令進行操作,也可以編寫成awk程式來進行更為複雜的運用。 ※查看awk的版本 ...
  • rmdir是常用的命令,該命令的功能是刪除空目錄,一個目錄被刪除之前必須是空的。(註意,rm r dir命令可代替rmdir,但是有很大危險性。)刪除某目錄時也必須具有對父目錄的寫許可權。 一.命令格式 rmdir [參數] 目錄 二.命令功能: 該命令從一個目錄中刪除一個或多個子目錄項,刪除某目錄時 ...
  • 前言 Ansible是一款極其簡單的IT自動化運維工具,基於Python開發,集合了眾多運維工具(puppet、cfengine、chef、func、fabric)的優點,實現了批量系統配置、批量程式部署、批量運行命令等功能。Ansible是基於模塊工作的,本身沒有批量部署的能力,真正具有批量部署的 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...