这篇文章讲解了 OS 接收到数据包后一系列的处理流程。
接收数据的过程
在 Linux 网络协议栈中,数据的接收过程是一个逐层解封装的过程,也就是说,数据在每一层都会去除相应的头部信息,然后传递给上一层。这个过程通常被称为数据解封装或解包。下面我们以一个 TCP/IP 网络模型中的 HTTP 响应为例来说明这个过程:
-
网络接口层(Ethernet):首先,网络接口层接收到一个以太网帧。这个帧包含了源 MAC 地址、目标 MAC 地址和以太网数据。然后,它会去除以太网头,将剩下的数据(也就是 IP 数据报)传递给网络层。
-
网络层(IP):在网络层,IP 协议会去除 IP 头,这个头包含了源 IP 地址、目标 IP 地址、生存时间(TTL)等信息。然后,它会将剩下的数据(也就是 TCP 段)传递给传输层。
-
传输层(TCP):在传输层,TCP 协议会去除 TCP 头,这个头包含了源端口、目标端口、序列号、确认号等信息。然后,它会将剩下的数据(也就是 HTTP 响应)传递给应用层。
-
应用层(HTTP):最后,在应用层,应用程序(例如浏览器)会接收到 HTTP 响应,这个响应包含了状态行、响应头和响应体等信息。
以上就是数据在 Linux 网络协议栈中的解封装和接收过程。数据在各层去除头部信息,最后变为一个完整的 HTTP 响应,可以被应用程序处理。需要注意的是,这个过程是一个简化的描述,实际的过程可能会更复杂,因为还需要考虑到错误检测、流量控制、拥塞控制等因素。
接下来展开每一部分详细讲解。
Linux 网卡接收数据的细节
网卡是计算机中的一个重要硬件设备,它的主要功能是接收和发送网络数据包。在接收数据包时,网卡会使用一种叫做直接内存访问(DMA)的技术。DMA是一种高效的数据传输方式,它允许硬件设备直接读写系统内存,而无需通过CPU,从而大大提高了数据传输的效率。
当网卡接收到一个网络数据包后,它会使用DMA技术,将数据包写入到系统内存的一个特定区域,这个区域被称为Ring Buffer(环形缓冲区)。环形缓冲区是一种特殊的数据结构,它的特点是在逻辑上形成一个环,当数据写入到环形缓冲区的末尾时,下一个数据会被写入到环形缓冲区的开始位置,从而形成一个环。
环形缓冲区的一个重要应用就是在网络数据的接收和发送中。当网卡接收到一个网络数据包后,会将数据包写入到环形缓冲区,然后通过一个中断信号,通知操作系统有新的网络数据包到达。操作系统在接收到这个中断信号后,会从环形缓冲区中读取数据包,并进行后续的处理。
需要注意的是,环形缓冲区的大小是有限的,如果网络数据包的接收速度超过了操作系统处理的速度,环形缓冲区可能会被填满,这时新到达的网络数据包就无法被写入到环形缓冲区,从而导致数据包的丢失。因此,操作系统需要及时地处理网络数据包,以防止环形缓冲区溢出。
环形缓冲区(Ring Buffer)
环形缓冲区(Ring Buffer)是一种特殊的数据结构,它在逻辑上形成一个环。当数据写入到环形缓冲区的末尾时,下一个数据会被写入到环形缓冲区的开始位置,从而形成一个环。
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | | |
| D1 | D2 | D3 | D4 | D5 | | | |
| | | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
^ ^
| |
| |
+-----------------------------+
Start End
在这个例子中,环形缓冲区有8个位置,目前已经写入了5个数据(D1, D2, D3, D4, D5)。当下一个数据(D6)到来时,它会被写入到环形缓冲区的开始位置,形成一个环:
+-----+-----+-----+-----+-----+-----+-----+-----+
| | | | | | | | |
| D6 | D2 | D3 | D4 | D5 | | | |
| | | | | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
^ ^
| |
| |
+-----------------------------+
Start End
在Linux中,网卡的Ring Buffer通常存储的是数据本身,而不是指针。当网卡接收到一个网络数据包时,它会使用DMA(Direct Memory Access)技术,将数据包直接写入到Ring Buffer中。这样做的好处是可以避免CPU的介入,从而提高数据传输的效率。
需要注意的是,虽然Ring Buffer中存储的是数据本身,但是这些数据通常是以缓冲区描述符(Buffer Descriptor)的形式进行管理的。每个缓冲区描述符都包含了一个指向实际数据的指针,以及其他一些元信息,如数据的长度、状态等。当操作系统需要处理网络数据包时,它会通过读取缓冲区描述符,来获取到实际的数据。
在Linux中,网络数据包本身是存储在环形缓冲区中的,而缓冲区描述符(Buffer Descriptor)则存储在内存的另一个特定区域。每个缓冲区描述符都包含了一个指向环形缓冲区中实际数据的指针,以及其他一些元信息,如数据的长度、状态等。操作系统通过读取和管理这些缓冲区描述符,来读取和处理环形缓冲区中的网络数据包。
+-------------------+-------------------+-------------------+-------------------+
| Buffer Desc 1 | Buffer Desc 2 | Buffer Desc 3 | Buffer Desc 4 |
+-------------------+-------------------+-------------------+-------------------+
| Pointer | Length | Pointer | Length | Pointer | Length | Pointer | Length |
+----------+--------+----------+--------+----------+--------+----------+--------+
| 0x1000 | 1500 | 0x2000 | 2000 | 0x3000 | 1000 | 0x4000 | 2500 |
+----------+--------+----------+--------+----------+--------+----------+--------+
在这个例子中,我们有4个缓冲区描述符,每个描述符都包含了一个指向实际数据的指针和数据的长度。例如,第一个描述符指向内存地址0x1000,数据长度为1500字节。
当操作系统需要处理网络数据包时,它会通过读取这些缓冲区描述符,来获取到实际的数据。例如,如果操作系统需要处理第一个数据包,它会读取第一个缓冲区描述符,然后根据描述符中的指针和长度,从内存中读取相应的数据。
如何通知 OS 从环形缓冲区中取数据?
当网卡接收到一个网络数据包并将其写入环形缓冲区后,它会通过发送一个中断信号来通知操作系统有新的网络数据包到达。这个中断信号被操作系统的中断处理程序(Interrupt Handler)捕获。
中断处理程序是操作系统的一部分,它的任务是响应硬件设备发出的中断信号。当中断处理程序接收到网卡发出的中断信号时,它会读取环形缓冲区中的数据,并将其传递给网络协议栈进行进一步处理。
这个过程是由硬件和操作系统内核自动完成的,不需要应用程序进行任何操作。这是因为网络数据包的接收和处理通常需要在非常短的时间内完成,而且需要操作系统级别的权限,因此这个过程通常是在操作系统内核中完成的。
这就是网卡如何通知操作系统取数据的基本过程。
中断过于频繁如何解决?
在高性能网络环境中,网络数据包的数量可能会非常多。如果每接收到一个数据包就触发一次中断,那么CPU就需要频繁地处理这些中断,这会导致CPU的大部分时间都被用于处理中断,而无法执行其他任务,从而影响系统的整体效率。
为了解决这个问题,Linux内核在2.6版本中引入了一种新的机制,叫做NAPI(New API)。NAPI的核心思想是混合使用中断和轮询两种方式来接收网络数据包。
在NAPI机制中,当网络数据包到达时,网卡会首先通过中断的方式通知CPU。然后,CPU会启动一个数据接收的服务程序,这个服务程序会使用poll
的方式来轮询数据。也就是说,服务程序会不断地检查是否有新的数据包到达,如果有,就立即处理,如果没有,就继续轮询。
这种方式的好处是,当网络数据包的数量较少时,可以通过中断的方式快速响应;当网络数据包的数量较多时,可以通过轮询的方式避免频繁的中断,从而提高系统的效率。
需要注意的是,虽然NAPI机制可以有效地提高系统的效率,但是它也有一些限制。例如,它需要硬件设备支持,并且在某些情况下,可能需要对驱动程序进行修改才能使用。
网卡接收数据过程中的硬件中断和软件中断
在Linux中,当网络数据包到达时,网卡会通过DMA(Direct Memory Access)技术将数据包写入到指定的内存地址,然后向CPU发起硬件中断。CPU在接收到硬件中断请求后,会根据中断表调用已经注册的硬件中断处理函数。
硬件中断处理函数的主要任务包括:
-
首先,它会暂时屏蔽中断。这是为了告诉网卡,CPU已经知道内存中有新的数据包,下次网卡再收到数据包时,可以直接将数据包写入内存,而不需要再通知CPU。这样可以避免CPU频繁地被中断,从而提高系统的效率。
-
然后,硬件中断处理函数会发起软中断,并恢复刚才屏蔽的中断。
至此,硬件中断处理函数的工作就完成了。接下来,主要的工作就交给了软件中断处理函数。
软中断处理函数的主要任务是读取内存中的数据包,并将其传递给网络协议栈进行进一步处理。这个过程可能会涉及到一些复杂的操作,如数据包的解析、协议的处理等,因此通常需要消耗一定的CPU时间。但是,由于软中断处理函数是在中断上下文中运行的,因此它可以在不影响其他任务的情况下,高效地处理网络数据包。
这就是Linux中网络数据包到达时,如何通知操作系统的基本过程。
硬件中断和软件中断的区别
硬件中断和软件中断是计算机系统中两种重要的中断类型。
硬件中断是由硬件设备产生的,通常是因为硬件设备需要CPU的注意。例如,当网络卡接收到一个网络数据包时,它会向CPU发送一个硬件中断。在这种情况下,硬件中断是用来通知CPU有新的网络数据包到达,需要处理。
在代码中,硬件中断通常由特定的中断处理函数来处理。例如,在Linux中,当网络卡接收到一个网络数据包并将其写入环形缓冲区后,它会通过发送一个中断信号来通知操作系统有新的网络数据包到达。这个中断信号被操作系统的中断处理程序(Interrupt Handler)捕获。
软件中断则是由软件程序产生的,通常是因为程序需要操作系统的服务。软件中断通常用于实现系统调用。例如,当一个程序需要读取一个文件时,它会通过发出一个软件中断来请求操作系统提供文件读取服务。
在代码中,软件中断通常由特定的中断处理函数来处理。例如,在Linux中,硬件中断处理函数在处理完硬件中断后,会发起一个软中断。软中断处理函数的主要任务是读取环形缓冲区中的数据,并将其传递给网络协议栈进行进一步处理。
总的来说,硬件中断和软件中断都是计算机系统中的重要机制,它们都是用来通知CPU有重要的任务需要处理。硬件中断通常由硬件设备产生,而软件中断通常由软件程序产生。
软件中断
在Linux内核中,软中断(SoftIRQ)是一种特殊的中断机制,主要用于处理一些不能在硬件中断上下文中完成的任务。软中断的处理通常由内核线程ksoftirqd来完成。
当硬件中断处理函数处理完硬件中断后,它会发起一个软中断。这个软中断的主要任务是读取内存中的数据包,并将其传递给网络协议栈进行进一步处理。
ksoftirqd线程会从环形缓冲区(Ring Buffer)中获取一个数据帧,这个数据帧用sk_buff结构体来表示。sk_buff是Linux内核中用于表示网络数据包的主要数据结构,它包含了网络数据包的所有信息,包括数据包的内容、长度、源地址和目标地址等。
获取到数据帧后,ksoftirqd线程会将其交给网络协议栈进行处理。网络协议栈会根据数据包的协议类型,将其传递给相应的协议处理函数,如IP协议处理函数、TCP协议处理函数等。这些协议处理函数会对数据包进行解析,提取出数据包的有效载荷,并将其传递给上层的应用程序。
这就是Linux内核中软中断处理的基本过程。通过这种方式,Linux内核可以在不影响其他任务的情况下,高效地处理网络数据包。
网络协议栈
网络协议栈是计算机网络中用于实现网络通信的一组协议的集合。这些协议按照功能的不同,被分为不同的层次,形成了一个协议栈。每一层都有其特定的功能,上层协议依赖于下层协议提供的服务。
在网络数据包的接收过程中,数据包会经过以下几个步骤:
-
网络接口层:这一层主要负责数据包的接收和发送。当网络接口层接收到一个数据包时,它会检查数据包的合法性。如果数据包不合法,就会被丢弃。如果数据包合法,就会找出数据包的上层协议类型(如IPv4或IPv6),然后去掉帧头和帧尾,将数据包交给网络层。
-
网络层:网络层主要负责数据包的路由和转发。当网络层接收到一个数据包时,它会取出IP包,然后判断数据包的下一步走向。如果数据包需要发送给本机,就会从IP头中找出上一层协议的类型(如TCP或UDP),然后去掉IP头,将数据包交给传输层。
-
传输层:传输层主要负责提供端到端的通信服务。当传输层接收到一个数据包时,它会取出TCP头或UDP头,然后根据四元组(源IP、源端口、目的IP、目的端口)找出对应的Socket,然后将数据放入Socket的接收缓冲区。
-
应用层:应用层主要负责处理应用程序的网络请求。当应用层程序调用Socket接口时,它会将内核的Socket接收缓冲区的数据拷贝到应用层的缓冲区,然后唤醒用户进程。
这就是一个网络数据包的接收过程。同样的,网络数据包的发送过程也会经过类似的步骤,只是方向相反。
总结
在Linux网络协议栈中,数据接收过程是逐层解封装的过程,包括网络接口层、网络层、传输层和应用层。每一层都会去除相应的头部信息,然后传递给上一层。当网络数据包到达时,网卡会通过DMA技术将数据包写入内存,然后发起硬件中断通知CPU。CPU接收到中断后,会调用硬件中断处理函数,处理完后发起软中断,由内核线程ksoftirqd处理,读取数据包并传递给网络协议栈进行进一步处理。
原文始发于微信公众号(everystep):Linux 内核接收数据的过程
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/269646.html