有趣! CPU 空閑時在干嘛?
人在空閑時會發呆會無聊,計算機呢?
假設你正在用計算機瀏覽網頁,當網頁加載完成后你開始閱讀,此時你沒有移動滑鼠,沒有敲擊鍵盤,也沒有網路通信,那么你的計算機此時在干嘛?
有的同學可能會覺得這個問題很簡單,但實際上,這個問題涉及從硬體到軟體、從 CPU 到作業系統等一系列環節,理解了這個問題你就能明白作業系統是如何作業的了,
你的計算機 CPU 使用率是多少?
如果此時你正在計算機旁,并且安裝有 Windows 或者 Linux ,你可以立刻看到自己的計算機 CPU 使用率是多少,
這是博主的一臺安裝有 Win10 的筆記本:

可以看到大部分情況下 CPU 利用率很低,也就在 8% 左右,而且開啟了 283 個行程,這么多行程基本上無所事事,都在等待某個特定事件來喚醒自己,就好比你寫了一個列印用戶輸入的程式,如果用戶一直不按鍵盤,那么你的行程就處于這種狀態,
有的同學可能會想也就你的比較空閑吧,實際上大部分個人計算機 CPU 使用率都差不多這樣(排除掉看電影、玩游戲等場景),如果你的使用率總是很高,風扇一直在嗡嗡的轉,那么不是軟體 bug 就有可能是病毒,,,
那么有的同學可能會問,剩下的 CPU 時間都去哪里了?
剩下的 CPU 時間去哪里了?
這個問題也很簡單,還是以 Win10 為例,打開任務管理器,找到 “詳細資訊” 這一欄,你會發現有一個 “系統空閑行程”,其 CPU 使用率達到了 99%,正是這個行程消耗了幾乎所有的 CPU 時間,

那么為什么存在這樣一個行程呢?以及這個行程什么時候開始運行呢?
接下來我們從作業系統講起,
代碼、行程與作業系統
當你用最喜歡的代碼編輯器撰寫代碼時,這時的代碼不過就是磁盤上的普通檔案,此時的程式和作業系統沒有半毛錢關系,作業系統也不認知這種文本檔案,

程式員寫完代碼后開始編譯,這時編譯器將普通的文本檔案翻譯成二進制可執行檔案,此時的程式依然是保存在磁盤上的檔案,和普通沒有本質區別,

但此時不一樣的是,該檔案是可執行檔案,也就是說作業系統開始 “懂得” 這種檔案,所謂 “懂得” 是指作業系統可以識別、決議、加載,因此必定有某種類似協議的規范,這樣編譯器按照這種協議生成可執行檔案,作業系統就能加載了,
在 Linux 下可執行檔案格式為 ELF ,在 Windows 下是 EXE ,
此時雖然作業系統可以識別可執行程式,但如果你不去雙擊一下(或者在Linux下運行相應命令)的依然和作業系統沒有半毛錢關系,
但是當你運行可執行程式時魔法就出現了,
此時作業系統開始將可執行檔案加載到記憶體,決議出代碼段、資料段等,并為這個程式創建運行時需要的堆區堆疊區等記憶體區域,此時這個程式在記憶體中就是這樣了:

最后,根據可執行檔案的內容,作業系統知道該程式應該執行的第一潭訓器指令是什么,并將其告訴 CPU ,CPU 從該程式的第一條指令開始執行,程式就這樣運行起來了,
一個在記憶體中運行起來的程式顯然和保存在磁盤上的二進制檔案是不一樣的,總的有個名字吧,根據“弄不懂原則”,這個名字就叫行程,英文名叫做Process,
我們把一個運行起來的程式叫做行程,這就是行程的由來,
此時作業系統開始掌管行程,現在行程已經有了,那么作業系統是怎么管理行程的呢?
調度器與行程管理
銀行想必大家都去過,實際上如果你仔細觀察的話銀行的辦事大廳就能體現出作業系統最核心的行程管理與調度,
首先大家去銀行都要排隊,類似的,行程在作業系統中也是通過佇列來管理的,
同時銀行還按照客戶的重要程度劃分了優先級,大部分都是普通客戶;但當你在這家銀行存上幾個億時就能升級為 VIP 客戶,優先級最高,每次去銀行都不用排隊,優先辦理你的業務,
類似的,作業系統也會為行程劃分優先級,作業系統會根據行程優先級將其放到相應的佇列中供調度器調度,

