一、問題描述: 在待機或正常使用過程中,時不時遇到桌面無響應的情況,但滑鼠正常移動。網路上大致給出以下幾種處理思路: 1.移除拓展塢,集線器2.打開設備管理器,通用串列匯流排控制器,對裡面每個設備的:“允許電腦關閉此設備以節約電源”,把勾去除3.通過命令徹底卸載小組件4.更換無線網卡驅動 本問題實際 ...
第3章實時嵌入式Linux
電腦系統與環境之間的交互通常是實時發生的,因此,對於作為電腦系統一部分的嵌入式設備來說,有關實時操作系統的討論也是一個重要話題。
本章將討論實時系統的特點,介紹在Linux嵌入式設備上實現實時性的主要方法。具體來說,本章將重點分析PREEMPT_RT內核補丁,該補丁可輕鬆應用於主線內核。然後,將分析調度延遲的概念以及導致延遲增加的主要原因。最後,結尾部分將重點介紹如何利用特定工具和方法(如Cyclictest提供的工具和方法)以及涉及剖析工具的更複雜工具來測量和分析延遲。
3.1 Linux與實時性
Linux中的實時性是一個非常有趣的話題,每當使用Linux作為操作系統的設備需要運行具有實時性要求的應用程式時,這個話題就會出現。
正如已經強調的那樣,Linux幾乎具備了在許多系統和ICS中採用所需的所有特性。然而,正如第 2.1節末尾所述,當需要運行的應用程式需要實時行為時,Linux就會受到影響。造成這一問題的主要原因是,Linux最初是作為通用操作系統設計的。因此,Linux在設計時並沒有考慮時間的確定性,因此它並不是一個實時操作系統。
分析Linux的設計方式,它的目的是提供最高級別的整體性能。這隱含地意味著,事情不會以確定的時間方式發生,因為一切都將以提高整體性能水平的方式進行,而眾所周知,這兩件事是背道而馳的。
儘管如此,考慮到Linux提供了許多有用的功能,但當它必須用於時間關鍵型應用程式時,就會遇到這個問題。
3.1.1 什麼是實時?
實時的含義是一個相當混亂的概念,人們在被問及實時系統的定義時,往往會給出許多相互矛盾的定義。因此,在繼續討論之前,有必要澄清實時系統的概念。
一些最混亂的定義涉及以下說法:
- 實時是指執行速度快
- 實時是指性能高
- 實時是指響應速度快
這些說法都有一定的道理,但仍有不足之處。事實上,在談到實時系統時:它並不是指最快的執行速度和最快的響應時間,也不是指最好的性能,而是指時間保證。換句話說,實時任務的定義並不是要求任務的執行速度越快越好,而是要求任務的執行速度在時間要求規定的範圍內。
如果一項任務必須在某個時間點(即截止日期)之前完成,那麼它就可以被定義為實時任務。因此,在處理實時系統時,在指定的時間段前完成任務並不重要,重要的是保證任務的最後期限始終得到遵守。
綜上所述,在討論實時系統時,任務在特定機器上執行的演算法的正確性不僅取決於演算法結果本身的正確性,還取決於演算法的執行時間。
這意味著,如果演算法沒有在指定的時間段內執行,相應的時間違規將導致獨立於演算法結果的錯誤條件。
當然,並不是所有的錯誤條件都是一樣的。因此,根據錯過截止時間造成的後果,系統可分為以下幾類:
- 硬實時:錯過最後期限會導致整個系統癱瘓。
- 穩健實時:可以容忍不頻繁的截止日期缺失,但服務質量可能會下降,因為截止日期後產生的結果完全無用。
- 軟實時:錯過截止日期後,產生的結果會降低,但仍可使用。不過,多次錯過截止日期後,系統質量可能會下降。
3.1.2 讓Linux實現實時運行
誰真正需要實時Linux?
- 工業
- 自動化
- 汽車
- 多媒體系統
- 航空航天
- 金融服務
不僅在嵌入式設備上,而且在功能更強大的電腦系統中,都需要具備實時功能。傳統上,使Linux具有實時性的方法主要有兩種:第一種是基於所謂的雙內核方法,這種方法並不是為了使實際的Linux內核具有實時性,另一種是代表新趨勢的內核方法。
雙內核(dual-kernel)方法
最著名的使Linux實時化的雙內核方法是RTAI和Xenomai:它們使用的方案如圖 3.1 所示。
在這種情況下,實際的Linux內核在微內核上運行,微內核確保實時任務的可調度性,併在每次需要時搶占整個Linux內核。
"Altenberg 說:"有了雙內核,當優先實時應用程式不在微內核上運行時,Linux 可以獲得一些運行時間。
這種方法最明顯的問題是,必須有人維護微內核,並支持將其移植到新的硬體平臺上。此外,由於 Linux 不能直接在硬體上運行,因此還需要定義和維護一個硬體抽象層。
當然,解決這些問題需要付出巨大的努力,這也是因為開發社區沒有維護通用Linux內核的社區那麼大。因此,考慮到所需的精力和需要維護的各種東西,這些雙內核方法通常要比實際的Linux主線版本晚幾步。更詳細地說,RTAI是米蘭大學開發的第一個嘗試。使用它,可以在內核空間編寫實時應用程式,而實時應用程式與用戶空間之間的交互是通過非常有限和特殊的方法完成的。RTAI的目的是獲得最低的延遲,支持的操作系統包括x86、x86_64 和一些 ARM 平臺支持。
"有了RTAI,你就可以編寫一個由微內核調度的內核模塊。這就像內核開發一樣,真的很難進入,也很難調試"。此外,由於RTAI的開發是在內核空間進行的,根據內核通用公共許可證(GPL)的規定,代碼必鬚髮布,這可能會使事情變得更加複雜,因為工業客戶往往希望使用封閉源代碼。
如今,RTAI的另一種雙內核替代方案是Xenomai,它已成為主流。它比RTAI支持更多的硬體平臺,但更重要的是,它提供了一種在用戶空間進行實時操作的解決方案。
"為此,他們提出了皮膚的概念,即不同實時操作系統(RTOS)(如Unix的可移植操作系統介面(POSIX))API 的模擬層。這樣就可以重新使用某些實時操作系統的現有代碼子集"。
不過,即使使用Xenomai,也需要維護一個單獨的微內核和一個硬體抽象層。此外,標準C庫無法使用,應用程式開發需要特殊工具。總之,雙內核方法剋服了Linux在管理實時應用程式方面的局限性。不過,所分析的兩種解決方案都有一些不容忽視的缺點,尤其是在處理大型複雜項目時。
in-kernel方法
由於使用雙內核方法可能會很複雜,這可能會阻礙開發人員使用Linux,因此最好的辦法就是使實際的Linux內核具有實時性,而不需要任何外部微內核的幫助,如圖 3.2 所示。
實時操作系統要想獲得確定性時序行為,首先必須提供搶占功能。具體來說,它需要在大部分時間都能搶占先機,因為優先順序較高的任務必須總是搶占其他不太重要的任務和優先順序較低的任務的先機。當然,一旦增加了搶占功能,就必須管理和解決其他眾所周知的問題,如優先順序倒置。
傳統上,使主線內核實時運行所需的所有轉換都是通過內核補丁完成的:即所謂的 PREEMPT_RT。
如今,PREEMPT_RT補丁已被廣泛使用,它是使Linux實現實時運行的主要方法。
3.1.3 PREEMPT_RT
PREEMPT_RT補丁代表了最常用的內核方法,可使Linux在實時場景中發揮作用。此外,由於PREEMPT_RT已獲官方支持,它允許使用標準POSIX,而不需要特殊的API來編寫實時應用程式。因此,PREEMPT_RT得到了很好的支持,並得到了官方社區的高度認可,其大部分功能已被納入主線內核。對此Linus Torvalds在2006 年的一次峰會上表示支持:"用Linux控制激光器太瘋狂了,但在座的每個人都有自己的瘋狂之處。因此,如果你想用 Linux 來控制工業焊接激光器,我對你使用 PREEMPT_RT 沒有意見"。
在繼續分析PREEMPT_RT補丁的實際作用之前,我們不妨先看看Linux系統中造成非確定性的最常見原因。
3.1.3.1 延遲和非確定性的來源
由於系統延遲的增加,各種原因都可能導致實時應用程式錯過最後期限。一般Linux系統中最常見的非確定性原因包括:
- 調度演算法
實時線程需要先於其他任務進行調度,因此需要實時調度策略。此外,該策略還必須管理根據任務截止日期分配的各種優先順序。
- 調度延遲
實時內核必須能夠在事件發生(如觸發中斷)後立即重新調度,調度越早越好。減少調度延遲是一個關鍵點,因此本節下文將對此進行詳細介紹。
- 未啟用搶占機制
在執行關鍵部分時,搶占機制可能被禁用。當然,由於進程無法搶占先機,這個問題可能會導致意想不到的延遲。因此,在實時情況下,必須禁用搶占機制。
- 優先順序倒置
這是一個眾所周知的問題,由於優先順序較高的線程被阻塞在優先順序較低的任務所持有的互斥任務上,可能會導致無限制的延遲。應對這一問題的最常用解決方案之一是實施優先順序繼承機制。這在時間上提高了持有互斥任務的低優先順序線程的優先順序,以免高優先順序任務被鎖定太長時間。
- 精確定時器
當需要滿足的最後期限非常小(幾毫秒或幾微秒)時,高解析度定時器對小時間量的敏感性至關重要。
- Page故障
在執行實時任務時,頁面故障可能會導致意想不到的延遲,因此需要一些可以鎖定記憶體的機制。
- 中斷
由於中斷會以不可預測的時間率發生,實時進程的延遲可能會顯著增加,尤其是當多個中斷接連發生時。為瞭解決這個問題,一種解決方案是將中斷作為內核線程運行,或者在多核CPU中,將中斷處理工作交由一個內核專門負責。
- 處理器緩存
緩存在CPU和主記憶體之間提供了高速緩衝區,但由於其本身的性質,緩存是非確定性的來源,特別是在多核設備上。
- 記憶體匯流排爭用
通常,當記憶體傳輸直接通過DMA外設進行時,用於移動數據的通道與CPU共用,這取決於可用帶寬,可能會減慢實時任務的執行速度。因此,當使用DMA通道時,延遲可能會增加。
- 電源管理
實時策略和電源管理策略完全背道而馳。事實上,由於從一種睡眠狀態切換到另一種睡眠狀態不可避免地需要時間,電源管理往往會導致更高的延遲。導致無法瞬時轉換的原因有很多:從時鐘頻率發生器需要時間來穩定,到穩壓器設備需要時間來提供穩定的輸出。因此,可以肯定的是,從低功耗狀態退出的設備不會立即對中斷或其他刺激做出響應,延遲也會因此而增加。
3.1.3.2 調度延遲
正如預期,調度延遲是實時系統的一個關鍵點。事實上,為了使實時任務不會錯過最後期限,它們需要在有事情要做時立即進行調度。
然而,在實際系統中,即使CPU處於空閑狀態,也沒有其他具有相同或更高優先順序的線程在運行,在喚醒事件發生的瞬間與相應線程開始執行的時間之間,總會存在一定的延遲。如圖3.3所示,這種延遲被稱為調度延遲。
第一個延遲是硬體中斷延遲:從中斷發生到啟動相應的中斷服務常式(ISR)。而這一延遲又由兩部分組成:第一部分(通常較小)是由中斷硬體本身造成的,其餘部分則是通過軟體禁用中斷造成的。因此,儘量縮短中斷被禁用的時間非常重要。
下一個延遲是處理程式延遲:它由ISR執行所需的時間決定,這個延遲主要取決於常式的編寫方式,希望它只需要微秒量級的短時間。
一旦ISR執行完畢,就會出現調度延遲:從通知內核運行調度程式的時間點到實際執行的時間點。最後的延遲由調度器持續時間給出:這是調度器演算法決定何時開始運行實時線程所需的執行時間。
當然,調度器延遲及其持續時間取決於內核是否可以搶占先機,事實上,如果內核正在關鍵部分運行某些代碼,重新調度就會延遲,從而導致總延遲增加。
既然我們已經分析了非確定性的主要原因,也瞭解了調度延遲的含義,那麼就可以回答"PREEMPT_RT到底是做什麼的 "這個問題了。
基本上,PREEMPT_RT補丁的主要作用是使Linux內核完全搶占式,同時解決非確定性的主要原因,以減少延遲。
更詳細地說,這些目標是通過以下方式實現的:
- 使用自旋鎖(spinlock)將內核鎖定原語轉換為可搶占式鎖定原語,並使用實時互斥器(real-time mutexes)重新實現這些原語。
- 使用新的可搶占式鎖定原語重新實現大多數關鍵部分 - 使用優先權繼承機制解決內核內 spinlocks 和 semaphores 的優先權反轉問題。
- 使用可搶占的內核線程管理中斷處理程式,即 PREEMPT_RT 補丁將軟中斷處理程式作為內核線程來管理。
- 改進舊的 Linux 定時器 API,以便在用戶空間上下文中也能管理高解析度的內核定時器。
事實上,標準的Linux內核已經包含了高解析度定時器、內核互斥和線程中斷處理程式。
然而,所有旨在減少內核在原子上下文中運行時間的內核核心修改,即增加內核可搶占時間的修改,都被保留在主線之外,因為它們具有相當的侵入性。
事實上,將這些修改納入主線後,只有一小部分Linux用戶能從中受益,因為平均延遲時間會更長,但確定性更高,這正是 PREEMPT_RT 的作用所在。
另一個問題與如何將PREEMPT_RT 修改應用到標準內核有關。
顧名思義,PREEMPT_RT補丁是必須應用於源代碼的內核補丁,因此,要啟用完整的實時功能,需要內核源代碼和PREEMPT_RT補丁。
除內核源代碼補丁外,還必須通過PREEMPT_RT_FULL內核配置選項啟用完全搶占式內核,如圖3.4所示,具體說明見第4.2.2節。
3.2 實時基準測試
一旦打上補丁並編譯了實時內核,最常見的問題就是要確定特定硬體及其配置是否能讓應用程式在不錯過截止日期的情況下運行。要解決這類問題,最好能有一個數學證明,說明應用程式絕不會錯過最後期限。
遺憾的是,Linux系統非常複雜,基本上不可能用數學方法證明一項任務在運行時不會錯過最後期限。因此,要確定在特定硬體上運行的應用程式是否會錯過最後期限,唯一的辦法就是進行實際的設備測量。
更詳細地說,可以確定並遵循不同的途徑來執行這些測量:也許,最直接的選擇是運行實際的實時應用程式,以瞭解其是否滿足截止日期要求。但遺憾的是,由於種種原因,運行實際應用程式並不總是可能的,因此需要採用其他方法。
具體來說,為了瞭解設備是否足夠實時,可以測量和分析系統的調度延遲。
事實上,如圖3.5所示,調度延遲越小,即系統響應事件發生所需的時間越短,執行實時計算的剩餘時間就越長。
總之,測量調度延遲有助於瞭解設備的實時性。
3.2.1 RT-tests
rt-tests是一個測試套件,其中包含測試各種實時Linux功能和調度延遲的工具:rt-tests 的主要工具包括
- cyclictest
- hackbench
- pip_stress
- pi_stress
- pmqtest
- ptsematest
- sigwaittest
3.2.1.1 Cyclictest
Cyclictest是一種廣泛使用的驗證最大調度延遲的工具:它運行一個非實時的主線程,該主線程依次啟動一定數量的"測量"線程,這些線程具有定義的實時優先順序(使用SCHED_FIFO調度),然後"測量"線程在定時器到期後以定義的時間間隔定期被喚醒,每次喚醒都會計算編程時間和有效喚醒時間之間的差值,並將其提供給主線程。
最後,主線程會跟蹤延遲值,並列印出最小值、平均值和最大值以及其他有用信息。
為了更好地理解Cyclictest的工作原理,可以對其源代碼進行分析,為此,在3.1和3.2中報告了其源代碼的簡化版本。
void *timethread(void *par){
// Thread set up
clock_gettime(&now);
next = now + interval;
while (!shutdown){
clock_nanosleep(&next); // Sleep
clock_gettime(&now); // Get current time
diff = now - next; // Compute the difference
update_stat(diff); // Update statistics
next += interval; // Compute the new wake-up time
}
}
int main (){
for(i=0; i<num_threads; i++){
pthread_create(timethread); // Creates threads
}
while(!shutdown){
for(i=0; i<num_threads; i++){
print_stat(stat[i], i); // Prints statistics
}
usleep(10000);
}
if(histogram){
print_hist(parameters , num_threads);
}
}
在處理 Cyclictest 時,需要牢記的一些最重要的因素如下:
- 主線程以較低的優先順序運行,因此與普通線程無異。
- 測量線程以較高的優先順序運行,即用戶指定的優先順序。
- Cyclictest不能提供確定性結果。
- 運行 Cyclictest 只會給出延遲的下限,事實上,除非CPU處於負載狀態,否則它不會給出最壞的情況。
- 在CPU空閑時運行Cyclictest幾乎毫無用處。
- 必鬚根據要測試的系統調整Cyclictest設置。
- 如果不考慮系統屬性,可能會出現某些延遲原因未涵蓋在內的情況。例如:由於Cyclictest以固定頻率喚醒線程,因此可能存在在Cyclictest線程運行前就已管理好的延遲源,在這種情況下,以不同周期運行Cyclictest可能會有所幫助。
- Cyclictest並不測量實時應用程式的中斷請求(IRQ)處理路徑,事實上,定時器的IRQ通常在IRQ上下文中得到完全處理,在更現實的情況下,處理程式會喚醒相應的線程,進而喚醒實時進程。
總之,在需要牢記的各種因素中,正如已經強調過的,Cyclictest必須在特定負載條件下運行,以確定系統最壞情況下的延遲。因此,正確使用Cyclictest的可能解決方案有
- 不要使用Cyclictest將延遲測量嵌入到實時應用程式中
實際上,這並不是一個真正的解決方案,但是,將測量嵌入到實時應用程式中,讓應用程式直接測量延遲的方式是最好的方法,儘管這並不總是可行的。
- 在Cyclictest運行時,運行正常的實時應用程式和非實時應用程式作為系統負載來測量延遲。
所有建議的解決方案都需要有系統實時應用程式。不過,在某些情況下,無法使用真實應用程式,必須尋找其他替代方案。
當需要分析系統的性能,而系統的實際最終應用又不可用時,就需要其他基準測試方法。在這種情況下,合成基準就能派上用場:合成基準是為了獲得可準確比較的結果而設計的易於重覆的測試場景。
合成基準通過孤立的調查,還可以檢查系統內最小的瓶頸,即它們是基於測試單個部件的想法,而不會受到影響結果的其他因素的交互作用。因此,系統的每個組件都要單獨測試,例如:先測試處理器,然後測試網路介面、磁碟等。
在沒有實際應用程式的情況下,這些基準確實非常有用,但必須仔細定義,如第 4.2 節所述。
3.2.1.2 Hackbench
Hackbench既是基準也是Linux內核調度程式的壓力測試,其主要任務是創建指定數量的線程或進程,並通過套接字或管道進行通信。此外,它還作為基準工具測量通信所需的時間。如前所述,空閑的內核會顯示較低的調度延遲,因此 Hackbench 可用作壓力程式,以獲得繁忙的內核,從而得到更真實的延遲結果。
Pip_stress使用三個進程創建優先順序反轉。具體來說,優先順序最低的進程持有一個互斥進程,它被中等優先順序的進程搶占,後者只是運行一個無限迴圈。然後,第三個進程,也就是優先順序最高的進程,試圖鎖定優先順序最低的任務已經持有的互斥任務。由於使用的是優先順序繼承互斥,因此允許優先順序最低的進程以較高的優先順序運行,這樣它就可以搶占優先順序中等的第二個進程。Pip_stress測試程式不將任何選項作為輸入,如果優先順序反轉問題得到解決,它應立即退出。因此,Pip_stress主要用於測試優先順序繼承機制是否正常工作。
Pi_stress用於測試優先順序繼承機制的程式。它運行多組線程。具體來說,每組線程都會導致優先順序倒置,如果優先順序繼承機制失效,就會出現死鎖。
Pmqtest啟動成對的線程,測量與POSIX消息隊列進行進程間通信的延遲。更詳細地說,它測量發送和接收消息之間的延遲。
Ptsematest啟動兩個線程,使用POSIX mutex測量進程間通信的延遲。具體來說,它測量的是釋放和獲取互斥之間的延遲。
Sigwaittest啟動兩個通過信號同步的線程,並測量發送信號和從sigwait()返回信號之間的延遲。也可以選擇啟動兩個進程而不是線程。
3.3 剖析工具
當想瞭解程式如何運行時,可以使用源代碼級調試器,如第2.2.1.2節所述的調試器。調試器允許深入瞭解系統正在做什麼。不過,調試器只能查看一小部分代碼,也就是說,調試操作僅限於特定的應用程式。
大多數情況下,經典調試方法並沒有問題,但有時需要一種方法來獲得系統的高層概覽,以瞭解哪些進程正在運行、CPU 和記憶體資源的使用情況等。
因此,除了傳統的調試技術外,還需要一些替代工具,以便獲得更全面的信息,說明系統是否按預期運行。
這些複雜的替代工具就是所謂的剖析和跟蹤工具:它們旨在找出瓶頸所在,以便在分析整個系統的同時解決性能問題。因此,當系統出現一些性能問題時,使用這類工具來分析問題所在是明智之舉。
在Linux環境中,最著名的剖析工具可能就是所謂的top,它可以顯示當前由內核管理的任務列表和其他系統概要信息。
除了top之外,還有其他工具,例如,剖析工具Perf提供了更強大的分析和剖析應用程式的手段。另外,如果問題和瓶頸是由Linux內核造成的,ftrace和LTTng等跟蹤工具也能提供收集所有所需信息的手段。
總之,剖析工具對於動態分析和測量系統狀態及其資源使用情況非常有用。此外,使用剖析工具還可以瞭解程式的複雜性、特定指令的使用次數、系統調用的頻率和持續時間、正確預測的分支數量等。因此,剖析工具通常用於優化程式的執行,並找出系統在性能方面的問題所在。
參考資料
- 軟體測試精品書籍文檔下載持續更新 https://github.com/china-testing/python-testing-examples 請點贊,謝謝!
- 本文涉及的python測試開發庫 謝謝點贊! https://github.com/china-testing/python_cn_resouce
- python精品書籍下載 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
- Linux精品書籍下載 https://www.cnblogs.com/testing-/p/17438558.html
3.3.1 ARM DS-5 Streamline
ARM DS-5 Streamline是ARM公司推出的各種剖析工具之一,正如ARM公司所稱,"DS-5 Streamline 為您提供了更多的選擇: "DS-5 Streamline 可以讓您比以往任何時候都更深入地瞭解軟體的執行情況。
Streamline是一種性能分析器,針對ARM設備進行了優化,是開發在ARM SoC上運行的優化應用程式的理想工具。例如,通過其時間線視圖,可以輕鬆讀取顯示性能計數器值和執行進程詳情的圖表,如圖 3.6 所示。
Streamline 允許為每個進程和線程選擇獨立顯示的信息,以便分析對特定用例有用的信息。DS-5 Streamline 套件可通過乙太網連接輕鬆使用,由三個主要軟體組件組成: 它由三個主要軟體組件組成:在開發主機上運行的 Streamline、持續收集所需信息的 Gator 內核模塊和管理主機與目標之間連接的 Gator 守護進程。圖 3.7 概述了 Streamline 的工作原理及其組成部分。
3.3.1.1 性能監測單元(PMU Performance Monitoring Unit)
如前所述,使用 Streamline 可以分析系統上運行的各種進程,獲取詳細信息,如:未命中預測分支的數量或緩存未命中的數量。
Streamline 通過使用集成在 SoC 中的性能監控單元(PMU)來獲取所有需要的信息。事實上,PMU 可通過監控 SoC 資源幫助收集有用的性能信息。