套接字缓冲区以及Net_device

news2025/3/14 21:43:43

基础网络模型图

一般网络设计分为三层架构和五层设计: 

一、三层架构

  1. 用户空间的应用层

    • 位于最上层,是用户直接使用的网络应用程序,如浏览器、邮件客户端、即时通讯软件等。这些程序通过系统调用(如 socket 接口)向内核空间的网络协议栈发起网络请求(如发送数据、建立连接)。
  2. 内核空间的网络协议栈层

    • 架构的核心层,负责实现网络协议(如 TCP/IP、UDP 等)的逻辑。包括数据的封装、解封装,协议规则处理(如路由选择、可靠传输控制),是网络数据处理的核心枢纽。
  3. 物理硬件层

    • 最底层,包含网卡等物理设备,负责将网络数据转换为电信号、光信号等物理信号进行传输,实现数据的实际发送与接收。

二、五层设计(协议栈层的细分)

  1. 系统调用接口层

    • 作为用户空间与内核空间的桥梁,提供标准化的系统调用接口(如 socket()send()recv() 等)。应用层通过这些接口请求内核的网络服务,内核也通过此层向应用返回处理结果。
  2. 协议无关的接口层

    • 屏蔽不同网络协议的差异,为上层提供统一的操作接口。无论底层是 TCP、UDP 还是其他协议,上层只需通过该层的统一接口即可访问网络功能,简化了应用开发。
  3. 网络协议实现层

    • 具体实现各种网络协议的逻辑,如:
      • IP 层:处理路由选择、数据包分片重组;
      • TCP 层:实现可靠传输、流量控制、拥塞控制;
      • UDP 层:处理无连接的快速数据传输。
        该层是协议栈的核心,决定了网络数据的传输规则。
  4. 驱动接口层

    • 为网络设备驱动程序提供统一的编程接口规范。无论网卡硬件如何差异,驱动程序只需遵循该接口标准,即可与内核协议栈交互,方便驱动开发与维护。
  5. 驱动程序层

    • 直接操作物理硬件(如网卡),实现数据的硬件层面收发。负责控制网卡寄存器、处理硬件中断,将内核协议栈的网络数据转换为网卡可处理的信号,或反之。

        其中Linux内核网络栈设计其中三层,分别是数据链路层、网络层和传输层。内核栈的任务就是将接收到的数据包从网络设备驱动程序传递给网络层(通常是ipv4或ipv6),接下来,如果数据包的目的地为当前设备,Linux内核网络栈就将其传递给传输层(TCP或者UDP协议监听套接字)。 如果数据包需要转发,就将其交给数据链路层进行传输。其中有可能产生数据包丢失、可能需要重组数据包、需要计算数据包的检验和等等。

一、套接字缓冲区管理数据

1.sk_buff

        在内核分析(收到)网络分组时,底层协议的数据将传递到更高的层。发送数据时顺序相反,各种协议产生的数据(首部和净荷)依次向更低的层传递,直至最终发送。这些操作的速度对网络子系统的性能有决定性的影响,因此内核使用一种特殊的结构,称为套接字缓冲区(socket buffer),它表示一个网络数据包,由双向链表构成,sk_buff结构表示一个包含报头的入站或出站数据包(SKB表示套接字缓冲区)。具体源码分析如下:

 

1. sk_buff 是什么?

sk_buff(Socket Buffer,简称 SKB)是 Linux 内核中用于管理网络数据包的核心数据结构。它表示一个包含协议首部(如 MAC、IP、TCP)和净荷的网络数据包,主要用于高效处理网络分组的收发和协议栈的分层操作。以下是其核心特点:

  • 双向链表结构sk_buff 通过 next 和 prev 指针构成双向链表,方便内核管理多个数据包(例如排队或重组分片)。

  • 指针分层:通过多组指针(如 mac_headernetwork_headertransport_header)快速定位不同协议层的首部,避免数据复制。

  • 动态调整:通过操作指针(如 headdatatailend)动态增删协议首部或调整数据区域,提升性能。


2. sk_buff 的结构与字段解析

