admin管理员组文章数量:1029427
virtio
- 目的 本文档分析vrtio-net内核驱动中三个收包方式(recv_small, recf_big, recv_mergeable)实现细节,根据其实现区别,确定三种收包方式下硬件处理包时需要实现的逻辑.
virtio-net驱动收包路径分析: virtio-net驱动收包流程中,这里以napi收包为例(virtio-net驱动不支持直接中断收包),函数调用为: 1).填充收包buf(根据feature 选择不同的add_recvbuf):
2).vq中断触发napi poll函数,进行收包动作(根据feature选择receive函数):
3) 函数选择取决于vi->mergeable 以及vi->big_packet 字段:
字段初始化在virito-net协商feature时设置:
即如果设备支持mergeable_rx_bufs feature,则add_recvbuf和receive_buf均使用mergebale版本. 否则如果设备支持任意接收方向分片合并卸载功能,则使能big_packet字段,add_recvbuf和receive_buf均使用big_packet版本. 否则则使用small版本. 兼容性排序为: mergeable > big_packet > small 3.small路径分析: 1)add_recvbuf_small: small路径会向rq自己管理的内存页中申请1920B(以x86系统为例)内存并使用rq的sg指定从padding后开始,hdr_len+1518B,然后将单个sg提交到rq中去:
其申请的内存布局如下:
2)receive_small: small的receive函数使用build_skb函数直接复用add_recv_buf_small中申请的buf创建skb,并将virtio_net_hdr写到私有协议字段cb中去:
3)硬件角度的small路径: 对于硬件而言,提交到rq中的recvbuf大小为1530B,其中前12B需要构建virt_net头部,以太网数据包可用缓冲区为1518B,此时硬件工作模式类似mtu为1518的普通网卡. 4. big_packet路径分析: 1)add_recvbuf_big: big路径下,先初始化[MAX_SKB_FRAGS + 2]个sg,共18个sg,后16个sg每个sg都申请一个单独的内存页,然后为前两个sg申请一个内存页,其中sg[0]大小为virtnet_hdr大小(12),sg[1]则从 sizeof(struct padded_vnet_hdr)偏移开始(长度为16,将hdr补齐4位得到),长度为剩余所有的内存页:其中内存页通过page->private 链接:
此时内存页布局为:
2)receive_big函数 receive_big路径调用了page_to_skb函数,然后返回了skb,我们关注该函数:
该函数中涉及到receve_big的路径如下: 1))如果收到一个小包,(包够小而且为buf申请的内存页空间减去包长,剩余部分足够容纳skb_info)则使用build_skb函数直接复用page[0],创建和配置skb,并且将剩余page释放:
2))如果收到一个大包(包横跨多个page),则先创建一个容量为128B的skb,
然后遍历len长度的page,将page作为frage加入到skb_info(skb)->nr_frags[i]中,然后释放未使用的page:
此时skb的结构为(以15140B包为例):
3). 硬件视角下的big路径: 对于硬件而言.加入rq的缓冲区为17个链接描述符,总大小为65536+ 4096个字节,其中前16个字节放virtio_hdr_padded结构体:
剩余65536+4080字节放整个数据包,此时网卡工作类似mtu为65536+4080的网卡. 4)注意点 : big路径下需要填充18个描述符填满整个vrting(大小为2的幂次方),不能完全填满,在剩余描述符不足够时,会跳出add_recvbuf_big函数返回-nospec. 5)效率分析: 采用该种方法收包,如果在较多小包情况下,不能充分利用申请的page资源以及rq的描述符资源. 5. mergeable 路径: 1).add_recvbuf_mergeable: mergeable路径首先获取rq的平均收包长度,然后从rq的page_frag中申请平均收包长度的内存资源,然后初始化一个rq的sg,并将其提交到rq的描述符中:
平均长度的计算为:
即一个64位对齐的,最小为min_buf_len + hdr_len 最大为PAGE_SIZE大小的包长平均数(加上virnet_hdr) 注意:在使用virtqueue_add_inbuf_ctx提交sg时,turesize(即当前buf可用的内存长度会写入到ctx中以便于receive_mergeable函数使用),因为这个长度会在设备写回描述符时被覆盖,而receive函数需要该长度来确认是否可以复用该段内存构建skb,即尾部空间是否足够容纳skb_share_info.见4.2.1中page_to_skb复用page创建skb的逻辑,
相比较small或者big路径,mergeable路径下的buf可用长度是不确定的,所以需要额外的上下文信息来提供. 2).receive_mergeable:
首先获取virnet_hdr的num_buffer确认包分片情况.
然后进入page_to_skb函数,将第一个buf构建成skb,这里分为需要分片和不需要两种情况: 1)).不分片: 如果当前buf的内存页资源足够构建skb,直接复用page构建skb,否则创建一个128B的skb,存储virnet_hdr,尝试复制剩余部分到skb中,如果skb不能容纳剩余长度,将剩余部分通过skb_info_frag[i]绑定给skb,统计包长后并返回skb:
2))有分片情况: 如果在有分片情况下,page_to_skb中,buf内存资源必然不足够构建skb,则走后一个路径,即创建一个128B的skb,将剩余部分通过skb_info_frag[i]绑定给skb,返回该skb并标记为head_skb. 然后根据num_buf,依次获取剩下的buf,并将其以skb_shareinfo_frag的形式连接到skb上,如果当前buf能够合并,将其合并到已有的skb_shareinfo_frag中, 如果head-skb的num_skb_frags达到16, 则新建一个skb,让其以head-skb的shareinfo中frag-list字段链接到head-skb,如果需要新建一个以上的skb,从第三个skb起,skb通过skb->next字段链接到之前的skb上.假设有68个分片,则skb的状态为:
完成skb创建后,统计此次收包总包长,将其加入mrg_avg_pkt_len计算mergeable收包的平均长度,该值在add_recvbuf_mergeable添加buf时作为buf len的参考值. 3)硬件视角下的mergeable 路径: 对于硬件而言,mergerable提供的描述符为非链式的,大小有变化的多个片段.比如64个1500B,64个1700B字节的描述符,然后是64个1600B诸如此类,那么对硬件而言,如果要使用这种描述符发包,因为需要在使用的virtnet_hdr中填写num_buffers:
所以需要在使用描述符时,计算包长和使用长度的关系,以计算该值,然后将其填入virtnet_hdr,然后通过dma将其发送出去,流程见如下流程图:
4)mergeable总结: 该种收包方式相比较于big_packet简单收包逻辑来说,避免了小包情况下,对描述符和内存页的浪费,同时也使驱动的逻辑复杂化,更适用于资源有限的情况下,对于大包占大多数或者追求低时延的场景下,使用big_packet模式能带来更好性能.
6.三种收包模式对比分析:
Small | big_packet | mergeable | |
---|---|---|---|
virtinet头部填充长度(所有填充在申请内存和提交描述符过程中处理,硬件只需要按照描述符长度填充hdr和packet即可) | 12B (virtnet_hdr) | 12B(virtnet_hdr)(有4字节的头部padding) | 12B (virtnet_hdr) |
IP头4字节对齐? | 是 | 否 | 否 |
Allco_Buf总长度 | 1920B(padding+virtnet_hdr+packet+skb_share_inifo+skb_align) | 17*pages = (65536+4096)B(page0 :virtnet_hdr+ 4B hdr _padding + packet)page []1-16] :packet | 收包长度的平均值,最小值为1530B,最大为4084B(page_size - virtnet_hdr) |
内存管理形式 | Rq->alloc_frag | Rq->pages | Rq->alloc_frags |
提交到描述符的长度 | 1530B(virtnet_hdr+packet) | (65536+4080)B减去virtnet_hdr 4Bpadding | 收包长度的平均值 |
Desc | 单个 | 18个链式描述符:desc[0]: vnet_hdrdesc[1]: 4080B datadesc[2-17]:65536BData | 单个 |
大包能力 | 无(mtu1518) | 具备(最大65536B)该值由驱动定义,之所以申请一个(65536+4096B的buf,是为了设备能将超过size的大包送上了,驱动能够感知设备broken状态. | 具备,大小驱动未限制,理论上可以收(queue_size * 收包平均长度) 大小的包,实际应用需要参考协议实现。 |
硬件处理方式 | 使用单个描述符,超出len则丢弃 | 使用链式描述符,填写len后,使用描述符. | 使用单个描述符,如果空闲描述符的len总长超过包长,使用描述符发包,否则notify vq,重新尝试发包/ |
驱动构建skb方法 | 复用buf(alloc buf时考虑了skb的share_info字段) | 如果首个buf足够在基础上构建skb,复用buf,否则构建一个128Bskb存储头部,然后将当前page剩余部分以及链接的其他page以skb的share_info的frags[i]链接到首个skb上 | 如果首个buf足够在基础上构建skb,复用buf,否则构建一个128Bskb存储头部,然后将当前page剩余部分以share_info的frags[i]链接到首个skb上,然后编译num_bufs - 1个描述符,将剩余描述符同样以share_info的frags[i]链接到首个skb上,如果首个skb分片数量不够使用,则使用skb->frag_list链接新建的skb,如果仍需要新的skb,将新的skb通过skb->next链接后续的skb |
virtio
- 目的 本文档分析vrtio-net内核驱动中三个收包方式(recv_small, recf_big, recv_mergeable)实现细节,根据其实现区别,确定三种收包方式下硬件处理包时需要实现的逻辑.
virtio-net驱动收包路径分析: virtio-net驱动收包流程中,这里以napi收包为例(virtio-net驱动不支持直接中断收包),函数调用为: 1).填充收包buf(根据feature 选择不同的add_recvbuf):
2).vq中断触发napi poll函数,进行收包动作(根据feature选择receive函数):
3) 函数选择取决于vi->mergeable 以及vi->big_packet 字段:
字段初始化在virito-net协商feature时设置:
即如果设备支持mergeable_rx_bufs feature,则add_recvbuf和receive_buf均使用mergebale版本. 否则如果设备支持任意接收方向分片合并卸载功能,则使能big_packet字段,add_recvbuf和receive_buf均使用big_packet版本. 否则则使用small版本. 兼容性排序为: mergeable > big_packet > small 3.small路径分析: 1)add_recvbuf_small: small路径会向rq自己管理的内存页中申请1920B(以x86系统为例)内存并使用rq的sg指定从padding后开始,hdr_len+1518B,然后将单个sg提交到rq中去:
其申请的内存布局如下:
2)receive_small: small的receive函数使用build_skb函数直接复用add_recv_buf_small中申请的buf创建skb,并将virtio_net_hdr写到私有协议字段cb中去:
3)硬件角度的small路径: 对于硬件而言,提交到rq中的recvbuf大小为1530B,其中前12B需要构建virt_net头部,以太网数据包可用缓冲区为1518B,此时硬件工作模式类似mtu为1518的普通网卡. 4. big_packet路径分析: 1)add_recvbuf_big: big路径下,先初始化[MAX_SKB_FRAGS + 2]个sg,共18个sg,后16个sg每个sg都申请一个单独的内存页,然后为前两个sg申请一个内存页,其中sg[0]大小为virtnet_hdr大小(12),sg[1]则从 sizeof(struct padded_vnet_hdr)偏移开始(长度为16,将hdr补齐4位得到),长度为剩余所有的内存页:其中内存页通过page->private 链接:
此时内存页布局为:
2)receive_big函数 receive_big路径调用了page_to_skb函数,然后返回了skb,我们关注该函数:
该函数中涉及到receve_big的路径如下: 1))如果收到一个小包,(包够小而且为buf申请的内存页空间减去包长,剩余部分足够容纳skb_info)则使用build_skb函数直接复用page[0],创建和配置skb,并且将剩余page释放:
2))如果收到一个大包(包横跨多个page),则先创建一个容量为128B的skb,
然后遍历len长度的page,将page作为frage加入到skb_info(skb)->nr_frags[i]中,然后释放未使用的page:
此时skb的结构为(以15140B包为例):
3). 硬件视角下的big路径: 对于硬件而言.加入rq的缓冲区为17个链接描述符,总大小为65536+ 4096个字节,其中前16个字节放virtio_hdr_padded结构体:
剩余65536+4080字节放整个数据包,此时网卡工作类似mtu为65536+4080的网卡. 4)注意点 : big路径下需要填充18个描述符填满整个vrting(大小为2的幂次方),不能完全填满,在剩余描述符不足够时,会跳出add_recvbuf_big函数返回-nospec. 5)效率分析: 采用该种方法收包,如果在较多小包情况下,不能充分利用申请的page资源以及rq的描述符资源. 5. mergeable 路径: 1).add_recvbuf_mergeable: mergeable路径首先获取rq的平均收包长度,然后从rq的page_frag中申请平均收包长度的内存资源,然后初始化一个rq的sg,并将其提交到rq的描述符中:
平均长度的计算为:
即一个64位对齐的,最小为min_buf_len + hdr_len 最大为PAGE_SIZE大小的包长平均数(加上virnet_hdr) 注意:在使用virtqueue_add_inbuf_ctx提交sg时,turesize(即当前buf可用的内存长度会写入到ctx中以便于receive_mergeable函数使用),因为这个长度会在设备写回描述符时被覆盖,而receive函数需要该长度来确认是否可以复用该段内存构建skb,即尾部空间是否足够容纳skb_share_info.见4.2.1中page_to_skb复用page创建skb的逻辑,
相比较small或者big路径,mergeable路径下的buf可用长度是不确定的,所以需要额外的上下文信息来提供. 2).receive_mergeable:
首先获取virnet_hdr的num_buffer确认包分片情况.
然后进入page_to_skb函数,将第一个buf构建成skb,这里分为需要分片和不需要两种情况: 1)).不分片: 如果当前buf的内存页资源足够构建skb,直接复用page构建skb,否则创建一个128B的skb,存储virnet_hdr,尝试复制剩余部分到skb中,如果skb不能容纳剩余长度,将剩余部分通过skb_info_frag[i]绑定给skb,统计包长后并返回skb:
2))有分片情况: 如果在有分片情况下,page_to_skb中,buf内存资源必然不足够构建skb,则走后一个路径,即创建一个128B的skb,将剩余部分通过skb_info_frag[i]绑定给skb,返回该skb并标记为head_skb. 然后根据num_buf,依次获取剩下的buf,并将其以skb_shareinfo_frag的形式连接到skb上,如果当前buf能够合并,将其合并到已有的skb_shareinfo_frag中, 如果head-skb的num_skb_frags达到16, 则新建一个skb,让其以head-skb的shareinfo中frag-list字段链接到head-skb,如果需要新建一个以上的skb,从第三个skb起,skb通过skb->next字段链接到之前的skb上.假设有68个分片,则skb的状态为:
完成skb创建后,统计此次收包总包长,将其加入mrg_avg_pkt_len计算mergeable收包的平均长度,该值在add_recvbuf_mergeable添加buf时作为buf len的参考值. 3)硬件视角下的mergeable 路径: 对于硬件而言,mergerable提供的描述符为非链式的,大小有变化的多个片段.比如64个1500B,64个1700B字节的描述符,然后是64个1600B诸如此类,那么对硬件而言,如果要使用这种描述符发包,因为需要在使用的virtnet_hdr中填写num_buffers:
所以需要在使用描述符时,计算包长和使用长度的关系,以计算该值,然后将其填入virtnet_hdr,然后通过dma将其发送出去,流程见如下流程图:
4)mergeable总结: 该种收包方式相比较于big_packet简单收包逻辑来说,避免了小包情况下,对描述符和内存页的浪费,同时也使驱动的逻辑复杂化,更适用于资源有限的情况下,对于大包占大多数或者追求低时延的场景下,使用big_packet模式能带来更好性能.
6.三种收包模式对比分析:
Small | big_packet | mergeable | |
---|---|---|---|
virtinet头部填充长度(所有填充在申请内存和提交描述符过程中处理,硬件只需要按照描述符长度填充hdr和packet即可) | 12B (virtnet_hdr) | 12B(virtnet_hdr)(有4字节的头部padding) | 12B (virtnet_hdr) |
IP头4字节对齐? | 是 | 否 | 否 |
Allco_Buf总长度 | 1920B(padding+virtnet_hdr+packet+skb_share_inifo+skb_align) | 17*pages = (65536+4096)B(page0 :virtnet_hdr+ 4B hdr _padding + packet)page []1-16] :packet | 收包长度的平均值,最小值为1530B,最大为4084B(page_size - virtnet_hdr) |
内存管理形式 | Rq->alloc_frag | Rq->pages | Rq->alloc_frags |
提交到描述符的长度 | 1530B(virtnet_hdr+packet) | (65536+4080)B减去virtnet_hdr 4Bpadding | 收包长度的平均值 |
Desc | 单个 | 18个链式描述符:desc[0]: vnet_hdrdesc[1]: 4080B datadesc[2-17]:65536BData | 单个 |
大包能力 | 无(mtu1518) | 具备(最大65536B)该值由驱动定义,之所以申请一个(65536+4096B的buf,是为了设备能将超过size的大包送上了,驱动能够感知设备broken状态. | 具备,大小驱动未限制,理论上可以收(queue_size * 收包平均长度) 大小的包,实际应用需要参考协议实现。 |
硬件处理方式 | 使用单个描述符,超出len则丢弃 | 使用链式描述符,填写len后,使用描述符. | 使用单个描述符,如果空闲描述符的len总长超过包长,使用描述符发包,否则notify vq,重新尝试发包/ |
驱动构建skb方法 | 复用buf(alloc buf时考虑了skb的share_info字段) | 如果首个buf足够在基础上构建skb,复用buf,否则构建一个128Bskb存储头部,然后将当前page剩余部分以及链接的其他page以skb的share_info的frags[i]链接到首个skb上 | 如果首个buf足够在基础上构建skb,复用buf,否则构建一个128Bskb存储头部,然后将当前page剩余部分以share_info的frags[i]链接到首个skb上,然后编译num_bufs - 1个描述符,将剩余描述符同样以share_info的frags[i]链接到首个skb上,如果首个skb分片数量不够使用,则使用skb->frag_list链接新建的skb,如果仍需要新的skb,将新的skb通过skb->next链接后续的skb |
本文标签: virtio
版权声明:本文标题:virtio 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1747585430a2182796.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论