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