理解 TCP/IP 网络栈 & 编写网络应用

理解TCP/IP网络层协议栈 1. TCP/IP的一些关键特性 面向连接 先建立连接,再进行数据传输。 双向字节流 使用字节流来实现双向数据通信 有序传输 数据的接收顺序和它们的发送顺序一致 基于ACK的可靠传输 没有收到ACK的时候,发送方会重传 流控 拥塞控制 2. 数据发送 参看如下这张图, 这张图里面的这些处理层可以分成三层 用户空间 内核空间 设备空间 用户空间和内核空间的任务都通过CPU来执行。用户空间和内核空间通常称为host,区别于设备空间。 2.1. 用户空间管理 .. 阅读详情

1.译注

之前在网上看到了这篇描述tcp网络栈原理的文章,感觉不错,决定抽空把这篇文章翻译一下。一来重新温习一下TCP相关知识,二来练练英文。 很久没翻译文章了难免有误,建议有能力的同学还是看一下原文。

2.概述

我们难以想象没有了TCP/IP之后的网络服务。所有我们开发并在NHN使用的网络服务都基于TCP/IP这个坚实的基础。理解数据如何通过网络传输可以帮助你通过调优、排查或引进新技术之类的手段提升性能。

本文将基于Linux OS和硬件层的数据流和控制流来描述网络栈的整体运行方式。

3.TCP/IP的关键特性

我如何设计一个能在快速传输数据的同时保证数据顺序并且不丢失数据的网络协议?TCP/IP在设计时就基于这个考虑。以下是在了解整体网络栈前需要知道的TCP/IP的主要特性:

TCP和IP
从技术上讲,TCP和IP处于不同的层,应该分别解释它们。但在这里我们把他们看做一个整体。

  1. 面向连接首先,传输数据前需要在两个终端之间建立连接(本地和远程)。在这里,“TCP连接标识符(TCP connection identifier)”是两个终端地址的组合,类似本地ip地址,本地端口号,远程ip地址,远程端口号的形式。
  2. 双向字节流通过字节流实现双向数据通信。
  3. 顺序投递接收者在接收数据时与发送者发送的数据顺序相同。因此,数据需要是有序的,为了表示这个顺序,TCP/IP使用了32位的int数据类型。
  4. 通过ACK实现可靠性当发送者向接收者发送数据,但没有收到来自接收方的ACK(acknowledgement,应答)时,发送者的TCP层将重发数据。因此,发送者的TCP层会把接收者还没有应答的数据暂存起来。
  5. 流量控制发送者的发送速度与接收者的接收能力相关。接收者会把它能接收的最大字节数(未使用的缓冲区大小,又叫接收窗口,receive window)告知发送者。发送者发送的最大字节数与接收者的接收窗口大小一致。
  6. 阻塞控制阻塞窗口是不同于接收窗口的另一个概念,它通过限制网络中的数据流的体积来防止网络阻塞。类似于接收窗口,发送者通过通过一些算法(例如TCP Vegas,Westwood,BIC,CUBIC)来计算发送对应的接收者的阻塞窗口能容纳的最多的数据。和流量控制不同,阻塞控制只在发送方实现。(译注:发送者类似于通过ack时间之类的算法判断当前网络是否阻塞,从而调节发送速度)

4.数据传输

网络栈有很多层。图一表示了这些层的类型:

图1:发送数据时网络栈中各层对数据的操作

这些层可以被大致归类到三个区域中:

  1. 用户区
  2. 内核区
  3. 设备区

用户区和内核区的任务由CPU执行。用户区和内核区被叫做“主机(host)”以区别于设备区。在这里,设备是发送和接收数据包的网络接口卡(Network Interface Card,NIC)。它有一个更常用的术语:网卡。

我们来了解一下用户区。首先,应用程序创建要发送的数据(图1中的“User Data”)并且调用write()系统调用来发送数据(在这里假设socket已经被创建了,对应图1中的“fd”)。当系统调用被调用之后上下文切换到内核区。

像Linux或者Unix这类POSIX系列的操作系统通过文件描述符(file descriptor)把socket暴露给应用程序。在这类系统中,socket是文件的一种。文件系统执行简单的检查并调用socket结构中指向的socket函数。

内核中的socket包含两个缓冲区。

  1. 一个用于缓冲要发送的数据
  2. 一个用于缓冲要接收的数据

当write()系统调用被调用时,用户区的数据被拷贝到内核内存中,并插入到socket的发送缓冲区末尾。这样来保证发送的数据有序。在图1中,浅灰色框表示在socket缓冲区中的数据。之后,TCP被调用了。

socket会关联一个叫做TCP控制块(TCP Control Block)的结构,TCB包含了处理TCP连接所需的数据。包括连接状态(LISTEN,ESTABLISHED,TIME_WAIT),接收窗口,阻塞窗口,顺序号,重发计时器,等等。

如果当前的TCP状态允许数据传输,一个新的TCP分段(TCP segment,或者叫数据包,packet)将被创建。如果由于流量控制或者其它原因不能传输数据,系统调用会在这里结束,之后会返回到用户态。(换句话说,控制权会交回到应用程序代码)

TCP分段有两部分

  1. TCP头
  2. 携带的数据

图2:TCP帧的结构

TCP数据包的payload部分会包含在socket发送缓冲区里的没有应答的数据。携带数据的最大长度是接收窗口、阻塞窗口和最大分段长度(maximum segment size,MSS)中的最大值。

之后会计算TCP校验值。在计算时,头信息(ip地址、分段长度和端口号)会包含在内。根据TCP状态可发送一个或多个数据包。

事实上,当前的网络栈使用了校验卸载(checksum offload),TCP校验和会由NIC计算,而不是内核。但是,为了解释方便我们还是假设内核计算校验和。

被创建的TCP分段继续走到下面的IP层。IP层向TCP分段中增加了IP头并且执行了IP路由(IP routing)。IP路由是寻找到达目的IP的下一跳IP地址的过程。

在IP层计算并增加了IP头校验和之后,它把数据发送到链路层。链路层通过地址解析协议(Address Resolution Protocol,ARP)搜索下一跳IP地址对应的MAC地址。之后它会向数据包中增加链路头,在增加链路头之后主机要发送的数据包就是完整的了。

在执行IP路由时,会选择一个传输接口(NIC)。接口被用于把数据包传送至下一跳IP。于是,用于发送的NIC驱动程序被调用了。

