8進(jìn)程調(diào)度的時(shí)機(jī)和進(jìn)程切換

安大大 + 原創(chuàng)作品轉(zhuǎn)載請(qǐng)注明出處 + 《Linux操作系統(tǒng)分析》MOOC課程


進(jìn)程調(diào)度與進(jìn)程調(diào)度時(shí)機(jī)分析

之所以有很多的進(jìn)程調(diào)度算法,是因?yàn)椴煌倪M(jìn)程對(duì)計(jì)算機(jī)資源的需求不同。
比如有的是IO密集型,它會(huì)頻繁的進(jìn)行I/O,通常會(huì)花費(fèi)很多時(shí)間等待I/O操作的完成,這樣處理I/O的時(shí)間可以處理其它進(jìn)程。對(duì)于CPU密集型的,其它交互式的進(jìn)程會(huì)受到影響,顯得反應(yīng)慢。這樣就需要有不同的算法來使得整個(gè)系統(tǒng)運(yùn)行的更高效。既能使人的感覺速度快,同時(shí)使得資源最大限度的使用。
另一種進(jìn)程的分類有:批處理進(jìn)程,實(shí)時(shí)進(jìn)程,交互式進(jìn)程。
Linux中的調(diào)度是多種調(diào)度策略和調(diào)度算法的混合。
根據(jù)不同的進(jìn)程使用不同的調(diào)度策略。
Linux的進(jìn)程根據(jù)優(yōu)先級(jí)排隊(duì),進(jìn)程的優(yōu)先級(jí)是動(dòng)態(tài)的。
內(nèi)核中的調(diào)度算法相關(guān)代碼使用了類似OOD中的策略模式。
將調(diào)度算法與其他部分分解耦合了,只考慮從運(yùn)行隊(duì)列里選擇next進(jìn)程。

進(jìn)程調(diào)度的時(shí)機(jī)

  • 中斷處理過程(包括時(shí)鐘中斷、I/O中斷、系統(tǒng)調(diào)用和異常)中,直接調(diào)用schedule(),或者返回用戶態(tài)時(shí)根據(jù)need_resched標(biāo)記調(diào)用schedule();
  • 內(nèi)核線程可以直接調(diào)用schedule()進(jìn)行進(jìn)程切換,也可以在中斷處理過程中進(jìn)行調(diào)度,也就是說內(nèi)核線程作為一類的特殊的進(jìn)程可以主動(dòng)調(diào)度,也可以被動(dòng)調(diào)度;
  • 用戶態(tài)進(jìn)程無法實(shí)現(xiàn)主動(dòng)調(diào)度,僅能通過陷入內(nèi)核態(tài)后的某個(gè)時(shí)機(jī)點(diǎn)進(jìn)行調(diào)度,即在中斷處理過程中進(jìn)行調(diào)度。

schedule()是一個(gè)內(nèi)核函數(shù),不是系統(tǒng)調(diào)用,沒法直接調(diào)用,只能間接的調(diào)用schedule()。
用戶態(tài)進(jìn)程只能被動(dòng)調(diào)度。
內(nèi)核線程是只有內(nèi)核態(tài)沒有用戶態(tài)的特殊進(jìn)程。
內(nèi)核線程可以主動(dòng)調(diào)度也可以被動(dòng)調(diào)度。


進(jìn)程上下文切換相關(guān)代碼分析

怎樣把當(dāng)前進(jìn)程,切換到next進(jìn)程。

中斷的前后,是在同一個(gè)上下文當(dāng)中,只是由用戶態(tài)轉(zhuǎn)向了內(nèi)核態(tài),但是它是同一個(gè)進(jìn)程。
進(jìn)程上下文切換是兩個(gè)進(jìn)程的切換。
進(jìn)程上下文切換包含更多的信息。
中斷保存上下文的方式:保存現(xiàn)場(chǎng),恢復(fù)現(xiàn)場(chǎng)。
進(jìn)程切換上下文的方式:switch_to

進(jìn)程的切換

  • 為了控制進(jìn)程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上執(zhí)行的進(jìn)程,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行,這叫做進(jìn)程切換、任務(wù)切換、上下文切換;

  • 掛起正在CPU上執(zhí)行的進(jìn)程,與中斷時(shí)保存現(xiàn)場(chǎng)是不同的,中斷前后是在同一個(gè)進(jìn)程上下文中,只是由用戶態(tài)轉(zhuǎn)向內(nèi)核態(tài)執(zhí)行;

  • 進(jìn)程上下文包含了進(jìn)程執(zhí)行需要的所有信息

    • 用戶地址空間:包括程序代碼,數(shù)據(jù),用戶堆棧等
    • 控制信息:進(jìn)程描述符,內(nèi)核堆棧等
    • 硬件上下文(注意中斷也要保存硬件上下文只是保存的方法不同)
  • schedule()函數(shù)選擇一個(gè)新的進(jìn)程來運(yùn)行,并調(diào)用context_switch進(jìn)行上下文的切換,這個(gè)宏調(diào)用switch_to來進(jìn)行關(guān)鍵上下文切換