以下是关键字段及其作用(基于用户提供的代码片段和描述):

  • 链表指针

    struct sk_buff *next;  // 指向下一个 sk_buff
    struct sk_buff *prev;  // 指向上一个 sk_buff

    用于将多个 SKB 组织成链表(例如接收队列或发送队列)。

  • 数据指针

    • head 和 end:指向数据缓冲区在内存中的起始和结束位置(固定边界)。

    • data 和 tail:指向当前协议层的数据起始和结束位置(随协议处理动态调整)。

  • 协议首部指针

    • mac_header:指向 MAC 层首部(如以太网帧头)。

    • network_header:指向网络层首部(如 IPv4/IPv6 头)。

    • transport_header:指向传输层首部(如 TCP/UDP 头)。


3. sk_buff 的使用场景
接收数据包(从底层到上层)
  1. 网卡驱动:收到原始数据后,分配一个 sk_buff,将数据拷贝到 head 和 end 之间的缓冲区。

  2. MAC 层处理:通过 mac_header 解析以太网帧头,剥离后更新 data 指针指向 IP 层数据。

  3. IP 层处理:通过 network_header 解析 IP 头,剥离后更新 data 指向传输层数据(如 TCP)。

  4. 传输层处理:通过 transport_header 解析 TCP/UDP 头,最终将净荷传递给应用层。

发送数据包(从上层到底层)
  1. 应用层:用户数据填充到 data 和 tail 之间。

  2. 传输层:通过 skb_push 添加 TCP/UDP 头,更新 transport_header

  3. 网络层:通过 skb_push 添加 IP 头,更新 network_header

  4. MAC 层:通过 skb_push 添加以太网帧头,更新 mac_header

  5. 网卡驱动:将 sk_buff 加入发送队列,最终由网卡发送。


4. 关键操作函数
  • 分配/释放

    struct sk_buff *skb = alloc_skb(size, GFP_KERNEL); // 分配 SKB
    kfree_skb(skb);                                   // 释放 SKB
  • 调整数据区域

    • skb_reserve(skb, len):预留头部空间(用于后续添加协议首部)。

    • skb_push(skb, len):向数据区域头部添加数据(如添加协议头)。

    • skb_pull(skb, len):从数据区域头部移除数据(如剥离协议头)。

    • skb_put(skb, len):扩展数据区域尾部(用于添加净荷)。


5. 示例:发送 TCP 数据包
// 1. 分配 SKB,预留协议头空间
struct sk_buff *skb = alloc_skb(MAX_SIZE, GFP_KERNEL);
skb_reserve(skb, ETH_HLEN + IP_HLEN + TCP_HLEN);

// 2. 填充应用层数据
memcpy(skb_put(skb, payload_len), user_data, payload_len);

// 3. 添加 TCP 头
skb_push(skb, TCP_HLEN);
skb->transport_header = skb->data;
tcp_header = (struct tcphdr *)skb->transport_header;
// 填充 TCP 头字段...

// 4. 添加 IP 头
skb_push(skb, IP_HLEN);
skb->network_header = skb->data;
ip_header = (struct iphdr *)skb->network_header;
// 填充 IP 头字段...

// 5. 添加 MAC 头
skb_push(skb, ETH_HLEN);
skb->mac_header = skb->data;
eth_header = (struct ethhdr *)skb->mac_header;
// 填充 MAC 头字段...

// 6. 将 SKB 交给网卡驱动发送
dev_queue_xmit(skb);

6. 性能优势
  • 零拷贝:原来的方式是头部位置不变,每次新插入头部信息,需要将原有头部以及数据向后拷贝,从而空出位置插入新的头部;而这种方式只需要将头部指针向前扩展就可以无需拷贝原有数据。

  • 高效链表管理:双向链表支持快速插入、删除和遍历数据包。

  • 分层解耦:各协议层只需操作自己的首部指针,无需关心其他层细节。

 

 2.sk_buff_data_t

1. 代码片段分析

用户提供的代码片段是一个条件编译的定义,用于在 32 位系统中定义 sk_buff_data_t 类型:

#ifdef Nt_5KRUH_DATA_USES_OFFSEI
typedef unsigned int sk_buff_data_t;
#else
typedef unsigned char *sk_buff_data_t;  // 修正后的正确语法(原代码存在笔误)
#endif
  • 条件编译逻辑

    • 如果定义了宏 Nt_5KRUH_DATA_USES_OFFSEI,则 sk_buff_data_t 被定义为 unsigned int

    • 否则,sk_buff_data_t 被定义为 unsigned char *)。


