深入理解Linux网络笔记(五):深度理解本机网络IO

本文为《深入理解Linux网络》学习笔记,使用的Linux源码版本是3.10,网卡驱动默认采用的都是Intel的igb网卡驱动

Linux源码在线阅读:https://elixir.bootlin.com/linux/v3.10/source

4、深度理解本机网络IO

1)、跨机网络通信过程
1)跨机数据发送

数据包的发送过程如下图:

用户数据被拷贝到内核态,然后经过协议栈处理后进入RingBuffer。随后网卡驱动真正将数据发送了出去。当发送完成的时候,是通过硬中断来通知CPU,然后清理RingBuffer

从代码的视角得到的流程如下图:

等网络发送完毕,网卡会给CPU发送一个硬中断来通知CPU。收到这个硬中断后会释放RingBuffer中使用的内存,如下图所示:

2)跨机数据接收

数据包的接收过程如下图:

当网卡收到数据以后,向CPU发起一个中断,以通知CPU有数据到达。当CPU收到中断请求后,会去调用网络驱动注册的中断处理函数,触发软中断。ksoftirqd检测到有软中断请求到达,开始轮询收包,收到后交由各级协议栈处理。当协议栈处理完并把数据放到接收队列之后,唤醒用户进程(假设是阻塞方式)

从内核组件和源码视角来看,流程如下图:

3)跨机网络通信汇总

那么汇总起来,一次跨机网络通信的过程如下图所示:

2)、本机发送过程

本机网络IO和跨机网络IO有差异的地方总共有两处,分别是路由和驱动程序

1)网络层路由

发送数据进入协议栈到达网络层的时候,网络层入口函数是ip_queue_xmit。在网络层里会进行路由选择,路由选择完毕,再设置IP头,进行netfilter的过滤,将包交给邻居子系统。网络层工作流程如下图所示:

对于本机网络IO来说,特殊之处在于在local路由表中就能找到路由项,对应的设备都将使用loopback网卡,也就是常说的lo设备

网络层入口函数ip_queue_xmit源码如下:

// net/ipv4/ip_output.c
int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{...// 检查socket中是否有缓存的路由表rt = (struct rtable *)__sk_dst_check(sk, 0);if (rt == NULL) {...// 没有缓存则展开查找// 查找路由项,并缓存到socket中rt = ip_route_output_ports(sock_net(sk), fl4, sk,daddr, inet->inet_saddr,inet->inet_dport,inet->inet_sport,sk->sk_protocol,RT_CONN_FLAGS(sk),sk->sk_bound_dev_if);...sk_setup_caps(sk, &rt->dst);}...
}

查找路由项的函数是ip_route_output_ports,它又依次调用ip_route_output_flow、__ip_route_output_key、fib_lookup函数。调用过程略过,直接看fib_lookup的关键代码

// include/net/ip_fib.h
static inline int fib_lookup(struct net *net, const struct flowi4 *flp,struct fib_result *res)
{struct fib_table *table;table = fib_get_table(net, RT_TABLE_LOCAL);if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))return 0;table = fib_get_table(net, RT_TABLE_MAIN);if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))return 0;return -ENETUNREACH;
}

在fib_lookup中将会对local和main两个路由表展开查询,并且先查询local后查询main。我们在Linux上使用ip命令可以查看到这两个路由表,这里只看local路由表(因为本机网络IO查询到这个表就终止了)

$ ip route list table local
local 10.143.x.y dev eth0 proto kernel scope host src 10.143.x.y 
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1

从上述结果可以看出,对于目的是127.0.0.1的路由在local路由表中就能够找到。fib_lookup的工作完成,返回__ip_route_output_key函数继续执行

// net/ipv4/route.c
struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{...if (fib_lookup(net, fl4, &res)) {...}if (res.type == RTN_LOCAL) {...dev_out = net->loopback_dev;...}...rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags);...return rth;
}

对于本机的网络请求,设备将全部使用net->loopback_dev,也就是lo虚拟网卡

