admin管理员组

文章数量:1029427

virtio

  1. 目的 本文档分析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

  1. 目的 本文档分析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