2006-02-22

浅谈"watchdog timeout"出现的原因

淺談"watchdog timeout"出現的原因

[閱讀 329 次]

xie_minix

最近有比較多的人談到網卡的」watchdog timeout「問題,究竟是什麼原因造成的,大多數人都把網卡的性能不佳做為問題的根源所在。我認為網卡的性能只是一方面的因素,他還涉及到緩衝的大 小、單位時間內的包的數量、及網卡驅動程序等一系列因素。以下將從源代碼的角度來對他進行分析。

首先,我們看看到底是哪個函數發出了「watchdog timeout」字符串,只要你查一下源代碼不難看出,在各網卡的驅動程序裡的XX_watchdog(XX是各網卡的名稱,如:8139是rl, AMD7990是pcn,Inter是fxp等等)函數發出的。函數比較簡單:
static void rl_watchdog(ifp)
struct ifnet  *ifp;

/*申明ifp是一個ifnet結構,結構存放了該網卡的輸入輸
出的函數指針和一些重要參數,當然也包括rl_watchdog函數的
指針*/

{
struct rl_softc  *sc;
sc = ifp->if_softc;
/*ifnet是softc結構的一個子集,softc包含了更多的該網卡的參數*/

printf("rl%d: watchdog timeout\n", sc->rl_unit);
/*打印出是哪塊網卡出現問題。
sc->rl_unit代表該種網卡的第幾快。我們知道在一個機器裡同樣
的網卡可能有幾塊,當然此參數是由網卡驅動程序的初始化程序
填充*/

ifp->if_oerrors++;
/*累計輸出出現的錯誤包數量(o代表輸出)*/

rl_txeof(sc);
/*這裡是每個驅動程序不同的,此處為8139的,不過我覺得用rl_stop(sc)更好*/

rl_rxeof(sc);
/*這裡也是每個驅動程序不同。我覺得來一個rl_reset(sc)也不錯。*/

rl_init(sc);/*這裡大家都一樣,重新初始芯片。*/

return;
}

好了,到這我們知道是XX_watchdog函數發出了watchdog timeout信息。那麼是誰來調用該函數呢? 我們接著來看另一個函數:if_slowtimo函數,該函數在if.c中。if.c是interface(接口的簡稱), 即系統在啟動過程中初始化時必須調用其中的一些函數。ifinit函數是其中被調用的一個,這個函數很簡單:
void
ifinit()
{
static struct timeout if_slowtim;

timeout_set(&if_slowtim, if_slowtimo, &if_slowtim);
/*簡單的說就是設置一定時器*/

if_slowtimo(&if_slowtim);
/*哈哈,等不急了,先調用了再說*/

}
這樣一來,if_slowtimo就成了一個一定時間內就要執行的一個函數了,此時候,大家也知道了if_slowtimo 的大概功能,無非是定時查看各網卡的發送數據的情況,如果沒發送完成,就給該卡加一個計數器,到計數器達到一定的值時還沒發送出去就調用該卡的 XX_watchdog函數。下面我們來看看if_slowtimo函數。
void

if_slowtimo(arg)

void *arg;

{

struct timeout *to = (struct timeout *)arg;

struct ifnet *ifp;

int s = splimp();
/*在做以下操作的時候必須關中斷*/

TAILQ_FOREACH(ifp, &ifnet, if_list)
{/*搜索每一個接口設備,TAILQ_FOREACH實際上是for(...)*/

 if (ifp->if_timer == 0 || --ifp->if_timer)

  continue;
/*如果是if_timer為0或if_timer減1以後還為真,實際上是對每塊網卡
的計數器if_timer減1後判斷他是否還大於0,小於0就調用watchdog
函數。*/

 if (ifp->if_watchdog) /*不過調用之前看看該卡有沒有watchdog函數*/

  (*ifp->if_watchdog)(ifp);

}

splx(s);

timeout_add(to, hz /
IFNET_SLOWHZ);
/*每次計時器完成後都會清除,你不得不又加上去。hz是計算
機的主頻,就是說調度的間隔時間是和主頻成正比的關係。*/

}

到這裡一切都很明白了,我們只要在驅動程序的輸出包時給定一個值,而輸出函數是一個直到輸出成功才跳出的循環,不成功他就一直重試來輸出此包,而我們上面的程序就會時間一到就給你的值減1,如果減到小於0了,就watchdog timeout。我們還是來看一看程序吧:
static void rl_start(ifp)

struct ifnet  *ifp;

