MAC地(dì / de)址的(de)學習是(shì)指使用分組中的(de)源MAC地(dì / de)址進行查表,最後添加或更新到(dào)MAC轉發表中。目的(de)MAC查找是(shì)指使用分組中的(de)目的(de)MAC進行查表,獲得該MAC在(zài)學習中保存的(de)端口号信息。兩個(gè)過程都需要(yào / yāo)對表進行遍曆操作,根據邏輯功能的(de)不(bù)同,其輸入輸出(chū)參數也(yě)不(bù)一樣。二層交換的(de)核心邏輯就(jiù)是(shì)這(zhè)兩個(gè)功能函數。
1)源MAC提取
首先,源MAC地(dì / de)址獲取要(yào / yāo)根據MAC層協議來(lái)解析,從其對應的(de)位置提取相應的(de)數據。其次,源MAC的(de)提取有多種方式,主要(yào / yāo)取決于(yú)對MAC地(dì / de)址的(de)操作方式,如相等比較。由于(yú)MAC地(dì / de)址是(shì)不(bù)規整的(de)數據類型,通常可以(yǐ)使用内存塊的(de)比較方式或拆分成幾部分的(de)方式比較,拆分一般可分爲(wéi / wèi)2+2+2;兩種方式都要(yào / yāo)使用指針傳遞參數。
/*分組源MAC指針獲取*/
&pkt->data[MAC_LEN]/*MAC_LEN宏定義爲(wéi / wèi)6,表示MAC地(dì / de)址占6個(gè)字節*/
/*判斷兩個(gè)MAC地(dì / de)址是(shì)否相等*/
int ether_addr_equal(u8 *addr1,u8 *addr2)
{
u16 *a = (u16 *)addr1;
u16 *b = (u16 *)addr2;
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) != 0;
}
2)學習過程
前面分析過,在(zài)學習過程中并不(bù)清楚原MAC轉發表中是(shì)否存在(zài)原表項,如果先查一次是(shì)否存在(zài),再查一次哪有空位用作存儲,則需要(yào / yāo)做兩次全表遍曆。所以(yǐ),針對MAC學習的(de)處理方式就(jiù)是(shì)不(bù)管有沒有,都當做是(shì)新增的(de)方式處理。若查表不(bù)存在(zài)則存儲在(zài)一個(gè)空白表項位置,若查表存在(zài),則刷新端口信息。
void learn_smac(u8 inport,u8 *smac)
{
int i = 0,j = -1;
u64 zero_mac = 0;/*定義一個(gè)全零MAC地(dì / de)址*/
xprintf("learn_smac->\n");
for(;i<>
{
if(!ether_addr_equal(smac,obx_mac_tbl->row[i].mac))
{
/*MAC轉發表當前i行的(de)MAC地(dì / de)址與輸入參數smac相等*/
if(obx_mac_tbl->row[i].port != inport)
{
/*這(zhè)個(gè)MAC地(dì / de)址發生了(le/liǎo)端口遷移*/
}
obx_mac_tbl->row[i].port = inport;
return;/*學習過程完成,立即返回*/
}
else if(j == -1 && !ether_addr_equal((u8 *)&zero_mac,obx_mac_tbl->row[i].mac))
{
j = i;/*記錄第一個(gè)找到(dào)爲(wéi / wèi)空白表項位置*/
}
}
/*j==-1說(shuō)明既沒有匹配上(shàng)MAC,也(yě)找不(bù)到(dào)空閑位置存儲*/
if(j == -1)
{
xprintf("learn_smac->Table overflow!\n");
return;
}
/*将該MAC存儲到(dào)j的(de)位置*/
memcpy(obx_mac_tbl->row[j].mac,smac,MAC_LEN);
obx_mac_tbl->row[j].port = inport;
xprintf("learn_smac->add new MAC,port:%d,index:%d\n",inport,j);
}
1)目的(de)MAC提取
目的(de)MAC提取與源MAC類似,在(zài)參數傳遞過程中均用指針方式,故其表示方式爲(wéi / wèi):
/*分組目的(de)MAC指針獲取*/
pkt->data/*數組名即爲(wéi / wèi)指針*/
2)查表過程
查表過程就(jiù)是(shì)一個(gè)簡單的(de)全表搜索,找到(dào)的(de)匹配的(de)MAC地(dì / de)址,則返回其學習到(dào)的(de)端口号。若是(shì)沒有找到(dào)匹配的(de)MAC,則需要(yào / yāo)用個(gè)特别的(de)數字(-1)來(lái)區分正常的(de)端口号。
int find_dmac(u8 inport,u8 *dmac)
{
int i = 0,ret = -1;/*匹配不(bù)到(dào)相同MAC,則返回-1*/
for(;i<>
{
if(obx_mac_tbl->row[i].port != inport
&& !ether_addr_equal(dmac,obx_mac_tbl->row[i].mac))
{
ret = obx_mac_tbl->row[i].port;
break;
}
}
xprintf("find_dmac->ret = %d\n",ret);
return ret;
}
1)表的(de)查找
表的(de)查找與表的(de)設計相關,如上(shàng)我們設計的(de)是(shì)一種簡單的(de)數組結構,故也(yě)隻能進行順序查找的(de)方式進行遍曆。這(zhè)種查表方式在(zài)實際應用場景下一般不(bù)會使用,但在(zài)設計原型系統時(shí)卻很方便。順序查表根據表的(de)大(dà)小和(hé / huò)使用條數增加會導緻查表速度越來(lái)越慢,上(shàng)述在(zài)源MAC學習過程中,會順帶把空閑位置也(yě)找出(chū)來(lái),減少一次表的(de)遍曆。那麽查目的(de)MAC時(shí)也(yě)需要(yào / yāo)遍曆一次表,我們是(shì)不(bù)是(shì)也(yě)可以(yǐ)都放在(zài)這(zhè)一次表的(de)遍曆中完成呢?當然是(shì)可以(yǐ)的(de),隻是(shì)這(zhè)樣實現對業務的(de)邏輯理解沒有那麽好,但對表的(de)遍曆隻需要(yào / yāo)一次即可,從執行速度上(shàng)來(lái)說(shuō)确實會提升。
另外,在(zài)對表的(de)高效性處理方面,一般不(bù)會采用全表項匹配或多字段匹配的(de)方法,在(zài)表設計時(shí)會使用一個(gè)有效位字段,通過有效位的(de)簡單比較就(jiù)可确定表項是(shì)否爲(wéi / wèi)空或存在(zài)有效數據。這(zhè)種方法普遍存在(zài)于(yú)硬件邏輯設計中,硬件的(de)查表方式也(yě)多種多樣,通常使用基于(yú)内容可尋址存儲器(CAM)方式查表,既簡單又高效。
2)分組輸出(chū)
二層交換的(de)分組輸出(chū)主要(yào / yāo)根據查目的(de)MAC的(de)結果來(lái)處理,當查詢到(dào)相應的(de)輸出(chū)端口後,即可從指定的(de)端口輸出(chū);當查不(bù)到(dào)該MAC的(de)端口信息時(shí),則隻能通過泛洪的(de)方式轉發,這(zhè)是(shì)在(zài)交換機層面确保數據不(bù)丢包的(de)一種措施,甯可多發包,也(yě)不(bù)丢包。當然,廣播地(dì / de)址也(yě)是(shì)需要(yào / yāo)泛洪的(de),多播地(dì / de)址則需要(yào / yāo)根據其組信息進行端口組發送。下一篇文章我們來(lái)說(shuō)一下分組輸出(chū)的(de)單播和(hé / huò)多播。
歡迎您和(hé / huò)學生們加入FAST開源項目群溝通與探讨,一起體驗不(bù)一樣的(de)系統設計過程。請先加微信号15116127200後邀請入群。

關注FAST開源社區
FAST一一開源、開放、高速、高效、可編程、可定義!軟硬件協同并行處理。