初次接续网络,花了大把时间啃源代码,好在数据组织结构不是很复杂,一路下来还算顺利.
此次写下这篇博客,仅作为本人学习路上的一篇学习笔记
函数find_entry的本质是做一个查找,对缓存表进行顺序的查找,可以分为3个步骤
第一步:查找整个缓存表,找出和目的IP地址匹配的表项(该表项一定处于pending或stable状态),若找到,则返回位置引索,找不到,进行下一步
第二步:说明表中没有相关表项,需要创建一个新表项,这时要查找第一个状态为empty的表项,若找到,则返回位置引索,若找不到,进行下一步
第三步:到这里,说明所有的表项都被使用了,如果调用函数的flags参数被设置为ETHARP_TRY_HARD,那么这一步必须执行,即找缓存表中最合适的表项,删除掉,给新表项使用
内核使用一次查找便可以完成上述三步工作,过程中使用了多个局部变量,分别记录存在时间最长的各状态表项
内核选择合适的引索顺序为:(1)empty状态表项的引索
(2)存在时间最长的stable状态表项
(3)存在时间最长且无数据缓冲的pending表项
(4)存在时间最长且有数据缓冲的pending表项
显然处于pending状态且有数据缓冲的表项被看作是最重要的
/*************************************************************************************************************
* 函数功能 :为ipaddr寻找一个匹配的表项或者建立一个新的表项
* 参数ipaddr:在ARP缓存表中要查找的或建立的ip地址
* 参数flags :是否硬性建立ARP表项
************************************************************************************************************/
static s8_t
#if LWIP_NETIF_HWADDRHINT
find_entry(struct ip_addr *ipaddr, u8_t flags, struct netif *netif)
#else /* LWIP_NETIF_HWADDRHINT */
find_entry(struct ip_addr *ipaddr, u8_t flags)
#endif /* LWIP_NETIF_HWADDRHINT */
{
s8_t old_pending = ARP_TABLE_SIZE, old_stable = ARP_TABLE_SIZE;
s8_t empty = ARP_TABLE_SIZE;
u8_t i = 0, age_pending = 0, age_stable = 0; //记录pending,stable表项的存在时间
#if ARP_QUEUEING
s8_t old_queue = ARP_TABLE_SIZE; //记录存在时间最长且缓冲队列不为空的pending表项
u8_t age_queue = 0; //old_queue表项的存在时间
#endif
//首先检测上次访问这个函数所保存的IP地址是否与此次一致,如果是,可以快速返回引索
if (ipaddr) //IP地址非空
{
#if LWIP_NETIF_HWADDRHINT //这条宏被定义为0
if ((netif != NULL) && (netif->addr_hint != NULL))
{
/* per-pcb cached entry was given */
u8_t per_pcb_cache = *(netif->addr_hint);
if ((per_pcb_cache < ARP_TABLE_SIZE) && arp_table[per_pcb_cache].state == ETHARP_STATE_STABLE)
{
/* the per-pcb-cached entry is stable */
if (ip_addr_cmp(ipaddr, &arp_table[per_pcb_cache].ipaddr))
{
/* per-pcb cached entry was the right one! */
ETHARP_STATS_INC(etharp.cachehit);
return per_pcb_cache;
}
}
}
#else /* #if LWIP_NETIF_HWADDRHINT */
//etharp_cached_entry保存了上次返回的引索
if (arp_table[etharp_cached_entry].state == ETHARP_STATE_STABLE) //检测上次建立的ARP表项是否还有效
{
if (ip_addr_cmp(ipaddr, &arp_table[etharp_cached_entry].ipaddr)) //检测上次的IP地址与此次是否一致
{
//要找的就是这个IP
ETHARP_STATS_INC(etharp.cachehit);
return etharp_cached_entry; //返回引索
}
}
#endif /* #if LWIP_NETIF_HWADDRHINT */
}
/************************************************************
* 以下代码要完成三个工作:
* a) 在缓存表中查找,并记录候选表项
* b) 选择候选表项
* c) 创建新表项
************************************************************
* 单一查找,记录以下信息
* 1) 第一个空表项的位置(如果存在)
* 2) 存在时间最长的stable表项 (如果存在)
* 3) 存在时间最长且缓冲队列为空的pending表项 (如果存在)
* 4) 存在时间最长且缓冲队列不为空的pending表项 (如果存在)
* 5) 查找与IP匹配的表项,不管该表项是pending还是stable状态
* 直到以上5项工作都完成,或者遍历了整个缓存表
************************************************************/
for (i = 0; i < ARP_TABLE_SIZE; ++i)
{
//这个if只会进入一次,当发现第一个空表项时进入,并记录第一个空表项的位置
if ((empty == ARP_TABLE_SIZE) && (arp_table[i].state == ETHARP_STATE_EMPTY))
{
LWIP_DEBUGF(ETHARP_DEBUG, ("find_entry: found empty entry %"U16_F"\n", (u16_t)i));
empty = i; //记录第一个空表项的位置
}
else if (arp_table[i].state == ETHARP_STATE_PENDING) //若表项为pending状态
{
if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) //检查IP是否匹配,匹配的话就直接返回引索
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: found matching pending entry %"U16_F"\n", (u16_t)i));
#if LWIP_NETIF_HWADDRHINT
NETIF_SET_HINT(netif, i);
#else /* #if LWIP_NETIF_HWADDRHINT */
etharp_cached_entry = i; //记录本次返回的位置引索
#endif /* #if LWIP_NETIF_HWADDRHINT */
return i; //匹配,返回引索
#if ARP_QUEUEING
}
else if (arp_table[i].q != NULL) //如果IP不匹配且缓冲队列不为空
{
if (arp_table[i].ctime >= age_queue) //如果该表项存在时间比old_queue记录的更长
{
old_queue = i; //更新old_queue
age_queue = arp_table[i].ctime; //更新age_queue
} //注:此处结合前面的循环,可找到缓冲队列不为空且存在时间最长的pending表项
#endif
}
else //如果IP不匹配且缓冲队列为空
{
if (arp_table[i].ctime >= age_pending) //如果该表项存在时间比old_pending记录的更长
{
old_pending = i; //更新old_pending
age_pending = arp_table[i].ctime; //更新age_pending
} //注:此处结合前面的循环,可找出缓冲队列为空且存在时间最长的pending表项
}
}
else if (arp_table[i].state == ETHARP_STATE_STABLE) //当前表项为stable状态
{
if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) //检查IP是否匹配,匹配的话就直接返回引索
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: found matching stable entry %"U16_F"\n", (u16_t)i));
#if LWIP_NETIF_HWADDRHINT
NETIF_SET_HINT(netif, i);
#else /* #if LWIP_NETIF_HWADDRHINT */
etharp_cached_entry = i;
#endif /* #if LWIP_NETIF_HWADDRHINT */
return i; //匹配,返回引索
} //查找存在时间最长的stable entry
else if (arp_table[i].ctime >= age_stable) //若IP不匹配且存在时间比age_stable记录的更长
{
old_stable = i; //更新old_stable
age_stable = arp_table[i].ctime; //更新age_stable
} //注:此处结合前面的循环,可找出存在时间最长的stable表项
}
}
//到这里函数未返回,则说明没有找到匹配的表项,需要创建一个新的表项
//如果没有找到空表项,且不允许回收表项,那就返回错误
if (((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_TRY_HARD) == 0))
|| ((flags & ETHARP_FIND_ONLY) != 0))
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: no empty entry found and not allowed to recycle\n"));
return (s8_t)ERR_MEM; //返回错误
}
/**********************************************************
* 选择对系统破坏最小的表项进行回收
* 1) 空表项
* 2) 存在时间最长的stable表项
* 3) 存在时间最长且缓冲队列为空的pending表项
* 4) 存在时间最长且缓冲队列挂载了数据包的pending表项
*
* 注:以下操作建立在ETHARP_TRY_HARD设置为硬性建表的基础上
**********************************************************/
//还存在空表项,创建一个新表项
if (empty < ARP_TABLE_SIZE)
{
i = empty;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting empty entry %"U16_F"\n", (u16_t)i));
}
//没有空表项,回收存在时间最长的stable表项
else if (old_stable < ARP_TABLE_SIZE)
{
i = old_stable; //记录表项位置
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting oldest stable entry %"U16_F"\n", (u16_t)i));
#if ARP_QUEUEING
//如果该表项等待队列中没有数据包,则不打印信息。如果队列中存在数据包,则打印一条错误提示信息
LWIP_ASSERT("arp_table[i].q == NULL", arp_table[i].q == NULL);
#endif
}
//选择回收stable表项失败,回收存在时间最长且没有数据包缓冲的penging状态表项
else if (old_pending < ARP_TABLE_SIZE)
{
i = old_pending; //记录表项位置
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting oldest pending entry %"U16_F" (without queue)\n", (u16_t)i));
#if ARP_QUEUEING
}
//选择回收stable和无缓冲的pending表项均失败,回收存在时间最长且有数据包挂载的pending状态表项
else if (old_queue < ARP_TABLE_SIZE)
{
i = old_queue; //记录表项位置
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("find_entry: selecting oldest pending entry %"U16_F", freeing packet queue %p\n", (u16_t)i, (void *)(arp_table[i].q)));
free_etharp_q(arp_table[i].q); //首先释放等待队列中的数据包
arp_table[i].q = NULL; //队列指针清空
#endif
//没有空表项且没有可回收的表项
}
else
{
return (s8_t)ERR_MEM; //返回错误
}
LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
if (arp_table[i].state != ETHARP_STATE_EMPTY) //如果选择的表项不是空表项,则进行以下处理
{
snmp_delete_arpidx_tree(arp_table[i].netif, &arp_table[i].ipaddr);
}
//回收表项,并将该表项状态置位空
arp_table[i].state = ETHARP_STATE_EMPTY;
//到这里,新表项已经创建成功
if (ipaddr != NULL)
{
ip_addr_set(&arp_table[i].ipaddr, ipaddr); //设置IP地址
}
arp_table[i].ctime = 0; //ctime清零
#if LWIP_NETIF_HWADDRHINT
NETIF_SET_HINT(netif, i);
#else /* #if LWIP_NETIF_HWADDRHINT */
etharp_cached_entry = i; //记录本次返回的位置引索
#endif /* #if LWIP_NETIF_HWADDRHINT */
return (err_t)i; //返回表项位置
}