前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux数据报文的来龙去脉

Linux数据报文的来龙去脉

作者头像
glinuxer
发布2019-04-10 15:00:20
1.7K0
发布2019-04-10 15:00:20
举报

作者:gfree.wind@gmail.com

作为网络领域的开发人员,我们经常要与Linux的数据报文打交道,一定要搞清楚数据报文是从何而来,又是如何离去。以前针对这个主题写过一些文章(主要是从源码角度),这次会更重视流程示意图(在细节上必然有所简化),争取在一篇文章中,就让大家理清数据报文的来龙去脉。

一、网卡把报文传到内核的流程图

图1. 网卡传递数据包到内核的流程图

1. 网卡在启动时会申请一个接收ring buffer,其条目都会指向一个skb的内存。

2. DMA完成数据报文从网卡硬件到内存到拷贝后,网卡发送一个中断通知CPU。

3. CPU执行网卡驱动注册的中断处理函数,中断处理函数只做一些必要的工作,如读取硬件状态等,并把当前该网卡挂在NAPI的链表中,同时会“触发”NET_RX_SOFTIRQ(其实就是设置对应软中断的标志位)。

4. CPU中断处理函数返回后,会检查是否有软中断需要执行。因第三步设置了NET_RX_SOFTIRQ,则执行报文接收软中断。

5. 在NET_RX_SOFTIRQ软中断中,执行NAPI操作,回调第三步挂载的驱动poll函数。

6. 驱动会对interface进行poll操作,检查网卡是否有接收完毕的数据报文。

7. 将网卡中已经接收完毕的数据报文取出,继续在软中断进行后续处理。

注:驱动对interface执行poll操作时,会尝试循环检查网卡是否有接收完毕的报文,直到设置的budget上限,或者已经就绪报文。

二、接收软中断将报文分发给协议栈的示意图

图2. 接收软中断将报文分发给对应协议栈

1. 假设是CPU0在执行接收软中断net_rx_action

2. 接收报文的网卡是否支持XDP,若支持且启用,则进行xdp的检查。

3. 查看是否启用了RPS & RFS。若启用且目的CPU非当前CPU,则将数据报文enque到目的CPU的backlog队列中,并在软中断中发送IPI中断给目的CPU。目的CPU的IPI处理函数会将目的CPU的backlog挂到其NAPI列表中,这样由其他CPU发送过来的报文,就会在其后面NAPI中被处理。

4. CPU0负责处理当前报文。如果网卡没有vlan offload,则需要软件剥掉vlan头,以便后面的报文处理。

5. 在分发报文时,可能会有多个handler关心此报文。所以在分发时,都是增加引用计数,然后给对应的处理函数。

6. 将报文分发给“ETH_P_ALL”的handler,即关心所有以太网报文的handler(不限于任何协议)

7. 通过以太网报文的协议,将数据报文分发给该协议的handler,如IPv4,IPv6,PPPoE等。

三、协议栈将数据报文发给套接字(以IPv4为例)的流程图

图3. 协议栈将报文发给套接字的流程图

报文从上到下的分发过程很相似。每层协议都会包含上层协议类型,然后根据类型进行分发。以IPv4协议栈(inet协议族)来说,初始化阶段,调用dev_add_pack注册了二层以太网的IP协议类型。而四层协议icmp、udp、tcp也是在inet初始化阶段,调用inet_add_protocol注册。

那么,报文接收的流程如下:

1. __netif_receive_skb_core处于二层协议处理阶段,其根据以太网的报文类型,从packet_type中找到匹配的三层协议。

2. 进入ip报文的处理函数ip_rcv,进行netfiler的prerouting阶段的检查。

3. 获得四层协议类型,调用其early_demux。这是一个优化,对于符合条件的报文,可以尽早处理。

4. 查找路由。对于发给本机的IP报文,其路由的input处理函数,即ip_local_deliver。

5. 继续netfilter的localin阶段的检查。

6. 通过检查后,将报文发给本机的raw socket。

7. 根据四层协议类型,调用匹配的四层协议处理函数。对于UDP报文来说,就是udp_rcv。

8. 根据源端口和目的端口,确定socket套接字。

9. 将skb报文加入套接字的接收队列。

四、报文从应用层到网卡的流程图

图4. 应用层发包到网卡的流程

1. 应用层调用socket系统调用,内核会在内部根据协议类型,创建一个socket对象,且其成员变量ops指向udp_proto(以UDP为例)。

2. 应用层调用send,write等发送函数,将socket fd和数据data传给内核。

3. 内核通过fd获得socket对象,并将应用层的数据复制到内核,调用socket成员变量对应的sendmsg。

4. 内核调用ip_route_output_flow查询路由。

5. 调用ip_make_skb,申请一个skb用于发送报文,并填充了IP头。

6. 调用udp_send、ip_send_skb,填充UDP报文头,计算IP头的checksum等。

7. 调用ip_localout,到达本机IP层发送报文的最后阶段,进行netfilter localout阶段的检查。

8. 调用neigh_output,即邻居层填充二层目的MAC。如果没有ARP信息,则缓存报文,发送ARP报文,进行二层地址解析。

9. 调用dev_queue_xmit,进入qdisc schedule即流量控制,也就是TC的实现。

10. 默认情况,网卡的qdisc策略是FIFO。被schedule的数据报文,通过dev_hard_start_xmit调用网卡驱动的ndo_start_xmit,将报文交给网卡进行发送。

通过以上四个分解的流程图,相信大家对于Linux数据报文的来龙去脉,有了一定的了解。如在文章开头所云,这些流程图都做了必要的简化。在很多步骤都可以进行展开,也涉及了更多细节。

一句话,内核的网络模块,既简单又复杂。它的流程很简单,但每一步的处理又涉及了很多细节,欢迎大家和我一起讨论研究。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 LinuxerPub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档