在这个时候,如果正在执行数据包捕获程序(例如tcpdump或wireshark)的话,内核将把数据包拷贝到这些程序的内存缓冲区中。用相同的方式,接收的数据包直接在驱动被捕获。通常来说,traffic shaper(没懂)函数被实现以在这个层上运行。

驱动程序(与内核)通过请求NIC制造商定义的通讯协议传输数据。

在接收到数据包传输请求之后,NIC把数据包从系统内存中拷贝到它自己的内存中,之后把数据包发送到网络上。在此时,由于要遵守以太网标准(Ethernet standard),NIC会向数据包中增加帧间隙(Inter-Frame Gap,IFG),同步码(preamble)和crc校验和。帧间隙和同步码用于区分数据包的开始(网络术语叫做帧,framing),crc用于保护数据(与TCP或者IP校验和的目的相同)。NIC会基于以太网的物理速度和流量控制决定数据包开始传输的时间。It is like getting the floor and speaking in a conference room.(没看懂)

当NIC发送了数据包,NIC会在主机的CPU上产生中断。所有的中断会有自己的中断号,操作系统会用这个中断号查找合适的程序去处理中断。驱动程序在启动时会注册一个处理中断的函数。操作系统调用中断处理程序,之后中断处理程序会把已发送的数据包返回给操作系统。

到此为止我们讨论了当应用程序执行了write之后,数据流经内核和设备的过程。但是,除了应用程序直接调用write之外,内核也可以直接调用TCP传输数据包。比如当接收到ACK并且得知接收方的接收窗口增大之后,内核会创建包含socket缓冲区剩余数据的TCP片段并且把数据发送给接收者。

5.数据接收

现在我们看一下数据是如何被接收的。数据接收是网络栈如何处理流入数据包的过程。图3展现了网络栈如何处理接收的数据包。

图3:接收数据时网络栈中各层对数据的操作

首先,NIC把数据包写入它自身的内存。通过CRC校验检查数据包是否有效,之后把数据包发送到主机的内存缓冲区。这里说的缓冲区是驱动程序提前向内核申请好的一块内存区域,用于存放接收的数据包。在缓冲区被系统分配之后,驱动会把这部分内存的地址和大小告知NIC。如果主机没有为驱动程序分配缓冲区,那么当NIC接收到数据包时有可能会直接丢弃它。

在把数据包发送到主机缓冲区之后,NIC会向主机发出中断。

之后,驱动程序会判断它是否能处理新的数据包。到目前为止使用的是由制造商定义的网卡驱动的通讯协议。

当驱动应该向上层发送数据包时,数据包必须被放进一个操作系统能够理解和使用的数据结构。例如Linux的sk_buff,或者BSD系列内核的mbuf,或者windows的NET_BUFFER_LIST。驱动会把数据包包装成指定的数据结构,并发送到上一层。

链路层会检查数据包是否有效并且解析出上层的协议(网络协议)。此时它会判断链路头中的以太网类型。(IPv4的以太网类型是0x0800)。它会把链路头删掉并且把数据包发送到IP层。

IP层同样会检查数据包是否有效。或者说,会检查IP头校验和。它会执行IP路由判断,判断是由本机处理数据包还是把数据包发送到其它系统。如果数据包必须由本地系统处理,IP层会通过IP header中引用的原型值(proto value)解析上层协议(传输协议)。TCP原型值为6。系统会删除IP头,并且把数据包发送到TCP层。

就像之前的几层,TCP层检查数据包是否有效,同时会检查TCP校验和。就像之前提到的,如果当前的网络栈使用了校验卸载,那么TCP校验和会由NIC计算,而不是内核。

之后它会查找数据包对应的TCP控制块(TCB),这时会使用数据包中的<源ip,源端口,目的IP,目的端口>做标识。在查询到对应的连接之后,会执行协议中定义的操作去处理数据包。如果接收到的是新数据,数据会被增加到socket的接收缓冲区。根据tcp连接的状态,此时也可以发送新的TCP包(比如发送ACK包)。此时,TCP/IP接收数据包的处理完成。

socket接收缓冲区的大小就是TCP接收窗口。TCP吞吐量会随着接收窗口变大而增加。过去socket缓冲区大小是应用或操作系统的配置的定值。最新的网络栈使用了一个函数去自动决定接收缓冲区的大小。

当应用程序调用read系统调用时,程序会切换到内核区,并且会把socket接收缓冲区中的数据拷贝到用户区。拷贝后的数据会从socket缓冲区中移除。之后TCP会被调用,TCP会增加接收窗口的大小(因为缓冲区有了新空间)。并且会根据协议状态发送数据包。如果没有数据包传送,系统调用结束。

6.网络栈开发方向

到此为止描述的网络栈中的函数都是最基础的函数。在1990年代早期的网络栈函数只比上面描述的多一些。但是最近的随着网络栈的实现层次变高,网络栈增加了很多函数和复杂度。

最新的网络栈可以按以下需求分类:

6.1.操作报文处理过程

它包括类似Netfilter(firewall,NAT)的功能和流量控制。通过在处理流程中增加用户可以控制的代码,可以由用户控制实现不同的功能。

6.2.协议性能

用于改进特定网络环境下的吞吐量、延迟和可靠性。典型例子是增加的流量控制算法和额外的类似SACK的TCP功能。由于已经超出了范围,在这里就不深入讨论协议改进了。

6.3.高效的报文处理

高效的报文处理指的是通过降低处理报文的CPU周期、内存用量和处理报文需要访问内存的次数这些手段,来提升每秒可以处理的报文数量。有很多降低系统延迟的尝试,比如协议栈并行处理(stack parallel processing)、报头预处理(header prediction)、零拷贝(zero-copy)、单次拷贝(single-copy)、校验卸载(checksum offload)、TSO(TCP Segment Offload)、LRO(Large Receive Offload)、RSS(Receive Side Scaling)等。

7.网络栈中的流程控制

现在我们看一下Linux网络栈内部流程的细节。网络栈基于事件驱动的方式运行,在事件产生时做出相应的反应。因此,没有一个独立的线程去运行网络栈。图1和图3展示了最简单的控制流程图。图4说明了更加准确的控制流程。

图4:网络栈中的流程控制

