孫菲艷 李彥峰 王娜 汪辰
摘要:上下文切換是Linux操作系統(tǒng)內(nèi)核優(yōu)化的一個(gè)關(guān)鍵參數(shù)指標(biāo),如何精確方便地測量上下文切換開銷顯得至關(guān)重要。本文說明了使用nanosleep()函數(shù)測試方法的不合理性,提出了一種在用戶態(tài)編寫應(yīng)用程序并且調(diào)用schedu_yield()系統(tǒng)調(diào)用主動(dòng)放棄處理器實(shí)現(xiàn)任務(wù)切換的測試方法,并且基于ARM Linux-3.2.0實(shí)驗(yàn)平臺(tái),與傳統(tǒng)的使用管道讀寫切換、在內(nèi)核態(tài)測試context_switch()函數(shù)的開銷等方法進(jìn)行了對比分析,結(jié)果表明,使用該方法測試上下文切換的準(zhǔn)確性和便捷性均有所提高。
關(guān)鍵詞:Linux;上下文切換;系統(tǒng)調(diào)用;用戶態(tài);內(nèi)核態(tài)
中圖分類號:TP316 文獻(xiàn)標(biāo)志碼:A 文章編號:1009-3044(2018)15-0047-04
A New Method for Testing the Performance of Linux Context Switch
SUN Fei-yan, LI Yan-feng, WANG Na, WANG Chen
(Nanjing Software Institute, Jinling Institute of Technology, Nanjing 211100, China)
Abstract: Context switch is a key parameter of the kernel optimization of Linux operating system. It is very important to measure the cost of context switch accurately and conveniently. This paper illustrates the irrationality of the test method using the nanosleep () function and presents a method, which is to write an user mode application program and use schedu_yield () system call to take the initiative to give up processor, and also compared with other methods like using traditional read / write with pipes to switch, using nanosleep() and testing the context_switch() function cost in the kernel mode directly based on the experimental platform of arm Linux - 3.2.0. And results show that this new method can improve the accuracy and convenience of the context switch testing.
Key words: Linux-3.2.0; context switch; system call; user mode; kernel mode
隨著信息技術(shù)、嵌入式技術(shù)的快速發(fā)展,Linux作為一種可裁剪、廣泛支持、易開發(fā)的通用操作系統(tǒng),也得到了越來越廣泛的應(yīng)用[1][2]。上下文切換延時(shí)作為linux操作系統(tǒng)內(nèi)核的任務(wù)調(diào)度子系統(tǒng)的主要性能指標(biāo),測試上下文切換延時(shí)已是一項(xiàng)重要的工作。上下文切換是保存上一個(gè)任務(wù)的執(zhí)行環(huán)境,準(zhǔn)備下一個(gè)將要運(yùn)行的任務(wù)的環(huán)境的必要操作。研究表明,上下文切換操作在操作系統(tǒng)中,每秒會(huì)發(fā)生幾十至幾百次,其帶來的時(shí)間開銷不可忽略[3]。有的測試程序采用nanosleep等睡眠函數(shù)來測試上下文切換[4][5],但是由于內(nèi)核中進(jìn)入睡眠狀態(tài)的實(shí)時(shí)任務(wù)是在軟中斷中被喚醒,在軟中斷沒有到來前實(shí)際上并不能保證兩個(gè)實(shí)時(shí)任務(wù)交替切換;也有很多測試程序采用創(chuàng)建管道讀寫的方式來測試上下文切換[6][7],該方法程序編寫相對復(fù)雜,所得到的測試結(jié)果是上下文切換延時(shí)和管道讀寫延時(shí)的總和,而管道讀寫延時(shí)的開銷相對較大,會(huì)使得測試結(jié)果不夠精確。本文提出了一種在用戶態(tài)編寫應(yīng)用程序調(diào)用schedu_yield()系統(tǒng)調(diào)用主動(dòng)放棄處理器實(shí)現(xiàn)任務(wù)切換的測試方法來提高上下文切換測試的精確度,同時(shí)也采用了在內(nèi)核態(tài)插樁的方法來進(jìn)行對比實(shí)驗(yàn),結(jié)果表明該方法相比在內(nèi)核態(tài)插樁測試操作更方便。
1 上下文切換
1.1 上下文切換
上下文切換又稱為任務(wù)上下文切換(包括進(jìn)程或者線程),是指CPU從一個(gè)任務(wù)切換到另一個(gè)任務(wù)的過程,在這個(gè)過程中,需要保存當(dāng)前任務(wù)的狀態(tài)和恢復(fù)另一個(gè)任務(wù)的狀態(tài),即當(dāng)前運(yùn)行任務(wù)轉(zhuǎn)為就緒(或者掛起、刪除)狀態(tài),另一個(gè)被選定的就緒任務(wù)成為運(yùn)行狀態(tài)。
1.2上下文切換開銷
上下文切換是操作系統(tǒng)內(nèi)核優(yōu)化的一個(gè)關(guān)鍵參數(shù)指標(biāo)。在任務(wù)間發(fā)生切換需要花費(fèi)大量的時(shí)間用于處理諸如:保存和恢復(fù)寄存器和內(nèi)存頁表、更新內(nèi)核相關(guān)數(shù)據(jù)結(jié)構(gòu)等操作[8][9]。上下文切換通常是計(jì)算密集型的。也就是說,它需要相當(dāng)可觀的處理器時(shí)間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時(shí)間。所以,上下文切換對系統(tǒng)來說意味著消耗大量的 CPU 時(shí)間。
從Linux內(nèi)核內(nèi)部實(shí)現(xiàn)來看,如圖1所示,上下文切換所花費(fèi)的延遲時(shí)間是從調(diào)度器選好要調(diào)度的任務(wù)(任務(wù)1)后到把任務(wù)上下文切換到另一個(gè)任務(wù)(任務(wù)2)所花費(fèi)的時(shí)間。即context_switch()函數(shù)的開銷[3]。
2 三種傳統(tǒng)的上下文切換測試機(jī)制
2.1 采用睡眠函數(shù)切換
有的學(xué)者采用睡眠函數(shù)實(shí)現(xiàn)切換的主要機(jī)制是:創(chuàng)建兩個(gè)實(shí)時(shí)任務(wù)TASK1和TASK2,在每個(gè)實(shí)時(shí)任務(wù)中插入代碼探測段,通過頻繁切換兩個(gè)實(shí)時(shí)任務(wù)來測試上下文切換時(shí)間,調(diào)度策略設(shè)置為SHCED_RR,優(yōu)先級設(shè)置為最高,主要偽代碼實(shí)現(xiàn)如下:
實(shí)時(shí)TASK1:
while(loops){
t1 = clock_gettime();
nanosleep(0);
t2 = clock_gettime();
switch_time = (t2 – t1)/2;
loops--;
}
實(shí)時(shí)TASK2:
while(loops){
nanosleep(0);
loops--;
}
通過在ARM Linux-3.2.0平臺(tái)上測試發(fā)現(xiàn),該方法并不能保證上下文切換在兩個(gè)實(shí)時(shí)任務(wù)之間交替發(fā)生,CPU實(shí)際上是在內(nèi)核線程ksoftirqd、TASK1、TASK2三個(gè)任務(wù)之間交替切換,主要原因是當(dāng)實(shí)時(shí)任務(wù)調(diào)用nanosleep()睡眠時(shí),會(huì)激活一個(gè)高精度定時(shí)器,同時(shí)在hrtimer_enqueue_reprogram()函數(shù)中會(huì)調(diào)用raise_softirq_irqoff(HRTIMER_SOFTIRQ)來喚醒hrtimer的軟中斷,而睡眠函數(shù)中的定時(shí)器到期檢查是在HRTIMER_SOFTIRQ軟中斷中,當(dāng)其中一個(gè)實(shí)時(shí)任務(wù)睡眠的時(shí)候,恰好另外一個(gè)實(shí)時(shí)任務(wù)已經(jīng)處于睡眠狀態(tài)且還沒有被HRTIMER_SOFTIRQ軟中斷喚醒,因此狀態(tài)仍然是TASK_INTERRUPTIBLE,而此時(shí)由于CPU中沒有其他的任務(wù)可以運(yùn)行,調(diào)度器會(huì)選擇內(nèi)核線程ksoftirqd來執(zhí)行HRTIMER_SOFTIRQ軟中斷,在ksoftirqd被執(zhí)行的時(shí)候會(huì)檢查實(shí)時(shí)任務(wù)是否到期進(jìn)而喚醒兩個(gè)實(shí)時(shí)任務(wù),則兩個(gè)實(shí)時(shí)任務(wù)接下來會(huì)輪流搶到CPU,因此是三個(gè)任務(wù)之間的切換,主要原因是實(shí)時(shí)任務(wù)被喚醒是在HRTIMER_SOFTIRQ軟中斷中,只有在軟中斷被執(zhí)行過之后才能喚醒兩個(gè)實(shí)時(shí)任務(wù)使其得到運(yùn)行,用該方法測試的時(shí)候內(nèi)核中的切換順序如下:
TASK1
TASK2
ksoftirqd
TASK1
TASK2
ksoftirqd
……
因此此時(shí)測試出來的上下文切換實(shí)際上也包括了內(nèi)核線程ksoftirqd的運(yùn)行時(shí)間,是不精確的。因此用該方法測試上下文切換是不合理的。
2.2 采用管道讀寫切換
管道測試是當(dāng)前被廣泛使用的上下文切換延時(shí)測試方法。主要實(shí)現(xiàn)機(jī)制也是創(chuàng)建兩個(gè)實(shí)時(shí)任務(wù),將實(shí)時(shí)任務(wù)調(diào)度策略設(shè)置為SCHED_FIFO,實(shí)時(shí)優(yōu)先級設(shè)置為最高,兩個(gè)任務(wù)利用管道循環(huán)讀寫n次,讀寫一個(gè)int,以此來進(jìn)行上下文切換,這樣測試出的上下文切換時(shí)間包括了管道讀寫的時(shí)間,同時(shí)編程相對復(fù)雜,實(shí)驗(yàn)發(fā)現(xiàn),該方法測試得到的上下文切換時(shí)間相對較大(見下述),精確度有待提高。
2.3 在內(nèi)核代碼中插樁測試
內(nèi)核中上下文切換主要是通過context_switch()函數(shù)來實(shí)現(xiàn)的,該函數(shù)位于kernel/sched.c中的schedule()函數(shù)中,該方法的主要機(jī)制是:運(yùn)行用戶態(tài)的應(yīng)用程序來實(shí)現(xiàn)兩個(gè)實(shí)時(shí)任務(wù)之間的相互切換,在內(nèi)核源碼的context_switch()函數(shù)前后插樁來獲取上下文切換前后的時(shí)間戳進(jìn)而得到上下文切換的時(shí)間,這里使用了ftrace提供的一個(gè)向ftrace跟蹤緩沖區(qū)輸出跟蹤信息的工具函數(shù)trace_printk(),ftrace是Linux內(nèi)核中提供的一種調(diào)試工具,使用ftrace可以對內(nèi)核中發(fā)生的事情進(jìn)行跟蹤,這在調(diào)試bug或者分析內(nèi)核時(shí)非常有用。trace_printk()的函數(shù)原型定義在內(nèi)核頭文件include/linux/kernel.h中,在激活配置CONFIG_TRACING后可以使用[10],如下:
schedule() {
……
trace_printk(“t1\n”);
context_switch;
trace_printk(“t2\n”);
……
}
運(yùn)行應(yīng)用程序來使兩個(gè)實(shí)時(shí)任務(wù)交替切換,得到的打印結(jié)果如下:
TASK TIMESTAMP
TASK1 t1
TASK2 t2
TASK2 t1
TASK1 t2
……
在上下文切換過程中,TASK1經(jīng)過context_switch()函數(shù)之后,會(huì)切換至TASK2,并且直接跳轉(zhuǎn)至TASK2被切換之前的代碼運(yùn)行處即trace_printk(“t2\n”)處,TASK2->t2 – TASK1->t1這個(gè)時(shí)間段內(nèi)即上下文切換一次的時(shí)間。
使用該方法測試雖然結(jié)果會(huì)相對更精確,但是需要修改內(nèi)核源碼,重新編譯內(nèi)核,相比用戶態(tài)測試操作更復(fù)雜更耗時(shí)。
3 采用sched_yield()實(shí)現(xiàn)上下文切換延時(shí)測試
3.1 測試原理分析
編寫用戶態(tài)應(yīng)用來實(shí)現(xiàn)內(nèi)核上下文切換的測試。應(yīng)用的主體是創(chuàng)建了兩個(gè)實(shí)時(shí)任務(wù)(進(jìn)程),調(diào)度策略設(shè)置為SCHED_FIFO,實(shí)時(shí)優(yōu)先級設(shè)置為最高(99)。這兩個(gè)任務(wù)都會(huì)調(diào)用sched_yield()系統(tǒng)調(diào)用函數(shù),按照man手冊說明,該函數(shù)主要是讓調(diào)用者放棄CPU,將其移動(dòng)到運(yùn)行隊(duì)列的尾部,調(diào)用其他任務(wù)來運(yùn)行。
子進(jìn)程起輔助作用,唯一的作用就是配合父進(jìn)程完成任務(wù)切換,主要代碼如下:
while (TRUE) {
......
sched_yield();
......
}
主要的延時(shí)計(jì)算在父進(jìn)程中實(shí)現(xiàn),主體代碼如下:
for (count = 0; count < MAX_ITERATIONS; ++count) {
......
clock_gettime(CLOCK_MONOTONIC, &before;); // (1)
sched_yield(); // (2)
clock_gettime(CLOCK_MONOTONIC, &after;); // (3)
diff = calcdiff_ns(after, before) / 2000.0; // (4)
/* cost = diff – overhead; */
......
}
父進(jìn)程控制整個(gè)測量的運(yùn)行周期,通過一個(gè)循環(huán)運(yùn)行MAX_ITERATIONS 次,每次執(zhí)行四步操作:
第(1)步:父進(jìn)程調(diào)用系統(tǒng)調(diào)用函數(shù)clock_gettime()以納秒級別的精度獲取調(diào)用sched_yield()前的絕對時(shí)間并存入before變量。
第(2)步:父進(jìn)程調(diào)用sched_yield() 讓出處理器,此時(shí)其他任務(wù),也就是子進(jìn)程的任務(wù)會(huì)被調(diào)度(其他任務(wù)不會(huì)被調(diào)度器選中,因?yàn)楸緶y試環(huán)境中創(chuàng)建的父子進(jìn)程是兩個(gè)實(shí)時(shí)任務(wù),其他非實(shí)時(shí)任務(wù)的調(diào)度優(yōu)先級都低于實(shí)時(shí)任務(wù))。此時(shí)內(nèi)核發(fā)生一次任務(wù)調(diào)度和上下文切換,執(zhí)行流切換到子進(jìn)程的任務(wù)。參考前面子進(jìn)程的執(zhí)行邏輯,當(dāng)子進(jìn)程獲得處理器后馬上又調(diào)用一次sched_yield() 讓出處理器,內(nèi)核再次發(fā)生任務(wù)切換,即第二次任務(wù)切換后父進(jìn)程再次獲得處理器執(zhí)行第三步。
第(3)步:父進(jìn)程再次調(diào)用系統(tǒng)調(diào)用函數(shù)clock_gettime()以納秒級別的精度獲取調(diào)用此時(shí)的絕對時(shí)間并存入after變量。
第(4)步:calcdiff_ns()是自己編寫的一個(gè)函數(shù)可以用來計(jì)算after和before兩個(gè)絕對時(shí)間之間的時(shí)間間隔diff,之所以要除以2000,原因有二,一是最終的結(jié)果單位筆者希望采用微秒,其次是根據(jù)前面第二步的描述,before和after之間實(shí)際執(zhí)行了兩次上下文切換。最后要注意的是,由于此方法是在用戶態(tài)編寫程序計(jì)算內(nèi)核上下文切換的時(shí)間,所以為了精確計(jì)算,原理上獲得的diff值還包含了系統(tǒng)調(diào)用等其他指令的開銷overhead,所以最終的一次上下文切換的開銷cost應(yīng)該還應(yīng)該從diff值中減去overhead的值,但經(jīng)過實(shí)際計(jì)算,由于diff的值本身已經(jīng)很低,而overhead的值幾乎可以忽略不計(jì),所以用diff值代替cost的值也是合理的。
一次完整的在兩個(gè)進(jìn)程之間兩次上下文切換的流程如圖2所示:
3.2 測試環(huán)境
測試的開發(fā)板使用的處理器型號為ARM架構(gòu)的 Cortex-A8,軟件環(huán)境為linux-3.2.0的內(nèi)核版本及測試需要的應(yīng)用程序,具體如表1所示[11]:
3.3 測試結(jié)果及分析
在Linux-3.2.0內(nèi)核下進(jìn)行了2000000次的測試,測試結(jié)果如圖2所示,圖的橫坐標(biāo)代表測試序列次,縱坐標(biāo)代表上下文切換延時(shí),單位是微秒(us),且圖中給出了延時(shí)的最小值(Min),平均值(Avg),最大值(Max),抖動(dòng)值(Jitter)。
從圖3,圖4可以看出,使用sched_yield()系統(tǒng)調(diào)用來進(jìn)行上下文切換測試得到的上下文切換Avg為2.8us,而使用管道讀寫的方式來測試得到的上下文切換Avg為8.6us,即采用sched_yield()系統(tǒng)調(diào)用測試使得上下文切換的開銷相比使用管道讀寫的方式減小很多;同時(shí),為了進(jìn)一步對比,本文也采用在上述2.3中介紹的在內(nèi)核態(tài)中插樁的方法來測試context_switch()函數(shù)的開銷,經(jīng)實(shí)驗(yàn)得到,內(nèi)核中插樁跟蹤得到的上下文切換的平均延時(shí)為2.5us(其中也包含跟蹤工具帶來的延時(shí)),可見,使用sched_yield()系統(tǒng)調(diào)用來切換的方法測試結(jié)果更接近內(nèi)核態(tài)中測試的context_switch()函數(shù)的開銷,結(jié)果更精確。
圖3,圖4中所給出的上下文測試結(jié)果中也包括了sched_yield()系統(tǒng)調(diào)用的開銷和管道讀寫的開銷。為了進(jìn)一步追求結(jié)果的精度,表2給出了通過編寫程序測量得到的sched_yield()和read()/write()的開銷,由表2可以看出,sched_yield()系統(tǒng)調(diào)用的開銷為1.5us,而使用管道讀寫的read()和write()的系統(tǒng)調(diào)用的開銷為3.8us,sched_yield()系統(tǒng)調(diào)用的開銷更小,可以近似忽略不計(jì),因此在不去掉系統(tǒng)調(diào)用開銷的情況下,采用sched_yield()測試上下文切換的結(jié)果相對更精確。
4 總結(jié)
本文利用sched_yield()系統(tǒng)調(diào)用函數(shù)可以主動(dòng)放棄CPU的特點(diǎn),提出了一種在用戶態(tài)測試上下文切換的新方法。將該方法與在用戶態(tài)使用管道讀寫切換的方法在ARM Linux-3.2.0的平臺(tái)上進(jìn)行了對比實(shí)驗(yàn)并且分析了使用nanosleep()睡眠函數(shù)測試不合理的原因,發(fā)現(xiàn)使用sched_yield()系統(tǒng)調(diào)用來進(jìn)行切換可以顯著提高測試結(jié)果的精度,而且sched_yield()系統(tǒng)調(diào)用的開銷與read()/write()系統(tǒng)調(diào)用的開銷相比要小很多,甚至可以忽略不計(jì)。同時(shí)也采用了在內(nèi)核態(tài)插樁的方法來測試上下文切換時(shí)間,發(fā)現(xiàn)使用sched_yield()函數(shù)的測試結(jié)果更接近于context_switch()函數(shù)的開銷,同時(shí)采用編寫用戶態(tài)應(yīng)用程序的方法操作更方便更快捷。本文提到的采用sched_yield()系統(tǒng)調(diào)用函數(shù)來編寫用戶態(tài)應(yīng)用程序的方法來測試上下文切換延時(shí)也可以應(yīng)用到操作系統(tǒng)的實(shí)時(shí)性等方面的測試。
參考文獻(xiàn):
[1] Cede?o W, Laplante P A. An Overview of Real-time Operating Systems[J]. Journal of the Association for Laboratory Automation, 2007, 12(12):40-45.
[2] Jinhui Q, Hui L D, Junchao Y. The Application of Qt/Embedded on Embedded Linux[C]// International Conference on Industrial Control and Electronics Engineering. IEEE Computer Society, 2012:1304-1307.
[3] Context switch, https://en.wikipedia.org/wiki/Context_switch
[4] 吳章金. Linux實(shí)時(shí)搶占補(bǔ)丁的研究與實(shí)踐[D]. 蘭州大學(xué), 2010.
[5] 張曉龍, 郭銳鋒, 陶耀東,等. Linux實(shí)時(shí)搶占補(bǔ)丁研究及實(shí)時(shí)性能測試[J]. 計(jì)算機(jī)工程, 2014, 40(10):304-307.
[6] 胡志剛, MAHAMMED M E, 余正軍,等. 嵌入式Linux實(shí)時(shí)性方法[J]. 中南大學(xué)學(xué)報(bào)(自然科學(xué)版), 2004, 35(4):638-642.
[7] 陽國貴, 姜波. 線程切換開銷分析工具的設(shè)計(jì)與實(shí)現(xiàn)[J]. 計(jì)算機(jī)應(yīng)用, 2010, 30(8):2052-2055.
[8] RobertLove. Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)[M]. 機(jī)械工業(yè)出版社, 2011.
[9] DANIEL P.BOVET \& MARCO CESATI.深入理解LINUX內(nèi)核:第3版[M].中國電力出版社,2006.
[10] Debugging the kernel using Ftrace - part 1. (2009-12-09) https://lwn.net/Articles/365835/
[11] MYC-AM335X 產(chǎn)品數(shù)據(jù)手冊(V2.0)光盤路徑MYD-AM335X_V14_2\01-Document\UserManual\Chinese