2. sk_buff_data_t 的用途

sk_buff_data_t 在 Linux 内核中用于表示 套接字缓冲区(sk_buff)的指针或偏移量,具体用途包括:

  • 管理数据缓冲区的指针(如 headdatatailend)。

  • 在内存中高效定位协议首部(如 MAC、IP、TCP 头)或净荷数据。


3. 使用场景
场景 1:使用偏移量(Nt_5KRUH_DATA_USES_OFFSEI 已定义)
  • 定义sk_buff_data_t 为 unsigned int,表示相对于某个基地址的偏移量。

  • 使用方式

    // 示例:获取实际指针
    char *base = skb->head;  // 缓冲区的基地址
    sk_buff_data_t data_offset = skb->data;  // 偏移量
    char *data_ptr = base + data_offset;     // 实际数据指针
  • 优势

    • 当缓冲区内存移动时,只需更新基地址,无需修改所有指针。

    • 节省内存(偏移量占 4 字节,指针在 32 位系统也占 4 字节,但逻辑更灵活)。

场景 2:使用指针(Nt_5KRUH_DATA_USES_OFFSEI 未定义)
  • 定义sk_buff_data_t 为 unsigned char *,直接指向内存地址。

  • 使用方式

    // 示例:直接访问数据
    unsigned char *data_ptr = skb->data;
  • 优势

    • 直接操作指针,无需计算偏移量,代码更简洁。

    • 适用于对性能要求极高且缓冲区位置固定的场景。

 3.从套接字缓冲区获取TCP/UDP首部:

1. tcp_hdr 函数
  • 作用
    该函数用于获取指向 sk_buff(套接字缓冲区)中 TCP 协议头 的指针。
    通过 skb_transport_header(skb) 获取传输层头部指针后,将其强制转换为 struct tcphdr * 类型(TCP 头结构体)。

  • 使用案例
    在接收 TCP 数据包时,解析 TCP 头部字段(如源端口、目的端口、序列号、确认号等)。

    // 示例:获取 TCP 头部并解析源端口
    struct sk_buff *skb = ...;  // 接收到的数据包
    struct tcphdr *tcp = tcp_hdr(skb);
    u16 src_port = ntohs(tcp->source);  // 源端口(网络字节序转主机字节序)
    u16 dst_port = ntohs(tcp->dest);    // 目的端口

2. udp_hdr 函数
  • 作用
    该函数用于获取指向 sk_buff 中 UDP 协议头 的指针。
    通过 skb_transport_header(skb) 获取传输层头部指针后,将其强制转换为 struct udphdr * 类型(UDP 头结构体)。

  • 使用案例
    在处理 UDP 数据包时,解析 UDP 头部字段(如源端口、目的端口、长度、校验和等)。

    // 示例:获取 UDP 头部并计算校验和
    struct sk_buff *skb = ...;  // 接收到的数据包
    struct udphdr *udp = udp_hdr(skb);
    u16 src_port = ntohs(udp->source);  // 源端口
    u16 len = ntohs(udp->len);          // UDP 数据包长度

4. Linux内核提供用于操作套接字缓冲区的标准函数

1. alloc_skb 函数

作用:分配 sk_buff 结构体及关联的数据缓冲区,用于创建新的套接字缓冲区,供网络协议栈封装数据使用。

案例

#include <linux/skbuff.h>  

// 模拟分配 sk_buff 用于网络数据发送  
void example_alloc_skb() {  
    struct sk_buff *skb;  
    // 分配 2048 字节的缓冲区,GFP_KERNEL 表示内核态分配内存  
    skb = alloc_skb(2048, GFP_KERNEL);  
    if (!skb) {  
        // 分配失败处理  
        return;  
    }  
    // 填充数据(示例)  
    memcpy(skb->data, "network data", 12);  
    skb->len = 12; // 设置数据长度  
    // 后续可将 skb 交给协议层发送  
    kfree_skb(skb); // 使用完毕释放  
}  

2. skb_copy 函数

作用:复制 sk_buff 结构体及其关联的数据(包括头部和数据部分),生成完全独立的副本,原 skb 与副本互不影响。

案例

