Linux 网络初步阅读
文章目录一些名词初始化网卡接收分组NAPI详细步骤高层协议IPv4接收ip_rcvip_rcv_finiship_local_deliverip_local_deliver_finish发送ip_forwardip_forward_finish一些名词NIC:网络接口控制器,Network Interface Controller,就是网卡NAPI(New API):适配超高速网络适配器的网络AP
文章目录
一些名词
- NIC:网络接口控制器,Network Interface Controller,就是网卡
- NAPI(New API):适配超高速网络适配器的网络API
- igb:英特尔千兆网卡驱动程序
初始化网卡
以igb驱动为例
igb_init_module {
pci_register_driver
}
内核会根据不同网卡选择不同的函数指针,然后启动网卡。然后会执行下面几步:
- 注册 struct net_device_ops 结构,这个结构是用来保存一些关于网卡的操作函数指针。
- 把Mac地址赋给网卡。
- 创建一个struct net_device结构,这个结构就代表一个网络设备。
接收分组
NAPI
先看NAPI:主要是用来适配高速设备
意思就是如果在高速设备上,每来一个数据包就发一个中断给CPU,CPU的时间都花费在处理中断上面了,因为是高速设备,一次可能来几个包,这几个包只给一次中断,让CPU一次处理完几个包,就可以释放一些CPU时间出来。
详细步骤
- 数据到达网卡:网卡通过DMA方式把数据包放到一个环形队列里面(ring buffer)【struct softnet_data】,这个队列在初始化的时候会分配物理内存。这个队列里面有一个sk_buff_head,所以这个环形队列里面的成员就是一个个struct sk_buff。如果网卡支持RSS/multiqueue的话,那么可以有多个RX Queue,中断只能是一个CPU来处理,但是可以通过设置中断亲和性来把RX Queue和 CPU关联起来。
- netif_rx 激活 NET_RX_SOFTIRQ中断,处理函数为:net_rx_action。
- net_rx_action会调用网卡注册过的NAPI的poll函数,从上面的队列里面取出分组,主要是process_backlog。
- process_backlog会调用__skb_dequeue从队列头取第一个sk_buff,取出之后调用__netif_receive_skb,内部会调用deliver_skb开始交付到高层协议。
- 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
更多推荐
所有评论(0)