Linux网络系统

Posted by Run-dream Blog on June 11, 2021

网络分层

  • OSI 的标准七层模型

  • 业界标准的 TCP/IP 模型 Linux实际上使用的是这个

网络分层

二层到四层都是在 Linux 内核里面处理的,应用层例如浏览器、Nginx、Tomcat 都是用户态的。

内核里面对于网络包的处理是不区分应用的。

socket不属于任何一个网络分层,它是操作系统的概念。是用户态空间跟内核态空间的桥梁。

TCP/UDP

  • TCP 是面向连接的,UDP 是面向无连接的。
  • TCP 提供可靠交付,无差错、不丢失、不重复、并且按序到达;UDP 不提供可靠交付,不保证不丢失,不保证按顺序到达。
  • TCP 是面向字节流的,发送时发的是一个流,没头没尾;UDP 是面向数据报的,一个一个地发送。
  • TCP 是可以提供流量控制和拥塞控制的,既防止对端被压垮,也防止网络被压垮。

而这些特性是从本质上来讲,其实是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,并用这样的数据结构来保证面向连接的特性。

所谓的连接,就是两端数据结构状态的协同,两边的状态能够对得上。符合 TCP 协议的规则,就认为连接存在;两面状态对不上,连接就算断了。

所谓的可靠,也是两端的数据结构做的事情。不丢失其实是数据结构在“点名”,顺序到达其实是数据结构在“排序”,面向数据流其实是数据结构将零散的包,按照顺序捏成一个流发给应用层。

TCP

监听的 socket 和真正用来传送数据的 socket,是两个 socket,一个叫作监听 socket,一个叫作已连接 socket。成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

UDP

socket的数据结构

Socket创建

family 表示地址族 IP 还是本地文件通信

type Socket 的类型 SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW

protocol 协议

发送网络包

  • VFS 层:write 系统调用找到 struct file,根据里面的 file_operations 的定义,调用 sock_write_iter 函数。sock_write_iter 函数调用 sock_sendmsg 函数。
  • Socket 层:从 struct file 里面的 private_data 得到 struct socket,根据里面 ops 的定义,调用 inet_sendmsg 函数。
  • Sock 层:从 struct socket 里面的 sk 得到 struct sock,根据里面 sk_prot 的定义,调用 tcp_sendmsg 函数。
  • TCP 层:tcp_sendmsg 函数会调用 tcp_write_xmit 函数,tcp_write_xmit 函数会调用 tcp_transmit_skb,在这里实现了 TCP 层面向连接的逻辑。
  • IP 层:扩展 struct sock,得到 struct inet_connection_sock,根据里面 icsk_af_ops 的定义,调用 ip_queue_xmit 函数。
  • IP 层:ip_route_output_ports 函数里面会调用 fib_lookup 查找路由表。FIB 全称是 Forwarding Information Base,转发信息表,也就是路由表。在 IP 层里面要做的另一个事情是填写 IP 层的头。在 IP 层还要做的一件事情就是通过 iptables 规则。
  • MAC 层:IP 层调用 ip_finish_output 进行 MAC 层。MAC 层需要 ARP 获得 MAC 地址,因而要调用 ___neigh_lookup_noref 查找属于同一个网段的邻居,他会调用 neigh_probe 发送 ARP。有了 MAC 地址,就可以调用 dev_queue_xmit 发送二层网络包了,它会调用 __dev_xmit_skb 会将请求放入队列。
  • 设备层:网络包的发送会触发一个软中断 NET_TX_SOFTIRQ 来处理队列中的数据。这个软中断的处理函数是 net_tx_action。
  • 在软中断处理函数中,会将网络包从队列上拿下来,调用网络设备的传输函数 ixgb_xmit_frame,将网络包发到设备的队列上去。

拆包可以通过tso参数控制是在网卡上进行还是在内核由cpu处理

接收网络包

  • 硬件网卡接收到网络包之后,通过 DMA 技术,将网络包放入 Ring Buffer;

  • 硬件网卡通过中断通知 CPU 新的网络包的到来;

  • 网卡驱动程序会注册中断处理函数 ixgb_intr;

  • 中断处理函数处理完需要暂时屏蔽中断的核心流程之后,通过软中断 NET_RX_SOFTIRQ 触发接下来的处理过程;

  • NET_RX_SOFTIRQ 软中断处理函数 net_rx_action,net_rx_action 会调用 napi_poll,进而调用 ixgb_clean_rx_irq,从 Ring Buffer 中读取数据到内核 struct sk_buff;

  • 调用 netif_receive_skb 进入内核网络协议栈,进行一些关于 VLAN 的二层逻辑处理后,调用 ip_rcv 进入三层 IP 层;

  • 在 IP 层,会处理 iptables 规则,然后调用 ip_local_deliver 交给更上层 TCP 层;

  • 在 TCP 层调用 tcp_v4_rcv,这里面有三个队列需要处理,如果当前的 Socket 不是正在被读;取,则放入 backlog 队列,如果正在被读取,不需要很实时的话,则放入 prequeue 队列,其他情况调用 tcp_v4_do_rcv;

  • 在 tcp_v4_do_rcv 中,如果是处于 TCP_ESTABLISHED 状态,调用 tcp_rcv_established,其他的状态,调用 tcp_rcv_state_process;

  • 在 tcp_rcv_established 中,调用 tcp_data_queue,如果序列号能够接的上,则放入 sk_receive_queue 队列;如果序列号接不上,则暂时放入 out_of_order_queue 队列,等序列号能够接上的时候,再放入 sk_receive_queue 队列。

    至此内核接收网络包的过程到此结束,接下来就是用户态读取网络包的过程,这个过程分成几个层次。

  • VFS 层:read 系统调用找到 struct file,根据里面的 file_operations 的定义,调用 sock_read_iter 函数。sock_read_iter 函数调用 sock_recvmsg 函数

  • Socket 层:从 struct file 里面的 private_data 得到 struct socket,根据里面 ops 的定义,调用 inet_recvmsg 函数。

  • Sock 层:从 struct socket 里面的 sk 得到 struct sock,根据里面 sk_prot 的定义,调用 tcp_recvmsg 函

  • TCP 层:tcp_recvmsg 函数会依次读取 receive_queue 队列、prequeue 队列和 backlog 队列。