Linux內核分析(八)——進程的調度

禹曉博+ 原創作品轉載請註明出處 + 歡迎加入《Linux內核分析》MOOC網易雲課堂學習

一、進程調度簡析

       我們知道現在的操作系統想要看起來很流程必須在後臺進行快色的任務切換,才能達到表面上是哪個的多個進程同時執行的錯覺。進程的切換我們實際上我之前的文章中已經有說過了,實際上進程的切換正式爲了進程的調度做基礎性的功能準備,這個不用說就能理解吧~調度自認就是要不停頓切換了。

       在典型的Unix操作系統的調度算繁重必須實現幾個相互衝突的目標:那就是不但進程的響應時間要很快的同時,我們又要保證後臺進程或者說人物執行的吞吐量要高,不要出現進程的飢餓現象(就是有些進程總也不能被執行),這就需要保證進程的優先級無亂高低又要儘可能的被公平的執行,這就是調度要解決的一系列問題。也就是我們常常說的進程調度策略。

       Linux的調度基於分時技術:所謂的分時就是指將時間劃分成很小很小的片段,然後每個片段都相對公平的分給這一時刻需要執行的任務。這就我們所說的時間片調度。比如現在有是10個任務需要同時進行,每個任務都必須在1s內得到回饋,這樣我麼可以把1s分成10份,每過0.1s切換一個任務執行,這樣宏觀上看就是大家在同時的推進,當然實際上操作系統的時間片要小的多,比如1納秒之類的。

       進入正題,我們知道進程調度是操作系統的核心功能。調度器只是是調度過程中的一部分,進程調度是非常複雜的過程,需要多個系統協同工作完成。本文所關注的僅爲調度器,它的主要工作是在所有 RUNNING 進程中選擇最合適的一個。作爲一個通用操作系統,Linux 調度器將進程分爲三類首先是交互式進程,此類進程有大量的人機交互,因此進程不斷地處於睡眠狀態,等待用戶輸入。典型的應用比如編輯器 vi。此類進程對系統響應時間要求比較高,否則用戶會感覺系統反應遲緩。第二種就是批處理任務,此類進程不需要人機交互,在後臺運行,需要佔用大量的系統資源。但是能夠忍受響應延遲。比如編譯器。最後一種就是實時進程實時對調度延遲的要求最高,這些進程往往執行非常重要的操作,要求立即響應並執行。比如視頻播放軟件或飛機飛行控制系統,很明顯這類程序不能容忍長時間的調度延遲,輕則影響電影放映效果,重則機毀人亡。根據進程的不同分類 Linux 採用不同的調度策略。對於實時進程,採用 FIFO 或者 Round Robin 的調度策略。對於普通進程,則需要區分交互式和批處理式的不同。傳統 Linux 調度器提高交互式應用的優先級,使得它們能更快地被調度。而 CFS 和 RSDL 等新的調度器的核心思想“完全公平”。這個設計理念不僅大大簡化了調度器的代碼複雜度,還對各種調度需求的提供了更完美的支持。

1.1進程調度相關的數據結構

task_struct

       task_struct是進程在內核中對應的數據結構,它標識了進程的狀態等各項信息。其中有一項thread_struct結構的變量thread,記錄了CPU相關的進程狀態信息,如內核控制的斷點和棧指針等。在內核中獲得當前進程task_struct結構使用宏current,該宏讀取變量current_task得到指針。

thread_union   thread_info

       thread_union用於表示一個進程的內核態堆棧,當進程進入內核態時就會使用該進程對應的內核態堆棧。thread_union是一個聯合體,由stack和thread_info兩項組成。其中的stack用於直接訪問內核態堆棧的各項,而thread_info表示了堆棧頂部(低地址部分)用於特殊用途的部分。thread_info結構定義在include/asm-x86/thread_info_32.h,定義了本進程task_struct結構的指針等在進入內核初期馬上要訪問的輔助數據。在內核中得到當前thread_info用current_thread_info函數得到,它通過對當前棧指針進行計算得到thread_info的指針。

sched_class

       sched_class是Linux2.6中調度算法對外的統一界面。Linux使用這個概念將進程調度的具體策略和進程切換的過程隔離開,使得組織有序且可以實現對不同類進程採用不同的調度策略。在Linux-2.6.26中sched_class有fair_sched_class,rt_sched_class和idle_sched_class三個實例,分別組織在kernel下的sched_fair.c、sched_rt.c、sched_idletask.c中。這三個實例在初始化時被串成了一個鏈表,依次爲:rt,fair,idle

sched_entity   sched_rt_entity

    內核中對sched_entity的解釋爲“CFS stats for a schedulable entity (task, task-group etc)”,sched_rt_entity可類似解釋。從這裏可以看出這個結構的作用是存儲一些調度算法相關的進程的狀態。

