您現在的位置是:首頁 > 攝影首頁攝影

微控制器裡面的CPU使用率到底該怎麼計算?

由 魚鷹談微控制器 發表于 攝影2021-07-24
簡介這個任務的執行時間應該是固定的才對,但即使是使用了後面的高精度計算方式,CPU使用率還是會跳動,這是為什麼

實際使用率怎麼算

上週提到為什麼我們需要關注CPU利用率的問題,總結一句話就是,利用率越低,你的系統效率越高、響應越快,實時性越高。但是並沒有具體說該如何計算CPU利用率。

今天,藉助國產作業系統RT-Thread,我們開始實操一番。

在實操之前,需要簡單瞭解幾個概念。

鉤子函式,即以hook命名的那些函式。那麼什麼是鉤子函式呢?說白了,就是一個

函式指標

,只是這個函式比較特殊一點。

特殊在哪?作業系統某些指定位置才會設定鉤子函式,比如程式執行到空閒任務了,為了不修改系統原始碼(沒事別修改原始碼,很危險的事情,除非你是真大佬),系統會提供一個設定鉤子函式的函式介面給你,當你需要在空閒任務中執行某些功能時,用這個函式設定你的需要功能函式就可以了,等系統執行到空閒任務,他就會幫你呼叫這個函數了。

這個功能看著是不是有點眼熟,對的,和所謂的回撥函式是一個道理(我也不明白為啥叫鉤子函式,可能是因為和系統有關,和通用的回撥函式又有點區別,所以就稱之為鉤子函式吧,不過你不要管名稱,只要知道意思就行了)。

除了在空閒任務可以設定鉤子函式,還有可能在任務切換、系統啟動、任務建立等等關鍵的地方設定,當然了,這裡的每一個鉤子函式都是一個單獨的函式指標。

前面也說了,設定鉤子函式的目的只有一個,那就是可以讓你在不修改系統原始碼的情況下達到私人目的,讓系統的擴充套件性更強,比如今天說的內容(還有下次介紹的執行緒CPU使用率問題),如果系統沒有空閒鉤子函式的存在,你只能去修改系統原始碼才能達到目的啦。

還有文章所說的執行緒(task)、任務(thread),其實在RTOS中都是一樣的。在 uCOS、FreeRTOS 中,叫任務,RT-Thread 叫執行緒,只是叫的名稱不一樣,內容都是差不多的。

然後再大概說說怎麼計算的問題。也就是在空閒鉤子函數里面,我們需要幹什麼事情才能到達CPU計算的目的。

首先,第一步肯定是設定鉤子函式,其次就是鉤子函式該怎麼寫的問題。

這個網上一搜就出現了(魚鷹也是網上搜的程式碼),然後就要分析為什麼這麼寫。

前面說過,CPU利用率其實是首先計算一段時間內空閒任務執行時間,然後反推其他任務的執行時間。

這裡有兩個問題,一段時間是多少?空閒任務的執行時間怎麼計算?

先說第二個問題。用定時器時間掐?好像不好,因為你不知道什麼時候程式就離開了空閒任務跑去執行其他任務了,而即使你可以知道它什麼時候離開空閒任務的,那也會增加計算難度,不是好的方式。

那怎麼辦?還記得剛學微控制器時你是怎麼進行軟體延時的嗎?對,就是用這個方法,軟體延時!

只要程式執行到空閒任務了,就用一個變數不停自加。這樣就可以根據變數值來大概計算空閒任務的執行時間。

但是這裡又存在一個問題:如果這個變數一直自加,肯定會溢位,該怎麼解決。

加大變數的大小,比如原先使用一個位元組、兩個位元組的,那麼如果溢位,就用四個位元組、八個位元組。

但32位系統最大能支援的也就8個位元組了,如果還是溢位了咋辦?再套一個迴圈,一個迴圈的數加完了,再加另一數就行了。

但是還有一個問題,如果說自加的時間不做限制,那麼再多的變數也不行,而且還會影響CPU計算的實時性,也就不能實時反映CPU利用率了;而如果時間太短,如果剛好有任務的執行時間在這個範圍,那麼很可能你計算CPU利用率就直接是100%了。