#include <linux/skbuff.h>  

// 模拟复制 skb 用于协议处理  
void example_skb_copy() {  
    struct sk_buff *original_skb, *copied_skb;  
    original_skb = alloc_skb(1024, GFP_KERNEL);  
    if (!original_skb) return;  

    // 填充原始 skb 数据  
    memcpy(original_skb->data, "original data", 13);  
    original_skb->len = 13;  

    // 复制 skb  
    copied_skb = skb_copy(original_skb, GFP_KERNEL);  
    if (!copied_skb) {  
        kfree_skb(original_skb);  
        return;  
    }  

    // 修改副本数据(不影响原始 skb)  
    memcpy(copied_skb->data, "copied data", 11);  
    copied_skb->len = 11;  

    kfree_skb(original_skb);  
    kfree_skb(copied_skb);  
}  

3. skb_clone 函数

作用:克隆 sk_buff 结构体,共享底层数据缓冲区。新 skb 与原 skb 的数据部分共享内存,仅元数据(如头部指针、长度等)独立,节省内存开销。
案例

#include <linux/skbuff.h>  

// 模拟克隆 skb 共享数据  
void example_skb_clone() {  
    struct sk_buff *original_skb, *cloned_skb;  
    original_skb = alloc_skb(1024, GFP_KERNEL);  
    if (!original_skb) return;  

    // 填充原始 skb 数据  
    memcpy(original_skb->data, "shared data", 11);  
    original_skb->len = 11;  

    // 克隆 skb(共享数据)  
    cloned_skb = skb_clone(original_skb, GFP_KERNEL);  
    if (!cloned_skb) {  
        kfree_skb(original_skb);  
        return;  
    }  

    // 验证数据共享:修改原始 skb 数据,克隆的 skb 数据同步变化  
    memcpy(original_skb->data, "updated data", 12);  
    // 两者数据内容相同  
    kfree_skb(original_skb);  
    kfree_skb(cloned_skb);  
}  

1. skb_tailroom 函数

作用:计算 sk_buff 尾部剩余可用空间。通过 skb->end - skb->tail 计算尾部空间(非非线性缓冲区场景另有处理),用于判断能否在数据包末尾追加数据(如调用 skb_put 前检查空间)。

案例

#include <linux/skbuff.h>  

void use_skb_tailroom() {  
    struct sk_buff *skb = alloc_skb(2048, GFP_KERNEL);  
    if (!skb) return;  

    // 检查尾部空间是否足够追加 100 字节数据  
    int tail_space = skb_tailroom(skb);  
    if (tail_space >= 100) {  
        unsigned char *new_tail = skb_put(skb, 100);  
        if (new_tail) {  
            // 填充数据  
            memset(new_tail, 0xAA, 100);  
        }  
    }  
    kfree_skb(skb);  
}  
2. skb_headroom 函数

作用:计算 sk_buff 头部剩余可用空间。通过 skb->data - skb->head 得出头部空间大小,用于判断能否在数据包头部添加协议首部(如调用 skb_push 前确认空间)。

案例

#include <linux/skbuff.h>  

void use_skb_headroom() {  
    struct sk_buff *skb = alloc_skb(2048, GFP_KERNEL);  
    if (!skb) return;  

    // 检查头部空间是否足够添加 20 字节的协议首部  
    unsigned int head_space = skb_headroom(skb);  
    if (head_space >= 20) {  
        unsigned char *new_data = skb_push(skb, 20);  
        if (new_data) {  
            // 填充协议首部数据  
            memset(new_data, 0xBB, 20);  
        }  
    }  
    kfree_skb(skb);  
}  
3. skb_realloc_headroom 函数

作用:重新分配 sk_buff 的头部空间。当需要扩展头部空间(如原有头部空间不足)时,调整缓冲区,确保满足新的头部空间需求,返回新的 sk_buff 指针。

案例

#include <linux/skbuff.h>  

void use_skb_realloc_headroom() {  
    struct sk_buff *skb = alloc_skb(1024, GFP_KERNEL);  
    if (!skb) return;  

    // 原有头部空间不足,需扩展到 100 字节  
    unsigned int required_headroom = 100;  
    struct sk_buff *new_skb = skb_realloc_headroom(skb, required_headroom);  
    if (new_skb) {  
        skb = new_skb;  
        // 检查新头部空间  
        unsigned int new_head_space = skb_headroom(skb);  
        if (new_head_space >= required_headroom) {  
            // 成功扩展,可添加首部  
            unsigned char *new_data = skb_push(skb, 100);  
            if (new_data) {  
                memset(new_data, 0xCC, 100);  
            }  
        }  
    }  
    kfree_skb(skb);  
}  

 

二、管理套接字缓冲区数据      

        套接字缓冲区结构不仅包含上述指针,还包括用于处理相关的数据和管理套接字缓冲区自身的其他成员。下面列出的是一些最重要的成员:

  • tstamp 保存了分组到达的时间。
  • dev 指定了处理分组的网络设备。
  • sk 是一个指针,指向用于处理该分组的套接字对应的 socket 实例。
  • dst 表示接下来该分组通过内核网络实现的路由。
  • next 和 prev 用于将套接字缓冲区保存到一个双向链表中。

使用一个表头来实现套接字缓冲区的等待队列。结构如下:

 

三、补充net_device

1.net_device结构体

        net_device 结构体存储着网络设备的所有信息,每个设备都有这种结构。所有设备的 net_device 结构放在一个全局变量 dev_base 所有全局列表中。和 sk_buff 一样,整体结构相当庞大的。结构体中有一个 next 指针,用来连接系统中所有网络设备。内核把这些连接起来的设备组成一个链表,并由全局变量 dev_base 指向链表的第一个元素。net_device 结构体具体源码如下:

         该结构体包含设备参数:设备的IRQ号、设备的MAC地址、设备名称(eth1、eth0)、设备的标志(up、down)、与设备相关的组播地址清单、设备支持的功能、网络设备回调函数的对象(net_device_ops)、设备最后一次发送数据包的时间戳、设备最后一次接收数据包的时间戳。

1. 设备的 IRQ 号

  • 作用:IRQ(Interrupt Request,中断请求)号是网络设备与内核通信的 “信号通道”。当设备完成数据接收或准备好发送数据时,会通过触发 IRQ 中断通知内核。例如,网卡收到网络数据后,通过预设的 IRQ 号向内核发送中断信号,内核响应后调用对应的处理函数读取数据。
  • 意义:确保内核及时感知设备的状态变化,实现高效的数据交互,是设备与内核协同工作的基础。

2. 设备的 MAC 地址

  • 作用:MAC 地址是网络设备的物理层唯一标识符,遵循 IEEE 802 标准。在数据链路层,以太网帧通过源 MAC 和目标 MAC 地址实现设备间通信。例如,当主机向路由器发送数据时,数据帧的目标 MAC 地址会填写路由器接口的 MAC 地址。
  • 意义:保障数据链路层通信的准确性,是局域网内设备定位的关键标识。

3. 设备名称(如 eth1、eth0)

  • 作用:设备名称是用户和内核操作设备的 “身份标识”。用户通过名称(如 eth0)执行配置命令(如 ifconfig eth0 up 启用设备),内核通过名称管理设备的网络参数(如 IP 地址绑定)。
  • 意义:提供直观的设备区分方式,方便系统管理和用户操作。

4. 设备的标志(up、down)

  • 作用:标志位用于表示设备的工作状态。up 表示设备处于启用状态,可正常收发数据;down 表示设备禁用,停止数据传输。例如,使用 ifconfig eth0 down 命令将设备状态设为 down,此时设备不再处理网络数据。
  • 意义:内核和用户通过状态标志控制设备的工作流程,确保网络通信的可控性。

5. 与设备相关的组播地址清单

  • 作用:存储设备订阅的组播地址。当网络中传输组播数据(如视频会议、流媒体广播)时,设备仅接收目标地址属于自身组播清单的数据。例如,视频会议软件会将设备加入特定组播地址,设备只处理该组播地址的数据流。
  • 意义:实现高效的组播数据过滤,减少无关数据处理,优化网络带宽利用。

6. 设备支持的功能

  • 作用:记录设备支持的特性,如是否支持 VLAN 划分、硬件校验和卸载、巨帧(Jumbo Frame)等。例如,若设备支持硬件校验和卸载,内核会将 TCP/UDP 校验和计算任务交给设备硬件处理,减轻 CPU 负担。
  • 意义:内核根据设备功能优化数据处理流程,充分发挥硬件性能。

