java中的接口是类吗
786
2022-10-12
Linux内核中VLAN的实现过程(7)-Offload和数据接收
Linux内核中VLAN的实现过程(7)
本节主要关注和解析vlan设备数据接收的实现,代码位于net/8021q/vlan_core.c文件中。如果设备支持gro并使用gro进行vlan卸载加速,则数据接收会先走gro数据处理,合并后送到协议栈走通用vlan接收处理函数。
van卸载和gro数据接收
GRO(Generic Receive Offload),支持对多种协议进行卸载,是在协议栈接收报文时进行减负的一种处理方式,主要原理是在接收端通过把多个相关的报文(比如TCP分段报文)组装成一个大的报文后再传送给协议栈进行处理,因为内核协议栈对报文的处理都是对报文头部进行处理,如果相关的多个报文合并后只有一个报文头,这样就减少了协议栈处理报文个数,加快协议栈对报文的处理速度。
// 注册vlan offload初始化函数 fs_initcall(vlan_offload_init); static int __init vlan_offload_init(void) { unsigned int i; // 遍历需要卸载的vlan协议配置(vlan和qinq协议) for (i = 0; i < ARRAY_SIZE(vlan_packet_offloads); i++) // 注册卸载处理程序 // 将协议卸载处理程序添加到网络堆栈。传递的&proto_offload链接到内核列表中,并且在从内核列表中删除之前可能不会被释放。此调用不会休眠,因此它不能保证正在接收数据包的所有CPU 都会看到新的卸载处理程序(直到下一个接收到的数据包)。 dev_add_offload(&vlan_packet_offloads[i]); return 0; } static struct packet_offload vlan_packet_offloads[] __read_mostly = { { // 定义vlan协议(802.1q)、优先级、回调函数(gro_receive和gro_complete) .type = cpu_to_be16(ETH_P_8021Q), .priority = 10, .callbacks = { // 接收并合并的函数 .gro_receive = vlan_gro_receive, // 合并后的处理函数 .gro_complete = vlan_gro_complete, }, }, { // 定义qinq协议(802.1ad)、优先级、回调函数(gro_receive和gro_complete) .type = cpu_to_be16(ETH_P_8021AD), .priority = 10, .callbacks = { .gro_receive = vlan_gro_receive, .gro_complete = vlan_gro_complete, }, }, }; // 接收并合并的函数 // head: 等待合并的数据包链表头,skb:当前接收到的数据包 // 返回值如果为空,表示报文被合并后不需要现在送入协议栈。如果不为空,表示返回的报文需要立即送入协议栈。 // GRO功能使用skb结构体内私有空间cb[48]来存放gro所用到的一些信息。 static struct sk_buff *vlan_gro_receive(struct list_head *head, struct sk_buff *skb) { const struct packet_offload *ptype; unsigned int hlen, off_vlan; struct sk_buff *pp = NULL; struct vlan_hdr *vhdr; struct sk_buff *p; __be16 type; int flush = 1; // 从skb的gro私有信息中获取vlan偏移 off_vlan = skb_gro_offset(skb); hlen = off_vlan + sizeof(*vhdr); // 根据偏移量找到vlan头 vhdr = skb_gro_header_fast(skb, off_vlan); // 如果线性区内不包含vlan头,就把非线性区的vlan头部分拷贝到线性区,方便以后处理 // 如果skb是线性的,NAPI_GRO_CB(skb)->frag0为NULL,上边根据偏移量找vlan头是找不到的,这时可直接根据skb->data和偏移量找到vlan头 if (skb_gro_header_hard(skb, hlen)) { vhdr = skb_gro_header_slow(skb, hlen, off_vlan); if (unlikely(!vhdr)) goto out; } // vlan上层协议 type = vhdr->h_vlan_encapsulated_proto; // 查找上层协议的卸载加速配置 ptype = gro_find_receive_by_type(type); if (!ptype) goto out; flush = 0; // 遍历head链表上缓存的报文,设置same_flow(p当前位置,head表示链表,list表示链表元素),同一个流上的数据包将来可以合并 list_for_each_entry(p, head, list) { struct vlan_hdr *vhdr2; // 链表中当前包same_flow为0,不是同一个流,跳过 if (!NAPI_GRO_CB(p)->same_flow) continue; // 链表中当前包same_flow为1,进一步判断是否和接收到的数据是用一个流 vhdr2 = (struct vlan_hdr *)(p->data + off_vlan); // 判断vlan1头和vlan2头是否相等,返回0表示相等;如果不相等则表示不是同一个flow,清除same_flow位 if (compare_vlan_header(vhdr, vhdr2)) NAPI_GRO_CB(p)->same_flow = 0; } // 设置网络层要读取的gro数据偏移量 skb_gro_pull(skb, sizeof(*vhdr)); skb_gro_postpull_rcsum(skb, vhdr, sizeof(*vhdr)); // 调用上层协议的gro数据接收函数 pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, ipv6_gro_receive, inet_gro_receive, head, skb); out: // 设置flush标志,如果数据包无法与新的skb合并,则此值非零 skb_gro_flush_final(skb, pp, flush); return pp; } // 合并后的处理函数 // 该函数对合并好的报文进行进一步加工,比如更新校验和。 static int vlan_gro_complete(struct sk_buff *skb, int nhoff) { // vlan头 struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb->data + nhoff); // 上册协议 __be16 type = vhdr->h_vlan_encapsulated_proto; struct packet_offload *ptype; int err = -ENOENT; // 查找上层协议的卸载加速配置 ptype = gro_find_complete_by_type(type); if (ptype) // 调用上层协议的gro完成函数 err = INDIRECT_CALL_INET(ptype->callbacks.gro_complete, ipv6_gro_complete, inet_gro_complete, skb, nhoff + sizeof(*vhdr)); return err; }
协议栈通用vlan数据接收
net/core/dev.c文件中的__netif_receive_skb_core()调用了vlan_do_receive()来处理vlan数据包。
bool vlan_do_receive(struct sk_buff **skbp) { struct sk_buff *skb = *skbp; __be16 vlan_proto = skb->vlan_proto; u16 vlan_id = skb_vlan_tag_get_id(skb); struct net_device *vlan_dev; struct vlan_pcpu_stats *rx_stats; // 根据vlan id查找vlan设备,系统中必须要有与数据包中vlan id相同的vlan设备,否则结束处理 vlan_dev = vlan_find_dev(skb->dev, vlan_proto, vlan_id); if (!vlan_dev) return false; // 检查缓冲区是否是共享的,如果是,则克隆 // 如果缓冲区是共享的,则克隆缓冲区并删除旧副本一个引用,返回具有1个引用的新的缓冲区。 // 如果缓冲区未共享,则返回原始缓冲区。当从中断状态调用或自旋锁保持时,pri必须是GFP_ATOMIC,内存分配失败返回NULL skb = *skbp = skb_share_check(skb, GFP_ATOMIC); if (unlikely(!skb)) return false; // 检查vlan设备是否up if (unlikely(!(vlan_dev->flags & IFF_UP))) { kfree_skb(skb); *skbp = NULL; return false; } // 修改数据包来源设备为vlan设备,完成从宿主网卡到vlan设备的转变 skb->dev = vlan_dev; // VLAN设备可能具有与宿主设备不同的MAC地址,在此情况下物理设备驱动程序会赋值PACKET_OTHERHOST到skb的pkt_type。 // 这时就需要进一步判断数据包目的MAC是否为vlan的MAC地址,如果是,修改pkt_type为PACKET_HOST,表示为发往本机的数据包。 if (unlikely(skb->pkt_type == PACKET_OTHERHOST)) { /* Our lower layer thinks this is not local, let's make sure. * This allows the VLAN to have a different MAC than the * underlying device, and still route correctly. */ if (ether_addr_equal_64bits(eth_hdr(skb)->h_dest, vlan_dev->dev_addr)) skb->pkt_type = PACKET_HOST; } // 如果关闭VLAN_FLAG_REORDER_HDR选项(并且vlan设备不是macvlan设备也不是网桥成员),vlan_do_receive函数会重新把vlan信息插入到skb的payload中 if (!(vlan_dev_priv(vlan_dev)->flags & VLAN_FLAG_REORDER_HDR) && !netif_is_macvlan_port(vlan_dev) && !netif_is_bridge_port(vlan_dev)) { // 当前data指针和mac头之后的偏移量,后面使用此偏移量移动data指针到mac头之后 unsigned int offset = skb->data - skb_mac_header(skb); /* * vlan_insert_tag expect skb->data pointing to mac header. * So change skb->data before calling it and change back to * original position later */ // 移动data指针到mac头之后 skb_push(skb, offset); // 插入vlan tag skb = *skbp = vlan_insert_inner_tag(skb, skb->vlan_proto, skb->vlan_tci, skb->mac_len); if (!skb) return false; // 恢复data指定到vlan头之后 skb_pull(skb, offset + VLAN_HLEN); // 重新设置mac头长度(skb->network_header - skb->mac_header) skb_reset_mac_len(skb); } // 查找vlan设备入口vlan优先级,赋给数据包中的priority字段 skb->priority = vlan_get_ingress_priority(vlan_dev, skb->vlan_tci); // 清除硬件加速的vlan信息(因为后面用不到了) __vlan_hwaccel_clear_tag(skb); // 获取vlan设备每个cpu的rx统计信息 rx_stats = this_cpu_ptr(vlan_dev_priv(vlan_dev)->vlan_pcpu_stats); // 更新接收包数和字节数 u64_stats_update_begin(&rx_stats->syncp); rx_stats->rx_packets++; rx_stats->rx_bytes += skb->len; if (skb->pkt_type == PACKET_MULTICAST) rx_stats->rx_multicast++; u64_stats_update_end(&rx_stats->syncp); return true; }
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~