在內(nèi)核中任何位置都可以調(diào)用schedule

asmlinkage __visible void __sched schedule(void)
{
    struct task_struct *tsk = current;

    sched_submit_work(tsk);
    __schedule();//這里指向下邊的函數(shù)
}
EXPORT_SYMBOL(schedule);
static void __sched __schedule(void)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq *rq;
    int cpu;

need_resched:
    preempt_disable();
    cpu = smp_processor_id();
    rq = cpu_rq(cpu);
    rcu_note_context_switch(cpu);
    prev = rq->curr;

    schedule_debug(prev);

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

    /*
     * 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_irq(&rq->lock);

    switch_count = &prev->nivcsw;
    if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
        if (unlikely(signal_pending_state(prev->state, prev))) {
            prev->state = TASK_RUNNING;
        } else {
            deactivate_task(rq, prev, DEQUEUE_SLEEP);
            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, cpu);
                if (to_wakeup)
                    try_to_wake_up_local(to_wakeup);
            }
        }
        switch_count = &prev->nvcsw;
    }

    if (task_on_rq_queued(prev) || rq->skip_clock_update < 0)
        update_rq_clock(rq);

    next = pick_next_task(rq, prev);//這里邊包裝了使用了某種調(diào)度策略,從運(yùn)行隊(duì)列里挑出了下一個(gè)進(jìn)程
    clear_tsk_need_resched(prev);
    clear_preempt_need_resched();
    rq->skip_clock_update = 0;

    if (likely(prev != next)) {
        rq->nr_switches++;
        rq->curr = next;
        ++*switch_count;

        context_switch(rq, prev, next); /* unlocks the rq *///進(jìn)程上下文的切換
        /*
         * The context switch have flipped the stack from under us
         * and restored the local variables which were saved when
         * this task called schedule() in the past. prev == current
         * is still correct, but it can be moved to another cpu/rq.
         */
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
    } else
        raw_spin_unlock_irq(&rq->lock);

    post_schedule(rq);

    sched_preempt_enable_no_resched();
    if (need_resched())
        goto need_resched;
}
context_switch(struct rq *rq, struct task_struct *prev,
           struct task_struct *next)
{
    struct mm_struct *mm, *oldmm;

    prepare_task_switch(rq, prev, next);//提前做的一些準(zhǔn)備

    mm = next->mm;
    oldmm = prev->active_mm;
    /*
     * For paravirt, this is coupled with an exit in switch_to to
     * combine the page table reload and the switch backend into
     * one hypercall.
     */
    arch_start_context_switch(prev);

    if (!mm) {
        next->active_mm = oldmm;
        atomic_inc(&oldmm->mm_count);
        enter_lazy_tlb(oldmm, next);
    } else
        switch_mm(oldmm, mm, next);

    if (!prev->mm) {
        prev->active_mm = NULL;
        rq->prev_mm = oldmm;
    }
    /*
     * Since the runqueue lock will be released by the next
     * task (which is an invalid locking op but in the case
     * of the scheduler it's an obvious special-case), so we
     * do an early lockdep release here:
     */
    spin_release(&rq->lock.dep_map, 1, _THIS_IP_);

    context_tracking_task_switch(prev, next);
    /* Here we just switch the register state and the stack. */
    switch_to(prev, next, prev);//最關(guān)鍵的,切換寄存器的狀態(tài)和堆棧

    barrier();
    /*
     * this_rq must be evaluated again because prev may have moved
     * CPUs since it called schedule(), thus the 'rq' on its stack
     * frame will be invalid.
     */
    finish_task_switch(this_rq(), prev);
}

switch的匯編:

31#define switch_to(prev, next, last)                    \
32do {                                 \
33  /*                              \
34   * Context-switching clobbers all registers, so we clobber  \
35   * them explicitly, via unused output variables.     \
36   * (EAX and EBP is not listed because EBP is saved/restored  \
37   * explicitly for wchan access and EAX is the return value of   \
38   * __switch_to())                     \
39   */                                \
40  unsigned long ebx, ecx, edx, esi, edi;                \
41                                  \
42  asm volatile("pushfl\n\t"      /* save    flags */   \
43           "pushl %%ebp\n\t"        /* save    EBP   */ \
44           "movl %%esp,%[prev_sp]\n\t"  /* save    ESP   */ \
45           "movl %[next_sp],%%esp\n\t"  /* restore ESP   */ \
46           "movl $1f,%[prev_ip]\n\t"    /* save    EIP   */ \
47           "pushl %[next_ip]\n\t"   /* restore EIP   */    \
48           __switch_canary                   \
49           "jmp __switch_to\n"  /* regparm call  */ \
50           "1:\t"                        \
51           "popl %%ebp\n\t"     /* restore EBP   */    \
52           "popfl\n"         /* restore flags */  \
53                                  \
54           /* output parameters */                \
55           : [prev_sp] "=m" (prev->thread.sp),     \
56             [prev_ip] "=m" (prev->thread.ip),        \
57             "=a" (last),                 \
58                                  \
59             /* clobbered output registers: */     \
60             "=b" (ebx), "=c" (ecx), "=d" (edx),      \
61             "=S" (esi), "=D" (edi)             \
62                                       \
63             __switch_canary_oparam                \
64                                  \
65             /* input parameters: */                \
66           : [next_sp]  "m" (next->thread.sp),        \
67             [next_ip]  "m" (next->thread.ip),       \
68                                       \
69             /* regparm parameters for __switch_to(): */  \
70             [prev]     "a" (prev),              \
71             [next]     "d" (next)               \
72                                  \
73             __switch_canary_iparam                \
74                                  \
75           : /* reloaded segment registers */           \
76          "memory");                  \
77} while (0)

Linux系統(tǒng)的一般執(zhí)行過程分析

最一般的情況:正在運(yùn)行的用戶態(tài)進(jìn)程X切換到運(yùn)行用戶態(tài)進(jìn)程Y的過程

  1. 正在運(yùn)行的用戶態(tài)進(jìn)程X
  2. 發(fā)生中斷——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
  3. SAVE_ALL //保存現(xiàn)場(chǎng)
  4. 中斷處理過程中或中斷返回前調(diào)用了schedule(),其中的switch_to做了關(guān)鍵的進(jìn)程上下文切換
  5. 標(biāo)號(hào)1之后開始運(yùn)行用戶態(tài)進(jìn)程Y(這里Y曾經(jīng)通過以上步驟被切換出去過因此可以從標(biāo)號(hào)1繼續(xù)執(zhí)行)
  6. restore_all //恢復(fù)現(xiàn)場(chǎng)
  7. iret - pop cs:eip/ss:esp/eflags from kernel stack
  8. 繼續(xù)運(yùn)行用戶態(tài)進(jìn)程Y

幾種特殊情況

  • 通過中斷處理過程中的調(diào)度時(shí)機(jī),用戶態(tài)進(jìn)程與內(nèi)核線程之間互相切換和內(nèi)核線程之間互相切換,與最一般的情況非常類似,只是內(nèi)核線程運(yùn)行過程中發(fā)生中斷沒有進(jìn)程用戶態(tài)和內(nèi)核態(tài)的轉(zhuǎn)換;
  • 內(nèi)核線程主動(dòng)調(diào)用schedule(),只有進(jìn)程上下文的切換,沒有發(fā)生中斷上下文的切換,與最一般的情況略簡(jiǎn)略;
  • 創(chuàng)建子進(jìn)程的系統(tǒng)調(diào)用在子進(jìn)程中的執(zhí)行起點(diǎn)及返回用戶態(tài),如fork;
  • 加載一個(gè)新的可執(zhí)行程序后返回到用戶態(tài)的情況,如execve;

內(nèi)核是各種中斷處理過程和內(nèi)核線程的集合


Linux操作系統(tǒng)架構(gòu)概覽

? 任何計(jì)算機(jī)系統(tǒng)都包含一個(gè)基本的程序集合,稱為操作系統(tǒng)。
– 內(nèi)核(進(jìn)程管理,進(jìn)程調(diào)度,進(jìn)程間通訊機(jī)制,內(nèi)存管理,中斷異常處理,文件系統(tǒng),I/O系統(tǒng),網(wǎng)絡(luò)部分)
– 其他程序(例如函數(shù)庫(kù)、shell程序、系統(tǒng)程序等等)
? 操作系統(tǒng)的目的
– 與硬件交互,管理所有的硬件資源
– 為用戶程序(應(yīng)用程序)提供一個(gè)良好的執(zhí)行環(huán)境

**Linux系統(tǒng)的分層結(jié)構(gòu)**
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,115評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,234評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,621評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,822評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,380評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,128評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,319評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,548評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評(píng)論 1 291
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,048評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,285評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容