有一些图片http://vger.kernel.org/~davem/tcp_output.html
谷歌搜索,tcp_transmit_skb()
这是 tcp 数据路径的关键部分。在他的网站http://vger.kernel.org/~davem/上有一些更有趣的东西
在数据路径的user - tcp
传输部分,有1 个从用户到 skb 的副本skb_copy_to_page
(发送时tcp_sendmsg()
)和0 个副本( do_tcp_sendpages
由 调用tcp_sendpage()
)。需要复制以保留数据备份,以防未交付的段。内核中的 skb 缓冲区可以被克隆,但它们的数据将保留在第一个(原始)skb 中。Sendpage 可以从其他内核部分获取一个页面并保留它以进行备份(我认为有类似 COW 的东西)
调用路径(手动来自 lxr)。发送tcp_push_one
/__tcp_push_pending_frames
tcp_sendmsg() <- sock_sendmsg <- sock_readv_writev <- sock_writev <- do_readv_writev
tcp_sendpage() <- file_send_actor <- do_sendfile
收到tcp_recv_skb()
tcp_recvmsg() <- sock_recvmsg <- sock_readv_writev <- sock_readv <- do_readv_writev
tcp_read_sock() <- ... spliceread for new kernels.. smth sendfile for older
在接收中可以有1 个从内核到用户的副本skb_copy_datagram_iovec
(从 调用tcp_recvmsg
)。对于 tcp_read_sock() 可以有副本。它将调用sk_read_actor
回调函数。如果它对应于文件或内存,它可能(也可能不会)从 DMA 区域复制数据。如果是其他网络,它有一个接收到的数据包的 skb,并且可以就地重用其数据。
对于 udp - 接收 = 1 份副本 - 从 udp_recvmsg 调用的 skb_copy_datagram_iovec。传输 = 1 份副本-- udp_sendmsg -> ip_append_data -> getfrag (似乎是 ip_generic_getfrag 与用户的 1 份副本,但可能是没有页面复制的 smth sendpage/splicelike。)
一般而言,从用户空间发送/接收到用户空间时必须至少有 1 个副本,而在使用零拷贝(惊喜!)和内核空间源/目标缓冲区的数据时必须至少有 0 个副本。在不移动数据包的情况下添加所有标头,启用 DMA 的(所有现代)网卡将从启用 DMA 的地址空间中的任何位置获取数据。对于古老的卡 PIO 是需要的,所以会有一个副本,从内核空间到 PCI/ISA/smthelse I/O 寄存器/内存。
UPD:在从 NIC(但这取决于 nic,我也检查了 8139)到 tcp 堆栈的路径中,还有一个副本:从 rx_ring 到 skb,对于接收也是如此:从 skb 到 tx 缓冲区+1copy。您必须填写ip和tcp header,但是skb是否包含它们或放置它们?