如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭。
如果对具体实现不是很感兴趣,可直接此部分从四层模型的角度来分析recv实现。
数据链路层当数据包到达机器的物理网卡时会触发一个中断,中断处理程序分配skb_buff数据结构,并将从网卡I/O接收到的数据帧复制到skb_buff缓冲区,并设置skb_buff相应的参数。
然后发出软中断,通知内核接收新的数据帧。进入软中断处理流程,调用net_rx_action函数。进入 netif _receive_skb 处理流程。
netif_receive_skb 根据在全局数组 ptype_all 和 ptype_base 中注册的网络层数据报类型,将数据报发送到不同的网络层协议接收函数(INET域主要是ip_rcv和arp_rcv)。
网络层ip_rcv函数为网络层的入口函数。该函数做的第一件事就是数据校验,然后调用ip_rcv_finish这个函数。
ip_rcv_finish函数会调用ip_route_input函数来更新路由,然后寻找路由,决定消息是发送到本地机器,转发还是丢弃。
如果发送到本机,则调用ip_local_deliver函数,可以进行碎片整理(合并多个包),并调用ip_local_deliver_finish。最后调用下一层接口,包括tcp_v4_rcv(TCP)、udp_rcv(UDP)、icmp_rcv(ICMP)、igmp_rcv(IGMP)。如果需要转发,则进入转发流程,调用dev_queue_xmit,进入链路层处理流程。如果不是发送到本机,应该是转发,调用 ip_forward 进行转发 。
传输层在该层,我们会做一些完整性检查,如果发现问题就丢包。如果是tcp,则调用tcp_v4_do_rcv。
然后sk->sk_state == TCP_ESTABLISHED,调用tcp_rcv_builted,调用 tcp_data_queue 方法将消息放入队列。然后使用 tcp_ofo_queue 方法将消息插入接收到 Queued 。
应用层应用程序调用读取或者 recv 的时候,该调用被映射到 /net/socket.c 中的sys_recv系统调用,然后调用 sock_recvmsg 函数。
TCP 会调用 tcp_recvmsg。该函数从套接字缓冲区复制数据到缓冲区。
上述过程,我们总结下就是: 1、数据帧从外部网络到达网卡 2、网卡把帧DMA到内存Ring Buffer中 3、硬中断通知CPU 4、CPU响应硬中断,简单处理后发憷软中断 5、软中断进程处理软中断,调用网卡驱动注册的pool函数开始收包 6、帧被从Ring Buffer中摘下来,存储到skb中 7、协议层开始处理网络帧,并将处理完成后的数据放入socket的接收缓冲区中
上图为整个网络数据接收的函数调用过程,对月接收端来说,当有数据来的时候,都是通过终端来通知内核,最终通过回调,调用系统函数。下图是send和recv完整的函数调用过程