大部分軟件開(kāi)發(fā)項目依靠結合代碼檢查、結構測試和功能測試來(lái)識別軟件缺陷。盡管這些傳統技術(shù)非常重要,而且能發(fā)現大多數軟件問(wèn)題,但它們無(wú)法檢查出當今復雜系統中的許多共性錯誤。本文將介紹如何避免那些隱蔽然而常見(jiàn)的錯誤,并介紹的幾個(gè)技巧幫助工程師發(fā)現軟件中隱藏的錯誤。 結構測試或白盒測試能有效地發(fā)現代碼中的邏輯、控制流、計算和數據錯誤。這項測試要求對軟件的內部工作能夠一覽無(wú)遺(因此稱(chēng)為"白盒"或"玻璃盒"),以便了解軟件結構的詳細情況。它檢查每個(gè)條件表達式、數學(xué)操作、輸入和輸出。由于需要測試的細節眾多,結構測試每次檢查一個(gè)軟件單元,通常為一個(gè)函數或類(lèi)。 代碼審查也使用與實(shí)現缺陷和潛在問(wèn)題查找同樣復雜的技術(shù)。與白盒測試一樣,審查通常針對軟件的各個(gè)單元進(jìn)行,因為一個(gè)有效的審查過(guò)程要求的是集中而詳盡的檢查。 與審查和白盒測試不同,功能測試或黑盒測試假設對軟件的實(shí)現一無(wú)所知,它測試由受控輸入所驅動(dòng)的輸出。功能測試由測試人員或開(kāi)發(fā)人員所編寫(xiě)的測試過(guò)程組成,它們規定了一組特定程序輸入對應的預期程序輸出。測試運行之后,測試人員將實(shí)際輸出與預期輸出進(jìn)行比較,查找問(wèn)題。黑盒測試可以有效地找出未能實(shí)現的需求、接口問(wèn)題、性能問(wèn)題和程序最常用功能中的錯誤。 雖然將這些技術(shù)結合起來(lái)可以找出隱藏在一個(gè)特定軟件程序中的大部分錯誤,但它們也有局限。代碼審查和白盒測試每次只針對一小部分代碼,忽視了系統的其它部分。黑盒測試通常將系統作為一個(gè)整體來(lái)處理,忽視了實(shí)現的細節。一些重要的問(wèn)題只有在集中考察它們在整個(gè)系統內相互作用時(shí)的細節才能被發(fā)現;傳統的方法無(wú)法可靠地找出這些問(wèn)題。必須整體地檢查軟件系統,查找具體問(wèn)題的特定原因。由于詳盡徹底地分析程序中的每個(gè)細節和它與代碼中所有其它部分之間的相互作用通常是不大可能的,因此分析應該針對程序中已經(jīng)知道可能導致問(wèn)題的特定方面。本文將探討其中三個(gè)潛在的問(wèn)題領(lǐng)域: * 堆棧溢出 * 競爭條件 * 死鎖 讀者可在網(wǎng)上閱讀本文的第二部分,它將探討下列問(wèn)題: * 時(shí)序問(wèn)題 * 可重入條件 在采用多任務(wù)實(shí)時(shí)設計技術(shù)的系統中,以上所有問(wèn)題都相當普遍。 堆棧溢出 處理器使用堆棧來(lái)存儲臨時(shí)變量、向被調函數傳遞參數、保存線(xiàn)程“狀態(tài)”,等等。如果系統不使用虛擬內存(換句話(huà)說(shuō),它不能將內存頁(yè)面轉移到磁盤(pán)上以釋放內存空間供其它用途),堆棧將固定為產(chǎn)品出廠(chǎng)時(shí)的大小。如果由于某種原因堆棧越出了編程人員所分配的數量范圍,程序將變得不確定。這種不穩定可能導致系統發(fā)生嚴重故障。因此,確保系統在最壞情況下能夠分配到足夠的堆棧至關(guān)重要。 確保永不發(fā)生堆棧溢出的唯一途徑就是分析代碼,確定程序在各種可能情況下的最大堆棧用量,然后檢查是否分配了足夠的堆棧。測試不大可能觸發(fā)特定的瞬時(shí)輸入組合進(jìn)而導致系統出現最壞情況。 堆棧深度分析的概念比較簡(jiǎn)單: 1. 為每個(gè)獨立的線(xiàn)程建立一棵調用樹(shù)。 2. 確定調用樹(shù)中每個(gè)函數的堆棧用量。 3. 檢查每棵調用樹(shù),確定從樹(shù)根到外部“樹(shù)葉”的哪條調用路徑需要使用的堆棧最多。 4. 將每個(gè)獨立線(xiàn)程調用樹(shù)的最大堆棧用量相加。 5. 確定每個(gè)中斷優(yōu)先級內各中斷服務(wù)程序(ISR)的最大堆棧用量并計算其總和。但是,如果ISR本身沒(méi)有堆棧而使用被中斷線(xiàn)程的堆棧,則應將ISR使用的最大堆棧數加到各線(xiàn)程堆棧之上。 6. 對于每個(gè)優(yōu)先級,加上中斷發(fā)生時(shí)用來(lái)保存處理器狀態(tài)的堆棧數。 7.如果使用RTOS,則加上RTOS自身內部用途需要的最大堆棧數(與應用代碼引發(fā)的系統調用不同,后者已包含在步驟2中)。 除此之外,還有兩個(gè)重要事項需要考慮。首先,僅僅從高級語(yǔ)言源代碼建立的調用樹(shù)很可能并不完善。大部分編譯器采用運行時(shí)庫(run-time library)來(lái)優(yōu)化常用計算任務(wù),如大值整數的乘除、浮點(diǎn)運算等,這些調用只在編譯器產(chǎn)生的匯編語(yǔ)言中才可見(jiàn)。運行時(shí)庫函數本身可能使用大量的堆?臻g,在分析時(shí)必須將它們包括進(jìn)去。如果使用的是C++語(yǔ)言,則以下所有類(lèi)型的函數(方法)也都必須包含到調用樹(shù)內:結構器、析構器、重載運算符、復制結構器和轉換函數。所有的函數指針也都必須進(jìn)行解析,并且將它們調用的函數包含進(jìn)分析之中。 第二,編譯器使用一個(gè)C庫來(lái)實(shí)現memcpy()、cos()和atof ()等標準函數,而這些例程的源代碼可能無(wú)法得到。如果能夠得到它們的源代碼,就有可能確定程序用到的每個(gè)庫調用在最壞情況下的堆棧使用數量。如果這些庫只包含在目標文件中,則編譯器廠(chǎng)商必須提供每個(gè)庫例程使用的堆棧數。如果沒(méi)有這些信息,就無(wú)法通過(guò)分析來(lái)確定最壞情況下程序使用的最大堆棧數。幸運的是,許多面向嵌入式系統的編譯器廠(chǎng)商都提供這些信息。 通常,每次一個(gè)函數被調用時(shí),編譯器將使用堆棧來(lái)保存返回地址并傳遞函數參數。函數的自動(dòng)(局部)變量通常也在堆棧當中。不過(guò),由于編譯器會(huì )盡可能通過(guò)將參數或局部變量放入寄存器來(lái)優(yōu)化代碼,因此檢查匯編語(yǔ)言以精確地確定堆棧用量非常重要。編譯器也有可能在代碼中的其它地方選擇使用堆棧,如用堆棧來(lái)保存中間計算結果。 有些與編譯器一起打包銷(xiāo)售的開(kāi)發(fā)環(huán)境包含生成調用樹(shù)的工具,還有許多第三方的調用樹(shù)生成工具。但是,除非它們能夠對匯編語(yǔ)言進(jìn)行分析,否則這些工具可能會(huì )遺漏運行時(shí)庫和C庫的調用。不過(guò)無(wú)論在哪種情況下,開(kāi)發(fā)分析匯編語(yǔ)言文件并提取函數名稱(chēng)以及各函數內部調用的腳本都比較簡(jiǎn)單。分析的結果可寫(xiě)入一個(gè)文件,而這個(gè)文件能夠方便地輸入到表格之中。 確定了各個(gè)函數的堆棧用量之后,必須計算每個(gè)線(xiàn)程所需的最大堆棧數。由于一般程序通常涉及數百個(gè)函數,調用跨越多層深度,處理這些信息的一種簡(jiǎn)便方法就是采用分析表格。如表1所示,表格的各行包含了函數名稱(chēng)、該函數使用的最大堆棧數(包括調用其它函數所需的堆棧數),以及它調用的所有函數的清單。通過(guò)編程控制,這個(gè)表格從每個(gè)函數的"根"開(kāi)始迭代循環(huán),計算該函數及其調用的所有函數需要的堆棧。這些信息存放在堆棧路徑列中,這樣,采用每個(gè)線(xiàn)程根函數(如main)的堆棧路徑數據就可以方便地計算出需要的最大堆棧數了。這個(gè)過(guò)程包含了先前介紹的堆棧分析過(guò)程中的前四個(gè)步驟。 有時(shí)候,采用堆棧深度分析過(guò)程可能是無(wú)法做到,或者是不實(shí)際的。如果無(wú)法得到運行時(shí)庫或C庫的源代碼,而編譯器廠(chǎng)商又沒(méi)有提供任何堆棧使用信息,就不可能進(jìn)行完整的堆棧分析。在這種情況下,有兩種選擇: 1. 在測試期間,觀(guān)察堆棧所能達到的深度,并保證有較大的堆?臻g余量。 2. 檢測堆棧溢出,并采取改進(jìn)措施。 觀(guān)察堆棧深度的方法很簡(jiǎn)單: * 向整個(gè)內存堆棧區寫(xiě)入一個(gè)特定的數據圖案符號,如55AA。 * 在預期使用最大堆?臻g的條件下運行系統。 * 使用仿真器或其它工具檢查堆棧存儲區,看有多少符號圖案由于堆棧的使用而被改寫(xiě)了。 當然,這些步驟并不能保證在一些不同條件下不會(huì )需要更多的堆棧,但確實(shí)可以表明所需要的最小堆棧數。 使用帶內存管理單元(MMU)的處理器時(shí),有可能檢測出運行時(shí)的堆棧溢出現象。MMU將內存劃分為多個(gè)區域,用一個(gè)受保護的內存段來(lái)“警戒”堆棧區域。發(fā)生堆棧溢出時(shí),處理器將訪(fǎng)問(wèn)這個(gè)受保護段。這個(gè)操作將引發(fā)一個(gè)異常事件(如產(chǎn)生SIGSEGV信號),可被程序捕獲到。創(chuàng )建線(xiàn)程時(shí),與實(shí)時(shí) POSIX標準兼容的RTOS提供有這種堆棧警戒功能選項,大大簡(jiǎn)化了編程人員的工作。GNU工具等其它開(kāi)發(fā)環(huán)境包含有編譯器開(kāi)關(guān),可在程序中添加實(shí)現堆棧警戒功能所需的代碼,但它們仍然依靠底層操作系統來(lái)有效地處理堆棧溢出。但是,按照這種方式檢測溢出還只是問(wèn)題的一部分。為了使這類(lèi)設計更為有效,系統必須能夠從堆棧溢出中恢復過(guò)來(lái)并繼續正確地工作。 在一個(gè)對安全或任務(wù)要求嚴格的應用中,系統運行時(shí)在測試或檢測堆棧溢出期間監視堆棧的深度可能并不是一項足夠的風(fēng)險控制措施。對于一些應用,必須確保系統絕對不會(huì )越出所分配的堆棧范圍;只有通過(guò)完整的堆棧深度分析才能證明這一點(diǎn)。這意味著(zhù),如果整個(gè)程序在同一內存空間運行,則必須對所有代碼執行這項分析。不過(guò),如果使用MMU,分析?珊(jiǎn)化。在設計系統時(shí),可將所有關(guān)鍵代碼置于一個(gè)或多個(gè)獨立線(xiàn)程內,而這些線(xiàn)程分別在各自的保護內存段中運行。這樣,只要對這些關(guān)鍵線(xiàn)程進(jìn)行堆棧使用分析就可以了。當然,這項簡(jiǎn)化設計假定當非關(guān)鍵線(xiàn)程溢出其堆棧并失效時(shí),關(guān)鍵線(xiàn)程仍可正確執行。 由于分析工作所需的堆棧使用數據來(lái)自匯編語(yǔ)言清單,因此修改代碼時(shí),相應模塊的堆棧使用信息必須予以更新。如果使用不同的編譯器版本,或者改變了優(yōu)化設置,也必須復核整個(gè)分析過(guò)程。在理想情況下,編譯器將提供每個(gè)函數(如果不是每個(gè)線(xiàn)程的話(huà))的堆棧使用數量,因為它擁有計算需要的所有信息。例如,瑞薩公司提供有Call Walker,這是該公司高性能的Embedded Workshop開(kāi)發(fā)環(huán)境的一部分。這個(gè)工具可以圖形化地顯示每個(gè)函數使用的調用樹(shù)和堆棧,包括運行時(shí)庫和C庫的函數。Call Walker也能找出使用堆棧數量最大的路徑。使用這樣的工具可以實(shí)現步驟1到步驟3的自動(dòng)化。 競爭條件 當兩個(gè)或更多獨立線(xiàn)程同時(shí)訪(fǎng)問(wèn)同一資源時(shí),就出現了競爭條件。競爭條件的影響多種多樣,取決于具體的情況。清單1解釋了一個(gè)潛在的競爭條件。函數Update_Sensor()通過(guò)調用get_raw()來(lái)讀取傳感器的原始數據。在處理過(guò)程中,該數據被乘上一個(gè)定標因子,并加上一個(gè)偏移量。處理是在該數據的一個(gè)臨時(shí)副本上進(jìn)行的,然后,該臨時(shí)副本被寫(xiě)入共享變量。 如果在數據寫(xiě)入之前,使用shared_sensor的另一個(gè)線(xiàn)程或ISR先占(preempt)了這個(gè)線(xiàn)程,它將得到原來(lái)的傳感器讀數。使用臨時(shí)副本可以防止先占線(xiàn)程讀取只經(jīng)過(guò)部分處理的數據。不過(guò),如果這些代碼在一個(gè)數據總線(xiàn)不足32位的處理器上運行,就會(huì )存在競爭條件。 在一個(gè)8位或16位的處理器上,向shared_sensor的寫(xiě)入操作并不是一次性完成的。在8位處理器上,寫(xiě)入32位浮點(diǎn)值可能需要四條指令,在16位處理器上可能需要兩條指令。如果在對shared_sensor進(jìn)行連續寫(xiě)入中途Update_Sensor()被先占,則先占線(xiàn)程將從由一部分老數據和一部分新數據組成的shared_sensor讀取一個(gè)數值。根據應用的具體情況,這有可能造成嚴重的后果。解決的辦法是鎖定調度程序,或在更新共享變量期間禁止中斷。 消除競爭條件通常很簡(jiǎn)單,但找出隱藏在代碼中的競爭條件則需要仔細的分析。 對于由一個(gè)循環(huán)程序和不同ISR組成的簡(jiǎn)單系統,分析競爭條件很簡(jiǎn)單,只需檢查每個(gè)ISR并識別它引用的所有共享變量。共享變量通常是這些系統中的全局數據,一旦這些共享變量被找出來(lái)之后,就可以檢查它們在代碼中的各次使用情況。每次訪(fǎng)問(wèn)都必須按需要進(jìn)行保護,以避免潛在的沖突。在簡(jiǎn)單設計中,一般通過(guò)在關(guān)鍵代碼段周?chē)怪袛鄟?lái)實(shí)現保護。遵守下列規則可幫助避免競爭問(wèn)題: * 如果一個(gè)ISR對共享數據進(jìn)行寫(xiě)入,則該ISR之外的每次可中斷的讀操作都必須予以保護。 * 如果一個(gè)ISR對共享數據進(jìn)行寫(xiě)入,則該ISR之外的任何讀-修-寫(xiě)操作都必須予以保護。 * 如果一個(gè)ISR讀取共享數據,則對該數據的可中斷寫(xiě)操作必須予以保護。 * 如果一個(gè)ISR和其它代碼都要檢查一個(gè)硬件狀態(tài)標志,以便在使用某資源之前確定其可用性,如: if (!resource_busy) { // Use resource } 則從檢查標志之時(shí)開(kāi)始,到硬件設置標志表示資源不可用為止,必須采取保護措施。 對于使用了優(yōu)先級不同的多個(gè)線(xiàn)程的更為復雜的系統,其分析也非常相似。上述規則仍然適用于ISR使用的所有數據。此外,還必須識別出每個(gè)線(xiàn)程使用的共享數據。首先從系統中優(yōu)先級最高的線(xiàn)程開(kāi)始,找出它與任何優(yōu)先級較低的線(xiàn)程共享的所有數據,然后按照上述四條規則進(jìn)行保護。對于軟件使用的其它每個(gè)優(yōu)先級,再重復這一過(guò)程。 注意,如果系統采用了一種循環(huán)調度算法,則特定優(yōu)先級內的所有線(xiàn)程可在任意時(shí)刻相互先占。這意味著(zhù)前述四條分析規則在考慮較低優(yōu)先級的線(xiàn)程之外,還必須考慮同一優(yōu)先級的所有線(xiàn)程。 多線(xiàn)程系統通常使用某種類(lèi)型的操作系統,它能夠提供多種保護選擇?梢允褂没コ饣蛐盘柫,或者鎖定調度器。有時(shí)也可使用其它進(jìn)程間通信 (IPC)基本技術(shù):通過(guò)向消息隊列發(fā)送消息(而非修改共享變量)來(lái)表示數據已經(jīng)改變。在許多情況下,最好由單一線(xiàn)程來(lái)管理共享資源,它負責處理所有的讀寫(xiě)請求,并在內部防止訪(fǎng)問(wèn)沖突。 在復雜的代碼中辨認潛在的競爭條件可能是一項乏味而又耗時(shí)的工作。相應的輔助工具從用來(lái)識別全局數據訪(fǎng)問(wèn)的簡(jiǎn)單腳本到先進(jìn)的動(dòng)態(tài)分析程序如 Polyspace Verifier。雖然比較困難,但詳盡的代碼分析是識別這類(lèi)錯誤的唯一途徑。測試不大可能能夠建立重復觸發(fā)競爭條件所需的精確時(shí)序序列。 死鎖 在共享資源的系統中,防止訪(fǎng)問(wèn)沖突極為重要,但這有可能導致另一個(gè)問(wèn)題:死鎖。當通過(guò)"鎖定"一個(gè)資源來(lái)防止任何其它線(xiàn)程訪(fǎng)問(wèn)這個(gè)資源,以避免競爭條件時(shí),必須對設計進(jìn)行評估,確保絕對不會(huì )發(fā)生死鎖。死鎖測試通常沒(méi)有什么效果,因為只有某種特定順序的資源鎖定才可能產(chǎn)生死鎖,而一般的測試不大可能導致這種順序。 死鎖只不過(guò)是多線(xiàn)程環(huán)境中一個(gè)鎖定資源的問(wèn)題。以下四個(gè)條件必須同時(shí)具備,才會(huì )發(fā)生死鎖。防止其中任何一個(gè)條件出現都可以排除死鎖的可能性: * 相互排除---每次只有一個(gè)線(xiàn)程可以使用某個(gè)鎖定的資源; * 非先占---其它線(xiàn)程不能強迫另一個(gè)線(xiàn)程釋放資源; * 保持并等待---線(xiàn)程在等待需要的其它任何資源時(shí),保持它們已經(jīng)鎖定的資源; * 循環(huán)等待---存在一個(gè)線(xiàn)程循環(huán)鏈,其中每個(gè)線(xiàn)程保持鏈中下一個(gè)線(xiàn)程所需要的資源。 線(xiàn)程1首先鎖定Buf資源,在保持Buf時(shí),指向Bus,然后是Mux。如果線(xiàn)程1一直運行到結束,它最終將釋放所有這些資源。線(xiàn)程2運行時(shí),必須指向Bus、Sem,最后是Mux。線(xiàn)程3運行時(shí),需要Sem和Buf。 在這個(gè)設計實(shí)例中,無(wú)法保證任何一個(gè)線(xiàn)程能夠在另一個(gè)線(xiàn)程開(kāi)始執行之前結束。如果一個(gè)線(xiàn)程不能得到需要的某個(gè)資源,它將掛起執行(阻塞),直到該資源有效為止。在系統運行過(guò)程中,各線(xiàn)程都將對資源進(jìn)行鎖定或解鎖。由于各線(xiàn)程運行和指向其資源的相對時(shí)序各不相同,有可能出現由于各個(gè)線(xiàn)程正在等待被其它線(xiàn)程保持的資源,導致所有線(xiàn)程都無(wú)法運行的情況。例如,如果線(xiàn)程1保持Buf,線(xiàn)程2保持Bus,而線(xiàn)程3已經(jīng)取得了Sem,則系統將發(fā)生死鎖。因為按照從Buf到Bus到Sem,再回到Buf的線(xiàn)程分配箭頭,循環(huán)等待條件得到了滿(mǎn)足。 潛在死鎖問(wèn)題識別出來(lái)之后,通常很容易進(jìn)行修復。在圖2中,對線(xiàn)程3進(jìn)行了修改,使其在得到Sem之前首先設法指向Buf。這樣,循環(huán)等待的條件就被打破了,系統將不會(huì )再受到死鎖的影響。 一些操作系統過(guò)多地使用消息傳遞來(lái)進(jìn)行線(xiàn)程間通信和同步。在這些類(lèi)型的系統中,當某線(xiàn)程向另一個(gè)線(xiàn)程傳遞消息時(shí),發(fā)送線(xiàn)程將阻塞,直到從接收線(xiàn)程收到響應為止。接收線(xiàn)程通常將一直阻塞到從其它某個(gè)線(xiàn)程接收到一個(gè)消息為止。這些結構中也會(huì )發(fā)生死鎖。為了給一個(gè)基于消息的操作系統建立一張資源分配圖,我們利用消息通道來(lái)模擬分配的資源。圖3是一個(gè)例子。線(xiàn)程2建立了通道T2 Ch,當它未因為等待這個(gè)通道上的一個(gè)消息而阻塞時(shí),線(xiàn)程2就將"鎖定"這個(gè)通道。當它阻塞并等待一個(gè)消息時(shí),另一個(gè)線(xiàn)程可在這個(gè)通道上向它發(fā)送一個(gè)消息,并且這個(gè)消息將立即被接收到。 現在考慮下面這個(gè)系統:線(xiàn)程1指向Mutex并在通道T2 Ch上向線(xiàn)程2發(fā)送消息。在線(xiàn)程2中的某個(gè)地方,線(xiàn)程2在通道T3 Ch上向線(xiàn)程3發(fā)送消息。線(xiàn)程3也在通道T4 Ch上向線(xiàn)程4發(fā)送消息。在線(xiàn)程4中的某個(gè)地方,它也嘗試指向Mutex,如果得不到,它就將阻塞。顯然,各資源之間存在一條循環(huán)路徑,這表明有可能發(fā)生死鎖。例如,如果某一時(shí)刻線(xiàn)程1保持Mutex而線(xiàn)程4嘗試指向它,線(xiàn)程4就將在Mutex上阻塞。然后當線(xiàn)程3嘗試在通道T4 Ch上向線(xiàn)程4發(fā)送一個(gè)消息時(shí),線(xiàn)程3將阻塞,等待來(lái)自線(xiàn)程4的應答(因為線(xiàn)程4是由于等待Mutex而阻塞,不是為了等待這個(gè)消息)。類(lèi)似地,當線(xiàn)程2 嘗試向線(xiàn)程3發(fā)送一個(gè)消息時(shí),將被阻塞;線(xiàn)程1嘗試向線(xiàn)程2發(fā)送一個(gè)消息時(shí)也將阻塞,由于它仍然保持著(zhù)Mutex,所以系統將發(fā)生死鎖。 對付死鎖的最容易的辦法是通過(guò)設計進(jìn)行避免。采用以下任何一條設計約束都可排除死鎖出現的可能性: * 任意時(shí)刻線(xiàn)程鎖定的資源不超過(guò)一個(gè)。 * 線(xiàn)程開(kāi)始執行前就完全分配它所需的全部資源。 * 指向多個(gè)資源的線(xiàn)程必須按照一種系統范圍的預設順序來(lái)鎖定(并釋放)這些資源。 如果無(wú)法通過(guò)設計來(lái)避免死鎖,則應該建立資源分配圖。檢查資源分配圖可以識別潛在的死鎖。通過(guò)仔細跟蹤系統中的所有線(xiàn)程和它們鎖定的共享資源,可以維護資源分配圖并周期性地進(jìn)行檢查,及時(shí)發(fā)現循環(huán)等待的特征。 建立資源分配圖需要識別每個(gè)受保護的共享資源,以及指向其中某一資源的所有線(xiàn)程。如果使用一個(gè)操作系統,可以采用下面的過(guò)程步驟: 1. 識別所有可能阻塞的系統調用,如Mutex_Lock(),每個(gè)受保護的共享資源總是有一些與訪(fǎng)問(wèn)它有關(guān)的阻塞調用。 2. 識別出獲取共享資源的阻塞調用之后,在源代碼中查找它們的各次調用情況。 3. 對于每次調用,記錄下指向資源的線(xiàn)程名稱(chēng)和該資源的名稱(chēng)。通常調用本身將受保護的資源作為一個(gè)參數來(lái)傳遞,調用在源代碼中所處的位置表明了哪個(gè)線(xiàn)程需要該資源。通過(guò)這種方式,可以識別出所有受保護的資源以及分配資源的線(xiàn)程。 4. 建立資源分配圖,并檢查是否有任何資源存在循環(huán)路徑。當線(xiàn)程和共享資源較少時(shí),畫(huà)出資源分配圖比較簡(jiǎn)單。在較為復雜的系統中,最好將這些信息輸入分析表格,并編寫(xiě)一個(gè)宏來(lái)檢查線(xiàn)程和資源分配結構,以識別潛在的死鎖。編寫(xiě)好宏之后,就可以快速地對資源分配變化進(jìn)行重新評估。編寫(xiě)宏時(shí),可以忽略不會(huì )導致死鎖的資源之間的循環(huán)。在表2所示的例子中,各種資源之間有許多循環(huán),但只有線(xiàn)程6和線(xiàn)程7之間可能存在死鎖。 在一些類(lèi)型的系統中,預先確定每一個(gè)共享資源并建立分配圖是不實(shí)際或不可能的。此時(shí)可以增加一些額外的代碼,以便在系統運行時(shí)檢測出潛在的死鎖。許多不同的算法都致力于優(yōu)化這個(gè)檢測過(guò)程,但本質(zhì)上它們幾乎都動(dòng)態(tài)地建立某種資源分配圖。只要有線(xiàn)程請求、分配或釋放資源,分配圖就會(huì )被修改和檢測,以確定是否存在表明潛在死鎖的循環(huán)路徑。 檢測到某個(gè)死鎖之后,唯一的克服方法是強迫線(xiàn)程釋放關(guān)鍵的資源。通常,這意味著(zhù)中斷正保持著(zhù)所需資源的線(xiàn)程。對于某些應用,這種方法可能是無(wú)法接受的。另一個(gè)有趣的解決方案是在運行時(shí)收集資源分配情況并進(jìn)行事后分析處理,以確定在程序運行過(guò)程中是否有死鎖情況發(fā)生。盡管這種方法并不能防止在運行時(shí)發(fā)生死鎖,但它確實(shí)有助于在死鎖出現后發(fā)現問(wèn)題并進(jìn)行修復。 還有一些工具也可以用來(lái)幫助發(fā)現代碼中的死鎖。例如,Solaris程序設計員可以采用Sun公司的LockLint工具來(lái)對代碼進(jìn)行統計分析。它可以發(fā)現對鎖定技術(shù)的不一致用法,識別引起競爭條件和死鎖的許多原因。 本文來(lái)源:賽迪網(wǎng)技術(shù)社區 |