文/俞理超 胡益群 袁昌權 許光
當前,由于Linux資源完全公開,使得Linux的發(fā)展日益廣泛快速。基于Linux的各種應用已逐漸深入日常生活的方方面面,尤其是在嵌入式領域,由于內核可裁減定制,因此可隨意地根據用戶需求進行整個系統(tǒng)的定制與重構。Linux由于其內核特性,在網絡通信的使用上可能會存在令人棘手的問題,針對不同的用戶場景模型,需要進行不同的問題分析,可能涉及操作系統(tǒng)內核、任務調度、硬件驅動等原因。
現象描述:主控板運行Linux系統(tǒng),與DSP(信號處理機)進行tcp通信。在通信中,DSP時不時進行復位操作,主控中有監(jiān)測線程,監(jiān)測tcp中斷后,關閉當前tcp連接,并重新創(chuàng)建線程,與dsp建立tcp連接,應對通信錯誤。正常情況下,dsp復位后,主控仍能與DSP建立連接。在反復復位幾次后,出現主控與dsp無法正常通信的情況。
問題分析與復現:
在問題分析初期,我們懷疑現象與用戶程序有關,根據用戶描述,開始嘗試復現目標現象。根據用戶描述,linux先起一個TCP客戶端任務,去連DSP板起的TCP服務端,數據由linux發(fā)送給DSP服務端,節(jié)拍約為800ms;在linux中起另一個監(jiān)控線程,觀察TCP的connect狀態(tài),若觀察到TCP斷開,則釋放資源,并重啟線程,另啟一個TCP服務端。在發(fā)送過程中,若DSP板復位,監(jiān)控線程在正常重啟線程若干次后,會出現通訊/線程異常的情況。
為了復現現象,使用了單TCP線程加上監(jiān)控線程的模式,在反復重啟DSP的過程中,出現了類似用戶描述的現象,linux在發(fā)送過程中,若DSP復位(TCP客戶端異常結束,TCP異常斷開),linux進程自動結束的現象。根據此現象,查閱相關資料,得到以下的第一個結論。
現象機理:當Linux服務器監(jiān)聽并接受一個客戶端鏈接的時候,可以不斷向客戶端發(fā)送數據,這時如果客戶端斷開socket鏈接,服務器繼續(xù)向一個關閉的socket發(fā)送數據(send,write)的時候,系統(tǒng)會默認對服務器進程發(fā)送一個SIGPIPE信號,系統(tǒng)會出BrokePipe,關閉當前進程。經測試,Linux作為客戶端發(fā)送數據時,也會有同樣的問題。
系統(tǒng)里邊定義了三種處理方法:
(1)SIG_DFL /* Default action */信號專用的默認動作:
(a)如果默認動作是暫停線程,則該線程的執(zhí)行被暫時掛起。當線程暫停期間,發(fā)送給線程的任何附加信號都不交付,直到該線程開始執(zhí)行,但是SIGKILL除外。
(b)把掛起信號的信號動作設置成SIG_DFL,且其默認動作是忽略信號(SIGCHLD)。
(2)SIG_IGN /* Ignore action */忽略信號
(a)該信號的交付對線程沒有影響
(3)系統(tǒng)不允許把SIGKILL或SIGTOP信號的動作設置為SIG_DFL
(4)SIG_ERR /* Error return */
而TCP通信中send()函數的定義如下:
ssize_t send(int sockfd,const void *buff,size_t nbytes,int flags);
其中,send()函數的最后一個參數flag可以設MSG_NOSIGNAL,,禁止send()函數向系統(tǒng)發(fā)送異常消息,這樣在發(fā)送數據異常時,系統(tǒng)就不會異常退出。通過與項目人員溝通,協(xié)助其修改程序。
經修改后,線程退出的現象有所好轉,但依然存在,并且上述三種方法都無法徹底解決目標問題。進一步與項目人員交流發(fā)現,他們的linux程序比我們的測試程序更復雜,線程更多。在出現問題時,也發(fā)現了另一個bond1(外口)在ifconfig中查看,存在dropped包的現象?,F象如下:
程序共有10個子線程,分別是:
子線程1:tcp_recv接收DSP程序剛起來時的握手信號(1個字節(jié)),獲取DSP的IP地址以及端口號,子線程1在完成上述工作后,自動退出線程;
子線程2:multi_recv通過bond1雙冗余的外口接收陣元數據,累計一定數據量后轉發(fā)給子線程3、4、5,由這3個線程分別處理陣元數據;
子線程3:work_dp按格式轉換處理低頻數據,累積到1024*4096 float后通過信號量通知子線程6轉發(fā);
子線程4:work_hp按格式轉換處理高頻數據,累計到768*4096*3 float后通過信號量通知子線程7轉發(fā);
子線程5:work_senor按格式轉換處理處理傳感器數據,累積到512float,和低頻數據拼接在一起,由子線程6一起轉發(fā);
子線程6:tcp_dp_send,收到子線程3的信號量后,通過bond0內網卡雙網冗余轉發(fā)給DSP程序,當返回值sendNum<0時,清空資源,退出程序;
子線程7:tcp_hp_send,收到子線程4的信號量后,通過bond0內網卡雙網冗余轉發(fā)給DSP程序,當返回值sendNum<0時,清空資源,退出程序;
子線程8:監(jiān)控子線程6和子線程7的TCP的Connect State,發(fā)現有TCP連接斷開后重啟線程1、線程6、線程7、等待DSP重連;
子線程9:通過bond1定時上報心跳程序;
子線程10:給數據庫上報丟包信息;
系統(tǒng)工作流程:主線程運行后,將10個子線程創(chuàng)建并起動;其中可能涉及內存與調度/雙網網卡狀態(tài)等問題。
進一步測試發(fā)現,當DSP復位時,其0核和1核網絡同時復位,但線程6,7中判定其TCP斷開并非同時,當問題復現時,線程6的TCP狀態(tài)為斷開,監(jiān)控線程重啟線程1,6,但現場7的TCP未被判定斷開,最終導致了通信錯誤。
監(jiān)控線程未正常工作可能的原因是:
1、線程的切換優(yōu)先級不恰當,導致監(jiān)控線程中判斷線程7中的標志位值未改變;
2、DSP復位的網絡初始化問題導致。
針對上述兩種可能,選擇了修改程序優(yōu)先級以及信號量觸發(fā)監(jiān)控線程的模式來測試;在測試結果中,監(jiān)控線程正常的起到了kill線程并重啟的功能;在另一種解決方式中,采取了如下的方式:當一路TCP斷開時,直接kill線程6與線程7,并重啟線程1,6,7,這是基于DSP(TCP客戶端)兩路會同時重啟或斷開的情景。
關于存在dropped包現象:
在SUSE的kb中可以發(fā)現如下內容:
Beginning with kernel 2.6.37,it has been changed the meaning of dropped packet count.Before,dropped packets was most likely due to an error.Now,the rx_dropped counter shows statistics for dropped frames because of:
從2.6.37內核以后,改變了dropped包的統(tǒng)計方式,其不再是以錯誤包的方式統(tǒng)計,以下情況也會計入dropped包。其值只是做為一種狀態(tài)統(tǒng)計了。

最后一句中說明,在bond主備模式中,備用網卡接到的所有包都會計入dropped。
而redhat和其他發(fā)行對應的發(fā)行版如SUSE、ubuntu等來比,kernel和包都相對版本要低一些,這和其策略有關,未確認穩(wěn)定的,不會加入到當前的發(fā)行版本中。在未單獨升級過kernel的情況下,其只在rhel7中才會有該情況發(fā)生,而且其kb給出了前后統(tǒng)計方式不同的源碼,具體變化在rt_kernel core/dev.c文件中。
因此dropped包原因可能是TCP中斷導致的雙網切換有關。
針對linux的TCP通訊,linux不是一個完全照顧吞吐的系統(tǒng),也不是一個完全照顧響應的系統(tǒng),它是兩者的兼容,是一個軟實時系統(tǒng)。優(yōu)先級和調度策略均會影響Linux的工作狀態(tài),并且在內核在處理錯誤信息時,部分信號可能會直接殺死進程,導致程序異常。根據不同的應用場景與網絡問題,應同時考慮硬件、內核、任務調度等方面來考慮問題。在TCP通信過程中,任務調度會影響TCP的速率和響應,TCP通信異常的信號會導致內核直接殺死進程。