在图4中的Flow(1),应用程序通过系统调用去执行(使用)TCP。例如,调用read系统调用或者wirte系统调用并执行TCP。但是,这一步里没有数据包传输。

Flow(2)跟Flow(1)类似,在执行TCP后需要传输报文。它会创建数据包并且把数据包发送给驱动程序。驱动程序上层有一个队列。数据包首先被放入队列,之后队列的数据结构决定何时把数据包发送给驱动程序。这个是Linux的队列规则(queue discipline,qdisc)。Linux的传输管理函数用来管理队列规则。默认的队列规则是简单的先入先出队列。通过使用其它的队列管理规则,可以执行多种操作,例如人造数据丢包、数据包延迟、通信比率限制,等等。在Flow(1)和Flow(2)中,驱动和应用程序处于相同的线程。

Flow(3)展示了TCP使用的定时器超时的情况。比如,当TIME_WAIT定时器超时后,TCP被调用并删除连接。

类似Flow(3),Flow(4)是TCP的定时器超时并且需要发送TCP执行结果数据包的情况。比如,当重传计时器超时后,会发送“没有收到ACK”数据包。

Flow(3)和Flow(4)展示了定时器软中断的处理过程。

当NIC驱动收到中断时,它将释放已经传输的数据包。在大多数情况下,驱动的执行过程到这里就结束了。Flow(5)是数据包积累在传输队列里的情况。驱动请求软中断,之后软中断的处理程序把传输队列里堆积的数据包发送给驱动程序。

当NIC驱动程序接收到中断并且发现有新接收的数据包时,它会请求软中断。软中断会调用驱动程序处理接收到的数据包并且把他们传送到上一层。在Linux中,上面描述的处理接收到的数据包的过程叫做New API(NAPI)。它和轮询类似,因为驱动并不直接把数据包传送到上一层,而是上层直接来取数据。实际代码中叫做NAPI poll 或 poll。

Flow(6)展示了TCP执行完成,Flow(7)展示了需要更多数据包传输的情况。Flow(5、6、7)的NIC中断都是通过软中断执行的。

8.如何处理中断和接收数据包

中断处理过程是复杂的,但是你需要了解数据包接收和处理流程中的和性能相关的问题。图5展示了一次中断的处理流程。

图5:处理中断、软中断和接收数据

假设CPU0正在执行应用程序。在此时,NIC接收到了一个数据包并且在CPU0上产生了中断。CPU0执行了内核中断处理程序(irq)。这个处理程序关联了一个中断号并且内核会调用驱动里对应的中断处理程序。驱动在释放已经传输的数据包之后调用napi_schedule()函数去处理接收到的数据包,这个函数会请求软中断。

在执行了驱动的中断处理程序后,控制权被转移到内核中断处理程序。内核中的处理程序会执行软中断的处理程序。

在中断上下文被执行之后,软中断的上下文会被执行。中断上下文和软中断上下文会通过相同的线程执行,但是它们会使用不同的栈。并且中断上下文会屏蔽硬件中断;而软中断上下文不会屏蔽硬件中断。

处理接收到的数据包的软中断处理程序是net_rx_action()函数。这个函数会调用驱动程序的poll()函数。而poll()函数会调用netif_receive_skb()函数,并把接收到的数据包一个接一个的发送到上层。在软中断处理结束后,应用程序会从停止的位置重新开始执行。(After processing the softirq, the application restarts execution from the stopped point in order to request a system call.没太明白这一句的system call是啥意思)

因此,接收中断请求的CPU会负责处理接收数据包从始至终的整个过程。在Linux、BSD和Windows中,处理过程基本是类似的。

当你检查服务器CPU利用率时,有时你可以检查服务器的很多CPU中是否只有一个CPU在艰难执行软中断。这个现象产生的原因就是上文所解释的数据包接收的处理过程。为了解决这个问题开发出了多队列NIC(multi-queue NIC)、RSS和RPS。

下面是译者翻译的《理解TCP/IP网络栈&编写网络应用》的下篇,会通过讲解TCP的代码实现帮助大家理解发送、接收数据的流程,也描述了一些网卡、驱动等网络栈底层的原理。

8.数据结构

以下是一些关键数据结构。我们了解一下这些数据结构再开始查看代码。

8.1.sk_buff_structure

首先,sk_buff结构或skb结构代表一个数据包。图6展现了sk_buff中的一些结构。随着功能变得更强大,它们也变得更复杂了。但是还是有一些任何人都能想到的基本功能。

图6:数据包结构

8.1.1.包含数据和元数据

这个结构直接包含或者通过指针引用了数据包。在图6中,一些数据包(从Ethernet到Buffer部分)使用了指针,一些额外的数据(frags)引用了实际的内存页。

一些必要的信息比如头和内容长度被保存在元数据区。例如,在图6中,mac_header、network_header和transport_header都有相应的指针,指向链路头、IP头和TCP头的起始地址。这种方式让TCP协议处理过程变得简单。

8.1.2.如何增加或删除头

数据包在网络栈的各层中上升或下降时会增加或删除数据头。为了更有效率的处理而使用了指针。例如,要删除链路头只需要修改head pointer的值。

8.1.3.如何合并或切分数据包

为了更有效率的执行把数据包增到或从socket缓冲区中删除这类操作而使用了链表,或者叫数据包链。next和prev指针用于这个场景。

8.1.4.快速分配和释放

无论何时创建数据包都会分配一个数据结构,此时会用到快速分配器。比如,如果数据通过10Gb的以太网传输,每秒会有超过一百万个对象被创建和销毁。

9.TCP控制块(TCP Control Block)

其次,有一个表示TCP连接的数据结构,之前它被抽象的叫做TCP控制块。Linux使用了tcp_sock这个数据结构。在图7中,你可以看到文件、socket和tcp_socket的关系。

图7:TCP Connection结构

当系统调用发生后,它会找到应用程序在进行系统调用时使用的文件描述符对应的文件。对Unix系的操作系统来说,文件本身和通用文件系统存储的设备都被抽象成了文件。因此,文件结构包含了必要的信息。对于socket来说,使用独立的socket结构保存socket相关的信息,文件结构通过指针来引用socket。socket又引用了tcp_sock。tcp_sock可以分为sock,inet_sock等等,用来支持除了TCP之外的协议,可以认为这是一种多态。