7. 网络设备回调函数的对象(net_device_ops)

  • 作用net_device_ops 是一个函数指针集合,包含设备操作的核心函数,如 ndo_start_xmit(发送数据)、ndo_init(设备初始化)等。内核通过调用这些函数实现对设备的控制。例如,发送网络数据时,内核调用 ndo_start_xmit 触发设备的硬件发送流程。
  • 意义:提供统一的设备操作接口,屏蔽不同硬件的实现差异,确保内核网络子系统的通用性。

8. 设备最后一次发送 / 接收数据包的时间戳

  • 作用:记录设备最近一次发送和接收数据包的时间。这些时间戳用于网络统计、监控和调试,例如计算设备的空闲时间、分析网络流量突发情况。
  • 意义:帮助管理员或内核诊断网络性能问题,如通过时间戳发现设备长时间未接收数据,可能存在链路故障。

2.网络设备注册和注销函数 

  

1. register_netdevice 函数
  • 作用:将网络设备(通过 net_device 结构体表示)注册到 Linux 内核。内核通过此函数完成设备的初始化检查、资源登记,并将设备加入全局网络设备链表(由 dev_base 管理),使设备能够被内核网络子系统识别和管理。函数会校验设备状态(如 reg_state 是否为未初始化状态),确保注册过程合法。
2. unregister_netdevice 函数
  • 作用:从 Linux 内核中注销已注册的网络设备。它会移除设备在全局链表中的节点,释放设备相关资源(如网络配置、统计信息等),使设备不再被内核网络子系统管理,通常用于设备驱动卸载或设备移除场景。

使用案例

#include <linux/module.h>  
#include <linux/netdevice.h>  
#include <linux/etherdevice.h>  

// 简化模拟 net_device 结构体(实际内核中该结构体更复杂)  
struct net_device *sim_net_dev;  

// 初始化模拟网络设备  
static int __init net_device_module_init(void) {  
    // 分配 net_device 结构体  
    sim_net_dev = alloc_netdev(sizeof(struct net_device), "sim_dev", ether_setup);  
    if (!sim_net_dev) {  
        return -ENOMEM;  
    }  

    // 注册网络设备到内核  
    int ret = register_netdevice(sim_net_dev);  
    if (ret < 0) {  
        free_netdev(sim_net_dev);  
        return ret;  
    }  
    printk(KERN_INFO "Network device registered successfully\n");  
    return 0;  
}  

// 卸载模块时反注册设备  
static void __exit net_device_module_exit(void) {  
    if (sim_net_dev) {  
        unregister_netdevice(sim_net_dev);  
        free_netdev(sim_net_dev);  
        printk(KERN_INFO "Network device unregistered successfully\n");  
    }  
}  

module_init(net_device_module_init);  
module_exit(net_device_module_exit);  
MODULE_LICENSE("GPL");  
MODULE_DESCRIPTION("Net Device Registration Example");  

        在 Linux 内核的网络子系统中,net_device 结构体扮演着至关重要的角色,它是内核管理网络设备的核心数据结构,存储着网络设备的各种关键信息,如设备名称、MAC 地址、中断号等。为了让内核能够识别和管理这些网络设备,我们需要使用 register_netdevice 函数将设备注册到内核中,而当设备不再使用时,则使用 unregister_netdevice 函数进行注销操作。

        这些基本的设备管理操作是网络设备正常工作的基础,随着网络技术的发展和网络负载的不断变化,传统的网络设备管理和数据处理方式也面临着挑战。接下来,我们将探讨在网络设备驱动中为了应对高负载情况而引入的 NAPI 技术,以及网络设备驱动在数据包收发过程中的具体任务,了解它们如何在 net_device 结构体和注册、注销机制的基础上进一步优化网络性能。

1. NAPI 技术解析
  • 老式中断驱动模式缺陷:传统网络设备驱动采用 “中断驱动模式”,每接收一个数据包就触发一次中断。高负载时,频繁中断会导致 CPU 大量时间消耗在中断处理上,降低整体效率。
  • NAPI 技术优势:NAPI(New API)是一种混合模式技术。在低负载时,设备仍通过中断响应数据;高负载时,驱动切换为 “轮询模式”,一次性处理多个数据包,减少中断开销,提升网络数据处理效率。
