一些名词

  1. NIC:网络接口控制器,Network Interface Controller,就是网卡
  2. NAPI(New API):适配超高速网络适配器的网络API
  3. igb:英特尔千兆网卡驱动程序

初始化网卡

以igb驱动为例

igb_init_module {
    pci_register_driver
}

在这里插入图片描述
内核会根据不同网卡选择不同的函数指针,然后启动网卡。然后会执行下面几步:

  1. 注册 struct net_device_ops 结构,这个结构是用来保存一些关于网卡的操作函数指针。
  2. 把Mac地址赋给网卡。
  3. 创建一个struct net_device结构,这个结构就代表一个网络设备。

接收分组

NAPI

先看NAPI:主要是用来适配高速设备
在这里插入图片描述
意思就是如果在高速设备上,每来一个数据包就发一个中断给CPU,CPU的时间都花费在处理中断上面了,因为是高速设备,一次可能来几个包,这几个包只给一次中断,让CPU一次处理完几个包,就可以释放一些CPU时间出来。

详细步骤

  1. 数据到达网卡:网卡通过DMA方式把数据包放到一个环形队列里面(ring buffer)【struct softnet_data】,这个队列在初始化的时候会分配物理内存。这个队列里面有一个sk_buff_head,所以这个环形队列里面的成员就是一个个struct sk_buff。如果网卡支持RSS/multiqueue的话,那么可以有多个RX Queue,中断只能是一个CPU来处理,但是可以通过设置中断亲和性来把RX Queue和 CPU关联起来。
    在这里插入图片描述
  2. netif_rx 激活 NET_RX_SOFTIRQ中断,处理函数为:net_rx_action。
  3. net_rx_action会调用网卡注册过的NAPI的poll函数,从上面的队列里面取出分组,主要是process_backlog。
  4. process_backlog会调用__skb_dequeue从队列头取第一个sk_buff,取出之后调用__netif_receive_skb,内部会调用deliver_skb开始交付到高层协议。
  5. deliver_skb 这个函数会根据不同的协议簇去调用注册好的函数。比如IPv4的处理函数就是ip_rcv。并且会发一份数据给tcpdump之类的抓包工具(libpcap)。

高层协议

IPv4

接收
ip_rcv

deliver_skb之后,对于IP包,就会进入ip_rcv函数对skb进行进一步的处理

iph = ip_hdr(skb);
CHECK_IP_HEADER(iph);
NF_HOOK(NF_INET_PRE_ROUTING, ip_rcv_finish);

先从skb里面把IP头取出来,然后做一些校验,校验通过之后就放到netfilter的PRE_ROUTING链走一遍,调用iptables注册的一些函数,如果这个包不丢掉的话,那么就会跳到ip_rcv_finish这个函数继续处理。

ip_rcv_finish
ip_route_input_noref  -- > ip_route_input_common; //选路由
如果是转发:ip_forward
如果是本机:ip_local_deliver
ip_local_deliver

ip分片重组 ip_defrag(),再把skb放到netfilter,如果不丢包的话,则调用ip_local_deliver_finish

ip_local_deliver_finish

这个函数会找协议类型,是UDP,还是TCP,还是其他协议,然后根据协议的不同来调用不同的handler函数
在这里插入图片描述
每个协议的handler是:
在这里插入图片描述
下一步就是更高层协议的处理了。

发送
ip_forward

这种情况本机要把这个包转发,会检查这个包ttl字段,重新计算check_sum,再查找路由表,找到合适的dst,最后进入netfilter进行包过滤,如果没有丢掉则调用ip_forward_finish

ip_forward_finish

dst_output() --> ip_output() --> ip_finish_output() --> 判断时候需要分片,如果需要则调用ip_fragment,再把分片后的数据通过ip_finish_output2()发送出去。

UDP

udp_rcv

udp_rcv --> __udp4_lib_rcv
在这里插入图片描述
可以看到会去检查udp的check_sum字段,如果校验失败就直接丢掉这个包了。
如果检查成功并且找到相关的监听套接字的话,那么调用udp_queue_rcv_skb。

udp_queue_rcv_skb

会检查socket缓冲区还有没有空间,满了就丢掉。
在这里插入图片描述
这个revbuf很熟悉,这个套接字缓冲区的大小是可以通过setsockopt函数来设置的(SO_RCVBUF),但是 这个revbuf最大值不能超过net.core.rmem_max,这个net.core.rmem_max可以通过sysctl来设置

sudo sysctl -w net.core.rmem_max=8388608

然后再调用__udp_queue_rcv_skb --> sock_queue_rcv_skb --> __skb_queue_tail。到这一步数据包就确实到了套接字的缓冲区里面了。最后看这个套接字是否存活,存活的话调用sk_data_ready来通知套接字缓冲区有东西可以读取了(read/readv/epoll)这个会唤醒在sk_sleep队列上面的进程。

TCP

主图

在这里插入图片描述
tcp_v4_do_rcv这个函数会根据当前的状态去调用不同的函数。

被动建立

在这里插入图片描述

主动建立

在这里插入图片描述
关于TCP的东西有些多,在这里先不展开了。

一些文章

Rec Data
Send Data
How to receive a million packets per second
Ring Buffer

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