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