接下来的网络层仍然和跨机网络IO一样,最终会经过ip_finish_output,进入邻居子系统的入口函数dst_neigh_output

本机网络IO需要进行IP分片吗?

因为和正常的网络层处理过程一样,会经过ip_finish_output函数,在这个函数中,如果skb大于MTU,仍然会进行分片。只不过lo虚拟网卡的MTU比Ethernet要大很多。通过ifconfig命令就可以查到,物理网卡MTU一般为1500,而lo虚拟接口能有65535个

在邻居子系统函数中经过处理后,进入网络设备子系统(入口函数是dev_queue_xmit)

2)本机IP路由

问题:用本机IP(例如192.168.x.x)和用127.0.0.1在性能上有差别吗?

前面讲过,选用哪个设备是路由相关函数__ip_route_output_key确定的

// net/ipv4/route.c
struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{...if (fib_lookup(net, fl4, &res)) {...}if (res.type == RTN_LOCAL) {...dev_out = net->loopback_dev;...}...rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags);...return rth;
}

在fib_lookup函数里会查询到local路由表

$ ip route list table local
local 10.162.*.* dev eth0 proto kernel scope host src 10.162.*.*
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1

很多人在看到这个路由表的时候就被它迷惑了,以为上面的10.162.*.*真的会被路由到eth0(其中10.162.*.*是我的本机局域网IP,后面两段用*号隐藏起来了)

但其实内核在初始化local路由表的时候,把local路由表里所有的路由项都设置成了RTN_LOCAL,不只是127.0.0.1。这个过程是在设置本机IP的时候,调用fib_inetaddr_event函数完成设置的

// net/ipv4/fib_frontend.c
static int fib_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
{...switch (event) {case NETDEV_UP:fib_add_ifaddr(ifa);...break;case NETDEV_DOWN:fib_del_ifaddr(ifa, NULL);...break;}return NOTIFY_DONE;
}
// net/ipv4/fib_frontend.c
void fib_add_ifaddr(struct in_ifaddr *ifa)
{...fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);...
}

所以即使本机IP不用127.0.0.1,内核在路由项查找的时候判断类型是RTN_LOCAL,仍然会使用net->loopback_dev,也就是lo虚拟网卡

3)网络设备子系统

网络设备子系统的入口函数是dev_hard_start_xmit。之前讲述跨机发送过程时介绍过,对于真的有队列的物理设备,该函数进行了一系列复杂的排队等处理后,才调用dev_hard_start_xmit,从这个函数再进入驱动程序来发送。在这个过程中,甚至还有可能出发软中断进行发送,流程如下图:

但是对于启动状态的回环设备(q->enqueue判断为false)来说,就简单多了。没有队列的问题,直接进入dev_hard_start_xmit。接着进入回环设备的驱动里发送回调函数loopback_xmit,将skb发送出去,如下图所示:

下面来看看详细的过程,从网络设备子系统的入口函数dev_queue_xmit看起

// net/core/dev.c
int dev_queue_xmit(struct sk_buff *skb)
{...q = rcu_dereference_bh(txq->qdisc);...if (q->enqueue) { // 回环设备这里为falserc = __dev_xmit_skb(skb, q, dev, txq);goto out;}// 开始回环设备处理if (dev->flags & IFF_UP) {...rc = dev_hard_start_xmit(skb, dev, txq);...}...
}

在dev_queue_xmit函数中还将调用设备驱动的操作函数

// net/core/dev.c
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,struct netdev_queue *txq)
{// 获取设备驱动的回调函数集合opsconst struct net_device_ops *ops = dev->netdev_ops;...// 调用驱动的ndo_start_xmit进行发送rc = ops->ndo_start_xmit(skb, dev);...
}
4)驱动程序

回环设备的驱动程序的工作流程如下图:

loopback(回环)设备的驱动代码在drivers/net/loopback.c文件里

