66
77## 网络模型
88
9- 为了使得多种设备能通过网络相互通信,和为了解决各种不同设备在网络互联中的兼容性问题,国际标标准化组织制定了开放式系统互联通信参考模型( * pen System Interconnection Reference Model* ),也就是 OSI 网络模型,该模型主要有 7 层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。
9+ 为了使得多种设备能通过网络相互通信,和为了解决各种不同设备在网络互联中的兼容性问题,国际标准化组织制定了开放式系统互联通信参考模型( * Open System Interconnection Reference Model* ),也就是 OSI 网络模型,该模型主要有 7 层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。
1010
1111每一层负责的职能都不同,如下:
1212
1818- 数据链路层,负责数据的封帧和差错检测,以及 MAC 寻址;
1919- 物理层,负责在物理网络中传输数据帧;
2020
21- 由于 OSI 模型实在太复杂,提出的也只是概念理论上的分层,并没有提供具体的实现方案。事实上,我们比较常见,也比较实用的是四层模型,即 TCP/IP 网络模型,Linux 系统正是按照这套网络模型来实现网络协议栈的。
21+ 由于 OSI 模型实在太复杂,提出的也只是概念理论上的分层,并没有提供具体的实现方案。
22+
23+ 事实上,我们比较常见,也比较实用的是四层模型,即 TCP/IP 网络模型,Linux 系统正是按照这套网络模型来实现网络协议栈的。
2224
2325TCP/IP 网络模型共有 4 层,分别是应用层、传输层、网络层和网络接口层,每一层负责的职能如下:
2426
@@ -29,7 +31,7 @@ TCP/IP 网络模型共有 4 层,分别是应用层、传输层、网络层和
2931
3032TCP/IP 网络模型相比 OSI 网络模型简化了不少,也更加易记,它们之间的关系如下图:
3133
32- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/OSI与TCP.png )
34+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/OSI与TCP.png )
3335
3436不过,我们常说的七层和四层负载均衡,是用 OSI 网络模型来描述的,七层对应的是应用层,四层对应的是传输层。
3537
@@ -44,21 +46,21 @@ TCP/IP 网络模型相比 OSI 网络模型简化了不少,也更加易记,
4446
4547你从下面这张图可以看到,应用层数据在每一层的封装格式。
4648
47- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/封装.png )
49+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/封装.png )
4850
4951其中:
5052
5153- 传输层,给应用数据前面增加了 TCP 头;
5254- 网络层,给 TCP 数据包前面增加了 IP 头;
5355- 网络接口层,给 IP 数据包前后分别增加了帧头和帧尾;
5456
55- 这些新增和头部和尾部 ,都有各自的作用,也都是按照特定的协议格式填充,这每一层都增加了各自的协议头,那自然网络包的大小就增大了,但物理链路并不能传输任意大小的数据包,所以在以太网中,规定了最大传输单元(MTU)是 ` 1500 ` 字节,也就是规定了单次传输的最大 IP 包大小。
57+ 这些新增的头部和尾部 ,都有各自的作用,也都是按照特定的协议格式填充,这每一层都增加了各自的协议头,那自然网络包的大小就增大了,但物理链路并不能传输任意大小的数据包,所以在以太网中,规定了最大传输单元(MTU)是 ` 1500 ` 字节,也就是规定了单次传输的最大 IP 包大小。
5658
57- 当网络包超过 MTU 的大小,就会在网络层分片,以确保分片后的 IP 包不会超过 MTU 大小,如果 MTU 越小,需要的分包就越多,那么网络吞吐能力就越差,相反的,如果 MTU 越大,需要的分包就越小 ,那么网络吞吐能力就越好。
59+ 当网络包超过 MTU 的大小,就会在网络层分片,以确保分片后的 IP 包不会超过 MTU 大小,如果 MTU 越小,需要的分包就越多,那么网络吞吐能力就越差,相反的,如果 MTU 越大,需要的分包就越少 ,那么网络吞吐能力就越好。
5860
5961知道了 TCP/IP 网络模型,以及网络包的封装原理后,那么 Linux 网络协议栈的样子,你想必猜到了大概,它其实就类似于 TCP/IP 的四层结构:
6062
61- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/协议栈.png )
63+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/协议栈.png )
6264
6365
6466从上图的的网络协议栈,你可以看到:
@@ -68,72 +70,115 @@ TCP/IP 网络模型相比 OSI 网络模型简化了不少,也更加易记,
6870- 最下面的一层,则是网卡驱动程序和硬件网卡设备;
6971
7072
71- ---
72-
73-
7473## Linux 接收网络包的流程
7574
7675
77- 网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过 DMA 技术,将网络包放入到 Ring Buffer,这个是一个环形缓冲区。
76+ 网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过 DMA 技术,将网络包写入到指定的内存地址,也就是写入到 Ring Buffer ,这个是一个环形缓冲区,接着就会告诉操作系统这个网络包已经到达 。
7877
79- 那接收到网络包后,应该怎么告诉操作系统这个网络包已经到达了呢 ?
78+ > 那应该怎么告诉操作系统这个网络包已经到达了呢 ?
8079
8180最简单的一种方式就是触发中断,也就是每当网卡收到一个网络包,就触发一个中断告诉操作系统。
8281
83- 但是,这存在一个问题,在高性能网络场景下,网络包的数量会非常多,那么就会触发非常多的中断,要知道当 CPU 收到了中断,就会停下手里的事情,而去处理这些网络包,处理完毕后,才会回去继续其他事情,那么频繁地触发中断,则会导致 CPU 一直没玩没了的处理中断 ,而导致其他任务可能无法继续前进,从而影响系统的整体效率。
82+ 但是,这存在一个问题,在高性能网络场景下,网络包的数量会非常多,那么就会触发非常多的中断,要知道当 CPU 收到了中断,就会停下手里的事情,而去处理这些网络包,处理完毕后,才会回去继续其他事情,那么频繁地触发中断,则会导致 CPU 一直没完没了的处理中断 ,而导致其他任务可能无法继续前进,从而影响系统的整体效率。
8483
8584所以为了解决频繁中断带来的性能开销,Linux 内核在 2.6 版本中引入了 ** NAPI 机制** ,它是混合「中断和轮询」的方式来接收网络包,它的核心概念就是** 不采用中断的方式读取数据** ,而是首先采用中断唤醒数据接收的服务程序,然后 ` poll ` 的方法来轮询数据。
8685
87- 比如,当有网络包到达时,网卡发起硬件中断,于是会执行网卡硬件中断处理函数,** 中断处理函数处理完需要「暂时屏蔽中断」,然后唤醒「软中断」来轮询处理数据,直到没有新数据时才恢复中断,这样一次中断处理多个网络包** ,于是就可以降低网卡中断带来的性能开销。
86+ 因此,当有网络包到达时,会通过 DMA 技术,将网络包写入到指定的内存地址,接着网卡向 CPU 发起硬件中断,当 CPU 收到硬件中断请求后,根据中断表,调用已经注册的中断处理函数。
87+
88+ 硬件中断处理函数会做如下的事情:
89+
90+ - 需要先「暂时屏蔽中断」,表示已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知 CPU 了,这样可以提高效率,避免 CPU 不停的被中断。
91+ - 接着,发起「软中断」,然后恢复刚才屏蔽的中断。
92+
93+ 至此,硬件中断处理函数的工作就已经完成。
94+
95+ 硬件中断处理函数做的事情很少,主要耗时的工作都交给软中断处理函数了。
8896
97+ > 软中断的处理
8998
90- 那软中断是怎么处理网络包的呢?它会从 Ring Buffer 中拷贝数据到内核 struct sk_buff 缓冲区中,从而可以作为一个网络包交给网络协议栈进行逐层处理。
99+ 内核中的 ksoftirqd 线程专门负责软中断的处理,当 ksoftirqd 内核线程收到软中断后,就会来轮询处理数据。
100+
101+ ksoftirqd 线程会从 Ring Buffer 中获取一个数据帧,用 sk_buff 表示,从而可以作为一个网络包交给网络协议栈进行逐层处理。
102+
103+ > 网络协议栈
91104
92105首先,会先进入到网络接口层,在这一层会检查报文的合法性,如果不合法则丢弃,合法则会找出该网络包的上层协议的类型,比如是 IPv4,还是 IPv6,接着再去掉帧头和帧尾,然后交给网络层。
93106
94107到了网络层,则取出 IP 包,判断网络包下一步的走向,比如是交给上层处理还是转发出去。当确认这个网络包要发送给本机后,就会从 IP 头里看看上一层协议的类型是 TCP 还是 UDP,接着去掉 IP 头,然后交给传输层。
95108
96- 传输层取出 TCP 头或 UDP 头,根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据拷贝到 Socket 的接收缓冲区。
109+ 传输层取出 TCP 头或 UDP 头,根据四元组「源 IP、源端口、目的 IP、目的端口」 作为标识,找出对应的 Socket,并把数据放到 Socket 的接收缓冲区。
97110
98- 最后,应用层程序调用 Socket 接口,从内核的 Socket 接收缓冲区读取新到来的数据到应用层 。
111+ 最后,应用层程序调用 Socket 接口,将内核的 Socket 接收缓冲区的数据「拷贝」到应用层的缓冲区,然后唤醒用户进程 。
99112
100113至此,一个网络包的接收过程就已经结束了,你也可以从下图左边部分看到网络包接收的流程,右边部分刚好反过来,它是网络包发送的流程。
101114
102- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/收发流程.png )
115+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost3@main/操作系统/浮点/收发流程.png )
103116
117+ ## Linux 发送网络包的流程
104118
105- ----
119+ 如上图的右半部分,发送网络包的流程正好和接收流程相反。
106120
107- ## Linux 发送网络包的流程
108121
109- 如上图的有右半部分,发送网络包的流程正好和接收流程相反 。
122+ 首先,应用程序会调用 Socket 发送数据包的接口,由于这个是系统调用,所以会从用户态陷入到内核态中的 Socket 层,内核会申请一个内核态的 sk_buff 内存, ** 将用户待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区 ** 。
110123
124+ 接下来,网络协议栈从 Socket 发送缓冲区中取出 sk_buff,并按照 TCP/IP 协议栈从上到下逐层处理。
111125
112- 首先,应用程序会调用 Socket 发送数据包的接口,由于这个是系统调用,所以会从用户态陷入到内核态中的 Socket 层,Socket 层会将应用层数据拷贝到 Socket 发送缓冲区中 。
126+ 如果使用的是 TCP 传输协议发送数据,那么 ** 先拷贝一个新的 sk_buff 副本 ** ,这是因为 sk_buff 后续在调用网络层,最后到达网卡发送完成的时候,这个 sk_buff 会被释放掉。而 TCP 协议是支持丢失重传的,在收到对方的 ACK 之前,这个 sk_buff 不能被删除。所以内核的做法就是每次调用网卡发送的时候,实际上传递出去的是 sk_buff 的一个拷贝,等收到 ACK 再真正删除 。
113127
114- 接下来,网络协议栈从 Socket 发送缓冲区中取出数据包,并按照 TCP/ IP 协议栈从上到下逐层处理 。
128+ 接着,对 sk_buff 填充 TCP 头。这里提一下,sk_buff 可以表示各个层的数据包,在应用层数据包叫 data,在 TCP 层我们称为 segment,在 IP 层我们叫 packet,在数据链路层称为 frame 。
115129
116- 如果使用的是 TCP 传输协议发送数据,那么会在传输层增加 TCP 包头,然后交给网络层,网络层会给数据包增加 IP 包,然后通过查询路由表确认下一跳的 IP,并按照 MTU 大小进行分片 。
130+ 你可能会好奇,为什么全部数据包只用一个结构体来描述呢?协议栈采用的是分层结构,上层向下层传递数据时需要增加包头,下层向上层数据时又需要去掉包头,如果每一层都用一个结构体,那在层之间传递数据的时候,就要发生多次拷贝,这将大大降低 CPU 效率 。
117131
118- 分片后的网络包,就会被送到网络接口层,在这里会通过 ARP 协议获得下一跳的 MAC 地址,然后增加帧头和帧尾,放到发包队列中。
132+ 于是,为了在层级之间传递数据时,不发生拷贝,只用 sk_buff 一个结构体来描述所有的网络包,那它是如何做到的呢?是通过调整 sk_buff 中 ` data ` 的指针,比如:
119133
120- 这一些准备好后,会触发软中断告诉网卡驱动程序,这里有新的网络包需要发送,最后驱动程序通过 DMA,从发包队列中读取网络包,将其放入到硬件网卡的队列中,随后物理网卡再将它发送出去。
134+ - 当接收报文时,从网卡驱动开始,通过协议栈层层往上传送数据报,通过增加 skb->data 的值,来逐步剥离协议首部。
135+ - 当要发送报文时,创建 sk_buff 结构体,数据缓存区的头部预留足够的空间,用来填充各层首部,在经过各下层协议时,通过减少 skb->data 的值来增加协议首部。
121136
122- ----
137+ 你可以从下面这张图看到,当发送报文时,data 指针的移动过程。
138+
139+ ![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/sk_buff.jpg )
140+
141+ 至此,传输层的工作也就都完成了。
142+
143+ 然后交给网络层,在网络层里会做这些工作:选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片。处理完这些工作后会交给网络接口层处理。
144+
145+ 网络接口层会通过 ARP 协议获得下一跳的 MAC 地址,然后对 sk_buff 填充帧头和帧尾,接着将 sk_buff 放到网卡的发送队列中。
146+
147+ 这一些工作准备好后,会触发「软中断」告诉网卡驱动程序,这里有新的网络包需要发送,驱动程序会从发送队列中读取 sk_buff,将这个 sk_buff 挂到 RingBuffer 中,接着将 sk_buff 数据映射到网卡可访问的内存 DMA 区域,最后触发真实的发送。
148+
149+ 当数据发送完成以后,其实工作并没有结束,因为内存还没有清理。当发送完成的时候,网卡设备会触发一个硬中断来释放内存,主要是释放 sk_buff 内存和清理 RingBuffer 内存。
150+
151+ 最后,当收到这个 TCP 报文的 ACK 应答时,传输层就会释放原始的 sk_buff 。
152+
153+ > 发送网络数据的时候,涉及几次内存拷贝操作?
154+
155+ 第一次,调用发送数据的系统调用的时候,内核会申请一个内核态的 sk_buff 内存,将用户待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区。
156+
157+ 第二次,在使用 TCP 传输协议的情况下,从传输层进入网络层的时候,每一个 sk_buff 都会被克隆一个新的副本出来。副本 sk_buff 会被送往网络层,等它发送完的时候就会释放掉,然后原始的 sk_buff 还保留在传输层,目的是为了实现 TCP 的可靠传输,等收到这个数据包的 ACK 时,才会释放原始的 sk_buff 。
158+
159+ 第三次,当 IP 层发现 sk_buff 大于 MTU 时才需要进行。会再申请额外的 sk_buff,并将原来的 sk_buff 拷贝为多个小的 sk_buff。
123160
124161## 总结
125162
126- 电脑与电脑之间通常都是通话网卡 、交换机、路由器等网络设备连接到一起,那由于网络设备的异构性,国际标准化组织定义了一个七层的 OSI 网络模型,但是这个模型由于比较复杂,实际应用中并没有采用,而是采用了更为简化的 TCP/IP 模型,Linux 网络协议栈就是按照了该模型来实现的。
163+ 电脑与电脑之间通常都是通过话网卡 、交换机、路由器等网络设备连接到一起,那由于网络设备的异构性,国际标准化组织定义了一个七层的 OSI 网络模型,但是这个模型由于比较复杂,实际应用中并没有采用,而是采用了更为简化的 TCP/IP 模型,Linux 网络协议栈就是按照了该模型来实现的。
127164
128165TCP/IP 模型主要分为应用层、传输层、网络层、网络接口层四层,每一层负责的职责都不同,这也是 Linux 网络协议栈主要构成部分。
129166
130167当应用程序通过 Socket 接口发送数据包,数据包会被网络协议栈从上到下进行逐层处理后,才会被送到网卡队列中,随后由网卡将网络包发送出去。
131168
132169而在接收网络包时,同样也要先经过网络协议栈从下到上的逐层处理,最后才会被送到应用程序。
133170
171+ ----
172+
173+ 参考资料:
174+
175+ - Linux 网络包发送过程:https://mp.weixin.qq.com/s/wThfD9th9e_-YGHJJ3HXNQ
176+ - Linux 网络数据接收流程(TCP)- NAPI:https://wenfh2020.com/2021/12/29/kernel-tcp-receive/
177+ - Linux网络-数据包接收过程:https://blog.csdn.net/frank_jb/article/details/115841622
178+
134179---
135180
136181哈喽,我是小林,就爱图解计算机基础,如果觉得文章对你有帮助,别忘记关注我哦!
137182
138- ![ ] ( https://cdn.jsdelivr.net /gh/xiaolincoder/ImageHost2/%E5%85%B6%E4%BB%96/%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BB%8B%E7%BB%8D.png )
183+ ![ ] ( https://cdn.xiaolincoding.com /gh/xiaolincoder/ImageHost2/%E5%85%B6%E4%BB%96/%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BB%8B%E7%BB%8D.png )
139184
0 commit comments