有一些图片http://vger.kernel.org/~davem/tcp_output.html
用 Google 搜索 tcp_transmit_skb() 这是 tcp 数据路径的关键部分。他的网站上有一些更有趣的东西http://vger.kernel.org/~davem/
在user - tcp transmit 部分数据路径中有 1 份 从用户到带有skb_copy_to_page 的 skb(当通过 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
在 receive 中,可以有 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 是否包含它们或放置它们?