// drivers/net/loopback.c
static const struct net_device_ops loopback_ops = {.ndo_init      = loopback_dev_init,.ndo_start_xmit= loopback_xmit,.ndo_get_stats64 = loopback_get_stats64,
};

所以对dev_hard_start_xmit调用实际上执行的是loopback驱动里的loopback_xmit(loopback是一个纯软件性质的虚拟接口,并没有真正意义上对物理设备的驱动)

// drivers/net/loopback.c
static netdev_tx_t loopback_xmit(struct sk_buff *skb,struct net_device *dev)
{...// 剥离掉和原socket的联系skb_orphan(skb);...// 调用netif_rxif (likely(netif_rx(skb) == NET_RX_SUCCESS)) {...}return NETDEV_TX_OK;
}

在skb_orphan中先把skb上的socket指针去掉了(剥离出来)

注意,在本机网络IO发送的过程中,传输层下面的skb就不需要释放了,直接给接收方传过去就行,总算是省了一点点开销。不过可惜传输层的skb同样节约不了,还是要频繁地申请和释放

接着调用netif_rx,在该方法中最终会执行到enqueue_to_backlog(netif_rx->enqueue_to_backlog)

// net/core/dev.c
static int enqueue_to_backlog(struct sk_buff *skb, int cpu,unsigned int *qtail)
{...sd = &per_cpu(softnet_data, cpu);...__skb_queue_tail(&sd->input_pkt_queue, skb);...____napi_schedule(sd, &sd->backlog);...
}

在enqueue_to_backlog函数中,把要发送的skb插入softnet_data->input_pkt_queue队列 并调用____napi_schedule来触发软中断

// net/core/dev.c
static inline void ____napi_schedule(struct softnet_data *sd,struct napi_struct *napi)
{list_add_tail(&napi->poll_list, &sd->poll_list);__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

只有触发完软中断,发送过程才算完成了

3)、本机接收过程

发送过程触发软中断后,会进入软中断处理函数net_rx_action,如下图所示:

在跨机的网络包的接收过程中,需要经过硬中断,然后才能触发软中断。而在本机的网络IO过程中,由于并不真的过网卡,所以网卡的发送过程、硬中断就都省去了,直接从软中断开始

在软中断被触发以后,会进入NET_RX_SOFTIRQ对应的处理方法net_rx_action中

// net/core/dev.c
static void net_rx_action(struct softirq_action *h)
{...while (!list_empty(&sd->poll_list)) {...work = n->poll(n, weight);...	}...
}

对于igb网卡来说,poll实际调用的是igb_poll函数。那么loopback网卡的poll函数是哪个呢?由于poll_list里面是struct softnet_data对象,在net_dev_init中找到了对应的处理函数

// net/core/dev.c
static int __init net_dev_init(void)
{...for_each_possible_cpu(i) {...sd->backlog.poll = process_backlog;...}...
}

struct softnet_data默认的poll在初始化的时候设置成了process_backlog函数

// net/core/dev.c
static int process_backlog(struct napi_struct *napi, int quota)
{...while (work < quota) {...while ((skb = __skb_dequeue(&sd->process_queue))) {...__netif_receive_skb(skb);...}...// skb_queue_splice_tail_init()函数用于将链表a连接到链表b上,// 形成一个新的链表b,并将原来a的头编程空链表qlen = skb_queue_len(&sd->input_pkt_queue);if (qlen)skb_queue_splice_tail_init(&sd->input_pkt_queue,&sd->process_queue);...}...
}

skb_queue_splice_tail_init是把sd->input_pkt_queue里的skb链到sd->process_queue链表上去,__skb_dequeue是从sd->process_queue取下来包进行处理。这样和前面发送过程的结尾处就对上,发送过程是把包放到了input_pkt_queue队列里,如下图所示:

最后调用__netif_receive_skb将数据送往协议栈。在此之后的调用过程就和跨机网络IO又一致了。送往协议栈的调用链是__netif_receive_skb=>__netif_receive_skb_core=>deliver_skb,然后将数据包送入ip_rcv中。网络层再往后是传输层,最后唤醒用户进程

4)、总结