這就是作業系統需要實作的最核心功能,
現在準備作業已經就緒,
接下來的問題就是作業系統如何確定是否還有行程需要運行,
佇列判空:一個更好的設計
從上一節我們知道,實際上作業系統是用佇列來管理行程的,那么很顯然,如果佇列已經為空,那么說明此時作業系統內部沒有行程需要運行,這是 CPU 就空閑下來了,此時,我們需要做點什么,就像這樣:
if (queue.empty()) {
do_someting();
}
這些撰寫內核代碼雖然簡單,但內核中到處充斥著 if 這種例外處理的陳述句,這會讓代碼看起來一團糟,因此更好的設計是沒有例外,那么怎樣才能沒有例外呢?
很簡單,那就是讓佇列永遠不會空,這樣調度器永遠能從佇列中找到一個可供運行的行程,
而這也是為什么鏈表中通常會有哨兵節點的原因,就是為了避免各種判空,這樣既容易出錯也會讓代碼一團糟,

說到鏈表,在這里給大家分享一份刷題資料,認真過上一遍,國內BAT大廠面試演算法大部分題目都能做出來:Github瘋傳!阿里P8大佬寫的Leetcode刷題筆記,秒殺80%的演算法題!
就這樣,內核設計者創建了一個叫做空閑任務的行程,這個行程就是Windows 下的我們最開始看到的“系統空閑行程”,在 Linux 下就是第 0號行程,
當其它行程都處于不可運行狀態時,調度器就從佇列中取出空閑行程運行,顯然,空閑行程永遠處于就緒狀態,且優先級最低,
既然我們已經知道了,當系統無所事事后開始運行空閑行程,那么這個空閑行程到底在干嘛呢?
這就需要硬體來幫忙了,
一切都要歸結到硬體
在計算機系統中,一切最終都要靠 CPU 來驅動,CPU 才是那個真正干活的,

原來,CPU 設計者早就考慮到系統會存在空閑的可能,因此設計了一潭訓器指令,這個機器指令就是 halt 指令,停止的意思,
這條指令會讓部分CPU進入休眠狀態,從而極大減少對電力的消耗,通常這條指令也被放到回圈中執行,原因也很簡單,就是要維持這種休眠狀態,
值得注意的是,halt 指令是特權指令,也就是說只有在內核態下 CPU 才可以執行這條指令,程式員寫的應用都運行在用戶態,因此你沒有辦法在用戶態讓 CPU 去執行這條指令,
此外,不要把行程掛起和 halt 指令混淆,當我們呼叫 sleep 之類函式時,暫停運行的只是行程,此時如果還有其它行程可以運行那么 CPU 是不會空閑下來的,當 CPU 開始執行halt指令時就意味著系統中所有行程都已經暫停運行,
軟體硬體結合
現在我們有了 halt 機器指令,同時有一個回圈來不停的執行 halt 指令,這樣空閑任務行程的實際上就已經實作了,其本質上就是這個不斷執行 halt 指令的回圈,大功告成,
這樣,當調度器在沒有其它行程可供調度時就開始運行空間行程,也就是在回圈中不斷的執行 halt 指令,此時 CPU 開始進入低功耗狀態,

在 Linux 內核中,這段代碼是這樣寫的:
while (1) {
while(!need_resched()) {
cpuidle_idle_call();
}
}
其中 cpuidle_idle_call 最侄訓執行 halt 指令,注意,這里刪掉了大量代碼,實際 Linux 內核在實作空閑行程時還要考慮很多很多,不同型別的 CPU 可能會有深睡眠淺睡眠之類,作業系統必須要預測出系統可能的空閑時長并以此判斷要進入哪種休眠等等,但這并不是我們關注的重點,
總的來說,這就是計算機系統空閑時 CPU 在干嘛,就是在執行這一段代碼,本質上就是 CPU 在執行 halt 指令,
實際上,對于個人計算機來說,halt 可能是 CPU 執行最多的一條指令,全世界的 CPU 大部分時間都用在這條指令上了,是不是很奇怪,
更奇怪的來了,有的同學可能已經注意到了,上面的回圈可以是一個while(1) 死回圈,而且這個回圈里沒有break陳述句,也沒有return,那么作業系統是怎樣跳出這個回圈的呢?關于這一問題我們將在后續文章中講解,
總結
CPU 空閑時執行特定的 halt 指令,這看上去是一個很簡單的問題,但實際上由于 halt 是特權指令,只有作業系統才可以去執行,因此 CPU 空閑時執行 halt 指令就變成了軟體和硬體相結合的問題,
作業系統必須判斷什么情況下系統是空閑的,這涉及到行程管理和行程調度,同時,halt 指令其實是放到了一個 while 死回圈中,作業系統必須有辦法能跳出回圈,所以,CPU 空閑時執行 halt 指令并沒有看上去那么簡單,
希望這篇文章對大家理解 CPU 和作業系統有所幫助,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/278389.html
標籤:其他