比如說你一個任務需要執行10毫秒,然後你計算CPU的週期也是10毫秒,那麼可能剛好開始計算時跳到了那個任務執行,那麼你的變數就沒有自加了,也就會顯示100%利用率了。

這裡其實說的是前面的第一個問題,一段時間是多少?

對於這個時間,因為魚鷹看的書籍比較少,所以也沒有理論支撐(如果有道友知道的,不如留言)。

但是肯定既要考慮變數溢位(這個可以透過加迴圈方式解決),又要考慮實時性,還要考慮其他任務的

最大執行時間

,否則本來系統沒有問題的,但是因為你追求實時性,導致CPU利用率80%、90%的,那就很尷尬了。

以上討論如果沒有經驗可能比較難理解,所以建議大家在看完後面內容,實操過後,再回頭重新看一遍,這樣才有更深的理解。

現在再看CPU計算公式:

cpu_usage = (total_count – count)/ total_count × 100 %

cpu_usage: CPU利用率;

total_count:單位時間內全速執行下的變數值;

count:單位時間內空閒任務自加的變數值。

total_count這個值表現了微控制器全速執行下,所能達到的最大值。所謂全速執行,

即不響應中斷,也不去執行其他任務

,就單純讓它在一個地方持續執行一段時間,這個值可以體現CPU的算力有多大。

比如,51微控制器,可能這個值自加10毫秒之後只有100,STM32F1微控制器自加能到1000,而STM32F4微控制器能到2000,這樣就能體現他們之間的算力差別了。

這個值可以是動態的,也可以是靜態的。靜態有靜態的好處,動態有動態的好處。

所謂的靜態是指,在系統沒有執行任務時,關閉所有的中斷,自加這個值。這樣,這個值比較準確,但是如果一開始這個值計算錯了,那麼後面的計算肯定也是有問題的,而且如果系統啟動後長時間既不啟動任務,也不響應中斷,肯定對系統有一定的影響。但是好處是,系統消耗更少,因為他只計算一次。

而動態計算,則是在空閒任務中,當這個值為零時,計算一次,之後只會在空閒任務自加的變數值超過這個數時,才會更新這個值,這樣一來,最終還是能準確反映CPU利用率的。好處是,不需要在開機時關閉所有中斷,當然壞處是,前期可能不是很準,因為可能由於中斷原因導致計算的值較小(中斷處理時消耗了算力)。

廢話太多了一些,直接開始幹吧。新建一個檔案,複製如下程式碼:

微控制器裡面的CPU使用率到底該怎麼計算?

微控制器裡面的CPU使用率到底該怎麼計算?

微控制器裡面的CPU使用率到底該怎麼計算?

微控制器裡面的CPU使用率到底該怎麼計算?

以上的程式碼網上找的,首先分析這兩個宏,第二個宏就是前面所說的防止變數溢位用的,而第一個值就是CPU計算週期,這個值比較關鍵,後面再說。

首先在系統啟動前設定鉤子函式:

微控制器裡面的CPU使用率到底該怎麼計算?

然後,就沒有然後了。

對的,設定完之後就可以了,但為了讓我們能觀察到,可以打印出來。

微控制器裡面的CPU使用率到底該怎麼計算?

我們可以觀察效果如何,開始設定計算週期和任務延時函式一樣,10毫秒。

微控制器裡面的CPU使用率到底該怎麼計算?

測試結果:

微控制器裡面的CPU使用率到底該怎麼計算?

可以看到,因為是動態計算的,所以開始為0,因為系統首先執行其他任務,只有其它任務不執行時,才會開始執行空閒任務,所以CPU利用率為0。

但是即使後面有值了,你也會發現CPU利用率變化很大,0。82%~1。5%。而且你會發現除了開始的0。0%,後面又再次出現了,這又是怎麼回事?

透過設定斷點分析,發現,這是因為計算值超出了開始的值,重新設定了:

微控制器裡面的CPU使用率到底該怎麼計算?

這就是動態計算的一些問題了,它在一開始的一段時間裡,因為無法完全表現算力,只能通過後面不停的修正該值才能達到穩定。

現在修改計算週期 20 毫秒:

微控制器裡面的CPU使用率到底該怎麼計算?

發現它的表現更差勁,4。3%~11。61%,而且會週期性出現低利用率的情況。

再改,100毫秒:

微控制器裡面的CPU使用率到底該怎麼計算?

可以看到這個比較穩定了,13。71%~14。35%。

那麼這個測試程式碼實際情況的CPU利用率是多少呢?

我們可以透過前面的筆記《》大概計算執行緒執行時間:

微控制器裡面的CPU使用率到底該怎麼計算?

1。59毫秒,10毫秒執行週期,如果只有這個任務執行,大概1。59/10=15。9%(準確計算應該是 1。59/(10 + 1。59) =13。7%)。

和前面的100毫秒類似。

我們先不管前面的結果,先理解一下里面的計算方法。

首先,如果total_count開始為0,那麼開始第一次計算。這次計算會關閉排程器。

微控制器裡面的CPU使用率到底該怎麼計算?

計算過後,就不再進入。

之後就是動態計算過程:

微控制器裡面的CPU使用率到底該怎麼計算?

和第一次計算一樣,都是在一定時間內自加計數器,不同的是,這次不會關閉排程器,也就是說,如果有高優先順序任務就緒,那麼是可以執行其他任務的。

並且計時時間使用的是系統函式rt_tick_get(),單位為系統排程時間。測試環境中,系統排程時間為 1 毫秒。

有意思的是,在進行最終的計算時,採用了分步計算,首先計算整數,再計算小數。

為什麼要這樣做?效率!

這樣的計算方法,可以將浮點運算轉化成整型運算,這在沒有浮點運算單元的微控制器中,能大大減少計算時間。

另外,為了防止溢位,還使用了一個迴圈結構。

理解了以上內容,現在開始進行魚鷹式深度思考:

1、 上面的分步計算是否存在問題?

2、 關排程器只關閉了任務排程,但還是會響應中斷,這能夠體現微控制器最大算力嗎?

3、 使用rt_tick_get() 函式進行計時,精度是多少,會影響最終的計時嗎?

4、 有必要使用迴圈體嗎?如果單位時間內不溢位,是否不用迴圈體會更好?

5、 前面的CPU使用率為什麼會跳動,按理說任務的執行時間應該是確定的,也只有一個任務在執行,不應該跳動才對?

6、 10毫秒的計算和100毫秒的計算差別在哪?

7、 終極問題,如何精確計算CPU使用率?

上面的問題,如果只是粗略計算,其實都可以不用考慮,本著對技術的熱愛,還是聊一聊好了。

1) 分步計算,不知道你想到了什麼BUG?這個問題其實在以往的筆記都提過,這次再說一次。

當你在獲取CPU使用率時,如果剛好在更新這兩個值,那麼可能整數部分是上一次計算的值,而小數部分卻是這次計算的值,那麼肯定有問題。

這就涉及到資料完整性獲取的問題。怎麼解決。關排程器、關中斷都可以。

但是因為是粗略計算,那麼小數部分即使是錯誤的,也沒事。

2) 因為只關排程器,所以對於中斷還是會響應,比如說你設定計算週期為100毫秒,那麼1毫秒一次的systick中斷肯定會執行,那麼在100毫秒中,有100次進入中斷執行,而這些算力在上述演算法中是無法體現的。

3) rt_tick_get() 函式精度問題,因為這個是系統的軟體計時器,所以在測試環境中為1毫秒遞增一次,也就是說它的精度在1毫秒。因此,在100毫秒的計算週期裡面,有1% 的誤差存在,在10毫秒的計算週期裡面,誤差10%!

4) 有沒有必要用迴圈體?在1秒計算一次的情況下,即使不用迴圈體,也不會導致溢位問題。而且使用了迴圈體,還會導致精度降低,畢竟樣本少了。比如使用迴圈體最大值為100,不使用時為10000,哪個精度高?

5) CPU使用率跳動問題。因為是測試,所以只有一個任務在執行,而且任務很簡單。

微控制器裡面的CPU使用率到底該怎麼計算?

這個任務的執行時間應該是固定的才對,但即使是使用了後面的高精度計算方式,CPU使用率還是會跳動,這是為什麼?

第一,rt_kprintf函式執行時間是不固定的,不固定在哪,比如要顯示的變數開始是1,後面是1000,因此它輸出的字串不一樣,並且列印時間也不一樣,因為是查詢方式列印,所以差別很大!這就是我為什麼推薦DMA列印的原因,未使用前是10%,使用後可能就是1%,甚至更低。

第二點,也是非常容易忽視的一點,插入的中斷執行時間。

系統每隔1毫秒需要進入systick執行一次(或者其他中斷執行時間),如果說任務的執行時間超過1毫秒,那麼中間必然會先執行中斷,再執行任務,這樣一來,因為中斷的插入,導致時間不再那麼準確了。而當你把列印的時間控制在 1 毫秒以內,那麼CPU使用率會變的非常穩定。

第三:延時rt_thread_delay()函式本身的誤差,受到系統精度的影響,這個延時時間其實也不是固定的,會有一定的浮動。

6) 10毫秒和100毫秒計算的差別?

如果說你的

任務執行時間

小於1毫秒,那麼在10毫秒和100毫秒的計算差別不是很大,但是如果說計算週期變成了5毫秒,即使任務執行時間小於1毫秒的情況下,計算值也是會在

最大

最小

之間來回跳動的。而執行時間一旦超過1毫秒,那麼10毫秒和100毫秒的計算就有較大的差別。

並且測試的時候,因為系統延時時間是10毫秒,而計算的時候也是10毫秒的週期,所以出現了比較詭異的事情,因為按理說延時10毫秒,任務執行時間2。56毫秒,任務執行週期為12 毫秒(還記得前面所說的延時誤差嗎),CPU 使用率按理應該是 21。3 左右,實際上卻是 6。5% 左右,相差太大了,這就非常奇怪了。而且如果更改執行時間為1。5毫秒時(透過修改程式碼修改執行時間),發現計算值又正常了;而即使不修改執行時間,修改計算時間為100毫秒,又正常了,這是怎麼回事?

透過深入分析發現,剛好在主任務延時10毫秒的時候,切換到了空閒任務進行空閒時間計算,執行了9。4毫秒的時候,又切回到了主任務,所以計算時,得到了6。5%的計算值。

粗略表示如下所示:

微控制器裡面的CPU使用率到底該怎麼計算?

透過這個分析,你應該知道,計算CPU的時候,儘量不要使用和任務延時時間一樣的計算週期,否則會出現莫名其妙的事情;還有一點就是,任務的執行週期 = 任務執行時間 + 系統延時,而前面所介紹的計算方法只是粗略的表示,嚴格來說是有問題的。

7) 終極問題,如何提高計算精度?

透過以上分析,我們其實已經知道了計算時的一些問題點。首先,計算週期問題,這個可以根據系統來確定,但是千萬要注意前面的提到的問題。如果說500毫秒計算週期可以滿足要求的話,就沒必要使用50毫秒,不然你會發現計算值跳動很大。

其次,時間精度問題,這個問題老生常談了,魚鷹建議是DWT,如果沒有,找一個定時器代替也是可以的。

最後是單位時間算力問題,為了保證精確,可以關閉中斷進行第一次計算,或者用短一點的時間,比如1毫秒得到一個算力,如果計算週期為100毫秒,那這個算力乘以100就行了。當然如果系統時鐘不經常變的話,也可以透過靜態方式先得到單位時間的算力,之後就以它為標準就可以了。這樣就不會有長時間關中斷的情況出現了。

但是計算算力的時候,千萬千萬要注意一點的是,C語言轉化為彙編程式碼時,可能一樣的程式碼,

在不同的地方執行時間是不一樣的

(比如前面程式碼的第一次計算和後面的計算,看似一樣,但實際上有較大差別,原因就在於執行效率不一樣),這個涉及到暫存器比記憶體效率更高的問題,所以計算算力時,可以把它封裝成一個函式,這樣,只要最佳化等級不變,那麼函式的執行時間就可以認為是確定的。

微控制器裡面的CPU使用率到底該怎麼計算?

有幫助的話,記得關注哦!