本机网络IO的内核总体执行流程如下图:

1)127.0.0.1本机网络IO需要经过网卡吗?

不需要经过网卡。即使把网卡拔了,本机网络还是可以正常使用的

2)数据包在内核中是什么走向,和外网发送相比流程上有什么差别?

总的来说,本机网络IO和跨机网络IO比较起来,确实是节约了驱动上的一些开销。发送数据不需要进RingBuffer的驱动队列,直接把skb传给接收协议栈(经过软中断)。但是在内核其他组件上,可是一点儿都没少,系统调用、协议栈(传输层、网络层等)、设备子系统整个走了一遍。连驱动程序都走了(虽然对于回环设备来说只是一个纯软件的虚拟出来的东西)。所以即使是本机网络IO,切忌误认为没啥开销就滥用

3)访问本机服务时,使用127.0.0.1能比使用本机IP(例如192.168.x.x)更快吗?

使用本机IP和127.0.0.1没有差别,都是走虚拟的回环设备lo。这是因为内核在设置IP的时候,把所有的本机IP都初始化到local路由表里了,类型写死了是RTN_LOCAL。在后面的路由项选择的时候发现类型是RTN_LOCAL就会选择lo设备了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/122689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【笔录】TVP技术沙龙:寻宝AI时代

目录 引言大模型的应用案例大模型三问模型落地可行性考量维度AIGC的几个可行应用方向AIGC的存储LLM工程应用范式演进LLM编程协作范式变化 引言 大模型是10倍的机会&#xff0c;但并不是平均主义的机会&#xff0c;没有低垂的果实。 企业想在大模型的赛道上跑出成绩&#xff0c;…

光谱图像论文浅读

文章目录 Hyperspectral Image Super-Resolution via Deep Spatiospectral Attention Convolutional Neural Networks Hyperspectral Image Super-Resolution via Deep Spatiospectral Attention Convolutional Neural Networks 通过上采样高光谱保留其光谱特征&#xff0c;采用…

vscode连接服务器一直retry

解决方法 打开vscode控制面板&#xff0c;输入命令remote-ssh: kill vs code server on host 选择一直连接不上的服务器端口 重新连接

【鸿蒙软件开发】Stage模型开发概述应用/组件级配置

文章目录 前言一、基本概念1.1 UIAbility 组件1.2 ExtensionAbility 组件1.3 Context1.4 AbilityStage1.5 Stage模型开发流程应用组件开发了解进程模型了解线程模型应用配置文件 二、Stage模型应用/组件级配置2.1 为什么需要这个操作2.2 应用包名配置2.3 应用图标和标签配置2.4…

人工智能在疾病治疗中的应用:机遇与挑战

人工智能在疾病治疗中的应用&#xff1a;机遇与挑战 随着人工智能技术的飞速发展&#xff0c;其在诸多领域的应用价值日益显现。本文将探讨人工智能技术在疾病治疗中的应用&#xff0c;包括其背景意义、技术概述、具体应用、发展前景以及总结。 一、背景意义 随着医学技术的…

leetCode 169. 多数元素 + 摩尔投票法

169. 多数元素 - 力扣&#xff08;LeetCode&#xff09; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 class Solution { publ…

