专栏首页专注网络研发Linux数据报文的来龙去脉

Linux数据报文的来龙去脉

作者: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数据报文的来龙去脉,有了一定的了解。如在文章开头所云,这些流程图都做了必要的简化。在很多步骤都可以进行展开,也涉及了更多细节。

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

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

本文分享自微信公众号 - LinuxerPub(LinuxerPub)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JAVA单服务应用拆分成多个服务的实践(2)--服务的dubbo化

    上篇文章JAVA单服务应用拆分成多个服务的实践(1)--拆分的设计思想--提到,需要将各个应用微服务化. 我的应用是使用Spring boot ,没用spri...

    星痕
  • dubbo-start 原

    See provider/GreetingServiceImpl.java on GitHub.

    stys35
  • Java开发小技巧

    平时开发中有一些小技巧,都不算很有技术含量,但在工作中运用这些技巧确实可以提高工作效率,这里把这些小技分享出来。

    jeremyxu
  • 从 IM 通信 Web SDK 来看如何提高代码可维护性与可扩展性

    在架构设计和功能开发中,代码的可维护性和可扩展性一直是工程师不懈的追求。本文将以我工作中开发的 IM 通信服务 SDK 作为示例,和大家一起探讨下前端基础服务类...

    黄Java
  • dotnet core 通过 frp 发布自己的网站 搭建本地网站配置域名配置代理

    很多时候写出来的网站只能自己内网访问,本文告诉大家如何通过 Frp 将自己的 asp dotnet core 网站发布到外网,让小伙伴访问自己的网站 通过 fr...

    林德熙
  • HUE如何访问NameNode HA模式

    在配置HUE访问NameNode HA之前,我们先来了解一下WebHDFS与HttpFS:

    create17
  • 表单神器 Forminator – 最好用的免费版WordPress表单插件

    著名的Wordpress第三方开发服务商WPMU DEV(曾进入Alexa 排名前250)在自家网站上发起了一个用户需求调查,征询用户最想要的Wordpress...

    丘壑
  • 猫头鹰的深夜翻译:集成方式是如何影响微服务架构的

    当万维网首次出现时,集成不同类型的操作系统是一项主要的挑战。HTTP的出现使得不同的操作系统之间可以通过超文本使用统一的协议进行通信。

    眯眯眼的猫头鹰
  • 强大的WordPress表单插件 Forminator : 用API定制开发你的第一个插件

    从表面上, Forminator似乎只是一个不起眼的Wordpress表单插件(form plugin),但如果你花点时间浏览Forminator API ,你...

    丘壑
  • 猫头鹰的深夜翻译:API网关的重要性

    在非技术术语中,“网关或门是进入一个由墙围住的封闭空间的入口点。”同理,API网关是指位于防火墙或互联网后面 的服务的入口点。在微服务的世界中,网关坐镇于API...

    眯眯眼的猫头鹰

扫码关注云+社区

领取腾讯云代金券