嵌入式軟件設計中查找缺陷的幾個(gè)技巧

圖3:有消息傳遞的死鎖
一些操作系統過(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ù)的不一致用法,識別引起競爭條件和死鎖的許多原因。
評論