【SPSS】基于RFM+Kmeans聚类的客户分群分析(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

一文解决:Swagger API 未授权访问漏洞问题

Swagger 是一个用于设计、构建、文档化和使用 RESTful 风格的 Web 服务的开源软件框架。它通过提供一个交互式文档页面&#xff0c;让开发者可以更方便地查看和测试 API 接口。然而&#xff0c;在一些情况下&#xff0c;未经授权的访问可能会导致安全漏洞。本文将介绍如何解决 …

前端技术知识(含八股)总结 - 持续更新中

前端技术知识&#xff08;含八股&#xff09;总结 - 持续更新中 参考文献1.HTML和CSS1.1 语义化标签1.2 CSS 选择器及优先级 / position 定位 / box-sizing 属性 / transition / 继承属性&#xff08;如字体文字类的属性大部分有继承&#xff09;/ 行内元素和块级元素 / html的…

2.MySQL的调控按钮——启动选项和系统变量

2.MySQL的调控按钮——启动选项和系统变量 1.启动选项和配置文件1.1 在命令行上使用选项1.2 配置文件中使用选项1.2.1 配置文件路径1.2.2 配置文件的内容1.2.3 特定 MySQL 版本的专用选项组1.2.4 配置文件的优先级1.2.5 同一个配置文件中多个组的优先级1.2.6 defaults-file 的使…

使用Spring Boot限制在一分钟内某个IP只能访问10次

有些时候&#xff0c;为了防止我们上线的网站被攻击&#xff0c;或者被刷取流量&#xff0c;我们会对某一个ip进行限制处理&#xff0c;这篇文章&#xff0c;我们将通过Spring Boot编写一个小案例&#xff0c;来实现在一分钟内同一个IP只能访问10次&#xff0c;当然具体数值&am…

MyBatisPlus创建新的Mapper.xml映射文件而不使用框架自带的?

MyBatisPlus创建新的Mapper.xml映射文件而不使用框架自带的&#xff1f; 以后使用数据库框架的时候可以使用MyBatisPlus而不适用MyBatis&#xff0c;因为MyBatisPlus更为简便&#xff0c;像简单的增删改查操作&#xff0c;在MyBatisPlus中可以直接完成&#xff0c;不用写Mappe…

免费活动-11月4日敏捷武林上海站 | Scrum.org CEO 亲临现场

​​​​​​​ 活动介绍 过去的几年里&#xff0c;外界的风云变幻为我们的生活增添了一些不一样的色彩。在VUCA世界的浪潮里&#xff0c;每一个人都成为自己生活里的冒险家。面对每一次的变化&#xff0c;勇于探索未知&#xff0c;迎接挑战&#xff0c;努力追逐更好的自己。…

雨云虚拟主机使用教程WordPress博客网站搭建教程

雨云虚拟主机(RVH)使用教程与宝塔面板搭建WordPress博客网站的教程&#xff0c;本文会讲解用宝塔面板一键部署以及手动安装两种方式来搭建WordPress博客&#xff0c;选其中一种方式即可。 WordPress WordPress是使用PHP语言开发的博客平台&#xff0c;用户可以在支持PHP和MyS…

基于Java的民航售票管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

logback-classic包中ThrowableProxy递归缺陷StackOverflowError解析

logback-classic&#xff08;<1.2.12版本&#xff09;ThrowableProxy类中存在递归缺陷&#xff0c;会导致java.lang.StackOverflowError。改缺陷在1.2.12以上版本(包含该版本)中已修复。 如何复现&#xff1a; 两个异常彼此设置casue&#xff1a; 运行后报以下错误 以上写…

(免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设农产品销售管理系统。…

中颖单片机SH367309全套量产PCM,专用动力电池保护板开发资料

方案总体介绍 整套方案硬件部分共2块板子&#xff0c;包括MCU主板&#xff0c;采用SH79F6441-32作为主处理器。MCU主板包括2个版本。PCM动力电池保护板采用SH367309。 软件方案采用Keil51建立的工程&#xff0c;带蓝牙的版本&#xff0c;支持5~16S电池。 硬件方案--MCU主板 MC…

js中HTMLCollection如何循环

//不带索引 let divCon document.getElementsByClassName("el-form-item__error"); if (divCon.length > 0) {for (var item of divCon) {console.log("打印&#xff1a;", item.innerText);} }//带有索引 let divCon document.getElementsByClassNam…

【算法|动态规划 | 01背包问题No.1】AcWing 426. 开心的金明

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【AcWing算法提高学习专栏】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&a…