以igb驱动为例
igb_init_module { pci_register_driver }内核会根据不同网卡选择不同的函数指针,然后启动网卡。然后会执行下面几步:
注册 struct net_device_ops 结构,这个结构是用来保存一些关于网卡的操作函数指针。把Mac地址赋给网卡。创建一个struct net_device结构,这个结构就代表一个网络设备。先看NAPI:主要是用来适配高速设备 意思就是如果在高速设备上,每来一个数据包就发一个中断给CPU,CPU的时间都花费在处理中断上面了,因为是高速设备,一次可能来几个包,这几个包只给一次中断,让CPU一次处理完几个包,就可以释放一些CPU时间出来。
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分片重组 ip_defrag(),再把skb放到netfilter,如果不丢包的话,则调用ip_local_deliver_finish
这个函数会找协议类型,是UDP,还是TCP,还是其他协议,然后根据协议的不同来调用不同的handler函数 每个协议的handler是: 下一步就是更高层协议的处理了。
这种情况本机要把这个包转发,会检查这个包ttl字段,重新计算check_sum,再查找路由表,找到合适的dst,最后进入netfilter进行包过滤,如果没有丢掉则调用ip_forward_finish
dst_output() --> ip_output() --> ip_finish_output() --> 判断时候需要分片,如果需要则调用ip_fragment,再把分片后的数据通过ip_finish_output2()发送出去。
udp_rcv --> __udp4_lib_rcv 可以看到会去检查udp的check_sum字段,如果校验失败就直接丢掉这个包了。 如果检查成功并且找到相关的监听套接字的话,那么调用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_v4_do_rcv这个函数会根据当前的状态去调用不同的函数。
关于TCP的东西有些多,在这里先不展开了。
Rec Data Send Data How to receive a million packets per second Ring Buffer