Linux协议栈(3)——接收流程及函数
本章来看下,数据是如何从网络中接收并最后到达应用程序的。
网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息。
1.1.1.1 链路层
包到达机器的物理网卡时候触发一个中断,并将通过DMA传送到位于 linux kernel 内存中的rx_ring。
中断处理程序分配 skb_buff 数据结构,并将接收到的数据帧从网络适配器I/O端口拷贝到skb_buff 缓冲区中,并设置 skb_buff 相应的参数,这些参数将被上层的网络协议使用,例如skb->protocol;
然后发出一个软中断(NET_RX_SOFTIRQ,该变量定义在include/linux/interrupt.h 文件中),通知内核接收到新的数据帧。
进入软中断处理流程,调用 net_rx_action 函数。包从 rx_ring 中被删除,进入 netif _receive_skb 处理流程。
netif_receive_skb根据注册在全局数组 ptype_all 和 ptype_base 里的网络层数据报类型,把数据报递交给不同的网络层协议的接收函数(INET域中主要是ip_rcv和arp_rcv)。
1.1.1.2 网络层
网络IP层的入口函数在ip_rcv函数。
ip_rcv函数调用第三层协议的接收函数处理该skb包,进入第三层网络层处理。
该函数首先会做包括checksum在内的各种检查,如果需要的话会做 IP defragment(分片合并),最终到达 ip_rcv_finish 函数。
ip_rcv_finish 函数会调用ip_router_input函数,进入路由处理环节。会调用 ip_route_input 来更新路由,然后查找 route,决定该会被发到本机还是会被转发还是丢弃:
如果发到本机的话,调用 ip_local_deliver 函数,可能会做 de-fragment(合并多个包),并调用ip_local_deliver_finish。最后调用下一层接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。对于 TCP 来说,函数 tcp_v4_rcv 函数会被调用,从而处理流程进入 TCP 栈。
如果需要转发,则进入转发流程,调用 dev_queue_xmit,进入链路层处理流程。
如果不是发送到本机的话就要进行转发,则调用ip_forward转发。
如果是igmp管理数据包,则调用ip_mr_input函数进行组播路由的查找。
1.1.1.3 传输层
传输层 TCP 处理入口在tcp_v4_rcv函数(位于 linux/net/ipv4/tcp_ipv4.c 文件中),首先会做一些完整性检查,发现问题直接将包丢弃。
如果是tcp,则调用tcp_v4_do_rcv。
然后sk->sk_state == TCP_ESTABLISHED,调用tcp_rcv_established。
调用tcp_data_queue方法将报文放入队列中。然后用tcp_ofo_queue方法报文插入receive队列的。
1.1.1.4 应用层
应用调用 read 或者 recvfrom 时,该调用会被映射为/net/socket.c 中的 sys_recv 系统调用,并被转化为 sys_recvfrom 调用,然后调用 sock_recgmsg 函数。
对于 INET 类型的 socket,/net/ipv4/af_inet.c 中的 inet_recvmsg 方法会被调用,它会调用相关协议的数据接收方法。
TCP 会调用 tcp_recvmsg。该函数从 socket buffer 中拷贝数据到buffer。
UDP从用户空间可以调用三个recv()/recvfrom()/recvmsg() 中的任意一个来接收 UDP 包,这些系统调用最终都会调用内核中的 udp_recvmsg 方法。
如下图: