王 豐,印 釗
(武漢郵電科學研究院湖北武漢430074)
隨著信息技術的不斷發展,軟件開發周期越來越短,如何快速有效地檢驗軟件的可靠性成為一個重要問題。運行時驗證技術是一種軟件測試技術,其思想是通過持續監控并采集程序運行狀態信息來驗證軟件運行是否符合規范。早期的運行時驗證技術受限于單核處理器(Central Processing Unit,CPU)的性能不足,使用其測試軟件會影響目標軟件的運行速度,導致測試效率降低、測試成本增加。近幾年多核CPU電腦已經大規模普及,研究基于多核CPU的運行時驗證技術對于改進傳統的運行時驗證技術尤為重要。
本文在基于多核CPU的Ubuntu平臺上,對兩個不同的開源程序針對數組引用越界這種軟件錯誤進行運行時驗證技術的研究。通過實驗數據得出了基于多核CPU的運行時驗證技術能夠有效提高監控軟件效率、增加軟件可靠性的結論。
驗證技術是一種檢驗系統的行為是否符合規范的技術,主要的驗證技術有定理證明、模型檢測和測試3種[1]。定理證明和模型檢測主要從宏觀層面通過公式推導和建模來驗證系統的正確性,但一個正確的系統運行之后仍可能出現一些無法預料的錯誤,這就需要通過測試的方法,從微觀層面檢驗系統的正確性。運行時驗證技術是一種測試方法[2]。該技術的思想是:運行程序前對軟件進行相關配置,設計一個監控器,如果程序在運行過程中出現了不符合規范的異常,監控器能夠實時記錄和匯報軟件運行狀況。運行時驗證技術具有易于部署、可降低軟件測試的成本、可提高軟件測試效率等優點。
運行時驗證技術一般通過插樁的方法來實現。插樁是在程序源代碼中插入一段自定義的代碼,這段自定義的代碼的作用是收集記錄源程序的運行信息,比如數組引用情況、指針引用情況、函數的調用情況等評估程序運行狀態的重要指標。插入的自定義代碼叫做樁代碼,樁代碼不能影響程序結果,也不能破壞程序執行邏輯的完整性[3]。由于樁代碼需要實現信息獲取與傳遞功能,在運行程序時必然會占用系統資源、增加運行時間,這就是使用運行時驗證技術所帶來的性能損耗。
CPU是一臺計算機的運算核心和控制核心。它的功能主要是解釋計算機指令以及處理計算機軟件中的數據[4]。目前常用的多核CPU通常指的是單芯片多處理器(Chip Multiprocessors,CMP)。CMP的思想是將大規模并行處理器中的對稱多處理器集成到同一芯片內,各個處理器并行執行不同的進程[5]。這種依靠多個CPU并行地運行程序的方式被稱為并行處理。并發和并行是兩個常用的計算機概念。并發是指當有多個線程在操作,而系統只有一個CPU時,宏觀上有多個程序在同時運行,但微觀上這些程序只是串行地交替執行,在同一時刻只能有一個程序被執行[6]。并行是指當系統有不止一個CPU時,不同的線程可以在不同的CPU上執行,線程之間互不占用CPU資源,屬于真正的同時運行。
進程是系統進行資源分配和調度的基本單位,一個進程可以分為兩個部分:線程集合和資源集合。線程是進程中的一個動態對象,進程中的所有線程將共享進程里的資源。實現基于多核CPU的運行時驗證技術需要使用線程并行的編程技術。線程并行編程的主要內容是解決共享內存、消息傳遞和數據并行這3個方面的問題。本文使用POSIX threads線程庫,簡稱Pthreads線程庫[7],進行線程的相關操作。Pthreads線程庫是Linux系統中多線程編程的標準庫。與大多數線程庫的功能相似,Pthreads線程庫具有線程的創建和終止、線程同步、設置條件變量與信號量等功能[8]。
緩存溢出是一種輸入數據超出緩存區大小的程序錯誤,可導致程序運行出錯或者崩潰[9]。數組是程序中最常用的一種緩存區,函數調用數組的數組名出錯、調用數組的下標超過了數組定義的大小等錯誤被稱為“數組越界”。數組越界是一種緩存溢出的表現,是一種常見的程序錯誤。本文通過檢驗數組越界這一錯誤來證明運行時驗證技術的有效性。
本文的設計思想是:設置兩個線程,一個線程負責運行目標程序,另一個線程負責監控目標程序運行狀況,在運行目標程序之前,修改目標程序的源代碼,在源代碼中插入負責監控的樁代碼。為了模擬程序運行時出現錯誤,使用數組越界這種錯誤作為各種程序錯誤的代表。最后在其他條件相同的情況下使用單核CPU和多核CPU進行對比實驗,得到實驗結果和結論。下面是相關步驟的具體設計。
本文的實驗需要創建兩個線程:一個目標程序線程,一個監控器線程。在目標程序運行的過程中,監控器線程負責定時收集程序運行信息。一般的多核CPU系統能夠自動分配線程給不同的核心,但如果使用核綁定技術將目標程序線程和監控器線程分別綁定到用戶自定義的處理器核心上可以使效率更高。監控器線程如果一直處于運行狀態,會占用系統資源,導致目標程序性能降低,因此需要根據目標程序使用數組的次數來決定是否喚醒監控器線程,本次實驗把使用數組次數設為3000。每使用3000次數組,就喚醒監控器線程來處理信息,判斷這3000次數組使用中是否出現了數組越界的錯誤。監控器線程在對這些數據進行處理的過程中,會將每一個數據的處理結果寫入文件。故監控器線程的工作流程為:
1)監控器線程檢測目標程序進程是否使用數組,不是則休眠,使用數組則進入下一步;
2)記錄使用的數組信息,計數number(初始為0)加1;
3)number是否等于3000,等于則進入下一步,小于則返回第1步;
4)監控器線程集中處理之前收集的3000次數組的數據,處理完成后number清零。
5)結束。
兩個線程之間的通信使用一個結構體數組來實現。下面是兩個線程用來通信的全局結構體數組的定義:

該結構體傳遞的信息是目標程序運行中用到的數組下標。這里定義一個全局整形變量number作為結構體數組的下標。本次實驗希望通過運行時驗證技術來檢驗數組越界這一錯誤。檢驗數組是否越界的方法為:目標系統運行過程中,每次使用數組的信息都被會用結構體數組communication[i]里保存。獲取結構體數組下標的最大值、最小值和下標值subscript,如果使用的數組的下標subscript比min小或者比max大,那么可以認為目標程序運行過程中發生了數組越界的錯誤。每次目標程序調用一次數組就把結構體數組的信息存入一個文件中,按順序分配行號和列號,如圖1所示。

圖1 保存使用過的數組信息
如果檢測到發生數組越界的錯誤,那么將出錯的數組的行號列號存入到“error.txt”文件里。如圖2所示。
運行時驗證技術對不同程序的驗證效率是不同的,為了得出更嚴謹的結論,本次實驗使用PPETS math functions和Qsort兩個開源目標程序進行對比實驗。PPETS math functions是一款是計算的軟件,具有計算量大,運行時間較長,大量使用數組的特點。

圖2 保存產生錯誤的數組信息
Qsort程序是編譯器自帶的快速排序程序,通過調用math.h頭文件中的qsort()函數,程序會使用快速排序算法對數據進行排序,Qsort程序的代碼規模與復雜度遠小于PPETS math functions程序。
本次實驗把目標程序的執行時間作為運行時驗證技術的性能判定的參考值,在其他條件相同的情況下,程序執行時間越短表示性能越好。在Linux操作系統下,可以通過time命令來獲取一個程序的執行時間。
為了證明多核CPU能夠加速實現運行時驗證技術,本次實驗設計5種場景:
1)單核平臺下直接運行目標程序;
2)單核平臺下使用運行時驗證技術,運行目標程序;
3)多核平臺下直接運行目標程序;
4)多核平臺下不進行核綁定,使用運行時驗證技術,運行目標程序;
5)多核平臺下進行核綁定,使用運行時驗證技術,運行目標程序。
本次實驗使用的處理器為Intel酷睿i3-5005U,CPU主頻2 GHz,雙核心,操作系統是Linux Ubuntu,編程語言為C語言。實驗通過編寫代碼并運行得到實驗結果,實現本次實驗的關鍵就是插樁代碼和線程控制的代碼。
本次實驗兩個目標程序PPETS math functions和Qsort都是可以直接運行的,因此需要在目標程序中插入作為記錄程序運行信息的樁代碼,如下所示:

以上代碼的作用是如果目標程序運行時使用了數組,通過調用aStake()樁函數,將使用的數組的元素x[i]的下標最小值1、最大值2、下標i、存儲的文件名"test.txt"和存儲在文件中第3行第4列6個實參傳遞給結構體數組。
由于目標程序PPETS math functions和Qsort都是非常成熟的程序,一般情況很難出現運行錯誤。為了模擬目標程序出現運行時錯誤,實驗需要隨機產生一些越界的數組下標。調用

實驗新建一個監控器線程并將其與一個CPU進行核綁定,將目標程序與另一個CPU進行核綁定,這樣就能發揮多核CPU的優勢,達到監控程序與目標程序并行運行的效果。使用Pthreads線程庫提供的pthread_create()函數創建線程。Linux系統提供了調用函數來設置核親和性:sched_setaffinity(pid_t pid,unsigned int cpusetsize,cpu_set_t*mask),參數pid是目標線程或進程的id(若為0表示當前線程);參數cpusetsize指后一個參數mask所指向的內存結構對象的大小;指針參數mask指向類型為cpu_set_t的對象。模擬單核CPU運行程序只需把所有進程都放在一個核心上運行即可。
以下是監控器線程功能實現代碼:


圖3是多核平臺下把兩個線程綁定到不同的CPU成功的結果。

圖3 不同線程核綁定成功
正如設計中提到的,本次實驗有5種場景:
1)單核平臺下直接運行目標程序;
2)單核平臺下使用運行時驗證技術,運行目標程序;
3)多核平臺下直接運行目標程序;
4)多核平臺下不進行核綁定,使用運行時驗證技術,運行目標程序;
5)多核平臺下進行核綁定,使用運行時驗證技術,運行目標程序。
實驗對每種場景都進行了多次實驗,最終結果取多次實驗結果的平均值[10-18]。
圖4是目標程序為Qsort,運行場景1的一次結果,其中real時間是程序運行時間,實驗結果以real時間為參考。

圖4 PPETS math functions單核直接運行結果
圖5顯示的是目標程序為PPETS math functions的5種實驗場景的平均結果,其中場景1的運行時間為40.7 s,場景2的運行時間為42.2 s,場景3的運行時間為33.3 s,場景4的運行時間為35.1 s,場景5的運行時間為31 s。

圖5 目標程序為PPETS math functions的實驗結果
對于Qsort軟件中也對上述5種情況分別進行了實驗,圖6為實驗結果。其中場景1的運行時間為18.9 s,場景2的運行時間為23.4 s,場景3的運行時間為17.1 s,場景4的運行時間為20.5 s,場景5的運行時間為17.9 s。

圖6 目標程序為Qsort的實驗結果
通過對比以上實驗結果,可以得出以下實驗結論:
1)運行同一個目標程序,無論是使用運行時驗證技術還是直接運行,多核運行速度都比單核運行速度快。目標程序為PPETS math functions時,多核運行速度(未綁定核)比單核運行速度平均快17.5%,核綁定的情況下多核運行速度比單核運行速度平均快26.5%。目標程序為Qsort時,多核運行速度(未綁定核)比單核運行速度平均快10.9%,核綁定的情況下多核運行速度比單核運行速度平均快23.5%;
2)運行同一個目標程序,無論是單核還是多核,使用運行時驗證技術都會增加程序運行時間。目標程序為PPETS math functions時,時間平均增加了4.5%,目標程序為Qsort時,時間平均增加了21.8%;
3)運行同一個目標程序,多核運行時進行核綁定能夠在多核運行原先的基礎上進一步提高性能。目標程序為PPETS math functions時,核綁定之后速度提高11.7%。目標程序為Qsort時,核綁定之后速度提高12.7%;
4)對于不同的目標程序,目標程序規模越大,使用運行時驗證技術占用資源的比例就越少,軟件測試效率越高。
本文通過對相關技術的研究與分析,實現了基于多核CPU的運行時驗證技術并進行了實驗。分析實驗結果可以得出結論:與基于單核CPU的運行時驗證技術相比,本文所實現的基于多核CPU的運行時驗證技術能夠有效提高軟件的測試效率和運行速度,具有一定的實用價值。當然本文的設計還存在需要改進和完善的地方,比如插樁代碼和插樁的位置還可以進行優化,使得運行時驗證技術造成的損耗降低;監控器線程的功能還可以增加,使其能夠檢驗多種軟件運行錯誤。這些是今后需要研究和關注的方向。