所有TCP协议用到的状态信息都被存在tcp_sock里。例如顺序号、接收窗口、阻塞控制和重发送定时器都保存在tcp_sock中。

socket的发送缓冲区和接收缓冲区由sk_buff链表组成并被包含在tcp_sock中。为防止频繁查找路由,也会在tcp_sock中引用IP路由结果dst_entry。通过dst_entry可以简单的查找到目标的MAC地址之类的ARP的结果。dst_entry是路由表的一部分,而路由表是个很复杂的结构,在这篇文档里就不再讨论了。用来传送数据的网卡(NIC)可以通过dst_entry找到。网卡通过net_device描述。

因此,仅通过查找文件和指针就可以很简单的查找到处理TCP连接的所有的数据结构(从文件到驱动)。这个数据结构的大小就是每个TCP连接占用内存的大小。这个结构占用的内存只有几kb大小(排除了数据包中的数据)。但随着一些功能被加入,内存占用也在逐渐增加。

最后,我们来看一下TCP连接查找表(TCP connection lookup table)。这是一个用来查找接收到的数据包对应tcp连接的哈希表。系统会用数据包的<来源ip,目标ip,来源端口,目标端口>和Jenkins哈希算法去计算哈希值。选择这个哈希函数的原因是为了防止对哈希表的攻击。

10.追踪代码:如何传输数据

我们将会通过追踪实际的Linux内核源码去检查协议栈中执行的关键任务。在这里,我们将会观察经常使用的两条路径。

首先是应用程序调用了write系统调用时的执行路径。

当应用调用了write系统调用时,内核将在文件层执行write()函数。首先,内核会取出文件描述符对应的文件结构体,之后会调用aio_write,这是一个函数指针。在文件结构体中,你可以看到file_perations结构体指针。这个结构被通称为函数表(function table),其中包含了一些函数的指针,比如aio_read或者aio_write。对于socket来说,实际的表是socket_file_ops,aio_write对应的函数是sock_aio_write。在这里函数表的作用类似于java中的interface,内核使用这种机制进行代码抽象或重构。

sock_aio_write()函数会从文件结构体中取出socket结构体并调用sendmsg,这也是一个函数指针。socket结构体中包含了proto_ops函数表。IPv4的TCP实现中,proto_ops的具体实现是inet_stream_ops,sendmsg的实现是tcp_sendmsg。

tcp_sendmsg(译注:原文写的是tcp_sengmsg,应该是笔误)会从socket中取得tcp_sock(也就是TCP控制块,TCB),并把应用程序请求发送的数据拷贝到socket发送缓冲中(译注:就是根据发送数据创建sk_buff链表)。当把数据拷贝到sk_buff中时,每个sk_buff会包含多少字节数据?在代码创建数据包时,每个sk_buff中会包含MSS字节(通过tcp_send_mss函数获取),在这里MSS表示每个TCP数据包能携带数据的最大值。通过使用TSO(TCP Segment Offload)和GSO(Generic Segmentation Offload)技术,一个sk_buff可以保存大于MSS的数据。在这篇文章里就不详细解释了。

sk_stream_alloc_skb函数会创建新的sk_buff,之后通过skb_entail把新创建的sk_buff放到send_socket_buffer的末尾。skb_add_data函数会把应用层的数据拷贝到sk_buff的buffer中。通过重复这个过程(创建sk_buff然后把它加入到socket发送缓冲区)完成所有数据的拷贝。因此,大小是MSS的多个sk_buff会在socket发送缓冲区中形成一个链表。最终调用tcp_push把待发送的数据做成数据包,并且发送出去。

tcp_push函数会在TCP允许的范围内顺序发送尽可能多的sk_buff数据。首先会调用tcp_send_head取得发送缓冲区中第一个sk_buff,然后调用tcp_cwnd_test和tcp_send_wnd_test检查堵塞窗口和接收窗口,判断接收方是否可以接收新数据。之后调用tcp_transmit_skb函数来创建数据包。

tcp_transmit_skb会创建指定sk_buff的拷贝(通过pskb_copy),但它不会拷贝应用层发送的数据,而是拷贝一些元数据。之后会调用skb_push来确保和记录头部字段的值。send_check计算TCP校验和(如果使用校验和卸载checksum offload技术则不会做这一步计算)。最终调用queue_xmit把数据发送到IP层。IPv4中queue_xmit的实现函数是ip_queue_xmit。

ip_queue_xmit函数执行IP层的一些必要的任务。__sk_dst_check检查缓存的路由是否有效。如果没有被缓存的路由项,或者路由无效,它将会执行IP路由选择(IP routing)。之后调用skb_push来计算和记录IP头字段的值。之后,随着函数执行,ip_send_check计算IP头校验和并且调用netfilter功能(译注:这是内核的一个模块)。如果使用ip_finish_output函数会创建IP数据分片,但在使用TCP协议时不会创建分片,因此内核会直接调用ip_finish_output2来增加链路头,并完成数据包的创建。

最终的数据包会通过dev_queue_xmit函数完成传输。首先,数据包通过排队规则(译注:qdisc,上篇文章简单介绍过)传递。如果使用了默认的排队规则并且队列是空的,那么会跳过队列而直接调用sch_direct_xmit把数据包发送到驱动。dev_hard_start_xmit会调用实际的驱动程序。在调用驱动之前,设备的发送(译注:TX,transmit)被锁定,防止多个线程同时使用设备。由于内核锁定了设备的发送,驱动发送数据相关的代码就不需要额外的锁了。这里下次要讲(译注:这里是说原作者的下篇文章)的并行开发有很大关系。

ndo_start_xmit函数会调用驱动的代码。在这之前,你会看到ptype_all和dev_queue_xmit_nit。ptype_all是个包含了一些模块的列表(比如数据包捕获)。如果捕获程序正在运行,数据包会被ptype_all拷贝到其它程序中。因此,tcpdump中显示的都是发送给驱动的数据包。当使用了校验和卸载(checksum offload)或TSO(TCP Segment Offload)这些技术时,网卡(NIC)会操作数据包,所以tcpdump得到的数据包和实际发送到网络的数据包有可能不一致。在结束了数据包传输以后,驱动中断处理程序会返回发送了的sk_buff。

11.追踪代码:如何接收数据

一般来说,接收数据的执行路径是接收一个数据包并把数据加入到socket的接收缓冲区。在执行了驱动中断处理程序之后,首先执行的是napi poll处理程序。

