当前位置:首页>Linux>不懂 sk_buff,别再说你懂 Linux 内核了

不懂 sk_buff,别再说你懂 Linux 内核了

  • 2026-04-16 15:48:18
不懂 sk_buff,别再说你懂 Linux 内核了

在 Linux 内核的世界里,有一个数据结构,贯穿了整个网络子系统的每一个环节,它就是 sk_buff。很多人自称懂 Linux 内核,却对 sk_buff 一知半解,殊不知,跳过它,你对内核网络的理解永远停留在表面。作为内核网络的“灵魂载体”,sk_buff 承载着所有网络数据包的流转、解析与处理,从网卡接收数据到协议栈逐层解析,从应用层发送数据到网卡转发,每一步都离不开它的身影。

不懂 sk_buff,你就无法理解网络包如何在协议栈中层层封装、剥离,无法搞懂内核如何高效管理网络内存,更无法排查网络驱动、Netfilter 等核心模块的底层问题。它不是一个可有可无的结构体,而是打通 Linux 内核网络子系统的关键钥匙。今天,我们就拆解 sk_buff 的核心逻辑,搞懂它的本质、作用与底层实现,真正入门 Linux 内核网络,不再做“表面懂内核”的门外汉。

一、初识 sk_buff

面试题写作模版

1.1 什么是 sk_buff ?

sk_buff,即 socket buffer,是 Linux 网络子系统中用于管理网络数据包的基本数据结构 。它就像是一个多功能的 “包裹”,不仅装载着实际传输的网络数据,还附带了一系列与数据包相关的元数据,这些元数据记录了数据包在网络协议栈中传输时所需的各种信息。

从结构上看,sk_buff 包含了众多字段,每个字段都有着特定的用途。比如,它有指针指向数据包的数据部分,方便各层协议对数据进行操作;还有一些字段记录了数据包的长度、校验和、协议类型等关键信息。这些信息对于网络协议栈准确无误地处理数据包至关重要。

可以把 sk_buff 想象成一个快递包裹,包裹里装的货物就是网络数据,而包裹上的快递单则相当于元数据,上面记录了收件人地址(目标 IP)、寄件人地址(源 IP)、包裹重量(数据包长度)等信息,这些信息帮助快递(网络协议栈)准确地将包裹送达目的地。

1.2 不懂 sk_buff,难懂内核网络

(1)不懂网络包的封装与剥离。网络包在协议栈中的层层封装与剥离,是网络通信的核心流程之一,而 sk_buff 在这个过程中起着关键的纽带作用。当应用层的数据要发送到网络中时,就像一个要寄出的包裹,会依次经过传输层、网络层、数据链路层等,每一层都会给这个 “包裹” 加上特定的 “包装”,也就是协议头。比如传输层会添加 TCP 或 UDP 头,网络层添加 IP 头,数据链路层添加以太网头等 ,这个过程就叫封装。在接收端,数据则要经历相反的过程,一层层地把这些 “包装” 去掉,也就是剥离,才能把原始数据传递给应用层。

如果不了解 sk_buff,就很难理解这个封装与剥离的过程是如何精准有序地进行的。因为 sk_buff 就像是这个 “包裹” 的运输标签和存储容器,它记录了每一层添加的协议头信息,以及数据在各个层次间流转的状态。比如,在封装过程中,sk_buff 会预留相应的空间给每一层协议头的添加,就像提前规划好包裹的包装区域;在剥离过程中,sk_buff 又能根据记录的信息,准确地找到并去除每一层的协议头。要是对 sk_buff 一知半解,就如同在一个复杂的物流系统中,丢失了包裹的运输标签,根本无法理清数据在协议栈中是如何一步步被包装和拆解的。

(2)难以理解内核网络内存管理。在 Linux 内核中,高效的网络内存管理是保障网络性能的关键,而 sk_buff 正是实现这一高效管理的重要工具。内核需要不断地为接收到和要发送的网络数据包分配和释放内存,sk_buff 则为这个过程提供了统一且灵活的内存管理方式。它通过巧妙的内存布局设计,比如数据区和元数据区的合理划分,以及对内存碎片的有效控制,让内核能够快速地分配和回收内存,就像一个精心整理的仓库,货物(数据包)能够被高效地存放和取出。

当你不了解 sk_buff 时,就很难明白内核是如何做到这一点的。比如,sk_buff 有专门的函数用于分配和释放内存,像 alloc_skb 用于分配一个套接字缓冲区,kfree_skb 用于释放缓冲区。如果不理解这些函数的工作原理以及它们与 sk_buff 结构的配合,就无法理解内核是怎样在高并发的网络环境下,保证内存的高效利用,避免内存泄漏和内存碎片过多等问题的。这就好比在一个繁忙的仓库中,不了解货物存放和提取的规则,自然就无法理解仓库的高效运营机制。

(3)难以排查核心模块底层问题。在网络驱动、Netfilter 等 Linux 内核网络的核心模块中,sk_buff 扮演着不可或缺的角色,这也使得对它的了解成为排查这些模块底层问题的关键。以网络驱动为例,当网卡接收到数据时,会将数据封装成 sk_buff 传递给内核协议栈,在这个过程中,如果出现丢包、数据错误等问题,就需要深入到 sk_buff 层面去分析。比如,可能是 sk_buff 的内存分配失败导致无法接收数据,或者是在传递过程中 sk_buff 的某些元数据被错误修改,影响了数据的正确处理。

在 Netfilter 模块中,sk_buff 同样至关重要。Netfilter 是 Linux 内核中用于网络包过滤、NAT(网络地址转换)等功能的框架,它会对经过的 sk_buff 进行各种规则匹配和处理。如果不了解 sk_buff,当出现网络策略不生效、NAT 转换错误等问题时,就很难定位到问题的根源。

因为 Netfilter 对网络包的操作都是基于 sk_buff 进行的,不理解 sk_buff 就无法理解 Netfilter 是如何对网络包进行拦截、修改和转发的,也就难以排查其中出现的各种底层问题。这就像在一个复杂的电路系统中,不了解关键元件的工作原理,就很难找出电路故障的原因。

二、sk_buff 核心逻辑拆解

面试题写作模版

2.1 sk_buff 结构体

sk_buff 的结构就像是一个精心设计的工具箱,里面的每个 “工具” 都有着独特的用途,下面我们来深入剖析它的结构。在<include/linux/skbuff.h>头文件中对其进行了定义,它包含众多字段,每个字段都在网络数据处理中发挥着关键作用。

(1)内存布局相关字段。sk_buff 中与内存布局相关的字段主要有 next 和 prev,它们就像是链条上的连接点,用于将 sk_buff 结构体串联成双向链表 。通过这两个指针,内核可以方便地管理和遍历 sk_buff 链表,实现对网络数据包的高效组织和处理。比如在网络设备的接收队列中,接收到的数据包会以 sk_buff 的形式通过 next 和 prev 指针连接起来,等待上层协议的处理。

在多线程环境下,为了保证链表操作的安全性,qlen 字段记录了链表中元素的个数,lock 字段则是一个自旋锁,用于防止对链表的并发访问,确保在同一时刻只有一个线程能够对链表进行操作,避免数据冲突和错误。

// sk_buff 双向链表结构(内存布局)struct sk_buff {    // 串联成链表    struct sk_buff *next;   // 下一个包    struct sk_buff *prev;   // 上一个包    // 链表安全管理    __u32 qlen;             // 队列包数量    spinlock_t lock;        // 并发访问自旋锁};

(2)数据存储与长度相关字段。head 和 end 指针界定了整个缓冲区的边界,就像一个仓库的入口和出口,head 指向缓冲区的起始地址,end 指向缓冲区的结束地址。data 和 tail 指针则界定了数据区的边界,data 指向当前协议层有效负载的起始位置,tail 指向当前协议层有效负载的结束位置 。

例如在数据发送时,随着协议层不断添加协议头,data 指针会相应地移动,以准确指向有效数据的起始位置。len 字段表示数据包的总长度,包括数据和协议头的长度;data_len 字段表示非线性数据的长度,当数据包存在分页数据时,这个字段就会发挥作用;mac_len 字段表示 MAC 数据包头的长度,在链路层处理时会用到这个长度信息。

struct sk_buff {    // 整个缓冲区边界    unsigned char *head;  // 缓冲区起始    unsigned char *end;   // 缓冲区结束    // 有效数据边界    unsigned char *data;  // 有效数据起始    unsigned char *tail;  // 有效数据结束    // 长度信息    __u32 len;       // 总长度    __u32 data_len;  // 非线性数据长度    __u16 mac_len;   // MAC 头长度};

(3)协议头指针字段。transport_header、network_header 和 mac_header 分别指向传输层、网络层和链路层的协议头 。这些指针就像是快递包裹上的地址标签,方便网络协议栈的各层快速定位和处理相应的协议头信息。

当网络层的 IP 协议处理数据包时,通过 network_header 指针可以直接找到 IP 协议头,从而获取源 IP 地址、目的 IP 地址等关键信息,进行路由选择和数据包转发。在传输层,TCP 或 UDP 协议通过 transport_header 指针找到相应的协议头,根据端口号等信息进行数据的分发和重组。

struct sk_buff {    // 三层协议头指针(你文章重点)    unsigned char *mac_header;       // 链路层头    unsigned char *network_header;   // 网络层头 (IP)    unsigned char *transport_header; // 传输层头 (TCP/UDP)};

(4)其他关键字段。sock 指针关联到与该数据包相关的套接字,就像快递包裹与收件人的联系,对于本地产生或接收的数据包,这个指针指向对应的套接字结构,方便进行套接字相关的操作,如发送和接收数据的缓存管理等。dev 指针指向网络设备,表明数据包是从哪个网络设备接收或要发送到哪个网络设备,在数据的收发过程中,网络设备驱动会根据这个指针来进行设备相关的操作。

校验和字段用于确保数据在传输过程中的完整性,通过对数据包内容进行计算生成校验和,接收方可以通过校验和来验证数据是否正确。标志位字段则包含了各种标志信息,如 cloned 标志表示该 sk_buff 是否是克隆出来的,nohdr 标志表示是否只考虑有效负载,禁止修改包头等,这些标志位为网络协议栈的各层提供了更多的控制和判断依据 。

struct sk_buff {    struct sock *sk;     // 对应的套接字    struct net_device *dev; // 网络设备    // 校验和与标志    __wsum csum;    __u8 cloned:1,   // 是否克隆         nohdr:1;    // 是否禁止修改头};

2.2 skb 内存布局与四指针详解

在 sk_buff 结构体中,有四个指针非常重要,它们分别是 head、data、tail 和 end,它们就像是控制数据存储和操作的 “关键阀门”,直接影响着 sk_buff 的功能实现 。

  1. head 指针:它指向分配给 sk_buff 的整个缓冲区的起始位置,这个缓冲区就像是一个大仓库,head 指针标记着仓库的入口,是整个数据存储区域的起点。
  2. data 指针:表示当前有效数据的起始位置,当网络数据包在协议栈中进行封装和解封装操作时,data 指针会根据需要移动。比如在添加协议头时,data 指针会向上(向低地址方向)移动,为新的协议头腾出空间;在解封装协议头时,data 指针则会向下(向高地址方向)移动 。例如,当 IP 层接收到一个数据包时,需要解析 IP 头,此时就会通过移动 data 指针来跳过 IP 头部分,从而访问到 IP 数据部分。
  3. tail 指针:指向当前有效数据的末尾位置,skb_put 函数就是通过移动 tail 指针来实现向 sk_buff 中添加数据的操作。当有新的数据需要添加到 sk_buff 中时,tail 指针会向高地址方向移动,为新数据腾出空间,然后将新数据写入到 tail 指针所指向的位置之后 。
  4. end 指针:标记着整个缓冲区的结束位置,它与 head 指针相对,共同界定了这个 “数据仓库” 的边界,任何对数据的操作都不能超出这个边界范围。

这四个指针相互配合,共同维护着 sk_buff 中数据的完整性和正确性,而 skb_put 和 skb_pull 函数正是基于这些指针来实现数据的添加和提取操作的。理解它们的工作原理和相互关系,是掌握 sk_buff 操作的关键。

为了更高效地处理不同大小的网络数据包,sk_buff 采用了线性缓冲区和分页 frags 相结合的方式来存储数据 。

  1. 线性缓冲区:是指从 data 指针到 tail 指针之间的连续内存区域 。对于较小的数据包,所有数据都可以存储在这个连续的线性缓冲区中 。线性缓冲区的优点是访问速度快,因为数据在内存中是连续存储的,可以通过简单的指针偏移来访问数据 。例如,在处理一些小的控制数据包时,线性缓冲区能够快速地完成数据的读取和写入操作 。
  2. 分页 frags:当数据包较大,无法完全存储在连续的线性缓冲区中时,就会使用分页 frags 。分页 frags 允许将数据存储在多个不连续的内存页中,通过 skb_shared_info 结构体来管理这些分散的内存页 。每个内存页通过 skb_frag_t 结构体来描述,包括指向内存页的指针、页内偏移和数据长度等信息 。这种方式可以避免为了存储大数据包而分配连续的大内存块,提高了内存的利用率 。比如在传输大文件时,数据包可能会被分成多个分页存储 。

线性缓冲区和分页 frags 的结合,使得 sk_buff 能够灵活地适应各种大小的网络数据包,既保证了小数据包的高效处理,又解决了大数据包的存储问题 。

2.3 报文生命周期全流程剖析

(1)接收过程——当网络设备接收到一个数据包时,接收过程就开始了 。首先,网卡通过 DMA(直接内存访问)将数据包从网络介质读取到内核内存中的一个接收缓冲区 。接着,网卡会产生一个中断通知内核有新的数据到达 。 内核的中断处理程序会响应这个中断,它会从接收缓冲区中读取数据包,并为这个数据包创建一个 sk_buff 结构体 。在创建 sk_buff 时,会分配内存空间,并初始化 sk_buff 的各个字段,包括前面提到的四个指针 head、data、tail、end,此时 head、data、tail 指针通常指向同一位置,end 指针指向内存缓冲区的末尾 。 然后,中断处理程序会将数据包的数据从接收缓冲区拷贝到 sk_buff 的线性缓冲区中(如果数据包较小,适合线性缓冲区存储;如果数据包较大,可能会涉及到分页 frags 的处理) 。

拷贝完成后,tail 指针会根据数据的长度移动到数据的末尾位置,len 字段也会被设置为数据的实际长度 。 接下来,sk_buff 会被传递到网络协议栈的链路层进行处理 。链路层会解析数据包的链路层头部(如以太网头部),获取协议类型等信息,并根据协议类型将 sk_buff 进一步传递到网络层 。在这个过程中,可能会对 sk_buff 进行一些修改,比如更新一些元数据字段 。 网络层接收到 sk_buff 后,会检查 IP 头部的各种字段,如目的 IP 地址、源 IP 地址、TTL(生存时间)等 。如果目的 IP 地址是本机的 IP 地址,sk_buff 会被继续传递到传输层;如果目的 IP 地址不是本机的,且本机开启了转发功能,sk_buff 会进入转发流程 。

(2) 转发过程——当 sk_buff 进入转发流程时,首先会进行路由查找 。内核会根据 sk_buff 中的目的 IP 地址查询路由表,以确定数据包的下一跳地址和出接口 。 找到合适的路由后,会对 sk_buff 进行一些处理 。例如,会将 TTL 字段减 1,如果 TTL 减为 0,说明数据包在网络中已经经过了太多的跳数,可能存在路由环路,此时会丢弃该数据包,并向源地址发送一个 ICMP 超时消息 。 然后,sk_buff 会通过 Netfilter 框架的 NF_INET_FORWARD 钩子 。在这个钩子点,可以对数据包进行一些过滤和修改操作,比如防火墙规则的检查,如果数据包不符合转发规则,可能会被丢弃 。

接着,会进行 IP 分片处理(如果需要的话) 。如果数据包的大小超过了出接口的 MTU(最大传输单元),就需要将数据包分成多个较小的分片,每个分片都会有自己的 sk_buff 。这些分片在到达目的主机后会被重新组装 。最后,sk_buff 会被传递到链路层进行封装 。链路层会根据下一跳的 MAC 地址(通过 ARP 协议获取),为 sk_buff 添加链路层头部(如以太网头部),然后将封装好的数据包发送到出接口,进入发送流程 。

(3)发送过程——在发送过程中,首先 sk_buff 会被传递到网络设备驱动层 。驱动层会将 sk_buff 放入网络设备的发送队列中 。 网络设备会从发送队列中取出 sk_buff,并根据 sk_buff 中的数据和链路层头部信息,将数据包通过物理介质发送出去 。

在发送过程中,可能会涉及到一些硬件相关的操作,比如将数据转换为电信号或光信号 。 如果发送成功,sk_buff 所占用的内存资源会被释放 。如果发送失败,可能会根据具体的错误情况进行相应的处理,比如重新发送或者通知上层协议 。在整个发送过程中,sk_buff 作为数据包的载体,确保了数据能够准确无误地从本机发送到网络中 。

三、sk_buff 与协议栈交互流程

面试题写作模版

sk_buff 作为网络协议栈各层交互的核心载体,其与协议栈的交互贯穿数据包接收、转发、发送全流程,本质是 sk_buff 在链路层、网络层、传输层之间的传递,以及各层通过操作 sk_buff 完成协议封装与解析,是 sk_buff 核心逻辑的具象化体现,也是理解内核网络工作机制的关键。

3.1 发送数据时的交互流程

发送流程的交互核心是“自上而下”的协议封装,各层协议通过操作 sk_buff 的指针、填充协议头,完成数据从应用层到物理层的传递,每一步都依赖 sk_buff 承载数据和元信息。

(1)应用层到传输层:当我们在浏览器中输入一个网址并按下回车键,这一简单的操作背后,实则触发了一系列复杂的网络数据传输流程。应用层首先生成 HTTP 请求数据,这就像是我们写好的一封信件内容。这些数据随后通过 socket 接口发往传输层。

传输层有两个重要的协议,分别是 TCP 和 UDP。如果是基于 TCP 协议,它会像一个严谨的打包员,为数据添加 TCP 头部。这个头部包含了源端口号、目标端口号、序列号、确认号等关键信息,这些信息就像是信封上的重要备注,用于保证数据的可靠传输,确保信件能准确无误地送达,并且按顺序被接收方读取。若采用 UDP 协议,它则更像是一个简洁的投递员,为数据添加 UDP 头部,其中包含源端口号、目标端口号、长度和校验和等信息,注重的是数据的快速传输。以常见的 HTTP 请求为例,当我们访问网页时,应用层的 HTTP 请求数据会被 TCP 协议封装,添加 TCP 头部后,形成 TCP 数据段,准备进入下一层的处理。

// 应用层 → 传输层:数据封装示意// HTTP 应用数据const char *http_data = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n";// 传输层:为数据添加 TCP 头struct tcphdr tcp_header;  // TCP 头部结构tcp_segment = tcp_header + http_data;  // 封装成 TCP 段

(2)传输层到网络层:传输层的数据段顺利抵达网络层后,IP 协议开始发挥关键作用。它如同一个经验丰富的导航员,为数据添加 IP 头部,这个头部中记录着源 IP 地址、目标 IP 地址、协议类型等重要信息,这些信息就像是包裹上的收件人和寄件人地址以及运输说明,帮助数据在复杂的网络中找到正确的传输路径。

在这个过程中,IP 协议还肩负着路由选择和分片处理的重任。当数据需要跨网段进行通信时,就好比包裹要寄到远方的城市,IP 协议会根据目标 IP 地址,通过查询路由表来确定最佳的传输路径,找到下一个路由器或者目标主机的地址。如果数据的大小超过了网络链路所能承载的最大传输单元(MTU),IP 协议就会像一个细心的搬运工,将数据进行分片处理,把大包裹拆分成多个小包裹,以便能够在网络中顺利传输。例如,当我们访问国外的网站时,数据需要跨越不同的网络区域,IP 协议就会精心规划路径,并在必要时对数据分片,确保数据能够成功抵达目标服务器。

// 传输层 → 网络层:数据封装示意// 网络层:为 TCP 段添加 IP 头struct iphdr ip_header;    // IP 头部结构ip_packet = ip_header + tcp_segment;  // 封装成 IP 数据包

(3)网络层到数据链路层:网络层处理完数据后,数据便来到了数据链路层。在这里,数据会被封装成帧,就像是给包裹贴上了带有详细收件人信息的快递单,以便在本地网络中准确送达。

数据链路层会添加 MAC 头部,其中包含源 MAC 地址和目标 MAC 地址,这两个地址就像是快递单上的发件人和收件人的具体门牌号,用于在局域网内唯一标识设备,确保数据帧能准确无误地找到目标设备。还会添加帧尾,通常包含帧校验序列(FCS),用于检测数据在传输过程中是否出现错误,就像给包裹加上了一个质量检测标签。在局域网通信中,当一台主机要向另一台主机发送数据时,数据链路层会根据目标 IP 地址,通过 ARP 协议获取目标主机的 MAC 地址,然后将数据封装成帧,添加源 MAC、目标 MAC 与校验信息,再通过物理介质发送出去。

// 网络层 → 数据链路层:数据封装示意// 链路层:为 IP 包添加以太网头与校验尾struct ethhdr eth_header;  // 以太网(MAC)头部ethernet_frame = eth_header + ip_packet + FCS;  // 封装成以太网帧

(4)数据链路层到物理层:数据链路层完成数据的封装后,数据帧就来到了物理层。物理层的任务就像是一个辛勤的快递员,将数据帧转换为电信号、光信号或无线电波等信号形式,通过网线、光纤、无线信号等物理介质进行实际传输,将数据真正地 “送出去”,让它在网络的 “高速公路” 上飞驰。

// 最终通过物理层硬件发送send_ethernet_frame(ethernet_frame);

3.2 接收数据时的交互流程

接收流程的交互核心是“自下而上”的协议解析,各层协议通过操作 sk_buff 指针、剥离协议头,完成数据从物理层到应用层的传递,全程依赖 sk_buff 保存接收的数据和协议信息,与前文“报文生命周期-接收过程”相互呼应。

(1)物理层到数据链路层。当物理层接收到信号时,就如同快递员收到了包裹,它会将这些信号转换为数据链路层能够识别的帧。数据链路层在接收到帧后,会首先进行校验,检查帧的完整性和正确性,就像检查包裹是否完好无损,有没有在运输过程中出现损坏。它还会根据 MAC 地址进行过滤,只接收属于自己的帧,就像只收取寄给自己的包裹,将其他无关的帧丢弃。

例如,在一个局域网中,交换机连接着多台设备,当交换机的物理层接收到来自网线的信号并转换为帧后,数据链路层会根据帧中的 MAC 地址,判断该帧是否是发送给自己所连接的设备,如果是,则接收并进一步处理;如果不是,则直接丢弃。

// 物理层 → 数据链路层:接收帧// 物理层将电信号转为以太网帧struct ethhdr *eth_frame = (struct ethhdr *)phys_read_signal();// 数据链路层校验 + MAC 地址过滤if (eth_frame->checksum_valid() && is_my_mac(eth_frame->h_dest)) {    // 接收属于本机的帧else {    // 丢弃无效或非本机帧    drop_frame(eth_frame);}

(2) 数据链路层到网络层。数据链路层确认帧无误后,会将帧中的网络层数据包提取出来,去掉帧头和帧尾,就像打开包裹,取出里面的物品,然后将数据包传递给网络层。网络层收到数据包后,会仔细检查 IP 头部的信息,确认数据包的源 IP 地址和目标 IP 地址,就像查看物品上的寄件人和收件人信息,判断这个数据包是否是发送给自己的。它还会处理分片和路由相关的操作,如果数据包是分片传输过来的,网络层会将这些分片重新组装成完整的数据包,就像将拆分的物品重新组装起来;

同时,根据 IP 头部的目标 IP 地址,通过路由表来确定数据包的下一步传输方向。例如,当路由器接收到一个数据包时,它会检查 IP 头部,判断该数据包是要转发到其他网络还是直接发送给本地网络中的目标主机,如果需要转发,就会根据路由表选择合适的下一跳路由器。

// 数据链路层 → 网络层:解帧 & 提交 IP 包// 剥离以太网头部,取出 IP 数据包struct iphdr *ip_packet = (struct iphdr *)(eth_frame + 1);// 网络层:检查 IP 地址、重组分片if (is_my_ip(ip_packet->daddr)) {    // 数据包是发给本机的,继续向上传递    ip_defragment(ip_packet);  // 分片重组else {    // 转发数据包    ip_forward(ip_packet);}

(3)网络层到传输层。网络层处理完数据包后,会将其中的传输层数据段提取出来,去掉 IP 头部,将数据段传递给传输层。传输层接收到数据段后,TCP 和 UDP 协议会根据头部中的端口号,确定数据应该交付给哪个应用程序,就像根据包裹上的特殊标识,将物品送到正确的收件人手中。TCP 协议还会进行校验和重组操作,确保数据的可靠性,检查数据在传输过程中是否出现错误,以及将乱序到达的数据重新按顺序排列。

比如,当我们的电脑接收到来自网络的 TCP 数据段时,传输层会根据端口号判断这些数据是属于浏览器应用程序的,然后进行相应的处理。

// 网络层 → 传输层:解包 & 提交 TCP/UDP 段// 剥离 IP 头部,取出传输层段struct tcphdr *tcp_segment = (struct tcphdr *)(ip_packet + 1);// 传输层:按端口交付应用程序int dest_port = ntohs(tcp_segment->dest);if (port_belongs_to_app(dest_port)) {    // 数据交给对应 socket    tcp_validate_and_reorder(tcp_segment);  // TCP 校验 & 排序}

3.3 转发数据时的交互流程

转发流程是接收与发送流程的结合,核心是 sk_buff 在协议栈中的 “中转”,无需传递到应用层,直接由网络层转发到链路层发送,减少冗余操作,提升转发效率,是前文 “报文生命周期 - 转发过程” 的细节补充。

// 转发核心:skb 中转,不进入传输层 & 应用层// 仅在 链路层 → 网络层 → 链路层 之间流转struct sk_buff *skb;

当网络层接收到目的 IP 非本机的 sk_buff 时,先进行路由查找,确定转发的下一跳地址和出接口。

// 1. 判断是否需要转发(目标 IP 不是本机)if (!is_local_ip(skb->daddr)) {    // 2. 路由查找:获取下一跳 IP 和出口网卡    struct rtable *rt = ip_route_output(&skb->daddr);    struct net_device *out_dev = rt->dev;  // 出接口}

随后更新 sk_buff 的 TTL 字段(减 1,为 0 则丢弃并发送 ICMP 超时消息),重新计算 IP 校验和。

// 3. TTL -1ip_hdr(skb)->ttl--;// TTL 为 0,丢弃并发送 ICMP 超时if (ip_hdr(skb)->ttl == 0) {    icmp_send_timeout(skb);    kfree_skb(skb);    return;}// 4. 重新计算 IP 头部校验和ip_hdr(skb)->check = 0;ip_hdr(skb)->check = ip_fast_csum(ip_hdr(skb), ip_hdr(skb)->ihl);

若需要,调整链路层协议头中的 MAC 地址(更新为下一跳 MAC)。

// 5. 重新封装二层头部:刷新源 MAC & 目标 MAC(下一跳 MAC)eth_hdr(skb)->h_source = out_dev->dev_addr;    // 本机 MACeth_hdr(skb)->h_dest   = rt->next_hop_mac;     // 下一跳 MAC

无需经过传输层和应用层,直接将 sk_buff 传递给链路层,由链路层发送到目标设备,完成转发交互。

// 6. 直接交给链路层发送(不向上传递)dev_queue_xmit(skb);

四、sk_buff 核心操作全攻略

面试题写作模版

4.1 分配与释放:内存管理的基石

在 Linux 网络协议栈中,alloc_skb 和 dev_kfree_skb 是内存管理的关键函数 ,它们分别负责 sk_buff 的分配与释放,确保了网络数据包在内存中的合理存储与回收 。

(1)alloc_skb 函数:用于分配一个新的 sk_buff 结构体及其数据缓冲区 。其函数原型为 struct sk_buff *alloc_skb(unsigned int size, gfp_t priority),其中 size 参数指定了数据缓冲区的大小,priority 参数表示内存分配的优先级,常见的取值有 GFP_ATOMIC 和 GFP_KERNEL 。GFP_ATOMIC 用于原子上下文(如中断处理程序),它不会导致进程睡眠,确保在紧急情况下也能快速分配内存;GFP_KERNEL 用于进程上下文,允许进程睡眠以等待内存资源 。例如,在网络驱动接收数据包时,会调用 alloc_skb 分配内存来存储接收到的数据 :

#include <linux/skbuff.h>#include <linux/module.h>#include <linux/netdevice.h>// 网卡接收数据的函数staticvoidmock_net_rx(struct net_device *dev, int data_len){    // 分配 sk_buff,假设使用 GFP_KERNEL 优先级    struct sk_buff *skb = alloc_skb(data_len, GFP_KERNEL);    if (!skb) {        // 处理内存分配失败的情况        printk(KERN_ERR "Failed to allocate skb\n");        return;    }    // 这里可以进行数据拷贝等后续操作    // 例如从硬件缓冲区拷贝数据到 skb 的线性缓冲区    //...    // 处理完后,释放 skb(这里只是示例,实际可能会提交给协议栈处理)    dev_kfree_skb(skb);}

在这个示例中,alloc_skb 函数根据接收到的数据长度 data_len 分配了一个 sk_buff ,如果分配成功,就可以在后续步骤中将数据存储到 sk_buff 中 。

(2)dev_kfree_skb 函数:用于释放一个 sk_buff 及其所占用的内存资源 。在 sk_buff 不再被使用时,必须调用此函数来释放内存,以避免内存泄漏 。它会检查 sk_buff 的引用计数 users,如果 users 为 0,才真正释放内存;如果 users 大于 0,说明还有其他地方在使用这个 sk_buff,则只是减少引用计数 。在上述示例中,当 sk_buff 完成其使命后,通过 dev_kfree_skb(skb)释放了 sk_buff 所占用的内存 。

4.2 数据空间管理:指针操作的艺术

(1)skb_reserve 函数——skb_reserve 函数用于在 sk_buff 的线性缓冲区头部预留一定的空间 ,以便后续添加协议头 。其函数原型为 void skb_reserve(struct sk_buff *skb, int len),其中 skb 是指向 sk_buff 结构体的指针,len 是要预留的空间长度 。

在网络协议栈中,当数据包从上层协议传递到下层协议时,每层协议都需要在数据包前面添加自己的协议头 。例如,当 TCP 数据包传递到 IP 层时,IP 层需要在 TCP 数据包前面添加 IP 头 。这时就可以使用 skb_reserve 函数预留出足够的空间来存放 IP 头 。假设我们要发送一个 TCP 数据包,在分配 sk_buff 后,首先调用 skb_reserve 预留出 IP 头和 TCP 头的空间 :

#include <linux/skbuff.h>#include <linux/ip.h>#include <linux/tcp.h>#define IP_HDR_LEN 20#define TCP_HDR_LEN 20// 发送 TCP 数据包的函数staticvoidmock_send_tcp_packet(int payload_len){    // 分配 sk_buff,假设总大小为 IP 头+TCP 头+有效载荷    struct sk_buff *skb = alloc_skb(IP_HDR_LEN + TCP_HDR_LEN + payload_len, GFP_KERNEL);    if (!skb) {        printk(KERN_ERR "Failed to allocate skb\n");        return;    }    // 预留 IP 头和 TCP 头的空间    skb_reserve(skb, IP_HDR_LEN + TCP_HDR_LEN);    // 这里可以继续填充 TCP 有效载荷等后续操作    //...    // 处理完后,释放 skb(这里只是示例,实际可能会发送出去)    dev_kfree_skb(skb);}

在这个例子中,skb_reserve(skb, IP_HDR_LEN + TCP_HDR_LEN)函数调用使得 sk_buff 的 data 和 tail 指针向后移动了 IP_HDR_LEN + TCP_HDR_LEN 个字节,从而在缓冲区头部预留出了添加 IP 头和 TCP 头的空间 。

(2)skb_put 函数——skb_put 函数的原型定义在<include/linux/skbuff.h>头文件中,其原型为:

void *skb_put(struct sk_buff *skb, unsignedint len);

这个函数的主要功能是在 sk_buff 的数据区末端增加指定长度 len 的数据空间。具体来说,它会将 sk_buff 结构体中的 tail 指针向后移动 len 个字节,同时更新 len 字段,表示数据区的总长度增加了 len 字节。返回值是一个指针,指向新增加数据空间的起始位置,这个返回值非常有用,后续可以通过它将数据写入到新增加的空间中 。

下面通过一个简单的代码示例来深入理解 skb_put 函数的执行流程:

#include <linux/skbuff.h>#include <linux/module.h>MODULE_LICENSE("GPL");staticint __init my_init(void){    struct sk_buff *skb = alloc_skb(200, GFP_KERNEL); // 分配一个大小为 200 字节的 sk_buff    if (!skb) {        printk(KERN_ERR "Failed to allocate skb\n");        return -ENOMEM;    }    printk(KERN_INFO "Original tail: %p, len: %u\n", skb->tail, skb->len);    char data_to_put[] = "Hello, skb_put!";    int data_len = sizeof(data_to_put) - 1// 减去字符串结束符'\0'    void *put_ptr = skb_put(skb, data_len); // 调用 skb_put 函数增加数据空间    if (put_ptr) {        memcpy(put_ptr, data_to_put, data_len); // 将数据复制到新增加的空间    }    printk(KERN_INFO "New tail: %p, len: %u\n", skb->tail, skb->len);    // 释放 skb    kfree_skb(skb);    return 0;}staticvoid __exit my_exit(void){    printk(KERN_INFO "Module exiting\n");}module_init(my_init);module_exit(my_exit);

在上述代码中,首先使用 alloc_skb 函数分配了一个大小为 200 字节的 sk_buff。此时,tail 指针指向数据区的起始位置(与 data 指针相同),len 字段为 0 。然后定义了一个字符串 data_to_put,并计算其长度(不包括字符串结束符)。调用 skb_put(skb, data_len)函数,tail 指针会向后移动 data_len 个字节,同时 len 字段增加 data_len。返回的 put_ptr 指针指向新增加空间的起始位置,通过 memcpy 函数将字符串数据复制到这个位置 。最后,打印出操作前后 tail 指针和 len 字段的值,可以看到 tail 指针向后移动了,len 字段也增加了相应的长度,这就直观地展示了 skb_put 函数的工作过程 。

在实际的网络编程中,skb_put 函数有着广泛的应用场景。比如在链路层添加帧尾校验数据时,就可以使用 skb_put 函数。以以太网为例,以太网帧的帧尾通常包含 4 字节的 CRC 校验数据 。在构建以太网帧时,可以先使用 skb_put 函数在数据区末尾增加 4 字节的空间,然后将计算好的 CRC 校验值写入这个空间,从而完成帧尾校验数据的添加,确保数据在传输过程中的完整性 。代码示例如下:

// 假设已经构建好以太网帧的其他部分,存放在 skb 中struct sk_buff *skb =...;// 计算得到的 CRC 校验值unsigned int crc_value = calculate_crc(skb->data, skb->len); // 假设存在计算 CRC 的函数// 使用 skb_put 在帧尾增加 4 字节空间void *tail_ptr = skb_put(skb, 4);if (tail_ptr) {    // 将 CRC 校验值写入新增加的空间    *((unsigned int *)tail_ptr) = cpu_to_be32(crc_value);}

在这个例子中,calculate_crc 函数用于计算以太网帧数据部分的 CRC 校验值,然后通过 skb_put 函数在帧尾增加 4 字节空间,并将计算好的 CRC 校验值写入该空间,完成了链路层帧尾校验数据的添加操作,这是 skb_put 函数在实际网络编程中的一个典型应用场景 。

(3)skb_push——skb_push 函数用于在 sk_buff 的线性缓冲区头部添加数据,通常用于添加协议头 。其函数原型为 void *skb_push(struct sk_buff *skb, unsigned int len),它会将 sk_buff 的 data 指针向前移动 len 个字节(即向低地址方向移动),并返回移动后 data 指针的位置 ,用户可以在这个返回的位置上填充协议头数据 。

在构建网络数据包时,经常需要使用 skb_push 来添加协议头 。继续以上述发送 TCP 数据包的例子,在预留好空间并添加有效载荷后,可以使用 skb_push 来添加 TCP 头和 IP 头 :

#include <linux/skbuff.h>#include <linux/ip.h>#include <linux/tcp.h>#define IP_HDR_LEN 20#define TCP_HDR_LEN 20// 发送 TCP 数据包的函数staticvoidmock_send_tcp_packet(int payload_len){    // 分配 sk_buff,假设总大小为 IP 头+TCP 头+有效载荷    struct sk_buff *skb = alloc_skb(IP_HDR_LEN + TCP_HDR_LEN + payload_len, GFP_KERNEL);    if (!skb) {        printk(KERN_ERR "Failed to allocate skb\n");        return;    }    // 预留 IP 头和 TCP 头的空间    skb_reserve(skb, IP_HDR_LEN + TCP_HDR_LEN);    // 添加 TCP 有效载荷    char *payload = "This is a TCP payload";    void *data = skb_put(skb, payload_len);    memcpy(data, payload, payload_len);    // 添加 TCP 头    struct tcphdr *tcph = skb_push(skb, TCP_HDR_LEN);    tcph->source = htons(1234);    tcph->dest = htons(80);    tcph->seq = htonl(1);    tcph->ack_seq = htonl(0);    // 这里还可以继续设置 TCP 头的其他字段    // 添加 IP 头    struct iphdr *iph = skb_push(skb, IP_HDR_LEN);    iph->version = 4;    iph->ihl = 5;    iph->tos = 0;    iph->id = htons(1);    iph->frag_off = 0;    iph->ttl = 64;    iph->protocol = IPPROTO_TCP;    // 这里还可以继续设置 IP 头的其他字段    // 处理完后,释放 skb(这里只是示例,实际可能会发送出去)    dev_kfree_skb(skb);}

在这个示例中,首先使用 skb_push(skb, TCP_HDR_LEN)为 TCP 头腾出空间,并获取到新的 data 指针位置,然后在这个位置上填充 TCP 头的各个字段 。接着使用 skb_push(skb, IP_HDR_LEN)为 IP 头腾出空间,并填充 IP 头的字段 。

(4)skb_pull 函数——skb_pull 函数的原型定义在<include/linux/skbuff.h>头文件中,其原型为:

void *skb_pull(struct sk_buff *skb, unsignedint len);

这个函数的主要功能是从 sk_buff 的数据有效区移除指定长度 len 的数据 。具体来说,它会将 sk_buff 结构体中的 data 指针向后(向高地址方向)移动 len 个字节,同时将 len 字段减少 len 个字节,表示数据有效区的长度减少了 len 字节。返回值是一个指针,指向移除数据后的数据有效区的起始位置,通常用于跳过协议头或者处理完某一层协议之后,将数据传递给下一层进行处理 。例如,在网络协议栈中,当 IP 层接收到一个数据包时,需要跳过 IP 头,将数据传递给 TCP 层,就可以使用 skb_pull 函数来实现 。

下面通过一个具体的代码示例来深入理解 skb_pull 函数的执行流程:

#include <linux/skbuff.h>#include <linux/module.h>MODULE_LICENSE("GPL");staticint __init my_init(void){    struct sk_buff *skb = alloc_skb(200, GFP_KERNEL); // 分配一个大小为 200 字节的 sk_buff    if (!skb) {        printk(KERN_ERR "Failed to allocate skb\n");        return -ENOMEM;    }    // 假设已经有一些数据存放在 skb 中,这里简单模拟添加一些数据    char data[] = "This is a test packet";    int data_len = sizeof(data) - 1;    void *put_ptr = skb_put(skb, data_len);    if (put_ptr) {        memcpy(put_ptr, data, data_len);    }    printk(KERN_INFO "Original data: %p, len: %u\n", skb->data, skb->len);    // 假设要跳过前 10 个字节的数据(模拟跳过协议头)    int skip_len = 10;    void *pull_ptr = skb_pull(skb, skip_len);    if (pull_ptr) {        printk(KERN_INFO "New data: %p, len: %u\n", skb->data, skb->len);        // 输出跳过前 10 个字节后的数据        printk(KERN_INFO "Data after skb_pull: %.*s\n", skb->len, (char *)skb->data);    }    // 释放 skb    kfree_skb(skb);    return 0;}staticvoid __exit my_exit(void){    printk(KERN_INFO "Module exiting\n");}module_init(my_init);module_exit(my_exit);

在上述代码中,首先使用 alloc_skb 函数分配了一个大小为 200 字节的 sk_buff,然后通过 skb_put 函数向 sk_buff 中添加了一些测试数据 。此时,data 指针指向数据的起始位置,len 字段表示数据的长度 。接着,定义了要跳过的字节数 skip_len 为 10,调用 skb_pull(skb, skip_len)函数。执行该函数后,data 指针会向后移动 10 个字节,len 字段也会减少 10 。返回的 pull_ptr 指针指向跳过 10 个字节后的数据起始位置 。通过打印操作前后 data 指针和 len 字段的值,以及跳过数据后的数据内容,可以清晰地看到 skb_pull 函数的工作过程 。

在实际的网络编程中,skb_pull 函数有着广泛的应用场景。以网络协议解析为例,当一个数据包从网卡接收进来后,首先会到达数据链路层,在数据链路层完成帧头解析后,需要将数据传递给网络层进行 IP 头解析 。此时,就可以使用 skb_pull 函数跳过数据链路层的帧头,将数据传递给 IP 层 。代码示例如下:

// 假设已经接收到一个数据包存放在 skb 中struct sk_buff *skb =...;// 假设以太网帧头长度为 14 字节unsigned int eth_header_len = 14;// 跳过以太网帧头skb_pull(skb, eth_header_len);// 此时 skb->data 指向 IP 头,可以进行 IP 头解析struct iphdr *iph = (struct iphdr *)skb->data;// 进一步处理 IP 头相关信息,如检查 IP 版本、源 IP 地址、目的 IP 地址等

在这个例子中,通过 skb_pull 函数跳过了 14 字节的以太网帧头,使得 skb->data 指针指向 IP 头,从而方便后续对 IP 头进行解析和处理 。这是 skb_pull 函数在网络协议解析过程中的一个典型应用场景,它帮助开发者高效地处理网络数据包,实现网络协议栈的逐层解析和处理 。

4.3 克隆与复制:数据复用的技巧

(1) skb_clone:轻量级克隆。skb_clone 函数用于创建一个 sk_buff 的克隆体 。它只复制 sk_buff 结构体本身,而数据缓冲区部分则与原 sk_buff 共享 。这意味着克隆后的 sk_buff 和原 sk_buff 指向相同的数据,它们的 users 引用计数会增加 。其函数原型为 struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask),其中 skb 是要克隆的源 sk_buff,gfp_mask 是内存分配标志 。

当我们需要在不复制数据的情况下,创建一个新的 sk_buff 来处理相同的数据时,skb_clone 就非常有用 。比如在网络协议栈中,当一个数据包需要被多个模块处理,但每个模块都希望有自己独立的 sk_buff 结构体来记录处理状态时,就可以使用 skb_clone 。假设我们有一个接收到的数据包 skb,需要将其传递给两个不同的模块进行处理 :

#include <linux/skbuff.h>// 模块 1 处理函数staticvoidmodule1_process(struct sk_buff *skb){    // 这里进行模块 1 的处理操作    //...}// 模块 2 处理函数staticvoidmodule2_process(struct sk_buff *skb){    // 这里进行模块 2 的处理操作    //...}// 接收数据包并分发处理的函数staticvoidmock_receive_and_process_packet(struct sk_buff *skb){    // 克隆 skb    struct sk_buff *skb_clone1 = skb_clone(skb, GFP_KERNEL);    struct sk_buff *skb_clone2 = skb_clone(skb, GFP_KERNEL);    if (skb_clone1 && skb_clone2) {        module1_process(skb_clone1);        module2_process(skb_clone2);        // 处理完后,释放克隆的 skb        dev_kfree_skb(skb_clone1);        dev_kfree_skb(skb_clone2);    }    // 释放原始 skb    dev_kfree_skb(skb);}

在这个示例中,通过 skb_clone 函数创建了两个克隆的 sk_buff,分别传递给 module1_process 和 module2_process 函数进行处理 。由于克隆的 sk_buff 共享数据缓冲区,避免了数据的重复拷贝,提高了效率 。

(2)pskb_copy:深度复制。pskb_copy 函数用于创建一个 sk_buff 的深度副本 。它不仅复制 sk_buff 结构体,还会复制线性数据区域(如果存在非线性数据,对于 skb_shared_info 会使用指针指向的方式 ) 。这意味着新创建的 sk_buff 有自己独立的数据副本,与原 sk_buff 的数据不再共享 。其函数原型为 struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask),其中 skb 是要复制的源 sk_buff,gfp_mask 是内存分配标志 。

当我们需要对 sk_buff 的数据进行修改,且不希望影响原 sk_buff 的数据时,就需要使用 pskb_copy 。比如在对数据包进行一些过滤或修改操作时,如果直接在原 sk_buff 上操作可能会影响其他模块对数据的处理,这时就可以先使用 pskb_copy 创建一个副本,然后在副本上进行操作 。假设我们要对接收到的数据包进行修改,将其 IP 地址替换为另一个地址 :

#include <linux/skbuff.h>#include <linux/ip.h>// 修改 IP 地址的函数staticvoidmock_modify_ip_address(struct sk_buff *skb){    // 复制 skb    struct sk_buff *skb_copy = pskb_copy(skb, GFP_KERNEL);    if (!skb_copy) {        return;    }    struct iphdr *iph = ip_hdr(skb_copy);    // 修改 IP 地址    iph->saddr = htonl(INADDR_ANY);    iph->daddr = htonl(INADDR_LOOPBACK);    // 这里可以继续进行其他修改操作    //...    // 处理完后,释放复制的 skb    dev_kfree_skb(skb_copy);}

在这个例子中,通过 pskb_copy 函数创建了一个 sk_buff 的副本 skb_copy,然后在副本上修改了 IP 地址 ,这样原 sk_buff 的数据不会受到影响 。

4.4 数据拼接、裁剪、共享:高效处理的秘诀

(1)数据拼接——在网络编程中,有时需要将多个数据片段拼接成一个完整的数据包 。对于 sk_buff 来说,数据拼接可以通过巧妙地操作指针来实现 。一种常见的方法是使用 skb_put 函数将新的数据追加到已有的 sk_buff 的尾部 。假设我们有两个数据片段 data1 和 data2,需要将它们拼接成一个数据包 。首先分配一个足够大的 sk_buff 来容纳这两个数据片段 :

#include <linux/skbuff.h>#include <linux/string.h>// 数据拼接static struct sk_buff *mock_skb_concat(char *data1, int len1, char *data2, int len2) {    // 分配足够大的 sk_buff,容纳两个数据片段    struct sk_buff *skb = alloc_skb(len1 + len2, GFP_KERNEL);    if (!skb) {        printk(KERN_ERR "Failed to allocate skb for concat\n");        return NULL;    }    // 拼接第一个数据片段    void *ptr = skb_put(skb, len1);    memcpy(ptr, data1, len1);    // 拼接第二个数据片段    ptr = skb_put(skb, len2);    memcpy(ptr, data2, len2);    return skb;}

(2)数据裁剪——数据裁剪是指从 sk_buff 中截取部分数据,保留需要的内容,丢弃不需要的部分。常用的实现方式是结合 skb_pull(裁剪头部)和 skb_trim(裁剪尾部)函数,精准控制裁剪范围,不影响剩余数据的完整性。skb_trim 函数原型为 void skb_trim(struct sk_buff *skb, unsigned int len),作用是将 sk_buff 的有效数据长度裁剪为指定的 len,若原 len 大于指定 len,则从尾部丢弃多余数据,tail 指针会向 data 指针方向移动;若原 len 小于指定 len,则无操作。

示例:假设一个 sk_buff 中包含完整的数据包,我们需要裁剪掉头部的 MAC 头(14 字节)和尾部的校验和(4 字节),保留中间的有效数据:

#include <linux/skbuff.h>// 数据裁剪:去掉 MAC 头(14 字节)和尾部校验和(4 字节)staticvoidmock_skb_trim(struct sk_buff *skb){    if (!skb) return;    // 裁剪头部 14 字节 MAC 头    skb_pull(skb, 14);    // 裁剪尾部 4 字节校验和,计算剩余有效长度    unsigned int new_len = skb->len - 4;    skb_trim(skb, new_len);    // 此时 skb 的有效数据为裁剪后的内容}

(3)数据共享——sk_buff 的数据共享机制,核心是通过引用计数和共享数据缓冲区,实现多个 sk_buff 复用同一份数据,避免重复拷贝,提升内存利用率和数据处理效率。与 skb_clone 的轻量级克隆不同,数据共享更侧重“多 skb 共用数据区”,且支持灵活的引用管理,适用于多模块、多协议层同时访问同一份数据的场景。

数据共享的核心实现依赖 sk_buff 的 users 引用计数和 skb_shared_info 结构体:当多个 sk_buff 共享同一份数据时,所有 skb 的 users 计数会共同维护,只有当所有共享的 skb 都被释放(users 计数为 0),数据缓冲区才会真正被回收。同时,skb_shared_info 会记录数据的共享状态,确保数据不被意外修改。

常用的数据共享相关 API 及用法:

  1. skb_get():增加 sk_buff 的引用计数,用于表示“有新的模块需要使用这份数据”,避免数据被提前释放。函数原型为 struct sk_buff *skb_get(struct sk_buff *skb),调用后 skb->users 加 1,返回原 skb 指针。
  2. skb_share_check():检查 sk_buff 是否可共享,若当前 skb 是唯一使用者(users=1),则返回原 skb;若已被共享,则创建一个新的 skb 并复制数据(避免修改共享数据)。适用于“要修改数据,但不确定是否已被共享”的场景。

实战示例:多协议层共享 sk_buff 数据,避免重复拷贝。比如网络协议栈中,链路层、网络层同时需要访问同一份数据包数据,通过引用计数实现共享,无需各自拷贝数据:

#include <linux/skbuff.h>#include <linux/ip.h>// 链路层处理函数(共享数据给网络层)staticvoidlink_layer_process(struct sk_buff *skb){    // 增加引用计数,允许网络层共享数据    skb_get(skb);    // 将 skb 传递给网络层处理    network_layer_process(skb);    // 链路层处理完成,释放自身引用    dev_kfree_skb(skb);}// 网络层处理函数(共享链路层传递的数据)staticvoidnetwork_layer_process(struct sk_buff *skb){    struct iphdr *iph = ip_hdr(skb);    // 处理 IP 层逻辑,无需拷贝数据,直接访问共享数据区    printk(KERN_INFO "Destination IP: %pI4\n", &iph->daddr);    // 网络层处理完成,释放引用    dev_kfree_skb(skb);}

注意事项:数据共享时,若多个模块同时操作数据,需确保数据只读;若某一模块需要修改数据,必须先通过 pskb_copy 创建数据副本,再进行修改,避免影响其他共享模块的正常使用。

补充说明:数据共享与 skb_clone 的区别——skb_clone 是“结构体克隆、数据共享”,克隆后的 skb 有独立的结构体(可修改元数据),但共享数据;而数据共享更侧重“多 skb 共用同一数据区”,可能多个 skb 指向同一个结构体和数据区,核心都是通过引用计数保证数据安全

五、网络收发流程案例分析

面试题写作模版

下面结合具体代码,展示从应用层发送数据、经各层协议封装后由网卡发出,以及从网卡接收数据、逐层解封装并交付应用层的完整流程中,sk_buff 的创建、传递与变化过程。我们以一个基于 TCP 协议的简单应用程序为例进行说明。

当应用层调用 send 函数发送数据时,数据首先会进入到传输层的 TCP 模块。在这个过程中,内核会分配一个新的 sk_buff 来承载数据,调用 alloc_skb 函数,分配一块合适的内存空间,此时 sk_buff 中的 data 和 tail 指针都指向数据的起始位置,len 字段记录数据的长度。

// 分配 sk_buffstruct sk_buff *skb = alloc_skb(data_len, GFP_KERNEL);if (!skb) {    // 分配失败处理    return -ENOMEM;}// 将应用层数据拷贝到 sk_buff 的数据区memcpy(skb_put(skb, data_len), user_data, data_len);

接着,TCP 协议需要为数据添加 TCP 首部,调用 skb_push 函数,将 data 指针向低地址移动,为 TCP 首部腾出空间,然后填充 TCP 首部的各个字段,如源端口、目的端口、序列号、确认号等。

// 为 TCP 首部预留空间struct tcphdr *th = (struct tcphdr *)skb_push(skb, sizeof(struct tcphdr));// 填充 TCP 首部字段th->source = htons(local_port);th->dest = htons(remote_port);// 其他字段填充...

完成 TCP 首部的封装后,数据会被传递到网络层的 IP 模块。IP 模块同样调用 skb_push 函数,为 IP 首部预留空间,然后填充 IP 首部的字段,包括源 IP 地址、目的 IP 地址、协议类型等。

// 为 IP 首部预留空间struct iphdr *iph = (struct iphdr *)skb_push(skb, sizeof(struct iphdr));// 填充 IP 首部字段iph->version = 4;iph->ihl = 5;iph->tos = 0;iph->tot_len = htons(skb->len);iph->id = htons(ip_id++);iph->frag_off = 0;iph->ttl = 64;iph->protocol = IPPROTO_TCP;iph->saddr = local_ip;iph->daddr = remote_ip;// 计算 IP 首部校验和iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

最后,数据会被传递到链路层。链路层会根据网络设备的类型,为数据添加相应的链路层首部,如以太网首部。同样通过 skb_push 函数为链路层预留空间,并填充首部字段,如源 MAC 地址、目的 MAC 地址、协议类型等。完成封装后,链路层会将 sk_buff 传递给网卡驱动,由网卡驱动将数据发送出去。

// 为以太网首部预留空间struct ethhdr *eth = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr));// 填充以太网首部字段memcpy(eth->h_source, local_mac, ETH_ALEN);memcpy(eth->h_dest, remote_mac, ETH_ALEN);eth->h_proto = htons(ETH_P_IP);// 将 sk_buff 传递给网卡驱动发送dev_queue_xmit(skb);

完成发送流程后,当网卡接收到外部网络数据时,会将数据传递给链路层,开始完整的接收流程。链路层首先会根据接收到的数据创建一个 sk_buff,将数据拷贝到 sk_buff 的数据区,然后解析链路层首部,获取目的 MAC 地址等信息。如果目的 MAC 地址是本机的 MAC 地址,链路层会调用 skb_pull 函数,将 data 指针移动到链路层首部之后,去掉链路层首部,将 sk_buff 传递给网络层。

// 链路层接收数据,创建 sk_buffstruct sk_buff *skb = alloc_skb(len, GFP_ATOMIC);if (!skb) {    // 分配失败处理    return NET_RX_DROP;}// 将数据从网卡缓冲区拷贝到 sk_buff 的数据区skb_put(skb, len);memcpy(skb->data, rx_buffer, len);// 解析链路层首部struct ethhdr *eth = (struct ethhdr *)skb->data;if (memcmp(eth->h_dest, local_mac, ETH_ALEN) != 0) {    // 目的 MAC 地址不是本机,丢弃    kfree_skb(skb);    return NET_RX_DROP;}// 去掉链路层首部,传递给网络层skb_pull(skb, sizeof(struct ethhdr));netif_receive_skb(skb);

网络层接收到 sk_buff 后,会解析 IP 首部,检查目的 IP 地址是否是本机的 IP 地址。如果是,网络层会调用 skb_pull 函数,去掉 IP 首部,根据协议类型,将 sk_buff 传递给相应的传输层协议模块,如 TCP 或 UDP。

// 网络层接收 sk_buff,解析 IP 首部struct iphdr *iph = (struct iphdr *)skb->data;if (iph->daddr != local_ip) {    // 目的 IP 地址不是本机,进行转发或丢弃    if (ip_forward(skb)) {        return NET_RX_SUCCESS;    } else {        kfree_skb(skb);        return NET_RX_DROP;    }}// 去掉 IP 首部,根据协议类型传递给传输层skb_pull(skb, iph->ihl * 4);switch (iph->protocol) {    case IPPROTO_TCP:        tcp_v4_rcv(skb);        break;    case IPPROTO_UDP:        udp_rcv(skb);        break;    // 其他协议处理...    default:        kfree_skb(skb);        return NET_RX_DROP;}

传输层的 TCP 或 UDP 模块接收到 sk_buff 后,会解析相应的首部,根据端口号找到对应的套接字,将数据传递给应用层。TCP 模块还会进行数据的重组、校验等操作。

// TCP 层接收 sk_buff,解析 TCP 首部struct tcphdr *th = (struct tcphdr *)skb->data;struct sock *sk = inet_lookup_sk(th->source, th->dest);if (!sk) {    // 找不到对应的套接字,丢弃    kfree_skb(skb);    return;}// 去掉 TCP 首部,将数据传递给应用层skb_pull(skb, sizeof(struct tcphdr));skb_queue_tail(&sk->sk_receive_queue, skb);// 通知应用层有数据到达wake_up_interruptible(&sk->sk_sleep);

在整个网络收发流程中,sk_buff 就像是一个数据载体,从应用层到网卡发送、再从网卡接收回到应用层,它始终承载着数据和相关的控制信息,确保数据能够准确无误地被传输和处理。通过对 sk_buff 的一系列操作,如创建、数据拷贝、首部封装与解析、指针移动等,实现了网络数据在协议栈各层级间的高效流转,构成了 Linux 网络报文生命周期的核心交互逻辑。

六、常见问题与解决方案

面试题写作模版

6.1 内存不足 /skb 分配失败

在高并发的网络环境下,内存不足是一个常见的问题,它可能导致 sk_buff 的分配失败,进而影响网络数据的正常传输。当系统中同时有大量的网络连接在进行数据传输时,每个连接都需要分配一定数量的 sk_buff 来承载数据包,这就会对内存资源产生巨大的压力。如果此时系统的内存资源有限,无法满足 sk_buff 的分配需求,就会出现内存不足的情况。

为了解决内存不足的问题,我们可以从多个方面入手。通过 sysctl 命令或修改配置文件,我们可以调整 net.core.wmem_max、net.core.rmem_max 等参数,增加系统用于网络缓存的内存空间。就像给仓库增加存储空间,以便能存放更多的货物(数据包)。

# 增大网络接收/发送缓冲区sysctl -w net.core.rmem_max=16777216sysctl -w net.core.wmem_max=16777216sysctl -w net.core.netdev_max_backlog=10000

优化缓存机制也是关键。我们可以根据实际的网络流量和业务需求,合理地设置缓存的大小和淘汰策略。例如,采用 LRU(最近最少使用)算法来管理缓存,将长时间未使用的缓存数据淘汰掉,为新的数据腾出空间。还可以结合应用场景,使用 skb 内存池,对频繁使用的 sk_buff 进行预分配与复用,减少内存的重复分配与释放。

// 定义一个简单的 sk_buff 内存池struct sk_buff *skb_pool[1024];int pool_idx = 0;// 从内存池获取 skb(代替直接 alloc_skb,提升效率、避免分配失败)struct sk_buff *alloc_skb_from_pool(unsigned int size, gfp_t gfp){    if (pool_idx > 0)        return skb_pool[--pool_idx];    // 内存池空,才真正分配    return alloc_skb(size, gfp);}// 释放回内存池(不直接释放)voidfree_skb_to_pool(struct sk_buff *skb){    if (pool_idx < 1024) {        skb_pool[pool_idx++] = skb;        return;    }    // 池满,真正释放    kfree_skb(skb);}

6.2 数据丢失

数据丢失是另一个需要重视的问题,它可能由多种原因引起。缓冲区溢出是一个常见的原因,当数据包的大小超过了 sk_buff 缓冲区的容量时,就会发生缓冲区溢出,导致部分数据丢失。协议处理错误也可能导致数据丢失,比如在协议栈的某一层处理数据包时,如果出现错误的解析或操作,就可能使数据包无法正确地传递到下一层,从而造成数据丢失。

为了避免数据丢失,我们可以采取一些针对性的措施。对于缓冲区溢出的问题,我们可以通过扩大缓冲区的大小来解决。在进行网络配置时,根据网络流量的峰值情况,合理地设置缓冲区的大小,确保它能够容纳最大可能的数据包。

// 分配更大的缓冲区,避免溢出struct sk_buff *skb = alloc_skb(2048GFP_KERNEL);if (!skb) {    pr_err("分配 skb 失败,可能导致丢包\n");    return;}

优化协议栈的处理流程也非常重要。通过对协议栈代码的审查和优化,确保各层协议能够正确地解析和处理数据包,避免因协议处理错误而导致的数据丢失。还可以增加一些数据校验和重传机制,当发现数据在传输过程中出现错误或丢失时,能够及时进行重传,保证数据的完整性。

// 校验数据包长度,防止越界、截断、丢失if (skb->len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {    pr_warn("数据包长度异常,可能丢失数据,丢弃\n");    kfree_skb(skb);    return;}

6.3 性能瓶颈

性能瓶颈是影响网络系统效率的重要因素,它可能由多种原因导致。内存分配和释放频繁是一个常见的原因,当网络数据流量较大时,sk_buff 的创建和销毁操作会非常频繁,这会消耗大量的系统资源,导致性能下降。协议栈处理效率低也会成为性能瓶颈,比如在网络层的路由选择算法复杂度过高,或者传输层的拥塞控制算法不够优化,都会导致数据包在协议栈中的处理时间过长,影响网络传输的效率。

针对性能瓶颈,我们可以采取一系列优化策略。为了减少内存操作的开销,我们可以采用内存池技术,预先分配一定数量的 sk_buff,当有数据包需要处理时,直接从内存池中获取,而不是每次都进行内存分配和释放操作。这就像一个预先准备好工具的工具箱,需要工具时直接从箱子里拿,而不是每次都去制作新的工具。

// 高频收包场景使用内存池,避免频繁 alloc_skb/kfree_skbstruct sk_buff *skb = alloc_skb_from_pool(1536, GFP_ATOMIC);if (!skb)    return;// 处理完归还内存池,而不是直接释放process_skb(skb);free_skb_to_pool(skb);

优化协议栈的算法也是关键。在网络层,可以采用更高效的路由选择算法,减少路由计算的时间;在传输层,可以优化拥塞控制算法,使其能够更快速、准确地适应网络拥塞情况,提高数据传输的效率。

// 快速路径:直接转发,不经过完整协议栈(提升性能)if (is_forward_route(skb)) {    update_ttl_and_checksum(skb);    dev_queue_xmit(skb);  // 直接转发,性能提升显著    return;}

还可以通过硬件加速的方式,如采用高性能的网卡,来分担协议栈的处理压力,提升整体性能。例如,开启网卡 TSO、GSO、RSS 多队列特性,让硬件完成分片、校验、负载均衡,大幅降低 CPU 与内核开销。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-04-16 21:00:24 HTTP/2.0 GET : https://f.mffb.com.cn/a/485619.html
  2. 运行时间 : 0.076361s [ 吞吐率:13.10req/s ] 内存消耗:4,944.63kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=c8f27a4820c886b990c6e02a7c3a848b
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000537s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000691s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000352s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000276s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000491s ]
  6. SELECT * FROM `set` [ RunTime:0.000226s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000503s ]
  8. SELECT * FROM `article` WHERE `id` = 485619 LIMIT 1 [ RunTime:0.000592s ]
  9. UPDATE `article` SET `lasttime` = 1776344425 WHERE `id` = 485619 [ RunTime:0.000672s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000218s ]
  11. SELECT * FROM `article` WHERE `id` < 485619 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000582s ]
  12. SELECT * FROM `article` WHERE `id` > 485619 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001018s ]
  13. SELECT * FROM `article` WHERE `id` < 485619 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000950s ]
  14. SELECT * FROM `article` WHERE `id` < 485619 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000941s ]
  15. SELECT * FROM `article` WHERE `id` < 485619 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001837s ]
0.078027s