admin管理员组文章数量:1642239
文章目录
- 一些名词
- 初始化网卡
- 接收分组
- NAPI
- 详细步骤
- 高层协议
- IPv4
- 接收
- ip_rcv
- ip_rcv_finish
- ip_local_deliver
- ip_local_deliver_finish
- 发送
- ip_forward
- ip_forward_finish
- UDP
- udp_rcv
- udp_queue_rcv_skb
- TCP
- 主图
- 被动建立
- 主动建立
- 一些文章
一些名词
- 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
版权声明:本文标题:Linux 网络初步阅读 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1729333312a1196672.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论