俞冠中,韋雄,田青旺,史旭明
(國核自儀系統(tǒng)工程有限公司,上海 200241)
龍芯2K1000 處理器[1-2]是一款面向工業(yè)自動化與工業(yè)控制應用場景的高性能低功耗通用處理器,基于MIPS64架構,采用40 nm 制造工藝[3],主頻最高1 GHz,功耗小于5 W,支持64 位DDR2/3-1066 內(nèi)存,提供SPI、UART、I2S、I2C、USB2.0 等通用外設接口。
目前市場上,龍芯2K1000 板卡一般預裝Loongnix 操作系統(tǒng)。Loongnix 操作系統(tǒng)是一種基于Linux 內(nèi)核的圖形化界面操作系統(tǒng)。和Linux 系統(tǒng)一樣,Loongnix 系統(tǒng)也是分時系統(tǒng)[4],不能滿足對實時性要求較高的工業(yè)自動化場景(如電站控制[5-6])的要求。因此,需要針對Linux內(nèi)核影響實時性能的因素進行實時性改造和優(yōu)化。
目前,Linux 內(nèi)核實時化的有效方法是在Linux 內(nèi)核源文件中加入實時補丁,再編譯內(nèi)核,生成Linux 實時內(nèi)核。Linux 內(nèi)核實時補丁主要有三種:RED-Linux 補丁,Kurt-Linux 補丁以及實時搶占(RT-preempt)補丁[7-8]。REDLinux 補丁是美國加州大學歐文分校(University of California Irvine,UCI)開發(fā)的一種軟實時補丁[8]。Kurt-Linux是美國堪薩斯大學(University of Kansas,KU)開發(fā)的一種Linux 實時補丁,其內(nèi)核同時運行實時任務和非實時任務時,內(nèi)核不能被搶占[8-9]。RT-Preempt 補丁是由Ingo Molnar和Thomas Gleixner 開發(fā)和維護的一種完全可搶占式內(nèi)核的硬實時補丁[10],其實時性要明顯優(yōu)于前兩種Linux實時補丁,且RT-Preempt 是開源補丁,擁有強大社區(qū)支持[11],支持Linux 內(nèi)核版本也比前兩者豐富。綜上所述,本文提出一種基于RT-Preempt-Linux 實時內(nèi)核替換Loongnix 系統(tǒng)原生Linux 內(nèi)核的方法,實現(xiàn)Loongnix 實時性優(yōu)化和實時化改造。
Linux 進程切換機制依賴進程用戶態(tài)和進程內(nèi)核之間互相切換實現(xiàn)的[12]。進程需通過系統(tǒng)調(diào)用或中斷觸發(fā)來完成進程用戶態(tài)到進程內(nèi)核態(tài)的切換。進程切換時,內(nèi)核使用自旋鎖來確保數(shù)據(jù)的不沖突。進程進入臨界區(qū)操作數(shù)據(jù)時,其他進程只能阻塞,那么任務的時間確定性就無法保證[13]。所以,標準Linux 內(nèi)核的臨界區(qū)是不可搶占的。
Linux 中斷響應處理分為頂半(top-halves)部和底半(buttom-halves)部[11],也稱為上半部和下半部。上半部屬于硬中斷,會關閉中斷,屏蔽其他任何中斷請求。在關閉中斷時,系統(tǒng)外部事件無法得到響應,導致任務響應延遲。若標準Linux 出現(xiàn)大量外部IO 事件(如磁盤操作[11]),其他任務的延時時間會大大增加。
優(yōu)先級反轉(zhuǎn)(Priority Inversion)[13-14]是指高優(yōu)先級的任務被低優(yōu)先級的任務阻塞,反而中等優(yōu)先級的任務先于高優(yōu)先級的任務執(zhí)行的現(xiàn)象。低優(yōu)先級的進程PL 首先執(zhí)行,并占用了共用資源Rsrc,此時高優(yōu)先級進程PH開始執(zhí)行,進程PL 掛起。當進程PH 嘗試獲取Rsrc時,因Rsrc 被進程PL 占據(jù),進程PH 也掛起,進程PL 恢復運行。此時,不需要Rsrc 的中等優(yōu)先級進程PM 就緒運行,進程PL 掛起。進程PM 執(zhí)行結束后,進程L 恢復運行,直至放棄Rsrc,此時,高優(yōu)先級進程PH 才獲得CPU使用權。中優(yōu)先級進程PM 先于高優(yōu)先級進程PH 獲取CPU 使用權,優(yōu)先級發(fā)生反轉(zhuǎn)。優(yōu)先級反轉(zhuǎn)對操作系統(tǒng)實時性危害很大,增加了任務調(diào)度時間的不確定性,嚴重時引起系統(tǒng)崩潰[8]。目前,標準Linux 內(nèi)核并無應對優(yōu)先級反轉(zhuǎn)的機制。
RT-Preempt 補丁使用優(yōu)先級可繼承的互斥鎖(rt_mutex)重新實現(xiàn)自旋鎖來實現(xiàn)內(nèi)核鎖的可搶占性[13,15]。自旋鎖spin_lock()宏函數(shù)內(nèi)用禁止遷移migrate_disable()替代禁止搶占preempt_disable(),使自旋鎖可搶占。實時自旋鎖rt_spin_lock()替代原自旋鎖_raw_spin_lock()。rt_spin_lock()函數(shù)的實現(xiàn)中調(diào)用了rt_spin_lock_fastlock()函數(shù)。rt_spin_lock_fastlock()函數(shù)中調(diào)用了might_sleep()函數(shù)。might_sleep()函數(shù)的作用是允許當前進程進入睡眠狀態(tài),進程進入睡眠狀態(tài)則該進程交出了CPU 的使用權。那么,進程可以被搶占。需要指出的是內(nèi)核中非線程化的中斷不能被搶占,不能使用rt_mutex[10]。
RT-Preempt 補丁的中斷線程化處理是將中斷服務程序轉(zhuǎn)變?yōu)榭杀徊僮飨到y(tǒng)調(diào)度的線程斷線程的優(yōu)先級并不固定,用戶按需設置其優(yōu)先級,默認優(yōu)先級為50。
中斷線程化處理包含硬中斷的線程化處理和軟中斷的線程化處理兩部分[15]。硬中斷線程化在__setup _irq()函數(shù)中實現(xiàn),__setup_irq()函數(shù)調(diào)用kthread_create()函數(shù)創(chuàng)建線程,采用先進先出調(diào)度策略(SCHED_FIFO)。軟中斷線程化在spawn_ksoftirqd()中實現(xiàn),其線程建立過程與硬中斷線程化相同。需要指出的是:不是所有的中斷都需要中斷線程化處理,例如時鐘中斷應為最高優(yōu)先級,不能中斷線程化處理。struct irqaction 結構體中的flag 成員變量用來設置是否需要中斷線程化處理。
在高優(yōu)先級任務TH 等待低優(yōu)先級任務TL 占據(jù)共用資源Rsrc時,為了使低優(yōu)先級任務盡快運行并釋放Rsrc,操作系統(tǒng)會將TL 的優(yōu)先級提高到和TH 的優(yōu)先級一樣,直到TL 釋放Rsrc。當TL 的優(yōu)先級繼承了TH 的優(yōu)先級,中優(yōu)先級任務TM 就無法搶先TH 獲得CPU 的使用權。因此,優(yōu)先級反轉(zhuǎn)就不會產(chǎn)生。RT-Preempt 補丁通過優(yōu)先級可繼承rt_mutex 實現(xiàn)優(yōu)先級繼承策略。加入RT-Preempt 補丁編譯Linux后,rt_mutex 成為Linux 核心(kernel)的組成部分。
RT-Preempt 補丁的時鐘系統(tǒng)不像依賴標準Linux 一樣依賴系統(tǒng)滴答中斷計時,而是提供了一套新的時鐘架構,可以提供納秒級的精度。標準Linux 系統(tǒng)為了提高時鐘分辨率而升高系統(tǒng)滴答中斷的頻率情況不會在RT-Preempt-Linux 出現(xiàn),從而避免了系統(tǒng)符合變重性能降低的發(fā)生。
在一個時刻里,一個CPU 只能執(zhí)行一個任務。多個任務共享一個 CPU 需要依賴上下文切換(Context Switch)。上下文切換時間是指CPU 從一個任務切換到另一個任務所需的時間開銷。上下文切換時間決定了任務調(diào)度速度。因此,上下文切換時間是衡量操作系統(tǒng)實時性的關鍵指標[16]。Linux 支持多進程運行,所以,Linux 的上下文切換時間就是進程切換時間。
進程切換時間統(tǒng)計軟件的設計思路是通過讀管道(pipe)和寫管道來實現(xiàn)父子進程之間的同步,其程序流程如圖1 所示。父進程獲取當前時間,把當前時間寫入管道,后讀管道阻塞。子進程讀管道獲取父進程切換前的時間,再獲取當前時間,計算進程切換時間。進程調(diào)度策略采用時間片輪轉(zhuǎn)(SCHED_RR),進程優(yōu)先級設置為99,切換統(tǒng)計次數(shù)設定為1 000 次。每運行滿1 000次,打印切換平均用時,打印切換最大用時和切換最小用時。
圖1 進程切換時間統(tǒng)計軟件流程圖
線程是進程內(nèi)共享進程資源的一個最小執(zhí)行單元。線程切換時間大小體現(xiàn)了一個進程內(nèi)的任務調(diào)度速度。因此,線程切換時間也是評判操作系統(tǒng)實時性能的重要標志。
線程切換時間統(tǒng)計軟件由三個模塊組成:主線程,線程1 和線程2。主線程內(nèi)初始化功能所需的全局變量,如timespec 結構體對象等,初始化信號量,創(chuàng)建線程1和線程2,計算線程切換時間平均值,統(tǒng)計線程切換時間的最大值和最小值,并打印線程切換的平均用時、線程切換的最大用時和線程切換時間的最小用時。
線程1 的邏輯設計圖如圖2 所示,線程1 先等待信號量1,收到信號量1 后獲取當前時間,計算線程切換時間,再獲取當前時間,最后發(fā)送信號量2。信號量2 發(fā)送后,線程2 就被喚醒執(zhí)行。在主線程中初始化信號量1時,其參數(shù)Value 設置為1,首先運行線程1。
圖2 線程切換時間統(tǒng)計軟件流程圖
線程2 的設計邏輯與線程1 相同。線程切換時間統(tǒng)計軟件通過兩個信號量實現(xiàn)線程1 和線程2 的同步,其設計思想和進程切換時間統(tǒng)計軟件相似。
實時Loongnix 系統(tǒng)是用RT-Preempt-Linux 內(nèi)核替代Loongnix 系統(tǒng)原生標準Linux 內(nèi)核后的Loongnix 系統(tǒng)。RT-Preempt-Linux 內(nèi)核是在標準Linux 內(nèi)核源碼上加入RT-Preempt 補丁后編譯生成的。對安裝實時Loongnix 系統(tǒng)的龍芯2K1000 平臺進行性能測試。測試分為用自設計軟件測試進程切換時間和線程切換時間,以及用專用實時性能測試工具Cyclictest 測試任務響應延時時間。對未替換實時內(nèi)核的Loongnix 系統(tǒng)進行相同的性能測試并進行比較研究。
實時Loongnix 系統(tǒng)和原生Loongnix 系統(tǒng)性能測試的軟硬件環(huán)境如表1 所示。龍芯2K1000 處理器的工作主頻設置為800 MHz。由于電站控制項目要求的應用軟件需要在Linux 4.0 版本以上才能運行,因此,實時Loongnix系統(tǒng)選用的Linux 4.19 內(nèi)核,并在其基礎上加入對應版本的RT-Preempt 補丁。
表1 測試軟硬件環(huán)境
進程切換時間統(tǒng)計軟件的進程優(yōu)先級設置為99,統(tǒng)計次數(shù)設定為1 000 次。在龍芯2K1000 平臺上的實時Loongnix 系統(tǒng)運行20 次獲取總共2 萬次進程切換時間的統(tǒng)計數(shù)據(jù)。在龍芯2K1000 平臺上的原生Loongnix 系統(tǒng)進行相同的測試。測試結果見表2。
表2 實時Loongnix 與原生Loongnix 進程切換時間對比
實時Loongnix 進程切換時間為微秒級,而未使用RT-Preempt-Linux 內(nèi)核的原生Loongnix 進程切換時間達到2.51 ms。實時Loongnix 系統(tǒng)可以滿足電站控制應用的系統(tǒng)任務切換時間不大于1 ms 的性能需求。
對實時Loongnix(實線)與原生Loongnix(虛線)進程最大切換時間每千次切換統(tǒng)計一次的對比如圖3 所示。實時Loongnix 系統(tǒng)每千次切換的最大切換時間連線比較平滑,而原生Loongnix 系統(tǒng)每千次切換的最大切換時間連線抖動幅度比較大。因此,RT-Preempt-Linux 內(nèi)核對Loongnix 系統(tǒng)的上下文切換時間確定性提高明顯。
圖3 實時Loongnix 與原生Loongnix 最大進程切換時間對比
線程切換時間統(tǒng)計軟件的統(tǒng)計次數(shù)設定為1 000次。在龍芯2K1000 平臺上的實時Loongnix 系統(tǒng)運行20次獲取總共2 萬次線程切換時間的統(tǒng)計數(shù)據(jù)。在龍芯2K1000 平臺上的原生Loongnix 系統(tǒng)進行相同的測試。測試結果見表3。
表3 實時Loongnix 與原生Loongnix 線切換時間對比
實時Loongnix 線程平均切換時間和未使用RT-Preempt-Linux 內(nèi)核的原生Loongnix 線程切平均切換時間接近,但其線程切換最大用時也是微秒級的。原生loongnix的最大線程切換用時要超過4 ms。
對實時Loongnix(實線)與原生Loongnix(虛線)線程最大切換時間每千次切換統(tǒng)計一次的對比如圖4 所示。實時Loongnix 系統(tǒng)每千次切換的最大切換時間連線比較平滑且都在100 μs 左右,而原生Loongnix 系統(tǒng)每千次切換的最大切換時間連線抖動幅度大。因此,RT-Preempt-Linux 內(nèi)核對Loongnix 系統(tǒng)的線程切換時間確定性提高明顯。
圖4 實時Loongnix 與原生Loongnix 最大線程切換時間對比
Cyclictest 是一種開源的專業(yè)Linux 實時性能測試工具軟件,可以精確地測量任務喚醒延時。Cyclictest 測量線程線程時間喚醒的時間間隔,這個實際的時間間隔與線程睡眠設定時間的差就是任務喚醒時間的延時。這個延時由定時器中斷延時和線程調(diào)度延時組成。中斷響應時間和保存上下文的時間決定了中斷延時的大小。本次實驗使用Cyclictest 1.0。
4.4.1 單線程測試
Cyclictest 測試指令為:sudo ./cyclictest -l100000 -m-t1 -n -p90 -i200 -h2000 -q,其中,-l(loops)為循環(huán)個數(shù),本次測試設定為100 000,缺省為0;-m(mlockall)為鎖定當前和未來的內(nèi)存分配;-t[NUM](threads=NUM)為啟動線程個數(shù),本次測試為單線程測試,故設定為1;-n(nanosleep)使用精度為納秒的睡眠時間設置;-p(prio)為線程設置的優(yōu)先級,本次實驗優(yōu)先級設置為90;-i(interval)為線程的時間間隔,本次實驗設置為200 μs,缺省為1 000 μs;-h(histogram)為記錄延時時間,本次實驗跟蹤2 000μs以內(nèi)的延時;-q(quiet)為退出前打印結果。實時Loongnix 的測試結果如圖5 所示,原生Loongnix 的測試結果如圖6 所示。
圖5 實時Loongnix 系統(tǒng)Cyclictest 單線程測試結果
圖6 原生Loongnix 系統(tǒng)Cyclictest 單線程測試結果
RT-Preempt-Linux 內(nèi)核替換后成為實時系統(tǒng)的loongnix 系統(tǒng)單線程最大延遲時間微秒級。未實時化改造的原生Loongnix 系統(tǒng)單線程最大延時超過4 ms。
4.4.2 多線程測試
Cyclictest 測試指令為:sudo ./cyclictest -l100000 -m-t5 -n -p90 -i200 -q。其中線程數(shù)量設置為5。實時Loongnix 的測試結果如圖7 所示,原生Loongnix 的測試結果如圖8 所示。
圖7 實時Loongnix 系統(tǒng)Cyclictest 多線程測試結果
圖8 原生Loongnix 系統(tǒng)Cyclictest 多線程測試結果
RT-Preempt-Linux 內(nèi)核替換后成為實時系統(tǒng)的loongnix 系統(tǒng)5 個線程最大延遲時間均為微秒級。未實時化改造的原生Loongnix 系統(tǒng)所有5 個線程最大延時都大于2 ms。
本文首先分析了Loongnix 系統(tǒng)的標準Linux 內(nèi)核影響實時性能的3 個重要因素,探究了RT-Preempt-補丁的實時性優(yōu)化原理,提出一種基于RT-Preempt-Linux 實時內(nèi)核替換Loongnix 系統(tǒng)原生Linux 內(nèi)核的方法,實現(xiàn)Loongnix 實時性優(yōu)化和實時化改造,給出了兩種實時性能測試軟件的設計方法,并用自設計軟件和專用實時性測試工具軟件對實時化的Loongnix 系統(tǒng)和原生Loongnix系統(tǒng)進行實時性能測試與分析。測試結果表明,改造后的Loongnix 系統(tǒng)的實時性較改造前有了大幅提升,進程切換時間、線程切換時間以及任務延時都遠小于原生Loongnix 系統(tǒng),都能達到微秒級。