Linux协议栈(2)——发送流程及函数
本章会一步一步的分析,在linux内核中,数据是如何从网络中接收并最后到达应用程序的。
用户数据的发送流程如下图所示,不管是tfp,telnet,http都是类似的。当然我们在使用应用的时候,根本不会关注到这些加头的措施,因为要么是程序要么是内核帮助我们完成了。而我们现在所做的就是层层拨开他们去理解这协议栈的整个过程。发送过程中数据的变化如下:
1.1.1.1 应用层
首先网络应用调用Socket 的API函数 socket ()(该函数定义在/usr/include/sys/socket.h文件中) ,创建一个 socket(函数会调用系统调用 socket() ),并最终调用内核函数的 sock_create (定义在net/socket.c)方法,成功后返回一个socket描述符。
对于TCP,应用接着调用 connect()函数,使得客户端和服务器端通过该 socket 建立一个连接。然后可以调用send函数发出一个 message 给接收端。sock_sendmsg 被调用,调用相应协议的发送函数。
1.1.1.2 传输层
数据到了传输层的处理,以TCP协议为例。TCP主要处理:(1)构造 TCP segment (2)计算 checksum (3)发送回复(ACK)包 (4)滑动窗口(sliding windown)等保证可靠性的操作。
不同的协议针对的发送函数不一样:
TCP调用 tcp_sendmsg 函数。
UDP可以调用 send()/sendto()/sendmsg() 三个 system call 中的任意一个来发送 message,最终都会调用内核中的 udp_sendmsg() 函数。
如果是tcp协议的流程,tcp_sendmsg()的主要工作是把用户层的数据,填充到skb中。然后调用tcp_push_one()来发送,tcp_push_one函数调用tcp_write_xmit()函数,其又将调用发送函数tcp_transmit_skb,所有的SKB都经过该函数进行发送。最后进入到ip_queue_xmit到网络层。因为tcp会进行重传控制,所以有tcp_write_timer函数,进行定时。
udp协议使用udp_sendmsg函数,调用udp_send_skb函数,然后通过ip_append_data进入到网络层。
1.1.1.3 网络层
ip_queue_xmit(skb)会检查skb->dst路由信息。如果没有,就会去选择一个路由。
填充IP包的各个字段,比如版本、包头长度、TOS等。当报文的长度大于mtu,gso的长度不为0就会调用 ip_fragment 进行分片。ip_fragment 函数中,会检查 IP_DF 标志位,如果待分片IP数据包禁止分片,则调用 icmp_send()向发送方发送一个原因为需要分片而设置了不分片标志的目的不可达ICMP报文,并丢弃报文,即设置IP状态为分片失败,释放skb,返回消息过长错误码。
1.1.1.4 链路层
数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。这一层数据的单位称为帧(frame)。从dev_queue_xmit函数开始,位于net/core/dev.c文件中。
注意底层的net_rx、net_tx是在驱动中实现的。
如下图。