就像之前说的,net_rx_action函数是用于接收数据的软中断处理函数。首先,请求napi poll的驱动会检索poll_list,并且调用驱动的poll处理程序(poll handler)。驱动会把接收到的数据包包装成sk_buff,之后调用netif_receive_skb。

如果有模块在请求数据包,那么netif_receive_skb会把数据包发送给那个模块。类似于之前讨论过的发送的过程,在这里驱动接收到的数据包会发送给注册到ptype_all列表的那些模块,数据包在这里被捕获。

之后,根据数据包的类型,不同数据包会被传输到相应的上层。链路头中包含了2字节的以太网类型(ethertype)字段,这个字段的值标识了数据包的类型。驱动会把这个值记录到sk_buff中(skb->protocol)。每一种协议有自己的packet_type结构体,并且会把指向结构体的指针放入ptype_base哈希表中。IPv4使用的是ip_packate_type,类型字段中的值是IPv4类型(ETH_P_IP)。于是,对于IPv4类型的数据包会调用ip_recv函数。

ip_rcv函数执行IP层必要的操作。它会解析数据包中的长度和IP头校验和。在经过netfilter代码时会执行ip_local_deliver函数。如果需要的话还会装配IP分片。之后会通过netfilter的代码调用ip_local_deliver_finish函数,这个函数通过调用__skb_pull移除IP头,并且通过IP头的protocol值查找上层协议标记。类似于ptype_base,每个传输层协议会在inet_protos中注册自己的net_protocol结构体。IPv4 TCP使用tcp_protocol,于是会调用tcp_v4_rcv函数继续处理。

当数据包到达TCP层时,数据包的处理流程取决于TCP状态和包类型。在这里,我们将看到TCP连接处于ESTABLISHED状态时处理到达的数据包的过程。在没有出现出现丢包或者乱序等异常的情况下,服务器会频繁的执行这条路径。

首先,tcp_v4_rcv函数验证包的有效性,如果tcp头的大小比数据的偏移大时(th->doff < sizeof(struct tcphdr) /4)则说明包头有错误。(如果没有错误)之后会调用__inet_lookup_skb在存放TCP连接的哈希表里查找数据包所属的连接。从查找到的sock结构体可以找到所有需要的数据结构(比如tcp_sock),也可以取得对应的socket。

实际的协议在tcp_v4_do_rcv函数中处理。如果TCP正处于ESTABLISHED状态则会调用tcp_rcv_established(译注:原文写的是tcp_rcv_esablished,应该是笔误)。由于ESTABLISHED是最常用的状态,所以它被单独处理和优化了。tcp_rcv_established首先会执行头部预测(header prediction)的代码。头部预测会快速检测和处理常见情况。在这里常见的情况是没有在发送数据,实际收到的数据包正是应该收到的下一个数据包,例如TCP接收到了它期望收到的那个顺序号。在这种情况下在把数据加入到socket缓冲区、发送ack之后完成处理。

往前翻的话你会看到比较truesize和sk_forward_allow的语句。这个用来检查socket接收缓冲区中是否还有空闲空间来存放刚收到的数据。如果有的话,头部预测为“命中”(预测成功)。之后调用__skb_pull来删除TCP头。再之后调用__skb_queue_tail来把数据包增加到socket接收缓冲区。最终会视情况调用__tcp_ack_snd_check发送ACK。此时处理完成。

如果没有足够的空间,那么会走一条比较慢的路径。tcp_data_queue函数会新分配socket接收缓冲区的空间并把数据增加到缓冲区中。在这种情况下,接受缓冲区的大小在需要时会自动增加。和上一段说的快速路径不同,此时会在可行的情况下会调用tcp_data_snd_check发送一个新的数据回包。最终,如果需要的话会调用tcp_ack_snd_check来发送一个ACK包。

这两条路径下执行的代码量不大,因为这里讨论的都是通常情况。或者说,其它特殊情况的处理会慢的多,比如接收乱序这类情况。

12.驱动和网卡如何通讯

驱动(driver)和网卡(nic)之间的通讯处于协议栈的底层,大多数人并不关心。但是,为了解决性能问题,网卡会处理越来越多的任务。理解基础的处理方式会帮助你理解额外这些优化技术。

网卡和驱动之间使用异步通讯。首先,驱动请求数据传输时CPU不会等待结果而是会继续处理其它任务,之后网卡发送数据包并通知CPU,驱动程序返回通过事件接收的数据包(这些数据包可以看作是异步发送的返回值)。

和数据包传输类似,数据包的接收也是异步的。首先驱动请求接受接收数据包然后CPU去执行其它任务,之后网卡接收数据包并通知CPU,然后驱动处理接收到的数据包(返回数据)。

因此需要有一个空间来保存请求和响应(request and response)。大多数情况网卡会使用环形队列数据结构(ring structure)。环形队列类似于普通的队列,其中有固定数量的元素,每个元素会保存一个请求或一个相应数据。环形队列中元素是顺序的,“环形”的意思是队列虽然是定长的,但是其中的元素会按顺序重用。

图8展示了数据包传输的过程,你会看到如何使用环形队列。

图8:驱动和网卡之间的通讯:如何传输数据包

驱动接收到上层发来的数据包并创建网卡能够识别的描述符。发送描述符(send descriptor)默认会包含数据包的大小和内存地址。这里网卡需要的是内存中的物理地址,驱动需要把数据包的虚拟地址转换成物理地址。之后,驱动会把发送描述符添加到发送环形队列(TX ring)(1),发送环形队列中包含的实际是发送描述符。

之后,驱动会把请求通知给网卡(2)。驱动直接把数据写入指定的网卡内存地址中(译注:这里应该是网卡的寄存器地址,写入的是通知而不是数据包)。这种CPU直接向设备发送数据的传输方式叫做程序化I/O(Programmed I/O,PIO)。

网卡被通知后从主机的发送队列中取得发送描述符(3)。这种设备直接访问内存而不需要调用CPU的内存访问方式叫做直接内存访问(Direct Memory Access,DMA)。

在取得发送描述符后,网卡会得到数据包的地址和大小并且从主机内存中取得实际的数据包(4)。如果有校验和卸载(checksum offload)的话,网卡会在拿到数据后计算数据包的校验和,因此开销不大。

网卡发送数据包(5)之后把发送数据包的数量写入主机内存(6)。之后它会触发一次中断,驱动程序会读取发送数据包的数量并根据数量返回已发送的数据包。

在图9中,你会看到接收数据包的流程。

图9:驱动和网卡之间的通讯:如何接收数据包

首先,驱动会在主机内存中分配一块缓冲区用来接收数据包,之后创建接收描述符(receive descriptor)。接收描述符默认会包含缓冲区的大小和地址。类似于发送描述符,接收描述符中保存的也是DMA使用的物理地址。之后,驱动会把接收描述符加入到接收环形队列(RX ring)(1),接收环形队列保存的都是接收数据的请求。

通过PIO,驱动程序通知网卡有一个新的描述符(2),网卡会把新的描述符从接收队列中取出来,然后把缓冲区的大小和地址保存到网卡内存中(3)。

在接收到数据包时,网卡会把数据包发送到主机内存缓冲区中(5)。如果存在校验和卸载函数,那么网卡会在此时计算校验和。接收数据包的实际大小、校验和结果和其他信息会保存在独立的环形队列中(接收返回环形队列,receive return ring)(6)。接收返回队列保存了接收请求的处理结果,或者叫响应(response)。之后网卡会发出一个中断(7)。驱动程序从接收返回队列中获取接收到的数据包的信息,如果必要的话,还会分配新内存缓冲区并重复(1)和(2)。

调优网络栈时,大部分人会说环形队列和中断的设置需要被调整。当发送环形队列很大时,很多次的发送请求可以一次完成;当接收环形队列很大,可以一次性接收多个数据包。更大的环形队列对于大流量数据包接收/发送是很有用的。由于CPU在处理中断时有大量开销,大量大多数情况下,网卡使用一个计时器来减少中断。为了避免对宿主机过多的中断,发送和接收数据包的时候中断会被收集起来并且定期调用(interrupt coalescing,中断聚合)。

13.网络栈中的缓冲区和流量控制

在网络栈中流量控制在几个阶段被执行。图10展示了传输数据时的一些缓冲区。首先,应用会创建数据并把数据加入到socket发送缓冲区。如果缓冲区中没有剩余空间的话,系统调用会失败或阻塞应用进程。因此,应用程序到内核的发送速率由socket缓冲区大小来限制。

图10:数据发送相关的缓冲

TCP通过传输队列(qdisc)创建并把数据包发送给驱动程序。这是一个典型的先入先出队列,队列最大长度是txqueuelen,可以通过ifconfig命令来查看实际大小。通常来说,大约有几千个数据包。

驱动和网卡之间是传输环形队列(TX ring),它被认为是传输请求队列(transmission request queue)。如果队列中没有剩余空间的话就不会再继续创建传输请求,并且数据包会积累在传输队列中,如果数据包积累的太多,那么新的数据包会被丢弃。

网卡会把要发送的数据包保存在内部缓冲区中。这个队列中的数据包速度受网卡物理速度的影响(例如,1Gb/s的网卡不能承担10Gb/s的性能)。根据以太网流量控制,当网卡的接收缓冲区没有空间时,数据包传输会被停止。

当内核速度大于网卡时,数据包会堆积在网卡的缓冲区中。如果缓冲区中没有空间时会停止处理传输环形队列(TX ring)。越来越多的请求堆积在传输环形队列中,最终队列中空间被耗尽。驱动程序不能再继续创建传输请求数据包会堆积在传输队列(transmit queue)中。压力通过各种缓冲从底向上逐级反馈。

图11展示了接收数据包经过的缓冲区。数据包先被保存在网卡的接收缓冲区中。从流量控制的视角来看,驱动和网卡之间的接收环形缓冲区(RX ring)可以被看作是数据包的缓冲区。驱动程序从环形缓冲区取得数据包并把它们发送到上层。服务器系统的网卡驱动默认会使用NAPI,所以在驱动和上层之间没有缓冲区。因此,可以认为上层直接从接收环形缓冲区中取得数据,数据包的数据部分被保存在socket的接收缓冲区中。应用程序从socket接收缓冲区取得数据。

图11:与接收数据包相关的缓冲

不支持NAPI的驱动程序会把数据包保存在积压队列(backlog queue)中。之后,NAPI处理程序取得数据包,因此积压队列可以被认为是在驱动程序和上层之间的缓冲区。

如果内核处理数据包的速度低于网卡的速度,接收循环缓冲区队列(RX ring)会被写满,网卡的缓冲区空间(NIC internal buffer)也会被写满,当使用了以太流量控制(Ethernet flow control)时,接收方网卡会向发送方网卡发送请求来停止传输或丢弃数据包。

因为TCP支持端对端流量控制,所以不会出现由于socket接收队列空间不足而丢包的情况。但是,当使用UDP协议时,因为UDP协议不支持流量控制,如果应用程序处理速度不够的时候会出现socket接收缓冲区空间不足而丢包的情况。

在图10和图11中展示的传输环形队列(TX ring)和接收环形队列(RX ring)的大小可以用ethtool查看。在大多数看重吞吐量的负载情况下,增加环形队列的大小和socket缓冲区大小会有一些帮助。增加大小会减少高速收发数据包时由于缓冲区空间不足而造成的异常。

14.结论

最初,我计划只解释那些会对你编写网络程序有帮助的东西,执行性能测试和解决问题。即使是我最初的计划,在这篇文档中包含的内容也会很多。我希望这篇文章会帮助你开发网络程序并监控它们的性能。TCP/IP协议本身就十分复杂并有很多异常情况。但是,你不需要理解OS中TCP/IP协议相关的每一行代码来理解性能和分析现象。只需要理解它的上下文对你就会十分有帮助。

随着系统性能和操作系统网络栈实现的持续提升,最近的服务器能承受10-20Gb/s的TCP吞吐量而不出现任何问题。近期也出现了与性能相关的很多的新技术,例如TSO, LRO, RSS, GSO, GRO, UFO, XPS, IOAT, DDIO, TOE等等(译注:这些我就不翻译了……),让我们有些困惑。

在下篇文章中,我会从性能的观点解释关于网络栈,并且讨论这些技术的问题和收益。

By Hyeongyeop Kim, Senior Engineer at Performance Engineering Lab, NHN Corporation.

15.译者最后说几句

本来看着文章内容不多,但是实际翻译下来才发现有这么多字……翻译过程中发现有很多地方自己之前的理解也有一些错误,如果各位发现哪里翻译的有问题的话麻烦告知一下,感激不尽~~

对于TCP协议来说,虽然已经看过很多相关的文章,但是真正要完整的描述一次tcp数据包发送和接收的过程实际上还是很难做到的。对于TCP或者内核之类的文章很多,但是我观察了一下,大部分人的问题并不是看的文章不够多,而是看的不够仔细。

与其每天乐此不疲的mark一堆技术文章然后随便扫几眼,还不如安心把一篇文章完整的读透,甚至再退一步,把文章从头到尾通读一遍,那也是极好的。这也是这次翻译这篇文章的初衷,与各位共勉。


基因家族扩张收缩分析可视化(CAFE5) CafePlotter 是一个用于可视化 CAFE5 基因家族扩张收缩分析结果的 Python 工具 阅读详情

相关推荐

保姆级教程:用CST 2023一步步复现一个1-8GHz Vivaldi天线(含参数优化与结果分析)

本文提供了一份详细的CST 2023教程,指导读者如何从零开始设计并优化1-8GHz Vivaldi天线。内容涵盖工程设置、参数化建模、仿真加速技巧以及性能优化策略,特别适合需要实现宽带天线设计的工程师和研究人员。通过实战案例,帮助用户掌握Vivaldi天线的核心设计要点和CST高级功能应用。

weixin_30800987的博客 618

【网络协议 01】协议栈

网络协议栈

Jayson 博客 2991

机械臂 关节电机选型计算

电机 机械臂

hisen92的专栏 8983

(1) TCP/IP的特性

TCP/IP起源于美国国防部的实验性ARPAnet网络。下面介绍TCP/IP协议簇处理以下问题的方式。

qq_34810279的博客 927

直播间人气协议算法:从批量操作到数据接口的自动化实现

本文深入解析直播间人气协议算法的实现原理与技术细节,涵盖批量账号管理、IP代理方案、用户行为模拟算法及平台数据接口调用策略。通过逆向工程分析通信协议,结合算法设计实现自动化流量提升,同时探讨风控规避与系统性能优化方案,为开发者提供合规的技术实践参考。

weixin_30505225的博客 517

DSP28335做FFT傅里叶变换

1. 看了一下例程,居然没有FFT的例程,难道这个DSP28335不能做FFT吗?对了C2000系列是有C2000 ware这个库的。方便很多,不过目前不确定在C5000上运行的FFT能直接迁移到DSP28335上行不?测试了一下,是可以的,因为都是用的math.h,下面的一些正弦函数基本一样的 2. 硬件简介,其中DSP28335和TLV320AIC23B之间使用I2C接口进行寄存器配置,...

weixin_30390075的博客 3331

网络协议栈介绍

在发送方,驱动程序将数据包封装到适当的数据链路层帧中,并添加必要的控制信息,如源MAC地址、目标MAC地址、帧类型等。总的来说,Linux内核中的网络协议栈实现了完整的TCP/IP协议栈,并提供了丰富的网络协议支持和接口。在数据帧封装过程中,每个协议层都会在上一层的数据基础上添加自己的头部或尾部,并可能进行一些校验和控制信息的计算和添加。数据链路发送和接收:在数据链路层,会添加源和目的MAC地址以及数据链路层协议类型等信息,数据包被封装成数据链路层帧,并在发送端发送到物理介质上。

szqy00的博客 5543

OpenStack ussuri 私有云平台搭建企业级实战

openstack是一个云操作系统,这个操作系统控制着数据中心中的计算,存储和网络资源。所有这些资源的管理都是通过API来来实现的,并且管理资源都有相应的认证机制。在openstack中有一个叫做dashboard的仪表盘,这个仪表盘通过web界面可以管理这些在DC中的资源。除了能提供IaaS功能外,你还可以使用orchestration,错误管理和服务管理等服务来确保应用的高可用性。

悦分享 2338

7.7 网络(二)

不同角度介绍操作系统中的计算机网络

wwwyuewww的专栏 882

【笔记篇】Davinci Configurator BswM模块

AUTOSAR BSWM模块配置介绍

weixin_47571898的博客 1534

Gmsh+OCC对应版本库编译

输入 GMSH_DLL_EXPORT值C:\LIB\OCCT-7_8_0-i;9、如果只是生成库文件编译gmesh工程即可生成文件在C:\LIB\Gmsh\gmsh-4.13.1-Windows64-B\Debug 下gmsh.dll、gmsh.lib、gmsh.h、gmshc.h文件;3、CMAK编译Gmsh对应OCC版本的,CMAK选择源文件路径C:\LIB\Gmsh\gmsh-4.13.1-source,生成构建文件路径C:\LIB\Gmsh\gmsh-4.13.1-Windows64-B。

u013401364的博客 411

理解TCP/IP网络栈&编写网络应用(下)

1.摘要 这是《翻译:理解TCP/IP网络栈&编写网络应用》的下篇,文章中会通过讲解TCP的代码实现帮助大家理解发送、接收数据的流程,也描述了一些网卡、驱动等网络栈底层的原理。 原文地址:原文地址 原作者:Hyeongyeop Kim 翻译:@蛋疼的axb http://2baxb.me 上篇地址:翻译:理解TCP/IP网络栈&编写网络应用(上) 2.数据结构 以下是

qq51931373的专栏 1687

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

理解TCP/IP网络栈&编写网络应用(上)

1.摘要 之前在网上看到了这篇描述tcp网络栈原理的文章,感觉不错,决定抽空把这篇文章翻译一下。一来重新温习一下TCP相关知识,二来练练英文。由于原文太长,这里把文章分成上下两部分,分别对应了原理和代码。 很久没翻译文章了难免有误,建议有能力的同学还是看一下原文。 原文地址:原文地址 原作者:Hyeongyeop Kim 翻译:@蛋疼的axb http://2baxb.me 2.概述

qq51931373的专栏 586

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

翻译:理解TCP/IP网络栈&编写网络应用(上)

1.摘要 之前在网上看到了这篇描述tcp网络栈原理的文章,感觉不错,决定抽空把这篇文章翻译一下。一来重新温习一下TCP相关知识,二来练练英文。由于原文太长,这里把文章分成上下两部分,分别对应了原理和代码。 很久没翻译文章了难免有误,建议有能力的同学还是看一下原文。 原文地址:原文地址 2.概述 我们难以想象没有了TCP/IP之后的网络服务。所有我们开发并在NHN(原文发布的网站)使用的...

weixin_33738982的博客 198

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

翻译:理解TCP/IP网络栈&编写网络应用(下)

1.摘要 这是《翻译:理解TCP/IP网络栈&amp;编写网络应用》的下篇,文章中会通过讲解TCP的代码实现帮助大家理解发送、接收数据的流程,也描述了一些网卡、驱动等网络栈底层的原理。 原文地址:原文地址 2.数据结构 以下是一些关键数据结构。我们了解一下这些数据结构再开始查看代码。 2.1.sk_buff_structure 首先,sk_buff结构或skb结构代表一个数据包。图...

weixin_34315665的博客 250

微型TCP/IP网络栈:MicroTCP指南

微型TCP/IP网络栈:MicroTCP指南 项目介绍 MicroTCP是一款旨在作为学习工具而诞生的TCP/IP网络堆栈,由开发者在那不勒斯费德里科二世大学计算机网络课程期间着手开发。作为一个业余项目,它的目标是成为一个最小但完整的实现。目前,它支持ARP(RFC 826完全实现)、IPv4(不包括分片)、ICMP(足以响应ping请求的最小必要实现)以及TCP(完整但未经充分压力测试)。请注...

gitblog_00719的博客 826

DyscheOS内核网络栈实现:从TCP/IP协议到高性能网络通信

前往项目官网免费下载:[https://ar.openeuler.org/ar/](https://ar.openeuler.org/ar/?utm_source=csdn_blog) 在当今数字化时代,**DyscheOS内核网络栈**作为openEuler操作系统的重要组成部分,提供了从TCP/IP协议到高性能网络通信的完整解决方案。作为面向新手和普通用户的**终极网络通信指南**,本文将深

gitblog_06821的博客 188

理解 Linux 网络栈(1):Linux 网络协议栈简单总结

刘世民(Sammy Liu)以及可点击的本博客地址超级链接SammyLiu - 博客园 本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM + VxLAN 环境下的 Segmentation Offloading 技术(发送端) (4)QEMU/KVM + VxLAN 环境下的 Segmentation Offloading 技术(接收端) 1....

mabin2005的专栏 1428

OpenOnload网络栈架构解析:深入理解用户级网络加速的实现原理

OpenOnload是一款高性能用户级网络栈,通过绕过传统内核网络协议栈的瓶颈,显著提升网络应用程序的吞吐量和降低延迟。本文将深入解析OpenOnload的架构设计与实现原理,帮助开发者理解其如何实现网络加速。 ## 一、OpenOnload核心架构概览 OpenOnload的核心优势在于将网络处理从内核态转移到用户态,避免了传统内核网络栈的上下文切换和数据拷贝开销。其架构主要包含以下关键组件

gitblog_00256的博客 469

探索Linux内核网络栈:Zboya的阅读指南

探索Linux内核网络栈:Zboya的阅读指南 在软件开发的世界里,理解操作系统尤其是Linux内核的工作原理是提升技术水平的关键一步。对于网络工程师和开发者来说,深入学习Linux内核的网络栈(Netstack)更是必不可少。Zboya 创建的这个项目提供了一份详尽的Linux内核网络栈阅读指南,旨在帮助你更好地理解和利用这一强大的系统资源。 项目简介 该项目是一个逐步解析Linux内核网络处...

gitblog_00015的博客 378

Ansible Backport 自动化脚本详解:用 backport_of_line_adder.py 为 Backport PR 自动补全 “Backport of“ 引用

本文以 Ansible 仓库 [hacking/backport/](https://link.gitcode.com/i/c963214bdee7e7aa3b5a309978689105) 目录下的 README 文档为核心,讲解其中 backport PR 维护脚本 `backport_of_line_adder.py` 的用途、调用方式与交互流程,并结合 [脚本源码](https://lin

gitblog_00121的博客 507

smoltcp与标准网络栈对比:性能、资源占用和适用场景全面分析

在现代网络应用开发中,选择合适的网络栈对于系统性能和资源效率至关重要。smoltcp作为一个轻量级的TCP/IP协议栈,与标准网络栈相比具有独特的优势和适用场景。本文将从性能、资源占用和实际应用角度,为您详细解析两者的差异。🚀 ## 什么是smoltcp? smoltcp是一个独立的、事件驱动的TCP/IP协议栈,专为裸机实时系统设计。它的设计目标是简单性和鲁棒性,完全不需要堆分配,在稳定R

gitblog_00242的博客 530

Linux网络栈架构全景解析:从数据包路径到性能调优实战

计算机网络协议栈是操作系统核心组件,负责数据包的封装、传输与路由。其分层架构遵循OSI/TCP/IP模型,通过内核模块实现物理层到应用层的完整处理链路。理解网络栈原理对系统性能优化和故障排查具有重要技术价值,尤其在服务器运维、云计算和微服务架构等场景中。本文聚焦Linux网络栈实现,深入分析sk_buff数据结构设计、Netfilter钩子机制及路由子系统工作原理,并结合网络性能调优与容器网络实践,探讨如何通过监控工具链定位吞吐量瓶颈和连接异常问题。

weixin_30613433的博客 647

Understanding TCP/IP Network Stack

from: 《理解TCP/IP协议栈,实现网络应用》 《How NICs Work》 数据的发送 There are several layers and the layers are briefly classified into three areas: User area Kernel area Device area 在user area和kerne area处理...

weixin_33698043的博客 364

XNU网络子系统架构:从TCP/IP协议栈到Skywalk框架

XNU作为苹果操作系统的核心内核,其网络子系统架构融合了经典TCP/IP协议栈与现代网络虚拟化技术。本文将深入解析XNU网络架构的底层实现,从传统协议栈到Skywalk框架的演进,帮助开发者理解苹果设备网络通信的核心机制。 ## 一、XNU网络子系统的整体架构 XNU网络子系统采用分层设计,从硬件抽象层到应用接口层形成完整通信链路。核心代码主要分布在以下目录: - **BSD协议栈实现**:

gitblog_00730的博客 990
上一篇: LuCI2 (OpenWrt web 管理界面)
下一篇: 网络数据包收发流程(一):从驱动到协议栈
wdsfup
博客等级 码龄20年 31粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值