2. 网络设备驱动的数据包收发任务
  • 接收方向:驱动程序负责接收目的地址为本主机的数据包,先传递给网络层(如 IP 层),再由网络层传递给传输层(如 TCP/UDP 层)。
  • 发送方向:驱动程序处理本主机生成的外出数据包,或转换接收到的数据包。无论收发包,都需通过路由子系统执行查找操作,确定数据包的转发路径。

        每个SKB都有一个dev成员(一个net_device结构实例),对于到来的数据包,这个成员表示接收它的网络设备;而对于外出地数据包,它表示发送它的网络设备。 

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

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

相关文章

2024下半年真题 系统架构设计师 案例分析

案例一 软件架构 关于人工智能系统的需求分析&#xff0c;给出十几个需求。 a.系统发生业务故障时&#xff0c;3秒内启动 XXX&#xff0c;属于可靠性 b.系统中的数据进行导出&#xff0c;要求在3秒内完成&#xff0c;属于可用性 c.质量属性描述&#xff0c;XXX&#xff0c;属…

c++介绍智能指针 十二(2)

智能指针share_ptr,与unique_ptr不同&#xff0c;多个shar_ptr对象可以共同管理一个指针&#xff0c;它们通过一个共同的引用计数器来管理指针。当一个智能指针对象销毁时&#xff0c;计数器减一。当计数器为0时&#xff0c;会将所指向的内存对象释放。 #include<memory>…

西门子S7-1200 PLC远程调试技术方案(巨控GRM532模块)

三步快速实现远程调试 硬件部署 准备西门子S7-1200 PLC、巨控GRM552YW-C模块及编程电脑。GRM552YW-C通过网口与PLC连接&#xff0c;支持4G/5G/Wi-Fi/有线网络接入&#xff0c;无需复杂布线。 软件配置 安装GVCOM3配置软件&#xff0c;注册模块&#xff08;输入唯一序列号与密…

Mac上更改默认应用程序

Mac上为某些文件设置默认打开应用的时候&#xff0c;刚开始是通过打开方式&#xff0c;其他里面&#xff0c;勾选始终以此方式打开&#xff0c;但实际上这个功能并不太好用&#xff0c;经常会让人误以为已经设置好了。但是实际上只是在当前目录起作用。真正解决这个问题可以按照…

【开源+代码解读】Search-R1:基于强化学习的检索增强大语言模型框架3小时即可打造个人AI-search

大语言模型(LLMs)在处理复杂推理和实时信息检索时面临两大挑战:知识局限性(无法获取最新外部知识)和检索灵活性不足(传统方法依赖固定检索流程)。现有方法如检索增强生成(RAG)和工具调用(Tool-Use)存在以下问题: RAG:单轮检索导致上下文不足,无法适应多轮交互场景…

贪心算法和遗传算法优劣对比——c#

项目背景&#xff1a;某钢管厂的钢筋原材料为 55米&#xff0c;工作需要需切割 40 米&#xff08;1段&#xff09;、11 米&#xff08;15 段&#xff09;等 4 种规格 &#xff0c;现用贪心算法和遗传算法两种算法进行计算&#xff1a; 第一局&#xff1a;{ 40, 1 }, { 11, 15…

网络安全防护总体架构 网络安全防护工作机制

1 实践内容 1.1 安全防范 为了保障"信息安全金三角"的CIA属性、即机密性、完整性、可用性&#xff0c;信息安全领域提出了一系列安全模型。其中动态可适应网络安全模型基于闭环控制理论&#xff0c;典型的有PDR和P^2DR模型。 1.1.1 PDR模型 信息系统的防御机制能…

SpringCloud带你走进微服务的世界

认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部…

Python设计模式 - 建造者模式

定义 建造者模式是一种创建型设计模式&#xff0c;主要用于构建包含多个组成部分的复杂对象。它将对象的构建过程与表示分离&#xff0c;使得同样的构建过程可以创建不同的对象表示。 结构 抽象建造者&#xff08;Builder&#xff09;&#xff1a;声明创建产品的各个部件的方…

在 Ubuntu 上安装和配置 Docker 的完整指南