rq

       rq是當前CPU上就緒進程所組織成的隊列。這個結構體記錄了每個隊列的狀態。rq結構體中有cfs_rq和rt_rq兩個子結構,分別描述了該CPU上fair類型和rt類型進程的信息。

1.2 關於schedule函數的簡析

       (其實應該放在實驗裏說的呢~強迫症犯了於是就放在1.2吧,看起來結構上好一些)schedule函數是進程調度的入口,在kernel/sched.c中。除去繁瑣的檢查、統計、上鎖等操作,仔細觀察,其主流如下:


       第一句中的prev在之前被賦值爲rq->curr,因此是當前運行隊列正在運行的進程。從字面看是將當前進程放回隊列。第二句是從隊列中取出下一個可運行的進程,叫next。接下來是進程的上下文切換工作。首先判斷prev和next是否是同一個進程,若是,則不必切換。否則統計信息,接着設置rq->curr爲next,然後調用context_switch來進行實際的上下文切換。schedule函數的簡要分析結束。可見,理解進程的調度,核心是put_prev_task和pick _next_ task ;而理解進程的切換,核心是context_switch。下面就分兩條線索,分別說明進程的切換和調度的流程。

二、實驗過程

        這周的實驗過程可能有點簡陋啊,由於筆者這一整週都基本在面試某家集合了互聯網商務金融雲計算物流綜合業務各種多的公司有木有,一面就要一個多小時,一天要好幾面咩。啃啃,╮(╯▽╰)╭那位說了,面的啥啊?嘿嘿~不告訴你睡覺,所以不周到之處能望留言批評,我再補發。下週還要考試,真是任務緊啊~


       上面就是這幾週一直在做的大小S啦,大家都懂的吧~調試跟蹤內核啓動麼~。然後我們在進入一個新的終端控制檯輸入gdb開始調試啊,設置監視,加載文件符號表啊什麼的,這都很簡單的吧。


       按照慣例我們開始設置斷點了當然就是要設置到那個schedule函數上了剛纔分析過了。然後我們開到程序只想到可2866之後停了下來。然後我們看到在2867的地方有一個賦值過程就是講當前的任務信息拷貝到任務結構體中,繼續輸入list我們就可以看到更多的代碼了。


      繼續向下我們看到有一個設置當先進程狀態的語句在499那個地方。就是調度之後將進程狀態設置爲正在運行。


       然後我們又看到了自旋鎖這個東西,這個不用說了吧,爲什麼要有呢。實際上在調度的過程中很多和上下文保存和切換的時候是不能被打斷的,這個行爲是非搶佔的所以要有一些機制保證他不會被打斷,同時這個過程有經常發生,所以我們不能用過於複雜的同步技術,自旋鎖作爲輕量級的同步工具這裏就比較合適了(那位說了上次在之前的切換的文章裏面你不就說要單說說自旋鎖的咩,啃啃筆者是個說話算數的人,當時說的時候也強調了有機會~好麼


       其實在2831之後也是這種賦值的語句用於給當前的任務做切換用,然後就有又會進入下一次的schedule了。

三、總結

       進程的調度少不了進程的切換,中做的關鍵操作是:切換地址空間、切換內核堆棧、切換內核控制流程,加上一些必要的寄存器保存和恢復。這裏,除去地址空間的切換,其他操作要強調“內核”一詞。這是因爲,這些操作並非針對用戶代碼,切換完成後,也沒有立即跑到next的用戶空間中執行。用戶上下文的保存和恢復是通過中斷和異常機制,在內核態和用戶態相互切換時才發生的。從這個意義上講,切換地址空間纔是本質上想要達到的“用戶代碼和數據的切換”,其餘的切換不過是內核中不同的控制流程在“交接棒”而已。進程切換這裏當初領會起來比較難,但是一旦理解,就會深深佩服這一系列過程的巧妙。特別是switch_to宏,幾乎就是多一句嫌多,少一句嫌少。

       下面用幾幅圖總結一下switch_to的過程。

       首先是switch_to之前:


       切換堆棧之前:


       切換堆棧之後:


       push和jmp:


       switch_to返回:


       switch_to完成:


       看源代碼我們知道schedule這個函數的中心環節是一個for循環,它遍歷sched_class的每一個實例,並依次調用各個實例的pick_next_task函數。若返回非空,則將下一個進程設爲它。由此可見,Linux調度系統採用的是操作系統理論中的多級隊列調度,且上一個隊列中進程的優先級恆比下一個隊列中進程高。本文第一部分已述,sched_ class 鏈表依次爲rt、fair、idle。因此,只要有rt類型進程就緒,調度時就一定會被選擇,從而保證了rt類型進程的實時性。註釋中還提到一點,idle隊列中一定非空,因此在前兩個類型的進程都沒有就緒時,idle中的idle進程一定會被選中並調度,這保證了循環一定能終止。這裏可以看到系統idle進程的重要性。









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章