{

struct rl_softc  *sc;

struct mbuf  *m_head = NULL;

sc = ifp->if_softc;

while(RL_CUR_TXMBUF(sc) == NULL) {/* 1:當輸出緩衝區為空時才進行新的輸出*/

 IF_DEQUEUE(&ifp->if_snd, m_head);/*把將要輸出的數據加入到輸出隊列中。*/

 if (m_head == NULL)/* 2:申請內存失敗*/

  break;

 if (rl_encap(sc, m_head))
{/*8139卡的弱智表現在此,多加一個頭部,還要長字節對齊,影響

     到數據必須重新搬遷。花時間啊!*/

  IF_PREPEND(&ifp->if_snd, m_head);

  ifp->if_flags |= IFF_OACTIVE;

  break;

 }

 if (ifp->if_bpf)/* 3:如果包過濾存在就進行過濾*/

  bpf_mtap(ifp, RL_CUR_TXMBUF(sc));

 CSR_WRITE_4(sc, RL_CUR_TXADDR(sc),/* 4:以下為硬件輸出的IO指令*/

     vtophys(mtod(RL_CUR_TXMBUF(sc), caddr_t)));

 CSR_WRITE_4(sc, RL_CUR_TXSTAT(sc),

     RL_TXTHRESH(sc->rl_txthresh) |

     RL_CUR_TXMBUF(sc)->m_pkthdr.len);

 RL_INC(sc->rl_cdata.cur_tx);

}

if (RL_CUR_TXMBUF(sc) !=
NULL)/*如果傳送緩衝不為空,說明數據放到緩衝中已經準備傳了*/

 ifp->if_flags |= IFF_OACTIVE;/*加上正在傳標誌*/

ifp->if_timer = 5;/*設定計數器為5*/

return;

}


static void rl_intr(arg)

{

...  這中間我就不寫了

if ((status & RL_ISR_TX_OK) || (status & RL_ISR_TX_ERR))
 /*如果中斷後狀態寄存器的標識 是成功或出錯,就調用下面的程序。*/

 rl_txeof(sc);

...

}

再看rl_txeof:

static void rl_txeof(sc)

{

...

ifp->if_timer =
0;
/*哈哈,在這清0了,也就是說,只要你不是反覆在那傳,
不管傳輸錯誤和傳輸正確 都不會出現"watchdog timeout"*/

...

}

綜上所述:引起watchdog timeout的主要原因為:1、緩衝區不夠大,前面的沒發完後面的又跟的來了。2、內核的內存分配出現問題,此情況比較少發生。3、卡的質量(在IO時的吞吐量)。如何解決些問題:
首先我們必須查出導致出現該問題的原因,即是這問題中的哪個引起的,我們來修改if.h中定義一全局變量:
u_int8_t myerror; /*意思是出錯的原因代碼,按我列的來吧,1是緩衝區不夠...*/

在函數static void rl_start(ifp)中加入:

static void rl_start(ifp)

struct ifnet *ifp;

{

struct rl_softc *sc;

struct mbuf *m_head = NULL;

sc = ifp->if_softc;

u_int8_t tmperror;

if (RL_CUR_TXMBUF(sc) != NULL) {/*新加,如果是緩衝區不夠問題*/

myerror=1;

}

while(RL_CUR_TXMBUF(sc) == NULL) {

IF_DEQUEUE(&ifp->if_snd, m_head);

if (m_head == NULL)

{

myerror=2; /*內存分配出錯*/

break;

}

if (rl_encap(sc, m_head)) {

IF_PREPEND(&ifp->if_snd, m_head);

ifp->if_flags |= IFF_OACTIVE;

break;

}

if (ifp->if_bpf)

bpf_mtap(ifp, RL_CUR_TXMBUF(sc));

tmperror=myerror;/*在進行寫IO口前先保存前面出錯的原因*/

CSR_WRITE_4(sc, RL_CUR_TXADDR(sc),

vtophys(mtod(RL_CUR_TXMBUF(sc), caddr_t)));

CSR_WRITE_4(sc, RL_CUR_TXSTAT(sc),

RL_TXTHRESH(sc->rl_txthresh) |

RL_CUR_TXMBUF(sc)->m_pkthdr.len);

myerror=tmperror;/*上面兩句沒問題的話再還原前面的出錯原因*/

RL_INC(sc->rl_cdata.cur_tx);

}

if (RL_CUR_TXMBUF(sc) != NULL)

ifp->if_flags |= IFF_OACTIVE;

ifp->if_timer = 5;

return;

}

最後再改一下rl_watchdog中的顯示部分
printf("rl%d: watchdog timeout:error number is %x\n", sc->rl_unit,myerror);
當然這只是我個人的見解,可能有許多不足或沒考慮到的地方,也希望大家能提出更好、更容易的方法。

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.