最近在項目中使用了chan_ss7進行中國7號信令的接入,由於在接入過程中,遇到一些問題,需要對原始碼進行分析,以解決遇到的問題。下面先對chan_ss7進行邏輯分析。
與OSI7層模型類似,在SS7中,也是按照層次來進行組織的,從底到上依次是MTP1,MTP2,MTP3,ISUP/TUP/SCCP。MTP層主要保證使用者層資料(TUP/ISUP)的可靠端到端傳輸。在chan_ss7中對ISUP進行了較好的支援,對於TUP目前還沒有支援,國內的開源通訊(openvox)對這部分進行了擴充,增加了支援。這裡是以ISUP為例來說明。
chan_ss7編譯後,是以一個channel模組載入到asterisk中的。在chan_ss7中採用了asterisk較為常見的do_monitor線程對收到的訊息進行處理。另外,還有一個MTP線程來負責接收MTP訊息。為了避免死結問題,這兩個線程之間使用了一個receivedbuf進行中轉。
MTP線程接收到來自MTP層的資料後,將訊息封裝成evet,然後通過mtp_put函數,將event送入到receivedbuf中,然後再通過alert pipe方式通知do_monitor線程有新訊息到來需要處理。
在do_monitor線程中,從receivedbuf中逐個取出訊息,然後按照訊息的類型不同,分別進行處理。
訊息的處理過程,以ISUP訊息為例,
對於ISUP訊息,首先會通過chan_ss7.c中的process_event函數,根據訊息類型,轉到l4isup_event中。在l4isup_event中,首先對event進行解碼,解析出isup訊息,然後根據isup訊息中的cic尋找到對應的pvt結構,然後到process_isup_message根據訊息的類型的不同,調用對應的handler進行處理。
在process_isup_message中,根據msg的typ取值不同,向process_circuit_message傳入不同的函數指標,以完成handler的抽象,進行調用。
在process_circuit_message中,調用具體的handler之前,要完成幾個鎖的訪問問題,一個是global鎖,一個是pvt的鎖,一個是channel的鎖。這三個鎖的訪問順序,是需要仔細注意的,加鎖的順序必須是global->channel->pvt。加鎖完成後,是對應的handler的調用。處理結束後,釋放鎖,處理結束。
我的問題:在處理高並發、長呼叫的時候,有些呼入的長呼叫沒有正常掛機。如果被叫不主動掛機,主叫會一直在。並且,此時,所有的呼入/呼出信令全部被阻塞,整個ISUP層不會再接續處理。
分析:通過上面的執行過程分析來看,訊息的處理過程是在do_monitor線程中進行的。如果在處理某個訊息的時候,出現死結問題,該線程將不會返回,直到死結條件解除。特別是在process_circuit_message函數中,需要對三個鎖進行分別處理,極容易引入鎖死。
跟蹤:在實際測試中,對process_circuit_message進行了跟蹤,發現死結問題是在ast_channel_lock的時候引起的,此時,通過CHECK_BLOCKING這個宏判斷看,原來是在ast_write。通道線程在往線程中寫資料的時候。
進一步分析:作為asterisk中較為核心的channel.c,我堅信不會存在死結的問題。既然問題是在ast_write函數中,那麼產生的根源應該是在ss7_write函數中。在這個函數中,直接調用了write函數,對話路那個通道直接寫入語音資料。經過做log測試,看出,發生死結的時候,是從write函數中沒有返回。此處的write函數是同步的寫,必須等待寫結束後,才進行返回。問題的根源應該是在這裡。
解決方案:既然問題已經找到了,那麼該如何解決?
1.為了避免整個ISUP層被堵塞,應該避免死結的發生。起初的判斷是將處理過程process_circuit_message放到一個獨立的線程中去執行。由於這裡有個global鎖,多線程實際上被串列執行了,沒有多少意義。在process_circuit_message進行channel鎖的時候,使用trylock,多檢測幾次,如果仍然沒有鎖住channel,則對本次訊息,不予處理。等待下次重傳的訊息。
2.即便是處理重傳,但是在實際中會一直出現鎖在ast_write中。除非被叫端掛機,否則該通道一直會被佔用。為了避免這種情況的發生,在1基礎之上,對於拆線等事件,如果發現trylock失敗,轉而讓bridged通道去掛機。對於這種方案,目前還沒有進行測試。