初次接续网络,花了大把时间啃源代码,好在数据组织结构不是很复杂,一路下来还算顺利.
此次写下这篇博客,仅作为本人学习路上的一篇学习笔记
函数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; //返回表项位置 }