Docker 是一个开源的平台&#xff0c;旨在简化应用程序的开发、部署和运行。通过将应用程序及其依赖项打包到容器中&#xff0c;Docker 确保应用程序可以在任何环境中一致地运行。 目录 前言安装前的准备安装 Docker 步骤 1&#xff1a;更新包索引步骤 2&#xff1a;安装必要…

网络安全之数据加密(DES、AES、RSA、MD5)

刚到公司时&#xff0c;我的工作就是为app端提供相应的接口。之前app使用的是PHP接口&#xff0c;对数据加密方面做得比较少。到使用java接口时&#xff0c;老大开始让我们使用DES加密&#xff0c;进行数据传输&#xff0c;但是后来觉得DES是对称加密&#xff0c;密钥存在客户端…

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 校园周边美食探索及分享平台结构图…

vscode关闭仓库后如何打开

vscode源代码管理->更改->代码 关闭仓库后如何打开。 关闭仓库操作 打开仓库操作 1.按下 Ctrl Shift P&#xff08;Windows/Linux&#xff09;或 Cmd Shift P&#xff08;Mac&#xff09;打开命令面板。 2.在命令面板中输入 Git: Open Repository&#xff0c;然后选…

DeepSeek-R1 论文阅读总结

1. QA问答&#xff08;我的笔记&#xff09; Q1: DeepSeek如何处理可读性问题&#xff1f; 通过构建冷启动数据&#xff08;数千条长CoT数据&#xff09;微调基础模型&#xff0c;结合多阶段训练流程&#xff08;RL训练、拒绝采样生成SFT数据&#xff09;&#xff0c;并优化输…

Linux 》》Ubuntu 18 LTS 之后的版本 修改IP地址 主机名

进入目录 /etc/netplan 修改 50-cloud-init.yaml 》保存文件后&#xff0c;执行以下命令应用更改&#xff1a; sudo netplan apply 》》 DHCP模式 修改主机名 hostnamectl set-hostname xxxx 修改cloud.cfg 防止重启主机名还原 但测试下来 不修改&#xff0c; 重启 也不会还…

泰山派开发之—Ubuntu24.04下Linux开发环境搭建

简介 最近翻到了吃灰已久的泰山派&#xff0c;是刚出来的时候用优惠券买的&#xff0c;当时价格挺便宜的&#xff0c;最近给它翻出来了&#xff0c;打算试试做个项目。买的泰山派容量是2G16G&#xff0c;SOC芯片使用的是RK3566&#xff0c;搭载1TOP算力的NPU&#xff0c;并且具…

哈尔滨算力服务器托管推荐-青蛙云

哈尔滨年平均气温3.5摄氏度&#xff0c;有发展云计算和算力数据中心的天然优势 &#xff0c;今天为哈尔滨算力服务器托管服务商&#xff1a;青蛙云&#xff0c;黑龙江经营17年的老牌IDC服务商。 先来了解下算力服务器&#xff1a; 算力服务器&#xff0c;尤其是那些用于运行人…

openharmony体验

openharmony5 去年已经出来了 如果以前做过android开发的&#xff0c;学起来不难&#xff0c;关键 1&#xff1a;环境 DevEco Studio 5.0.3 Beta2 https://developer.huawei.com/consumer/cn/deveco-studio/ win10_64bit CPU amd64(不是arm的) 2:安装 执行EXE 安装就行&#x…

[Ai 力扣题单] 数组基本操作篇 27/704/344/386

题单分类:DeepSeek刷力扣辅助题单 存留记录-CSDN博客 27 27. 移除元素 - 力扣&#xff08;LeetCode&#xff09; 这道题就一个点 1.数组在内存上连续 所以要么赋值覆盖,要么移动覆盖,但是它要求了前 k 个元素 所以只能移动覆盖 所以我有了如下思考过程: 3223 , 3举例 如果是…

Linux入门 全面整理终端 Bash、Vim 基础命令速记

Linux入门 2025 超详细全面整理 Bash、Vim 基础命令速记 刚面对高级感满满的 终端窗口是不是有点懵&#xff1f;于是乎&#xff0c;这份手册就是为你准备的高效学习指南&#xff01;我把那些让人头大的系统设置、记不住的命令都整理成了对你更友好的格式&#xff0c;让你快速学…