Linux内核__netif_receive_skb_core中pt_prev作用及ptype遍历逻辑疑问
Linux内核__netif_receive_skb_core中ptype遍历逻辑的设计意图
在分析Linux内核收包处理函数__netif_receive_skb_core时,会发现它对ptype(协议类型处理结构体)的遍历逻辑比较特殊:
pt_prev = NULL; list_for_each_entry_rcu(ptype, &ptype_all, list) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } // 注:完整代码中此处还有处理最后一个pt_prev的逻辑 // if (pt_prev) // ret = deliver_skb(skb, pt_prev, orig_dev);
很多人会疑惑,为什么不直接用更直观的遍历逻辑:
list_for_each_entry_rcu(ptype, &ptype_all, list) ret = deliver_skb(skb, ptype, orig_dev);
这种“先交付前一个ptype、再保存当前ptype到pt_prev”的设计,核心目的是适配RCU机制下的链表安全遍历,同时避免处理逻辑干扰遍历流程,具体原因有两点:
规避RCU遍历中的节点删除风险:
deliver_skb的调用可能触发协议模块卸载,导致对应的ptype节点从RCU链表中被移除。RCU读临界区允许删除节点,但如果遍历当前节点时就调用deliver_skb,万一该节点被删除,后续的链表迭代可能出现指针失效问题。延迟一个节点处理,先完成当前节点的遍历再处理上一个,能确保遍历过程不受节点删除的干扰——RCU保证已遍历的节点在读临界区结束前不会被释放,处理pt_prev是安全的。防止skb提前释放中断遍历:
deliver_skb内部可能调用kfree_skb释放当前skb,如果遍历过程中直接处理当前节点,skb被释放后,后续遍历访问skb->dev->ptype_all等字段会触发空指针错误。延迟处理能确保先完成整个链表的节点遍历,再逐个处理ptype,即使某个ptype释放了skb,也不会影响已经完成的遍历步骤。
内容的提问来源于stack exchange,提问作者yuanjianpeng
相关产品推荐
相关产品推荐

