二、海思网卡数据流程

发布于:2025-06-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、整体框架图

![画板](https://i-blog.csdnimg.cn/img_convert/fe050a62fec309a49c9b0ea7326024d3.jpeg)

其中网络层向上提供网络协议接口,向下提供网络设备接口,在Linux内核实现中,链路层协议靠网卡驱动来实现,内核协议栈来实现网络层和传输层。内核对更上层的应用层提供socket接口来供用户进程访问。

网络协议接口:

协议接口层主要功能是给上层协议提供接收和发送的接口,当内核协议需要发送数据时,会通过调用dev_queue_xmit函数来发送数据,同样内核协议栈接收数据也是通过协议接口的netif_rx函数来进行的

//上层协议发送数据包时调用的接口
int dev_queue_xmit(struct sk_buff *skb)
    
//上层协议接收数据包的调用接口
int netif_rx(struct sk_buff *skb)

其中,skb表示要发送的数据,是一个skbuf的结构体指针。sk_buff(socket buffer)是Linux网络驱动中重要的结构体,用于在Linux网络子系统中的各层之间传输数据,该结构在整个网络收发过程中贯穿始终。在发送数据时,网络数据都是以sk_buff保存的,各个协议层都会在sk_buff中添加自己的协议头,最终由底层驱动将sk_buff中的数据发送出去。在接收数据时,网络底层驱动将接收到的原始数据打包成sk_buff,然后发送给上层协议,上层协议会逐步去掉对应头部,然后将最终数据发送给用户。

网络设备接口

网络设备接口的主要功能是为千变万化的网络设备定义了统一、 抽象的数据结构 net _device 结构体,实现多种硬件在软件层次上的统一。Linux内核使用 net _device结构体表示一个具体的网络设备,网络驱动的核心就是初始化net_device 结构体中的各个成员变量,然后将初始化完成以后的net_device 注册到 Linux内核中。

二、结构体

```c struct net_device { /* 设备标识 */ char name[IFNAMSIZ]; // 设备名称(如"eth0"),IFNAMSIZ通常为16字节 struct hlist_node name_hlist; // 用于设备名称哈希表的链表节点 struct dev_ifalias __rcu *ifalias; // 设备别名(可设置的描述性字符串)
/* I/O 硬件资源 */
unsigned long       mem_end;         // 设备共享内存结束地址
unsigned long       mem_start;       // 设备共享内存起始地址
unsigned long       base_addr;       // I/O 基地址
int         irq;                     // 分配的中断号

/* 设备状态和列表管理 */
unsigned long       state;           // 设备状态标志位(如__LINK_STATE_START)
struct list_head    dev_list;        // 全局设备链表节点
struct list_head    napi_list;       // NAPI轮询模式设备链表
struct list_head    unreg_list;      // 注销中的设备链表
struct list_head    close_list;      // 关闭中的设备链表
struct list_head    ptype_all;       // 所有协议包处理函数链表
struct list_head    ptype_specific;  // 特定协议包处理函数链表

/* 邻接设备关系 */
struct {
    struct list_head upper;          // 上层设备列表(如VLAN子设备)
    struct list_head lower;          // 下层设备列表(如物理网卡)
} adj_list;

/* 设备功能特性 */
netdev_features_t   features;        // 当前启用的功能标志(如校验和卸载)
netdev_features_t   hw_features;     // 硬件支持的功能
netdev_features_t   wanted_features; // 用户请求的功能
netdev_features_t   vlan_features;   // VLAN设备支持的功能
netdev_features_t   hw_enc_features; // 硬件加密支持的功能
netdev_features_t   mpls_features;   // MPLS支持的功能
netdev_features_t   gso_partial_features; // GSO部分分段功能

/* 设备标识 */
int         ifindex;                 // 设备全局唯一索引
int         group;                   // 设备组ID

/* 统计信息 */
struct net_device_stats stats;       // 传统网络统计(收发包数量)
atomic_long_t       rx_dropped;      // 接收丢弃包计数
atomic_long_t       tx_dropped;      // 发送丢弃包计数
atomic_long_t       rx_nohandler;    // 无处理程序的接收包计数
atomic_t        carrier_up_count;    // 载波开启计数(物理连接)
atomic_t        carrier_down_count;  // 载波关闭计数

/* 无线扩展 */

#ifdef CONFIG_WIRELESS_EXT
const struct iw_handler_def *wireless_handlers; // 无线操作函数集
struct iw_public_data *wireless_data; // 无线特定数据
#endif

/* 操作函数集 */
const struct net_device_ops *netdev_ops;    // 核心网络设备操作(如发送/接收)
const struct ethtool_ops *ethtool_ops;      // ethtool配置接口

#ifdef CONFIG_NET_SWITCHDEV
const struct switchdev_ops *switchdev_ops; // 交换设备操作
#endif
#ifdef CONFIG_NET_L3_MASTER_DEV
const struct l3mdev_ops *l3mdev_ops; // L3主设备操作
#endif
#if IS_ENABLED(CONFIG_IPV6)
const struct ndisc_ops *ndisc_ops; // IPv6邻居发现操作
#endif
#ifdef CONFIG_XFRM_OFFLOAD
const struct xfrmdev_ops *xfrmdev_ops; // IPsec卸载操作
#endif
#if IS_ENABLED(CONFIG_TLS_DEVICE)
const struct tlsdev_ops *tlsdev_ops; // TLS设备操作
#endif
const struct header_ops *header_ops; // 头部操作(创建/解析数据包头)

/* 标志位 */
unsigned int        flags;           // 标准设备标志(IFF_UP等)
unsigned int        priv_flags;      // 内部设备标志
unsigned short      gflags;          // 全局标志(极少使用)
unsigned short      padded;          // 填充标志(内部使用)

/* 状态信息 */
unsigned char       operstate;       // RFC2863操作状态
unsigned char       link_mode;       // 物理链路模式
unsigned char       if_port;         // 端口类型(如AUI/BNC)
unsigned char       dma;             // DMA通道(传统设备使用)

/* 数据包相关 */
unsigned int        mtu;             // 最大传输单元
unsigned int        min_mtu;         // 最小允许MTU
unsigned int        max_mtu;         // 最大允许MTU
unsigned short      type;            // 硬件类型(ARPHRD_ETHER等)
unsigned short      hard_header_len; // 硬件头部长度
unsigned char       min_header_len;  // 最小有效头部长度
unsigned short      needed_headroom; // 发送包需预留的头部空间
unsigned short      needed_tailroom; // 发送包需预留的尾部空间

/* 地址信息 */
unsigned char       perm_addr[MAX_ADDR_LEN]; // 永久硬件地址(MAC)
unsigned char       addr_assign_type; // 地址分配类型(随机/用户设置等)
unsigned char       addr_len;        // 硬件地址长度(MAC为6)
unsigned short      neigh_priv_len;  // 邻居子系统私有数据长度
unsigned short      dev_id;          // 设备实例ID
unsigned short      dev_port;        // 设备端口标识
spinlock_t      addr_list_lock;      // 地址列表自旋锁
unsigned char       name_assign_type; // 设备名分配类型
bool            uc_promisc;          // 单播混杂模式标志
struct netdev_hw_addr_list   uc;     // 单播地址列表
struct netdev_hw_addr_list   mc;     // 组播地址列表
struct netdev_hw_addr_list   dev_addrs; // 所有硬件地址列表

#ifdef CONFIG_SYSFS
struct kset *queues_kset; // sysfs队列集合
#endif

/* 模式计数器 */
unsigned int        promiscuity;     // 混杂模式引用计数
unsigned int        allmulti;        // 全组播模式引用计数

/* 协议私有数据指针 */

#if IS_ENABLED(CONFIG_VLAN_8021Q)
struct vlan_info __rcu *vlan_info; // VLAN配置信息
#endif
#if IS_ENABLED(CONFIG_NET_DSA)
struct dsa_port *dsa_ptr; // 分布式交换架构端口
#endif
#if IS_ENABLED(CONFIG_TIPC)
struct tipc_bearer __rcu *tipc_ptr; // TIPC承载结构
#endif
#if IS_ENABLED(CONFIG_IRDA) || IS_ENABLED(CONFIG_ATALK)
void *atalk_ptr; // AppleTalk协议数据
#endif
struct in_device __rcu *ip_ptr; // IPv4特定数据(地址配置等)
#if IS_ENABLED(CONFIG_DECNET)
struct dn_dev __rcu *dn_ptr; // DECnet协议数据
#endif
struct inet6_dev __rcu *ip6_ptr; // IPv6特定数据
#if IS_ENABLED(CONFIG_AX25)
void *ax25_ptr; // AX.25协议数据
#endif
struct wireless_dev *ieee80211_ptr; // 无线设备数据
struct wpan_dev *ieee802154_ptr; // IEEE 802.15.4设备数据
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
struct mpls_dev __rcu *mpls_ptr; // MPLS设备数据
#endif

/* ----------- 接收路径常用字段(缓存线对齐)----------- */
unsigned char        *dev_addr;      // 当前硬件地址(可覆盖perm_addr)
struct netdev_rx_queue   *_rx;       // 接收队列数组
unsigned int        num_rx_queues;   // 分配的接收队列数
unsigned int        real_num_rx_queues; // 激活的接收队列数
struct bpf_prog __rcu   *xdp_prog;   // 附加的XDP程序
unsigned long       gro_flush_timeout; // GRO超时时间
rx_handler_func_t __rcu *rx_handler; // 接收处理函数(如桥接)
void __rcu      *rx_handler_data;    // 接收处理函数私有数据

#ifdef CONFIG_NET_CLS_ACT
struct mini_Qdisc __rcu *miniq_ingress; // 入口迷你Qdisc
#endif
struct netdev_queue __rcu *ingress_queue; // 入口流量控制队列
#ifdef CONFIG_NETFILTER_INGRESS
struct nf_hook_entries __rcu *nf_hooks_ingress; // Netfilter入口钩子
#endif
unsigned char broadcast[MAX_ADDR_LEN]; // 广播地址
#ifdef CONFIG_RFS_ACCEL
struct cpu_rmap *rx_cpu_rmap; // 接收流CPU亲和性映射
#endif
struct hlist_node index_hlist; // 设备索引哈希链表节点

/* ----------- 发送路径常用字段(缓存线对齐)----------- */
struct netdev_queue  *_tx ____cacheline_aligned_in_smp; // 发送队列数组
unsigned int        num_tx_queues;   // 分配的发送队列数
unsigned int        real_num_tx_queues; // 激活的发送队列数
struct Qdisc        *qdisc;          // 根队列规则(QoS)

#ifdef CONFIG_NET_SCHED
DECLARE_HASHTABLE (qdisc_hash, 4); // Qdisc哈希表
#endif
unsigned int tx_queue_len; // 每个发送队列最大长度
spinlock_t tx_global_lock; // 全局发送锁(已弃用)
int watchdog_timeo; // 发送超时时间(触发watchdog)
#ifdef CONFIG_XPS
struct xps_dev_maps __rcu *xps_cpus_map; // CPU发送包映射
struct xps_dev_maps __rcu *xps_rxqs_map; // RX队列映射
#endif
#ifdef CONFIG_NET_CLS_ACT
struct mini_Qdisc __rcu *miniq_egress; // 出口迷你Qdisc
#endif

/* 看门狗和状态管理 */
struct timer_list   watchdog_timer;  // 发送超时定时器
int __percpu        *pcpu_refcnt;    // 每CPU引用计数
struct list_head    todo_list;       // 待处理操作列表
struct list_head    link_watch_list; // 链路状态监视列表

/* 注册状态机 */
enum { NETREG_UNINITIALIZED=0,       // 未初始化
       NETREG_REGISTERED,            // 注册完成
       NETREG_UNREGISTERING,         // 正在注销
       NETREG_UNREGISTERED,          // 注销待完成
       NETREG_RELEASED,              // 已释放
       NETREG_DUMMY,                 // NAPI轮询用的虚拟设备
} reg_state:8;                       // 注册状态(8位)
bool            dismantle;           // 设备是否正在拆除
enum {
    RTNL_LINK_INITIALIZED,           // RTNL链接已初始化
    RTNL_LINK_INITIALIZING,          // RTNL链接初始化中
} rtnl_link_state:16;                // RTNL链接状态(16位)
bool            needs_free_netdev;   // 注销后是否自动释放
void (*priv_destructor)(struct net_device *dev); // 私有数据析构函数

#ifdef CONFIG_NETPOLL
struct netpoll_info __rcu *npinfo; // netpoll配置(远程抓包)
#endif

/* 网络命名空间 */
possible_net_t      nd_net;          // 所属网络命名空间

/* 中层协议私有数据 */
union {
    void                    *ml_priv;    // 通用指针
    struct pcpu_lstats __percpu     *lstats; // 每CPU轻量统计
    struct pcpu_sw_netstats __percpu    *tstats; // 软件网络统计
    struct pcpu_dstats __percpu     *dstats; // 流量控制统计
    struct pcpu_vstats __percpu     *vstats; // 虚拟设备统计
};

/* 二层协议扩展 */

#if IS_ENABLED(CONFIG_GARP)
struct garp_port __rcu *garp_port; // GARP应用端口
#endif
#if IS_ENABLED(CONFIG_MRP)
struct mrp_port __rcu *mrp_port; // MRP应用端口
#endif

/* 设备模型关联 */
struct device     dev;               // 关联的Linux设备模型结构
const struct attribute_group *sysfs_groups[4]; // sysfs属性组
const struct attribute_group *sysfs_rx_queue_group; // RX队列属性组

/* 链接操作 */
const struct rtnl_link_ops *rtnl_link_ops; // RTnetlink操作函数集

/* GSO配置 */
unsigned int        gso_max_size;    // 最大GSO分段大小(65536)
u16         gso_max_segs;            // 最大GSO分段数量(65535)

#ifdef CONFIG_DCB
const struct dcbnl_rtnl_ops *dcbnl_ops; // 数据中心桥接操作
#endif

/* 流量优先级配置 */
s16         num_tc;                  // 流量类别数量
struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE]; // TC到TX队列映射
u8          prio_tc_map[TC_BITMASK + 1]; // 优先级到TC映射

/* 其他协议相关 */

#if IS_ENABLED(CONFIG_FCOE)
unsigned int fcoe_ddp_xid; // FCoE直接数据放置ID
#endif
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
struct netprio_map __rcu *priomap; // 网络优先级cgroup映射
#endif

/* 物理层 */
struct phy_device   *phydev;         // 关联的PHY设备
struct sfp_bus      *sfp_bus;        // SFP/SFP+总线

/* 锁初始化 */
struct lock_class_key   *qdisc_tx_busylock; // Qdisc忙锁类
struct lock_class_key   *qdisc_running_key; // Qdisc运行锁类

/* 杂项标志 */
bool            proto_down;          // 协议关闭状态
unsigned        wol_enabled:1;       // Wake-on-LAN使能标志

};


其中

 const struct net_device_ops *netdev_ops;    // 核心网络设备操作(如发送/接收)

 const struct ethtool_ops *ethtool_ops;      // ethtool配置接口

 struct phy_device   *phydev;         // 关联的PHY设备

```c
struct net_device_ops {
    /**
     * @ndo_init: 设备初始化回调(注册时调用)
     * @dev: 网络设备对象
     * 返回:0=成功,负值=错误码
     */
    int (*ndo_init)(struct net_device *dev);

    /**
     * @ndo_uninit: 设备反初始化回调(注销时调用)
     * @dev: 网络设备对象
     */
    void (*ndo_uninit)(struct net_device *dev);

    /**
     * @ndo_open: 打开网络设备(如ifconfig up)
     * @dev: 网络设备对象
     * 返回:0=成功,负值=错误码
     * 功能:分配资源、启动队列、开启中断
     */
    int (*ndo_open)(struct net_device *dev);

    /**
     * @ndo_stop: 关闭网络设备(如ifconfig down)
     * @dev: 网络设备对象
     * 返回:0=成功,负值=错误码
     * 功能:释放资源、停止队列、关闭中断
     */
    int (*ndo_stop)(struct net_device *dev);

    /**
     * @ndo_start_xmit: 数据包发送核心函数
     * @skb: 待发送的数据包缓冲区
     * @dev: 网络设备对象
     * 返回:NETDEV_TX_OK=发送成功,NETDEV_TX_BUSY=队列已满
     * 功能:将数据包放入DMA队列并启动传输
     */
    netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);

    /**
     * @ndo_set_rx_mode: 设置接收模式
     * @dev: 网络设备对象
     * 功能:配置广播/组播/混杂模式
     */
    void (*ndo_set_rx_mode)(struct net_device *dev);

    /**
     * @ndo_set_mac_address: 设置MAC地址
     * @dev: 网络设备对象
     * @addr: 新MAC地址指针
     * 返回:0=成功,负值=错误码
     */
    int (*ndo_set_mac_address)(struct net_device *dev, void *addr);

    /**
     * @ndo_validate_addr: 校验MAC地址有效性
     * @dev: 网络设备对象
     * 返回:0=地址有效,负值=无效
     */
    int (*ndo_validate_addr)(struct net_device *dev);

    /**
     * @ndo_do_ioctl: 设备专属IO控制命令
     * @dev: 网络设备对象
     * @ifr: 接口请求结构体
     * @cmd: IOCTL命令号
     * 返回:0=成功,负值=错误码
     */
    int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);

    /**
     * @ndo_change_mtu: 修改最大传输单元(MTU)
     * @dev: 网络设备对象
     * @new_mtu: 新MTU值
     * 返回:0=成功,负值=错误码
     */
    int (*ndo_change_mtu)(struct net_device *dev, int new_mtu);

    /**
     * @ndo_tx_timeout: 发送超时处理
     * @dev: 网络设备对象
     * 功能:当数据包发送超时触发,需重置硬件
     */
    void (*ndo_tx_timeout)(struct net_device *dev);

#ifdef CONFIG_NET_POLL_CONTROLLER
    /**
     * @ndo_poll_controller: 轮询模式中断模拟(诊断用)
     * @dev: 网络设备对象
     * 功能:在NAPI禁用时模拟中断
     */
    void (*ndo_poll_controller)(struct net_device *dev);
#endif

    /**
     * @ndo_get_stats64: 获取64位网络统计信息
     * @dev: 网络设备对象
     * @storage: 统计信息存储结构体
     */
    void (*ndo_get_stats64)(struct net_device *dev, struct rtnl_link_stats64 *storage);

    /**
     * @ndo_vlan_rx_add_vid: 添加VLAN ID过滤
     * @dev: 网络设备对象
     * @proto: VLAN协议(如htons(ETH_P_8021Q))
     * @vid: VLAN ID
     * 返回:0=成功,负值=错误码
     */
    int (*ndo_vlan_rx_add_vid)(struct net_device *dev, __be16 proto, u16 vid);

    /**
     * @ndo_vlan_rx_kill_vid: 移除VLAN ID过滤
     * @dev: 网络设备对象
     * @proto: VLAN协议
     * @vid: VLAN ID
     * 返回:0=成功,负值=错误码
     */
    int (*ndo_vlan_rx_kill_vid)(struct net_device *dev, __be16 proto, u16 vid);

    /**
     * @ndo_setup_tc: 流量控制设置
     * @dev: 网络设备对象
     * @type: 控制类型(如TC_SETUP_QDISC)
     * @type_data: 配置数据
     * 返回:0=成功,负值=错误码
     * 功能:实现QoS策略
     */
    int (*ndo_setup_tc)(struct net_device *dev, enum tc_setup_type type, void *type_data);

    /**
     * @ndo_fix_features: 硬件特性修正
     * @dev: 网络设备对象
     * @features: 请求的特性标志
     * 返回:修正后的实际支持特性
     * 功能:调整不可用特性(如无TSO支持时禁用NETIF_F_TSO)
     */
    netdev_features_t (*ndo_fix_features)(struct net_device *dev, netdev_features_t features);

    /**
     * @ndo_bpf: eBPF程序管理
     * @dev: 网络设备对象
     * @bpf: eBPF指令集结构
     * 返回:0=成功,负值=错误码
     * 功能:加载/管理eBPF过滤器
     */
    int (*ndo_bpf)(struct net_device *dev, struct netdev_bpf *bpf);

    /**
     * @ndo_xdp_xmit: XDP数据包快速发送
     * @dev: 网络设备对象
     * @n: 发送包数量
     * @xdp: XDP帧数组
     * @flags: 发送标志
     * 返回:成功发送包数
     * 功能:绕过内核协议栈直接发送
     */
    int (*ndo_xdp_xmit)(struct net_device *dev, int n, struct xdp_frame **xdp, u32 flags);
};

struct sk_buff

struct sk_buff {
    union {
        struct {
            /* 链表管理 */
            struct sk_buff *next;    // 指向下一个 sk_buff(用于队列管理)
            struct sk_buff *prev;    // 指向上一个 sk_buff(用于队列管理)
            union {
                struct net_device *dev;  // 网络设备指针(收发数据的网卡)
                unsigned long dev_scratch; // 临时存储设备信息(如 UDP 路径)
            };
        };
        struct rb_node rbnode;       // 红黑树节点(用于 netem/TCP 重排序)
        struct list_head list;       // 通用链表头
    };

    union {
        struct sock *sk;             // 所属的 socket 结构
        int ip_defrag_offset;        // IP 分片重组偏移量
    };

    union {
        ktime_t tstamp;              // 时间戳(包到达/离开时间)
        u64 skb_mstamp;              // 高精度时间戳(旧版)
    };

    char cb[48] __aligned(8);        // 控制块(各协议层私有数据区,如 TCP/UDP 状态)

    union {
        struct {
            unsigned long _skb_refdst; // 目标路由项(含无引用计数标记)
            void (*destructor)(struct sk_buff *); // 析构回调函数
        };
        struct list_head tcp_tsorted_anchor; // TCP 排序队列锚点
    };

#ifdef CONFIG_XFRM
    struct sec_path *sp;             // 安全路径(IPsec 处理)
#endif
#if defined(CONFIG_NF_CONNTRACK)
    unsigned long _nfct;             // 连接跟踪信息(Netfilter)
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
    struct nf_bridge_info *nf_bridge; // 桥接 Netfilter 数据
#endif

    /* 数据长度 */
    unsigned int len;                // 数据总长度(含协议头)
    unsigned int data_len;           // 分片数据长度(非线性区)
    __u16 mac_len;                   // MAC 头长度
    __u16 hdr_len;                   // 可写头长度(克隆时有效)

    /* 队列与克隆 */
    __u16 queue_mapping;             // 多队列设备的队列 ID
    __u8 cloned:1,                   // 是否为克隆包
         nohdr:1,                    // 是否跳过头操作
         fclone:2,                   // 克隆状态(FCLONE_* 枚举)
         peeked:1,                   // 是否已被查看(避免重复统计)
         head_frag:1,                // 是否来自页片段
         xmit_more:1,                // 更多数据待发送(优化吞吐)
         pfmemalloc:1;               // 内存来自 PFMEMALLOC 保留区

    /* 包类型与校验和 */
    __u8 pkt_type:3;                 // 包类型(PACKET_HOST 等)
    __u8 ignore_df:1;                // 允许本地分片(忽略 DF 标志)
    __u8 nf_trace:1;                // Netfilter 跟踪标志
    __u8 ip_summed:2;                // 校验和状态(CHECKSUM_*)
    __u8 ooo_okay:1;                 // 允许乱序队列(TCP 用)

    /* 哈希与卸载 */
    __u8 l4_hash:1;                  // L4 哈希已计算
    __u8 sw_hash:1;                  // 软件计算哈希
    __u8 wifi_acked_valid:1;         // wifi_acked 有效标志
    __u8 wifi_acked:1;               // WIFI 确认状态
    __u8 no_fcs:1;                   // 跳过 FCS 校验(由网卡处理)
    __u8 encapsulation:1;            // 封装协议(如 IP-in-IP)
    __u8 encap_hdr_csum:1;           // 封装层需要校验和
    __u8 csum_valid:1;               // 校验和有效

    /* 校验和与协议 */
    __u8 csum_complete_sw:1;         // 软件完成校验和
    __u8 csum_level:2;               // 校验和层级(0=外层,1=内层)
    __u8 csum_not_inet:1;            // 使用 CRC32c 校验
    __u8 dst_pending_confirm:1;      // 需确认邻居路由
#ifdef CONFIG_IPV6_NDISC_NODETYPE
    __u8 ndisc_nodetype:2;           // IPv6 邻居发现节点类型
#endif
    __u8 ipvs_property:1;            // 是否被 IPVS 管理

    /* 卸载与流量控制 */
    __u8 inner_protocol_type:1;      // 内层协议类型
    __u8 remcsum_offload:1;          // 远程校验卸载
#ifdef CONFIG_NET_SWITCHDEV
    __u8 offload_fwd_mark:1;         // 交换设备转发标记
#endif
#ifdef CONFIG_NET_CLS_ACT
    __u8 tc_skip_classify:1;         // 跳过流量分类
    __u8 tc_at_ingress:1;            // 标记入口流量
    __u8 tc_redirected:1;            // 已被 TC 动作重定向
    __u8 tc_from_ingress:1;          // 重定向自入口
#endif
#ifdef CONFIG_TLS_DEVICE
    __u8 decrypted:1;                // 是否已解密(TLS)
#endif

#ifdef CONFIG_NET_SCHED
    __u16 tc_index;                  // 流量控制索引(QoS)
#endif

    /* 校验和位置 */
    union {
        __wsum csum;                 // 校验和值
        struct {
            __u16 csum_start;        // 校验和起始偏移
            __u16 csum_offset;       // 校验和数据偏移
        };
    };

    /* 优先级与标识 */
    __u32 priority;                  // 包优先级(QoS)
    int skb_iif;                     // 输入设备索引(ifindex)
    __u32 hash;                      // 包哈希值(负载均衡)
    __be16 vlan_proto;               // VLAN 协议类型(802.1Q/AD)
    __u16 vlan_tci;                  // VLAN 标签控制信息

#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
    union {
        unsigned int napi_id;        // NAPI ID(轮询上下文)
        unsigned int sender_cpu;     // 发送 CPU ID(XPS 用)
    };
#endif

#ifdef CONFIG_NETWORK_SECMARK
    __u32 secmark;                   // 安全标记(SELinux)
#endif

    union {
        __u32 mark;                  // 防火墙标记(iptables MARK)
        __u32 reserved_tailroom;     // 保留尾部空间
    };

    /* 内层协议头 */
    __be16 inner_protocol;           // 内层协议(如隧道中的 IP)
    __u16 inner_transport_header;    // 内层传输头偏移
    __u16 inner_network_header;      // 内层网络头偏移
    __u16 inner_mac_header;          // 内层 MAC 头偏移

    /* 外层协议头 */
    __be16 protocol;                 // 协议类型(接收驱动设置)
    __u16 transport_header;          // 传输层头偏移(TCP/UDP)
    __u16 network_header;            // 网络层头偏移(IP)
    __u16 mac_header;                // MAC 层头偏移

    /* 数据缓冲区 */
    sk_buff_data_t tail;             // 指向有效数据尾部
    sk_buff_data_t end;              // 指向缓冲区结束
    unsigned char *head;             // 指向缓冲区头部
    unsigned char *data;             // 指向有效数据头部

    unsigned int truesize;           // 缓冲区总大小(包括 sk_buff)
    refcount_t users;                // 引用计数(克隆时增加)
};

三、网卡的注册流程

3.1 net_device 的申请和删除

编写网络驱动时,首先需要申请net_device,使用alloc_netdev函数来申请net_device,这是一个宏,宏定义如下:
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
	alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)

net/core/dev.c

/**
 * alloc_netdev_mqs - 分配并初始化网络设备结构体
 * @sizeof_priv: 私有数据区大小(驱动私有结构体大小)
 * @name: 网络设备名称(如 "eth0")
 * @name_assign_type: 名称分配类型(NET_NAME_ENUM等)
 * @setup: 设备初始化回调函数
 * @txqs: 发送队列数量
 * @rxqs: 接收队列数量
 * 
 * 核心功能:分配完整的网络设备对象(struct net_device)及其关联资源
 * 返回值:成功返回net_device指针,失败返回NULL
 */
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
        unsigned char name_assign_type,
        void (*setup)(struct net_device *),
        unsigned int txqs, unsigned int rxqs)
{
    struct net_device *dev;
    unsigned int alloc_size;
    struct net_device *p; // 原始分配的内存起始地址

    // 检查设备名长度(必须小于IFNAMSIZ,通常16字节)
    BUG_ON(strlen(name) >= sizeof(dev->name));

    // 队列数量检查(必须大于0)
    if (txqs < 1) {
        pr_err("alloc_netdev: Unable to allocate device with zero queues\n");
        return NULL;
    }
    if (rxqs < 1) {
        pr_err("alloc_netdev: Unable to allocate device with zero RX queues\n");
        return NULL;
    }

    // ===== 内存分配计算 =====
    alloc_size = sizeof(struct net_device); // 基础结构体大小
    if (sizeof_priv) {
        // 私有数据区32字节对齐处理
        alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); 
        alloc_size += sizeof_priv; // 追加私有数据区
    }
    // 整体内存32字节对齐(多分配31字节用于指针校准)
    alloc_size += NETDEV_ALIGN - 1;

    // 分配内核内存(清零初始化)
    p = kvzalloc(alloc_size, GFP_KERNEL | __GFP_RETRY_MAYFAIL);
    if (!p)
        return NULL;

    // ===== 指针对齐处理 =====
    dev = PTR_ALIGN(p, NETDEV_ALIGN);  // 对齐到32字节边界
    dev->padded = (char *)dev - (char *)p; // 记录填充字节数(释放时用)

    // ===== 核心资源初始化 =====
    // 1. 分配每CPU引用计数器
    dev->pcpu_refcnt = alloc_percpu(int);
    if (!dev->pcpu_refcnt)
        goto free_dev;

    // 2. 初始化MAC地址
    if (dev_addr_init(dev))
        goto free_pcpu;

    // 3. 初始化组播/单播地址列表
    dev_mc_init(dev);
    dev_uc_init(dev);

    // 4. 设置默认网络命名空间
    dev_net_set(dev, &init_net);

    // 5. 设置GSO分段参数
    dev->gso_max_size = GSO_MAX_SIZE;  // 最大分段尺寸(64KB)
    dev->gso_max_segs = GSO_MAX_SEGS;  // 最大分段数(65535)

    // ===== 关键数据结构初始化 =====
    // 初始化各种链表头(NAPI/注销/关闭/链路监控等)
    INIT_LIST_HEAD(&dev->napi_list);           // NAPI轮询列表
    INIT_LIST_HEAD(&dev->unreg_list);          // 设备注销列表
    INIT_LIST_HEAD(&dev->close_list);          // 关闭设备列表
    INIT_LIST_HEAD(&dev->link_watch_list);     // 链路状态监控列表
    INIT_LIST_HEAD(&dev->adj_list.upper);      // 上层设备邻接列表
    INIT_LIST_HEAD(&dev->adj_list.lower);      // 下层设备邻接列表
    INIT_LIST_HEAD(&dev->ptype_all);           // 所有协议处理列表
    INIT_LIST_HEAD(&dev->ptype_specific);      // 特定协议处理列表
    
    // 流量控制哈希表初始化(如果启用)
#ifdef CONFIG_NET_SCHED
    hash_init(dev->qdisc_hash);
#endif

    // 设置默认标志(允许释放目标缓存)
    dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM;

    // ===== 驱动特定初始化 =====
    // 调用驱动提供的setup回调(初始化net_device_ops等)
    setup(dev);

    // 设置默认发送队列长度(若无驱动指定)
    if (!dev->tx_queue_len) {
        dev->priv_flags |= IFF_NO_QUEUE;      // 标记无队列限制
        dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; // 默认值1000
    }

    // ===== 队列系统初始化 =====
    // 配置发送队列
    dev->num_tx_queues = txqs;                // 请求的发送队列数
    dev->real_num_tx_queues = txqs;           // 实际启用队列数
    if (netif_alloc_netdev_queues(dev))       // 分配发送队列内存
        goto free_all;

    // 配置接收队列
    dev->num_rx_queues = rxqs;                // 请求的接收队列数
    dev->real_num_rx_queues = rxqs;           // 实际启用队列数
    if (netif_alloc_rx_queues(dev))           // 分配接收队列内存
        goto free_all;

    // ===== 最终设置 =====
    strcpy(dev->name, name);                  // 复制设备名称
    dev->name_assign_type = name_assign_type; // 记录名称来源
    dev->group = INIT_NETDEV_GROUP;           // 初始设备组

    // 设置默认ethtool操作(如果驱动未指定)
    if (!dev->ethtool_ops)
        dev->ethtool_ops = &default_ethtool_ops;

    // 初始化Netfilter钩子(ingress方向)
    nf_hook_ingress_init(dev);

    return dev; // 返回初始化完成的设备结构体

// ===== 错误处理路径 =====
free_all:
    free_netdev(dev); // 释放整个网络设备
    return NULL;

free_pcpu:
    free_percpu(dev->pcpu_refcnt); // 释放每CPU计数器
free_dev:
    netdev_freemem(dev); // 释放设备内存
    return NULL;
}
EXPORT_SYMBOL(alloc_netdev_mqs);

free_netdev

/**
 * free_netdev - 释放网络设备及其关联资源
 * @dev: 要释放的网络设备指针
 * 
 * 功能:逆序释放 alloc_netdev_mqs 分配的所有资源
 * 注意:必须在设备注销后调用(通常在 unregister_netdev 之后)
 */
void free_netdev(struct net_device *dev)
{
    struct napi_struct *p, *n;

    might_sleep();  // 标注可能睡眠的函数(因后续操作可能阻塞)

    /* 释放队列系统资源 */
    netif_free_tx_queues(dev);   // 释放所有发送队列
    netif_free_rx_queues(dev);   // 释放所有接收队列

    /* 释放流量控制入口队列 */
    kfree(rcu_dereference_protected(dev->ingress_queue, 1));  // 安全解除RCU引用

    /* 清理地址列表 */
    dev_addr_flush(dev);  // 清除所有MAC地址(单播/组播)

    /* 删除所有NAPI上下文 */
    list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
        netif_napi_del(p);  // 从全局列表中移除并释放NAPI结构

    /* 释放引用计数器 */
    free_percpu(dev->pcpu_refcnt);  // 释放每CPU引用计数
    dev->pcpu_refcnt = NULL;        // 防止野指针

    /* 处理未注册设备的特殊情况 */
    if (dev->reg_state == NETREG_UNINITIALIZED) {
        // 设备从未注册过(分配后直接释放)
        netdev_freemem(dev);  // 直接释放设备内存
        return;
    }

    /* 状态验证(必须已完成注销) */
    BUG_ON(dev->reg_state != NETREG_UNREGISTERED);  // 确保设备已注销
    dev->reg_state = NETREG_RELEASED;  // 更新状态为"已释放"

    /* 通过设备引用计数机制最终释放 */
    put_device(&dev->dev);  // 触发dev->release回调 (通常是netdev_release)
}
EXPORT_SYMBOL(free_netdev);

3.2 注册和注销

```c int register_netdev(struct net_device *dev) { int err;
if (rtnl_lock_killable())
	return -EINTR;
err = register_netdevice(dev);
rtnl_unlock();
return err;

}
EXPORT_SYMBOL(register_netdev);


```c
void unregister_netdev(struct net_device *dev)
{
	rtnl_lock();
	unregister_netdevice(dev);
	rtnl_unlock();
}

四、数据收发流程

4.1、 网络结构体初始化

原文链接:[https://blog.csdn.net/zhangyanfei01/article/details/110621887](https://blog.csdn.net/zhangyanfei01/article/details/110621887)
1 ksoftirqd内核线程初始化
linux的软中断都是在专门的内核线程中进行的,该线程数量不是1,而是N,其中N等于机器的核数
2 网络子系统初始化
在网络子系统初始化的过程中,会为每个CPU初始化softnet_data,也会为RX_SOFTIRQ和TX_SOFTIRQ注册处理函数,流程图如下

画板

static int __init net_dev_init(void)
{
	int i, rc = -ENOMEM;

	BUG_ON(!dev_boot_phase);

	if (dev_proc_init())
		goto out;

	if (netdev_kobject_init())
		goto out;

	INIT_LIST_HEAD(&ptype_all);
	for (i = 0; i < PTYPE_HASH_SIZE; i++)
		INIT_LIST_HEAD(&ptype_base[i]);

	INIT_LIST_HEAD(&offload_base);

	if (register_pernet_subsys(&netdev_net_ops))
		goto out;

	/*
	 *	Initialise the packet receive queues.
	 */
    //为每个CPU都申请一个softnet数据接口,这个数据结构里的poll_list用于等待驱动
    //程序将其poll函数注册进来
	for_each_possible_cpu(i) {
		struct work_struct *flush = per_cpu_ptr(&flush_works, i);
		struct softnet_data *sd = &per_cpu(softnet_data, i);

		INIT_WORK(flush, flush_backlog);

		skb_queue_head_init(&sd->input_pkt_queue);
		skb_queue_head_init(&sd->process_queue);
#ifdef CONFIG_XFRM_OFFLOAD
		skb_queue_head_init(&sd->xfrm_backlog);
#endif
		INIT_LIST_HEAD(&sd->poll_list);
		sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
		sd->csd.func = rps_trigger_softirq;
		sd->csd.info = sd;
		sd->cpu = i;
#endif

		init_gro_hash(&sd->backlog);
		sd->backlog.poll = process_backlog;
		sd->backlog.weight = weight_p;
	}

	dev_boot_phase = 0;

	/* The loopback device is special if any other network devices
	 * is present in a network namespace the loopback device must
	 * be present. Since we now dynamically allocate and free the
	 * loopback device ensure this invariant is maintained by
	 * keeping the loopback device as the first device on the
	 * list of network devices.  Ensuring the loopback devices
	 * is the first device that appears and the last network device
	 * that disappears.
	 */
	if (register_pernet_device(&loopback_net_ops))
		goto out;

	if (register_pernet_device(&default_device_ops))
		goto out;
    // 注册NET_TX_SOFTIRQ的中断处理函数
	open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    // 注册NET_RX_SOFTIRQ的中断处理函数
	open_softirq(NET_RX_SOFTIRQ, net_rx_action);

	rc = cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, "net/dev:dead",
				       NULL, dev_cpu_dead);
	WARN_ON(rc < 0);
	rc = 0;
out:
	return rc;
}
//注册方式记录到softirq_vec,后面ksofrirqd线程收到软中断后,会使用这个变量来找到没一种软中断对应的处理函数
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

3 协议栈初始化

画板

//net\ipv4\af_inet.c
static int __init inet_init(void)
{
...
	/*
	 *	Add all the base protocols.
	 */
     // 将TCP和UDP对应的处理函数都注册到inet_protos数组中
	if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
		pr_crit("%s: Cannot add ICMP protocol\n", __func__);
	if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
		pr_crit("%s: Cannot add UDP protocol\n", __func__);
	if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
		pr_crit("%s: Cannot add TCP protocol\n", __func__);
#ifdef CONFIG_IP_MULTICAST
	if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
		pr_crit("%s: Cannot add IGMP protocol\n", __func__);
#endif

...

	dev_add_pack(&ip_packet_type);

...
}

// 将TCP和UDP对应的处理函数都注册到inet_protos数组中
int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
{
	if (!prot->netns_ok) {
		pr_err("Protocol %u is not namespace aware, cannot register.\n",
			protocol);
		return -EINVAL;
	}

	return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
			NULL, prot) ? 0 : -1;
}
EXPORT_SYMBOL(inet_add_protocol);

//
void dev_add_pack(struct packet_type *pt)
{
	struct list_head *head = ptype_head(pt);

	spin_lock(&ptype_lock);
	list_add_rcu(&pt->list, head);
	spin_unlock(&ptype_lock);
}

// ptype_base 存储着ip_rev()函数的处理地址
static inline struct list_head *ptype_head(const struct packet_type *pt)
{
	if (pt->type == htons(ETH_P_ALL))
		return pt->dev ? &pt->dev->ptype_all : &ptype_all;
	else
		return pt->dev ? &pt->dev->ptype_specific :
				 &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}

这里需要记住inet protos记录着UDP、TCP的处理函数地址,ptype_base存储着ip_rcv()函数的处理地址。后面将讲到软中断中会通过ptypebase找到ip_rcv()函数地址,进而将IP包正确地送到ip_rcv()中执行。在ip_rcv中将会通过inet protos找到TCP或者UDP的处理函数,再把包转发给udp rcv0或tcp v4rc(函数。建议大家好好读一读inet init这个函数的代码。

4 驱动初始化
![画板](https://i-blog.csdnimg.cn/img_convert/7371775cbc00ff4c581ce7d3e8709fb4.jpeg)

higmac_dev_probe //2 调用驱动probe

higmac_dev_probe_queue

 higmac_dev_probe_init
static int higmac_dev_probe_init(struct platform_device *pdev,
				 struct higmac_netdev_local *priv, struct net_device *ndev)
{
	int ret;
    //4 napi 初始化
	higmac_init_napi(priv);
	spin_lock_init(&priv->rxlock);
	spin_lock_init(&priv->txlock);
	spin_lock_init(&priv->pmtlock);

	/* init netdevice */
	ndev->irq = priv->irq[0];
	ndev->watchdog_timeo = 3 * HZ; /* 3HZ */
    // 5 网卡操作函数集
	ndev->netdev_ops = &hieth_netdev_ops;
    //6 ethtool 操作函数集
	ndev->ethtool_ops = &hieth_ethtools_ops;

	if (priv->has_rxhash_cap)
		ndev->hw_features |= NETIF_F_RXHASH;
	if (priv->has_rss_cap)
		ndev->hw_features |= NETIF_F_NTUPLE;
	if (priv->tso_supported)
		ndev->hw_features |= NETIF_F_SG |
				     NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
				     NETIF_F_TSO | NETIF_F_TSO6;

#if defined(CONFIG_HIGMAC_RXCSUM)
	ndev->hw_features |= NETIF_F_RXCSUM;
	higmac_enable_rxcsum_drop(priv, true);
#endif

	ndev->features |= ndev->hw_features;
	ndev->features |= NETIF_F_HIGHDMA | NETIF_F_GSO;
	ndev->vlan_features |= ndev->features;

	timer_setup(&priv->monitor, higmac_monitor_func, 0);

	device_set_wakeup_capable(priv->dev, 1);
	/*
	 * when we can let phy powerdown?
	 * In some mode, we don't want phy powerdown,
	 * so I set wakeup enable all the time
	 */
	device_set_wakeup_enable(priv->dev, 1);

	priv->wol_enable = false;

	priv->msg_enable = netif_msg_init(debug, DEFAULT_MSG_ENABLE);

#if defined(CONFIG_HIGMAC_DDR_64BIT)
	if (!has_cap_cci(priv->hw_cap)) {
		struct device *dev = &pdev->dev;
		ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); /* 64bit */
		if (ret) {
			pr_err("dma set mask 64 failed! ret=%d", ret);
			return ret;
		}
	}
#endif

	/* init hw desc queue */
	ret = higmac_init_hw_desc_queue(priv);
	if (ret)
		return ret;

	return 0;
}

//napi 初始化
void higmac_init_napi(struct higmac_netdev_local *priv)
{
	struct higmac_napi *q_napi = NULL;
	int i;
	if (priv == NULL || priv->netdev == NULL)
		return;
	for (i = 0; i < priv->num_rxqs; i++) {
		q_napi = &priv->q_napi[i];
		q_napi->rxq_id = i;
		q_napi->ndev_priv = priv;
		netif_napi_add(priv->netdev, &q_napi->napi, higmac_poll,
			       NAPI_POLL_WEIGHT);
	}
}

注册操作函数

static const struct net_device_ops hieth_netdev_ops = {
	.ndo_open = higmac_net_open,
	.ndo_stop = higmac_net_close,
	.ndo_start_xmit = higmac_net_xmit,
	.ndo_tx_timeout = higmac_net_timeout,
	.ndo_set_rx_mode = higmac_set_multicast_list,
	.ndo_set_features = higmac_set_features,
	.ndo_do_ioctl = higmac_ioctl,
	.ndo_set_mac_address = higmac_net_set_mac_address,
	.ndo_change_mtu = eth_change_mtu,
	.ndo_get_stats = higmac_net_get_stats,
};

higmac_net_open

static int higmac_net_open(struct net_device *dev)
{
    struct higmac_netdev_local *ld = netdev_priv(dev);  // 从net_device获取私有数据
    unsigned long flags;

    /*------- 1. 时钟使能 -------*/
    clk_prepare_enable(ld->macif_clk);  // 启用MAC接口时钟
    clk_prepare_enable(ld->clk);        // 启用网卡主时钟

    /*------- 2. MAC地址配置 -------*/
    /*
     * 在时钟启用后重新写入MAC地址:
     * 通过ifconfig设置的MAC地址在网卡关闭时(时钟禁用)可能未实际写入硬件寄存器
     */
    higmac_hw_set_mac_addr(dev);  // 将MAC地址写入硬件寄存器

    /*------- 3. 链路状态初始化 -------*/
    /* 
     * 设置默认载波状态为断开:
     * 1) 必须在phy_start前调用
     * 2) 内核后续根据PHY状态更新此状态
     */
    netif_carrier_off(dev);  // 通知内核链路层无载波信号

    /*------- 4. NAPI初始化 -------*/
    higmac_enable_napi(ld);  // 启用NAPI收包机制(混合中断+轮询)

    /*------- 5. PHY层启动 -------*/
    phy_start(ld->phy);      // 启动PHY状态机(自动协商/链路检测)

    /*------- 6. 数据通路使能 -------*/
    higmac_hw_desc_enable(ld);   // 激活DMA描述符引擎
    higmac_port_enable(ld);      // 启用MAC端口收发功能

    /*------- 7. 中断使能 -------*/
    higmac_irq_enable_all_queue(ld);  // 开启所有队列中断

    /*------- 8. 接收缓冲区准备 -------*/
    spin_lock_irqsave(&ld->rxlock, flags);  // 加锁保护接收队列
    higmac_rx_refill(ld);  // 为DMA描述符预分配SKB接收缓冲区
    spin_unlock_irqrestore(&ld->rxlock, flags);  // 解锁

    /*------- 9. 监控定时器设置 -------*/
    ld->monitor.expires = jiffies + HIGMAC_MONITOR_TIMER;  // 设置首次到期时间
    mod_timer(&ld->monitor, ld->monitor.expires);  // 启动周期监控定时器

    /*------- 10. 网络队列激活 -------*/
    netif_start_queue(dev);  // 允许上层协议栈向网卡发送数据包

    return 0;  // 返回成功
}
5 启动网卡
当上面初始化完成之后,就可以启动网卡了,回忆前面网卡驱动初始化时,我们提到了驱动向内核注册了 structure net_device_ops 变量,它包含着网卡启用、发包、设置mac 地址等回调函数(函数指针)。当启用一个网卡时(例如,通过 ifconfig eth0 up),net_device_ops 中的 higmac_net_open方法会被调用。它通常会做以下事情:

画板

static int higmac_net_open(struct net_device *dev)
{
	struct higmac_netdev_local *ld = netdev_priv(dev);
	unsigned long flags;

	clk_prepare_enable(ld->macif_clk);
	clk_prepare_enable(ld->clk);

	/*
	 * If we configure mac address by
	 * "ifconfig ethX hw ether XX:XX:XX:XX:XX:XX",
	 * the ethX must be down state and mac core clock is disabled
	 * which results the mac address has not been configured
	 * in mac core register.
	 * So we must set mac address again here,
	 * because mac core clock is enabled at this time
	 * and we can configure mac address to mac core register.
	 */
	higmac_hw_set_mac_addr(dev);

	/*
	 * We should use netif_carrier_off() here,
	 * because the default state should be off.
	 * And this call should before phy_start().
	 */
	netif_carrier_off(dev);
    // 使能napi
	higmac_enable_napi(ld);
	phy_start(ld->phy);

	higmac_hw_desc_enable(ld);
	higmac_port_enable(ld);
    //打开中断
	higmac_irq_enable_all_queue(ld);

	spin_lock_irqsave(&ld->rxlock, flags);
    //分配rx队列
	higmac_rx_refill(ld);
	spin_unlock_irqrestore(&ld->rxlock, flags);

	ld->monitor.expires = jiffies + HIGMAC_MONITOR_TIMER;
	mod_timer(&ld->monitor, ld->monitor.expires);

	netif_start_queue(dev);

	return 0;
}

4.2 数据发送

主要是参考大神的文章做一些记录

https://blog.csdn.net/zhangyanfei01/article/details/116725966?spm=1001.2014.3001.5502

1 网卡准备工作
```c static int higmac_init_hw_desc_queue(struct higmac_netdev_local *priv) { struct device *dev = NULL; // Linux设备结构指针 struct higmac_desc *virt_addr = NULL; // 描述符虚拟地址 dma_addr_t phys_addr = 0; // 描述符物理地址(DMA地址) int size, i, ret = 0; // size: 内存大小, i: 循环索引, ret: 操作结果
/* 1. 参数有效性检查 */
if (priv == NULL || priv->dev == NULL)     // 检查私有数据指针和设备指针
    return -EINVAL;                        // 无效参数错误

dev = priv->dev;                           // 获取关联的设备结构
if (dev == NULL)                           // 二次验证设备指针
    return -EINVAL;

/* 2. 核心队列描述符数量初始化 */
priv->RX_FQ.count = RX_DESC_NUM;   // 接收空闲队列大小(待填充包缓冲区)
priv->RX_BQ.count = RX_DESC_NUM;   // 接收缓冲队列大小(已接收包缓冲区)
priv->TX_BQ.count = TX_DESC_NUM;   // 发送缓冲队列大小(待发送包缓冲区)
priv->TX_RQ.count = TX_DESC_NUM;   // 发送回收队列大小(已发送包回收区)

/* 3. RSS多队列扩展初始化 */
// 为每个RSS接收队列分配描述符(从基础队列后开始)
for (i = 1; i < RSS_NUM_RXQS; i++)
    priv->pool[BASE_QUEUE_NUMS + i].count = RX_DESC_NUM;

/* 4. 遍历所有队列分配DMA描述符内存 */
for (i = 0; i < (QUEUE_NUMS + RSS_NUM_RXQS - 1); i++) {
    // 计算当前队列所需内存大小
    size = priv->pool[i].count * sizeof(struct higmac_desc);

    /* 4.1 内存分配策略选择 */
    if (has_cap_cci(priv->hw_cap)) {  // 硬件支持缓存一致性(CCI)
        // 使用kmalloc分配普通内核内存(物理连续)
        virt_addr = kmalloc(size, GFP_KERNEL);
        if (virt_addr != NULL) {
            // 安全清零内存(避免敏感数据泄漏)
            ret = memset_s(virt_addr, size, 0, size);
            // 转换虚拟地址到物理地址
            phys_addr = virt_to_phys(virt_addr);
        }
    } else {  // 不支持CCI的常规模式
        // 分配DMA一致性内存(保证设备可访问且缓存一致)
        virt_addr = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL);
        if (virt_addr != NULL)
            ret = memset_s(virt_addr, size, 0, size);  // 清零新分配内存
    }

    /* 4.2 错误检查处理 */
    if (ret != EOK)  // 内存清零操作失败
        printk("memset_s err, ret = %d : %s %d.\n", ret, __func__, __LINE__);
    
    if (virt_addr == NULL)  // 内存分配失败
        goto error_free_pool;  // 跳转至清理流程

    /* 4.3 绑定内存到队列结构 */
    priv->pool[i].size = size;            // 记录分配的内存大小
    priv->pool[i].desc = virt_addr;       // 存储描述符虚拟地址
    priv->pool[i].phys_addr = phys_addr;  // 存储描述符物理地址(DMA地址)
}

/* 5. 初始化描述符队列内存结构 */
if (higmac_init_desc_queue_mem(priv) == -ENOMEM)  // 关联描述符与队列管理结构
    goto error_free_pool;  // 内存不足时跳转清理

/* 6. 硬件寄存器配置 */
higmac_hw_set_desc_addr(priv);  // 将物理地址写入网卡DMA寄存器

/* 7. 调试信息输出 */
if (has_cap_cci(priv->hw_cap))  // 若启用CCI特性打印提示
    pr_info("higmac: ETH MAC supporte CCI.\n");

return 0;  // 初始化成功

/* 8. 错误处理路径 */
error_free_pool:
// 逆序释放所有已分配的队列内存
higmac_destroy_hw_desc_queue(priv);
return -ENOMEM; // 返回内存不足错误
}


由此可见,初始化了四个pool,

```c
个数:
    priv->RX_FQ.count = RX_DESC_NUM;   // 接收空闲队列大小(待填充包缓冲区)
    priv->RX_BQ.count = RX_DESC_NUM;   // 接收缓冲队列大小(已接收包缓冲区)
    priv->TX_BQ.count = TX_DESC_NUM;   // 发送缓冲队列大小(待发送包缓冲区)
    priv->TX_RQ.count = TX_DESC_NUM;   // 发送回收队列大小(已发送包回收区)
定义:
    #define RX_FQ		pool[0]
    #define RX_BQ		pool[1]
    #define TX_BQ		pool[2]
    #define TX_RQ		pool[3]

其中4个队列的各个含义

RX_FQ:存储待使用的接收描述符,供网卡硬件抓取并填充接收到的数据包。
RX_BQ:暂存已收到数据但未提交给内核协议栈的描述符。
TX_BQ:存储待发送的数据包描述符,由驱动填充数据后提交给网卡硬件发送。
TX_RQ:存储已完成发送的描述符,供驱动回收资源(如释放DMA缓存)。
2 send系统调用
![](https://i-blog.csdnimg.cn/img_convert/406f25b2811a88733b69d90af477f49c.png)
send
 
//file: net/socket.c
//sendto系统调用
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
		unsigned int, flags, struct sockaddr __user *, addr,
		int, addr_len)
{
	return __sys_sendto(fd, buff, len, flags, addr, addr_len);
}

/*
 *	Send a datagram down a socket.
 */
//send系统调用
SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len,
		unsigned int, flags)
{
	return __sys_sendto(fd, buff, len, flags, NULL, 0);
}


int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
		 struct sockaddr __user *addr,  int addr_len)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err;
	struct msghdr msg;
	struct iovec iov;
	int fput_needed;

	err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);
	if (unlikely(err))
		return err;
    //1 根据fd找到socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;
    //2 构建msghdr 
	msg.msg_name = NULL;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_namelen = 0;
	if (addr) {
		err = move_addr_to_kernel(addr, addr_len, &address);
		if (err < 0)
			goto out_put;
		msg.msg_name = (struct sockaddr *)&address;
		msg.msg_namelen = addr_len;
	}
	if (sock->file->f_flags & O_NONBLOCK)
		flags |= MSG_DONTWAIT;
	msg.msg_flags = flags;
    //3 发送数据
	err = sock_sendmsg(sock, &msg);

out_put:
	fput_light(sock->file, fput_needed);
out:
	return err;
}
3 传输层处理
4 网络层发送处理
5 邻居子系统
6 网络设备子系统
7 软中断调度
8 海思网卡驱动发送
无论是对于用户进程的内核态,还是对于软中断上下文,都会调用到网络设备子系统中的 dev_hard_start_xmit 函数。在这个函数中,会调用到驱动里的发送函数higmac_net_xmit

在驱动函数里,将 skb 会挂到 RingBuffer上,驱动调用完毕后,数据包将真正从网卡发送出去。

我们来看看实际的源码

//file: net/core/dev.c
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
   struct netdev_queue *txq)
{
 //获取设备的回调函数集合 ops
 const struct net_device_ops *ops = dev->netdev_ops;
 
 //获取设备支持的功能列表
 features = netif_skb_features(skb);
 
 //调用驱动的 ops 里面的发送回调函数 ndo_start_xmit 将数据包传给网卡设备
 skb_len = skb->len;
 rc = ops->ndo_start_xmit(skb, dev);
}

其中 ndo_start_xmit 是网卡驱动要实现的一个函数,是在 net_device_ops 中定义的。

//file: include/linux/netdevice.h
struct net_device_ops {
 netdev_tx_t  (*ndo_start_xmit) (struct sk_buff *skb,
         struct net_device *dev);
 
}

在 higmac网卡驱动源码中,我们找到了。

static const struct net_device_ops hieth_netdev_ops = {
	.ndo_open = higmac_net_open,
	.ndo_stop = higmac_net_close,
	.ndo_start_xmit = higmac_net_xmit,
	.ndo_tx_timeout = higmac_net_timeout,
	.ndo_set_rx_mode = higmac_set_multicast_list,
	.ndo_set_features = higmac_set_features,
	.ndo_do_ioctl = higmac_ioctl,
	.ndo_set_mac_address = higmac_net_set_mac_address,
	.ndo_change_mtu = eth_change_mtu,
	.ndo_get_stats = higmac_net_get_stats,
};
static netdev_tx_t higmac_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
    /* 获取网卡私有数据结构(包含寄存器基址、描述符队列等)*/
    struct higmac_netdev_local *ld = netdev_priv(dev);
    struct higmac_desc *desc = NULL;
    unsigned long txflags;
    int ret;
    u32 pos;

    /* 检查skb长度有效性(防止异常包)*/
    if (unlikely(higmac_check_skb_len(skb, dev) < 0))
        return NETDEV_TX_OK;  // 无效包直接丢弃

    /* 计算当前发送位置:
     * 从TX_BQ_WR_ADDR寄存器读取写指针(DMA硬件位置)
     * dma_cnt()将字节偏移转换为描述符索引 */
    pos = dma_cnt(readl(ld->gmac_iobase + TX_BQ_WR_ADDR));

    /* 获取发送队列锁(防止多核竞争)*/
    spin_lock_irqsave(&ld->txlock, txflags);

    /* 检查目标槽位是否已被占用(双重检测)*/
    if (unlikely(ld->tx_skb[pos] || ld->TX_BQ.skb[pos])) {
        dev->stats.tx_dropped++;       // 更新丢包统计
        dev->stats.tx_fifo_errors++;   // FIFO错误计数
        netif_stop_queue(dev);         // 停止队列(避免堆积)
        spin_unlock_irqrestore(&ld->txlock, txflags);
        return NETDEV_TX_BUSY;         // 返回“繁忙”状态
    }

    /* 关联skb到发送队列 */
    ld->TX_BQ.skb[pos] = skb;  // 加入环形缓冲区队列
    ld->tx_skb[pos] = skb;     // 加入状态跟踪数组
    desc = ld->TX_BQ.desc + pos;  // 获取对应DMA描述符

    /* TSO(TCP分段卸载)处理路径 */
    if (ld->tso_supported) {
        ret = higmac_xmit_gso(ld, skb, (struct higmac_tso_desc *)desc, pos);
        if (unlikely(ret < 0)) {
            ld->tx_skb[pos] = NULL;
            ld->TX_BQ.skb[pos] = NULL;
            spin_unlock_irqrestore(&ld->txlock, txflags);
            
            if (ret == -ENOTSUPP)   // 硬件TSO不支持则回退到软件分段
                return higmac_sw_gso(ld, skb);
                
            dev_kfree_skb_any(skb);  // 释放skb内存
            dev->stats.tx_dropped++;  // 更新丢包统计
            return NETDEV_TX_OK;
        }
    } 
    /* 常规数据包处理路径 */
    else {
        ret = higmac_net_xmit_normal(skb, dev, desc, pos);
        if (unlikely(ret < 0)) {
            spin_unlock_irqrestore(&ld->txlock, txflags);
            return NETDEV_TX_OK;  // 发送失败直接返回
        }
    }

    /* 关键内存屏障:确保CPU写入完成后再触发DMA */
    higmac_sync_barrier();  // 刷新CPU写缓存(确保描述符更新对硬件可见)

    /* 更新发送队列写指针:
     * 1. pos递增(环形队列处理)
     * 2. 将新位置写入TX_BQ_WR_ADDR寄存器(通知DMA控制器)*/
    pos = dma_ring_incr(pos, TX_DESC_NUM);
    //触发硬件DMA(点火指令)
    writel(dma_byte(pos), ld->gmac_iobase + TX_BQ_WR_ADDR);

    /* 更新统计信息 */
    netif_trans_update(dev);       // 记录最后发送时间戳
    dev->stats.tx_packets++;       // 发送包数+1
    dev->stats.tx_bytes += skb->len;  // 累加发送字节数
    netdev_sent_queue(dev, skb->len); // 通知Qdisc层发送完成

    spin_unlock_irqrestore(&ld->txlock, txflags);
    return NETDEV_TX_OK;  // 返回发送成功
}
/*  
 * 常规数据包发送处理函数(非TSO路径)  
 * 参数:  
 *   skb   - 待发送的数据包  
 *   dev   - 网络设备结构  
 *   desc  - DMA描述符指针  
 *   pos   - 描述符在队列中的位置  
 */  
static int higmac_net_xmit_normal(struct sk_buff *skb, struct net_device *dev,  
                  struct higmac_desc *desc, u32 pos)  
{  
    struct higmac_netdev_local *ld = netdev_priv(dev);  // 获取网卡私有数据  
    dma_addr_t addr;  // DMA映射地址  

    /* === DMA 地址映射逻辑 === */  
    /* 情况1:硬件不支持CCI(Cache Coherent Interconnect)时 */  
    if (!has_cap_cci(ld->hw_cap)) {  
        /* 通过标准DMA接口映射SKB数据缓冲区 */  
        addr = dma_map_single(ld->dev, skb->data, skb->len, DMA_TO_DEVICE);  
        if (unlikely(dma_mapping_error(ld->dev, addr))) {  // 映射失败处理  
            dev_kfree_skb_any(skb);         // 释放SKB内存  
            dev->stats.tx_dropped++;        // 更新丢包统计  
            ld->tx_skb[pos] = NULL;         // 清空槽位状态  
            ld->TX_BQ.skb[pos] = NULL;  
            return -1;  // 返回错误  
        }  
        desc->data_buff_addr = (u32)addr;  // 记录DMA地址到描述符  

        /* 64位系统特殊处理 */  
        #if defined(CONFIG_HIGMAC_DDR_64BIT)  
        desc->rxhash = (addr >> REG_BIT_WIDTH) & TX_DESC_HI8_MASK;  
        #endif  
    }  
    /* 情况2:硬件支持CCI时 */  
    else {  
        /* 直接通过虚拟地址转物理地址(避免DMA映射开销) */  
        addr = virt_to_phys(skb->data);  // 虚拟地址→物理地址转换  
        desc->data_buff_addr = (u32)addr;  

        #if defined(CONFIG_HIGMAC_DDR_64BIT)  
        desc->rxhash = (addr >> REG_BIT_WIDTH) & TX_DESC_HI8_MASK;  
        #endif  
    }  

    /* === 描述符字段设置 === */  
    desc->buffer_len = HIETH_MAX_FRAME_SIZE - 1;  // 缓冲区最大长度  
    desc->data_len = skb->len;      // 实际数据长度  
    desc->fl = DESC_FL_FULL;        // 标记描述符已填充  
    desc->descvid = DESC_VLD_BUSY;  // 标记描述符有效且忙碌  

    return 0;  // 成功返回  
}  

通过如下路径发送数据

在这里插入图片描述

当数据发送完成以后,其实工作并没有结束。因为内存还没有清理。当发送完成的时候,网卡设备会触发一个硬中断来释放内存。

在发送完成硬中断里,会执行 RingBuffer 内存的清理工作

这里有个很有意思的细节,无论硬中断是因为是有数据要接收,还是说发送完成通知,从硬中断触发的软中断都是 NET_RX_SOFTIRQ。这个我们在第一节说过了,这是软中断统计中 RX 要高于 TX 的一个原因。

好我们接着进入软中断的回调函数 。在这个函数里,我们注意到有一行 higmac_xmit_reclaim,参见源码:

static irqreturn_t hisi_femac_interrupt(int irq, void *dev_id)
{
    int ints;
    struct net_device *dev = (struct net_device *)dev_id; // 从dev_id获取网络设备结构
    struct hisi_femac_priv *priv = netdev_priv(dev); // 获取设备私有数据(含寄存器基地址等)

    // 读取全局中断状态寄存器(GLB_IRQ_RAW)
    ints = readl(priv->glb_base + GLB_IRQ_RAW);

    // 检查是否是需要处理的中断(DEF_INT_MASK包含RX/TX中断标志)
    if (likely(ints & DEF_INT_MASK)) {
        // 清除中断标志(写1清除机制)
        writel(ints & DEF_INT_MASK, priv->glb_base + GLB_IRQ_RAW);
        // 临时禁用同类中断(防止NAPI处理期间重复触发)
        hisi_femac_irq_disable(priv, DEF_INT_MASK);
        // 调度NAPI轮询(触发软中断,后续执行higmac_poll)
        napi_schedule(&priv->napi);
    }

    return IRQ_HANDLED; // 告知内核中断已处理
}
static int higmac_poll(struct napi_struct *napi, int budget)
{
    // 通过napi结构获取包含它的容器结构(q_napi)
    struct higmac_napi *q_napi = container_of(napi, struct higmac_napi, napi);
    struct higmac_netdev_local *priv = q_napi->ndev_priv; // 设备私有数据
    int work_done = 0; // 记录处理的数据包数量
    int num;
    u32 ints;
    u32 raw_int_reg, raw_int_mask;

    dev_hold(priv->netdev); // 增加设备引用计数,防止卸载时内存释放

    // 根据队列ID选择中断寄存器和掩码(支持多队列场景)
    if (q_napi->rxq_id) {
        raw_int_reg = RSS_RAW_PMU_INT; // 多队列的中断寄存器
        raw_int_mask = def_int_mask_queue((u32)q_napi->rxq_id); // 队列特定掩码
    } else {
        raw_int_reg = RAW_PMU_INT; // 单队列的中断寄存器
        raw_int_mask = DEF_INT_MASK; // 全局中断掩码
    }

    do {
        // 如果是队列0(主队列),回收已发送的SKB(释放DMA资源)
        if (!q_napi->rxq_id)
            higmac_xmit_reclaim(priv->netdev);
        
        // 处理接收数据包(返回实际处理的数量)
        num = higmac_rx(priv->netdev, budget - work_done, q_napi->rxq_id);
        work_done += num;

        // 若已用完预算(work_done >= budget),退出循环
        if (work_done >= budget)
            break;

        // 再次检查中断状态,处理处理期间到达的新数据包
        ints = readl(priv->gmac_iobase + raw_int_reg);
        ints &= raw_int_mask;
        writel(ints, priv->gmac_iobase + raw_int_reg); // 清除新中断
    } while (ints || higmac_rxq_has_packets(priv, q_napi->rxq_id)); // 持续轮询直到无新数据

    // 若所有包处理完毕(work_done < budget),结束轮询
    if (work_done < budget) {
        napi_complete(napi); // 将NAPI移出轮询状态
        higmac_irq_enable_queue(priv, q_napi->rxq_id); // 重新启用硬件中断
    }

    dev_put(priv->netdev); // 减少设备引用计数
    return work_done; // 返回实际处理的数据包数
}
static void higmac_xmit_reclaim(struct net_device *dev)
{
    struct sk_buff *skb = NULL;       // 待释放的Socket Buffer
    struct higmac_desc *desc = NULL;   // DMA描述符指针
    struct higmac_netdev_local *priv = netdev_priv(dev);  // 获取网卡私有数据
    unsigned int bytes_compl = 0;      // 已完成的字节数统计
    unsigned int pkts_compl = 0;       // 已完成的包数统计
    struct cyclic_queue_info dma_info; // DMA队列信息结构体
    u32 i;

    spin_lock(&priv->txlock);  // 获取发送队列自旋锁,防止并发访问

    /* 读取DMA队列的硬件状态 */
    dma_info.start = dma_cnt(readl(priv->gmac_iobase + TX_RQ_RD_ADDR));  // 当前读指针(软件已处理位置)
    dma_info.end = dma_cnt(readl(priv->gmac_iobase + TX_RQ_WR_ADDR));     // 当前写指针(硬件完成位置)
    dma_info.num = CIRC_CNT(dma_info.end, dma_info.start, TX_DESC_NUM);   // 计算待回收的描述符数量

    /* 遍历待回收的描述符 */
    for (i = 0, dma_info.pos = dma_info.start; i < dma_info.num; i++) {
        skb = priv->tx_skb[dma_info.pos];  // 获取描述符关联的skb
        if (unlikely(skb == NULL)) {       // 异常检测:skb不应为空
            netdev_err(dev, "inconsistent tx_skb\n");
            break;
        }

        /* 校验skb与发送队列的一致性 */
        if (skb != priv->TX_BQ.skb[dma_info.pos]) {
            netdev_err(dev, "wired, tx skb[%d](%p) != skb(%p)\n",
                   dma_info.pos, priv->TX_BQ.skb[dma_info.pos], skb);
            if (priv->TX_BQ.skb[dma_info.pos] == SKB_MAGIC)  // 特殊标记跳过
                goto next;
        }

        /* 统计完成信息 */
        pkts_compl++;
        bytes_compl += skb->len;

        /* 释放资源:解除DMA映射,更新描述符状态 */
        desc = priv->TX_RQ.desc + dma_info.pos;
        if (higmac_xmit_reclaim_release(dev, skb, desc, dma_info.pos) < 0)
            break;

        /* 清理队列标记 */
        priv->TX_BQ.skb[dma_info.pos] = NULL;
next:
        priv->tx_skb[dma_info.pos] = NULL;
        dev_consume_skb_any(skb);  // 释放skb内存 
        
        /* 移动到下一个描述符 */
        dma_info.pos = dma_ring_incr(dma_info.pos, TX_DESC_NUM);
    }

    /* 更新硬件读指针(通知硬件描述符可重用) */
    if (dma_info.pos != dma_info.start)
        writel(dma_byte(dma_info.pos), priv->gmac_iobase + TX_RQ_RD_ADDR);

    /* 通知内核完成统计 */
    if (pkts_compl || bytes_compl)
        netdev_completed_queue(dev, pkts_compl, bytes_compl);  // 

    /* 若队列曾被阻塞,唤醒发送队列 */
    if (unlikely(netif_queue_stopped(priv->netdev)) && pkts_compl)
        netif_wake_queue(priv->netdev);  // 

    spin_unlock(&priv->txlock);  // 释放自旋锁
}

4.3 数据接收

1 硬中断处理
![画板](https://i-blog.csdnimg.cn/img_convert/a82d96a21a2225af1cbed01e090b9940.jpeg)

注意:当RingBuffer满的时候,新来的数据包将给丢弃。ifconfig查看网卡的时候,可以里面有个overruns,表示因为环形队列满被丢弃的包。如果发现有丢包,可能需要通过ethtool命令来加大环形队列的长度。

网卡接收分为两个阶段,上半部和底半步

1.1)上半部
```c higmac_dev_probe_phy higmac_request_irqs higmac_interrupt /* 硬件中断处理函数 */ static irqreturn_t higmac_interrupt(int irq, void *dev_id) { /* 1. 获取中断上下文:dev_id 指向绑定中断时注册的 higmac_napi 结构 */ struct higmac_napi *q_napi = (struct higmac_napi *)dev_id; /* 2. 获取网卡私有数据结构 */ struct higmac_netdev_local *ld = q_napi->ndev_priv; u32 ints; u32 raw_int_reg, raw_int_mask;
/* 3. 关键检查:如果该队列的中断已被禁用(例如正在NAPI处理中),直接返回未处理 */
if (higmac_queue_irq_disabled(ld, q_napi->rxq_id))
    return IRQ_NONE; // 告知内核此中断未被处理

/* 4. 根据队列ID选择中断寄存器配置 */
if (q_napi->rxq_id) {
    // 处理RSS多队列中断
    raw_int_reg = RSS_RAW_PMU_INT;       // 多队列中断状态寄存器
    raw_int_mask = def_int_mask_queue((u32)q_napi->rxq_id); // 队列专属中断掩码
} else {
    // 处理默认队列中断
    raw_int_reg = RAW_PMU_INT;           // 默认中断状态寄存器
    raw_int_mask = DEF_INT_MASK;         // 默认中断掩码
}

/* 5. 读取并清除中断状态位 */
ints = readl(ld->gmac_iobase + raw_int_reg); // 读取原始中断状态
ints &= raw_int_mask;                     // 过滤有效中断位
writel(ints, ld->gmac_iobase + raw_int_reg); // 写回清除中断标志

/* 6. 中断触发条件判断 */
if (likely(ints || higmac_rxq_has_packets(ld, q_napi->rxq_id))) {
    /* 7. 关键操作:禁用当前队列中断(防止中断风暴) */
    higmac_irq_disable_queue(ld, q_napi->rxq_id);
    
    /* 8. 触发底半部:调度NAPI轮询(higmac_poll)*/
    napi_schedule(&q_napi->napi);
}

/* 9. 告知内核中断已处理 */
return IRQ_HANDLED;

}


可以看出list_add_tail修改了CPU变量softnet_data里的poll_list,将驱动napi_struct传过来的poll_list添加了进来。 其中softnet_data中的poll_list是一个双向列表,其中的设备都带有输入帧等着被处理。紧接着__raise_softirq_irqoff触发了一个软中断NET_RX_SOFTIRQ

```c
napi_schedule
  __napi_schedule
    ____napi_schedule
static inline void ____napi_schedule(struct softnet_data *sd,
				     struct napi_struct *napi)
{
	list_add_tail(&napi->poll_list, &sd->poll_list);
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

可以看出触发了NET_RX_SOFTIRQ的软中断

画板

1.2)底半部
从前面的分析结果可知,网卡硬中断的处理函数是higmac_poll,我发现这里
static int higmac_poll(struct napi_struct *napi, int budget)
{
    // 获取napi上下文
	struct higmac_napi *q_napi = container_of(napi,
						  struct higmac_napi, napi);
	struct higmac_netdev_local *priv = q_napi->ndev_priv;
	int work_done = 0;
	int num;
	u32 ints;
	u32 raw_int_reg, raw_int_mask;

	dev_hold(priv->netdev);
	if (q_napi->rxq_id) {
		raw_int_reg = RSS_RAW_PMU_INT;
		raw_int_mask = def_int_mask_queue((u32)q_napi->rxq_id);
	} else {
		raw_int_reg = RAW_PMU_INT;
		raw_int_mask = DEF_INT_MASK;
	}

	do {
		if (!q_napi->rxq_id)
			higmac_xmit_reclaim(priv->netdev); // 默认队列:回收已发送的SKB
		num = higmac_rx(priv->netdev, budget - work_done, q_napi->rxq_id); // 接收数据包
		work_done += num;
		if (work_done >= budget) // 达到预算则退出
			break;

		ints = readl(priv->gmac_iobase + raw_int_reg); // 读取中断状态
		ints &= raw_int_mask;
		writel(ints, priv->gmac_iobase + raw_int_reg);// 清除已处理中断
	} while (ints || higmac_rxq_has_packets(priv, q_napi->rxq_id));// 循环条件

	if (work_done < budget) {
		napi_complete(napi);
		higmac_irq_enable_queue(priv, q_napi->rxq_id);
	}

	dev_put(priv->netdev);
	return work_done;
}

budget是用来控制net_rx_action函数主动退出的,目的是保证网络包的接收不霸占CPU不放。 等下次网卡再有硬中断过来的时候再处理剩下的接收数据包。其中budget可以通过内核参数调整。 这个函数中剩下的核心逻辑是获取到当前CPU变量softnet_data,对其poll_list进行遍历, 然后执行到网卡驱动注册到的poll函数。

static int higmac_rx(struct net_device *dev, int limit, int rxq_id)
{
    // 获取网卡私有数据结构(含寄存器基地址、队列状态等)
    struct higmac_netdev_local *ld = netdev_priv(dev);
    struct higmac_desc *desc = NULL;      // 指向DMA描述符
    struct cyclic_queue_info dma_info;    // 环形队列信息(读/写指针位置)
    u32 rx_bq_rd_reg, rx_bq_wr_reg;       // 接收队列的读/写寄存器地址
    u16 skb_id;                           // SKB在环形队列中的索引
    u32 i;

    // 根据队列ID选择对应的寄存器地址(多队列支持)
    rx_bq_rd_reg = rx_bq_rd_addr_queue(rxq_id);  // 读指针寄存器
    rx_bq_wr_reg = rx_bq_wr_addr_queue(rxq_id);  // 写指针寄存器

    /* 获取环形队列状态 */
    dma_info.start = dma_cnt(readl(ld->gmac_iobase + rx_bq_rd_reg));  // 当前软件读指针位置
    dma_info.end = dma_cnt(readl(ld->gmac_iobase + rx_bq_wr_reg));    // 当前硬件写指针位置
    dma_info.num = CIRC_CNT(dma_info.end, dma_info.start, RX_DESC_NUM); // 有效数据包数量(环形计算)
    if (dma_info.num > limit)  // 限制处理数量不超过NAPI预算
        dma_info.num = limit;

    /* 内存屏障:确保CPU读取最新的描述符内容 */
    rmb();  // 防止乱序执行导致数据不一致

    /* 遍历有效数据包 */
    for (i = 0, dma_info.pos = dma_info.start; i < dma_info.num; i++) {
        // 根据队列ID选择描述符池
        if (rxq_id)
            desc = ld->pool[BASE_QUEUE_NUMS + rxq_id].desc + dma_info.pos;  // RSS队列描述符
        else
            desc = ld->RX_BQ.desc + dma_info.pos;  // 默认队列描述符
        
        skb_id = desc->skb_id;  // 获取SKB索引(预分配缓冲区标识)

        // 处理数据包:将DMA数据转SKB并提交协议栈
        if (unlikely(higmac_rx_skb(dev, desc, skb_id, rxq_id)))
            break;  // 处理失败则退出

        /* 释放环形队列中的SKB引用 */
        spin_lock(&ld->rxlock);
        ld->rx_skb[skb_id] = NULL;  // 清除SKB指针(避免重复处理)
        spin_unlock(&ld->rxlock);
        
        // 环形递增:移动到下一个描述符位置
        dma_info.pos = dma_ring_incr(dma_info.pos, RX_DESC_NUM);
    }

    /* 更新硬件读指针(告知硬件描述符已处理) */
    if (dma_info.pos != dma_info.start)
        writel(dma_byte(dma_info.pos), ld->gmac_iobase + rx_bq_rd_reg);

    /* 补充接收缓冲区(关键!) */
    spin_lock(&ld->rxlock);
    higmac_rx_refill(ld);  // 预分配新SKB并填充到RX_FQ队列
    spin_unlock(&ld->rxlock);

    return dma_info.num;  // 返回实际处理的数据包数量
}

接下来分析数据发送函数

higmac_rx_skb
   higmac_rx_skbput(dev, skb, desc, rxq_id);  
/* 将接收到的数据包提交给网络协议栈 */
static void higmac_rx_skbput(struct net_device *dev, struct sk_buff *skb,
			     struct higmac_desc *desc, int rxq_id)
{
	// 获取网卡私有数据结构(含队列、DMA映射等信息)
	struct higmac_netdev_local *ld = netdev_priv(dev);
	dma_addr_t addr;  // DMA映射地址
	u32 len;          // 实际接收数据长度
	int ret;          // 校验和检查返回值

	// 1. 从硬件描述符获取实际数据包长度
	len = desc->data_len;  // 硬件DMA完成后填充的实际数据长度

	// 2. 解除DMA映射(非CCI架构需要)
	if (!has_cap_cci(ld->hw_cap)) {
		// 获取描述符中的DMA地址
		addr = desc->data_buff_addr;
		
		// 64位系统特殊处理:组合高位地址
#if defined(CONFIG_HIGMAC_DDR_64BIT)
		addr |= (dma_addr_t)(desc->reserve31) << REG_BIT_WIDTH;
#endif
		
		// 解除DMA映射(安全释放IOMMU资源)
		dma_unmap_single(ld->dev, addr, HIETH_MAX_FRAME_SIZE,
				 DMA_FROM_DEVICE);
	}

	// 3. 填充SKB数据区(核心操作)
	skb_put(skb, len);  // 移动skb->tail指针,扩展数据区

	// 4. 长度异常检测(防御性编程)
	if (skb->len > HIETH_MAX_FRAME_SIZE) {
		netdev_err(dev, "rcv len err, len = %d\n", skb->len);
		dev->stats.rx_errors++;            // 更新错误统计
		dev->stats.rx_length_errors++;     // 长度错误统计
		dev_kfree_skb_any(skb);            // 立即释放异常SKB
		return;  // 终止处理流程
	}

	// 5. 设置网络协议类型(关键协议栈接口)
	skb->protocol = eth_type_trans(skb, dev);  // 解析以太网类型(如IP/ARP)

	// 6. 初始化校验和状态(默认需软件计算)
	skb->ip_summed = CHECKSUM_NONE;  // 标记需要协议栈计算校验和

	// 7. 硬件校验和处理(如果启用)
#if defined(CONFIG_HIGMAC_RXCSUM)
	ret = higmac_rx_checksum(dev, skb, desc);
	if (unlikely(ret)) {
		/* 
		 * 校验失败时:
		 * - 已记录dev->stats.rx_crc_errors
		 * - 可能已释放skb
		 */
		return;  // 直接退出,不提交协议栈
	}
#endif

	// 8. RSS多队列处理(如果启用)
	if ((dev->features & NETIF_F_RXHASH) && desc->has_hash) {
		// 设置SKB哈希值(用于多队列负载均衡)
		skb_set_hash(skb, desc->rxhash, desc->l3_hash ?
			     PKT_HASH_TYPE_L3 : PKT_HASH_TYPE_L4);
	}

	// 9. 记录接收队列ID(多队列支持)
	skb_record_rx_queue(skb, rxq_id);  // 标记数据包来源队列

	// 10. 提交给协议栈(核心操作!)
	napi_gro_receive(&ld->q_napi[rxq_id].napi, skb);
	/* 
	 * 此函数完成:
	 * - GRO(Generic Receive Offload)数据包合并
	 * - 唤醒协议栈处理
	 * - 最终调用netif_receive_skb进入IP层
	 */

	// 11. 更新网卡统计信息
	dev->stats.rx_packets++;  // 接收包计数递增
	dev->stats.rx_bytes += len;  // 接收字节数累加
}

设置填充skb之后,进入napi_gro_receive

gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
	skb_mark_napi_id(skb, napi);
	trace_napi_gro_receive_entry(skb);

	skb_gro_reset_offset(skb);

	return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}

<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">dev_gro_receive</font>这个函数代表的是网卡GRO特性,可以简单理解成把相关的小包合并成一个大包就行,目的是减少传送给网络栈的包数,这有助于减少 CPU 的使用量。我们暂且忽略,直接看<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">napi_skb_finish</font>, 这个函数主要就是调用了 netif_receive_skb_internal

static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
{
	switch (ret) {
	case GRO_NORMAL:
		if (netif_receive_skb_internal(skb))
			ret = GRO_DROP;
		break;

	case GRO_DROP:
		kfree_skb(skb);
		break;

	case GRO_MERGED_FREE:
		if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
			napi_skb_free_stolen_head(skb);
		else
			__kfree_skb(skb);
		break;

	case GRO_HELD:
	case GRO_MERGED:
	case GRO_CONSUMED:
		break;
	}

	return ret;
}

在netif_receive_skb_internal 中,将数据发送到协议栈

2 网络协议栈处理
![画板](https://i-blog.csdnimg.cn/img_convert/21e41a528e1a579678abb398670ce22b.jpeg)
netif_receive_skb_internal
  __netif_receive_skb
    __netif_receive_skb_one_core
       __netif_receive_skb_core
       /* 核心网络接收函数:处理从网络设备接收到的数据包 */
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc,
                    struct packet_type **ppt_prev)
{
    struct packet_type *ptype, *pt_prev;
    rx_handler_func_t *rx_handler;     // 接收处理函数指针(如桥接/MACVLAN)
    struct net_device *orig_dev;       // 原始接收设备
    bool deliver_exact = false;        // 精确分发标志
    int ret = NET_RX_DROP;             // 默认返回值(丢弃)
    __be16 type;                       // 以太网协议类型(如IP/ARP)

    /* 1. 时间戳处理 */
    // 检查是否需要硬件时间戳(绕过预队列)
    net_timestamp_check(!netdev_tstamp_prequeue, skb);

    /* 2. 内核追踪点 */
    trace_netif_receive_skb(skb);  // 触发ftrace事件

    /* 3. 保存原始设备 */
    orig_dev = skb->dev;

    /* 4. 重置协议头指针 */
    skb_reset_network_header(skb);  // 重置网络层头指针
    // 如果传输层头未设置,则重置传输层头
    if (!skb_transport_header_was_set(skb))
        skb_reset_transport_header(skb);
    skb_reset_mac_len(skb);  // 重置MAC头长度

    pt_prev = NULL;  // 初始化上一个协议处理器

another_round:  // 标签:用于VLAN/RX处理后的重新处理
    /* 5. 设置数据包元数据 */
    skb->skb_iif = skb->dev->ifindex;  // 记录接收接口索引

    /* 6. CPU接收统计 */
    __this_cpu_inc(softnet_data.processed);  // 递增每CPU处理计数

    /* 7. VLAN标签处理 */
    if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
        skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
        // 剥离VLAN标签(支持Q-in-Q)
        skb = skb_vlan_untag(skb);
        if (unlikely(!skb))  // 剥离失败则退出
            goto out;
    }

    /* 8. 跳过TC分类的检查 */
    if (skb_skip_tc_classify(skb))
        goto skip_classify;

    /* 9. 内存压力处理 */
    if (pfmemalloc)  // 如果是内存紧急分配的数据包
        goto skip_taps;  // 跳过抓包点

    /* 10. 全局抓包处理(如tcpdump)*/
    // 遍历全局协议类型链表(原始套接字)
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);  // 分发到协议处理器
        pt_prev = ptype;  // 更新上一个处理器
    }

    /* 11. 设备特有抓包处理 */
    // 遍历设备特定的协议类型链表
    list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);
        pt_prev = ptype;
    }

skip_taps:
#ifdef CONFIG_NET_INGRESS
    /* 12. 入口流量控制处理 */
    if (static_branch_unlikely(&ingress_needed_key)) {
        // 执行TC入口过滤(如限速/整形)
        skb = sch_handle_ingress(skb, &pt_prev, &ret, orig_dev);
        if (!skb)  // 数据包被丢弃
            goto out;

        /* 13. Netfilter入口钩子 */
        if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
            goto out;
    }
#endif
    /* 14. 重置流量控制信息 */
    skb_reset_tc(skb);  // 清除tc_index等字段

skip_classify:
    /* 15. 内存压力协议检查 */
    // 检查协议是否支持内存紧急分配(如TCP)
    if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
        goto drop;  // 不支持则丢弃

    /* 16. VLAN接收处理 */
    if (skb_vlan_tag_present(skb)) {  // 检查VLAN标签是否存在
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        // 处理VLAN数据包(可能改变skb->dev)
        if (vlan_do_receive(&skb))
            goto another_round;  // 需要重新处理
        else if (unlikely(!skb))  // 处理失败
            goto out;
    }

    /* 17. 接收处理函数(如桥接/MACVLAN) */
    rx_handler = rcu_dereference(skb->dev->rx_handler);
    if (rx_handler) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        /* 18. 调用RX处理函数 */
        switch (rx_handler(&skb)) {
        case RX_HANDLER_CONSUMED:  // 已被消费(如桥接转发)
            ret = NET_RX_SUCCESS;  // 标记成功处理
            goto out;
        case RX_HANDLER_ANOTHER:   // 需重新处理(设备改变)
            goto another_round;
        case RX_HANDLER_EXACT:     // 要求精确匹配分发
            deliver_exact = true;
            // 注意:没有break,继续执行PASS
        case RX_HANDLER_PASS:      // 继续协议栈处理
            break;
        default:
            BUG();  // 未知返回值,触发内核错误
        }
    }

    /* 19. 剩余VLAN标签处理 */
    if (unlikely(skb_vlan_tag_present(skb))) {
        // VLAN ID非0则标记为其他主机的包
        if (skb_vlan_tag_get_id(skb))
            skb->pkt_type = PACKET_OTHERHOST;
        skb->vlan_tci = 0;  // 清除VLAN信息
    }

    /* 20. 获取协议类型 */
    type = skb->protocol;  // 如ETH_P_IP, ETH_P_ARP

    /* 21. 协议分发(核心路由) */
    // 非精确匹配模式:通过哈希表分发
    if (likely(!deliver_exact)) {
        deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &ptype_base[ntohs(type) & PTYPE_HASH_MASK]);
    }

    /* 22. 设备特有协议处理 */
    deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                   &orig_dev->ptype_specific);

    /* 23. 设备改变后的补充分发 */
    if (unlikely(skb->dev != orig_dev)) {
        deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &skb->dev->ptype_specific);
    }

    /* 24. 最终分发或丢弃 */
    if (pt_prev) {  // 存在有效的协议处理器
        // 确保分片数据可安全处理
        if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
            goto drop;
        *ppt_prev = pt_prev;  // 返回最后匹配的协议类型
    } else {
drop:
        /* 25. 无处理器时的丢弃处理 */
        if (!deliver_exact)
            atomic_long_inc(&skb->dev->rx_dropped);  // 通用丢弃计数
        else
            atomic_long_inc(&skb->dev->rx_nohandler); // 无协议处理器计数
        kfree_skb(skb);  // 释放SKB内存
        ret = NET_RX_DROP;  // 返回丢弃状态
    }

out:
    return ret;  // 返回处理结果(成功/丢弃)
}

接着__netif_receive_skb_core取出protocol,它会从数据包中取出协议信息,然后遍历注册在这个协议上的回调函数列表。ptype_base 是一个 hash table,在协议注册小节我们提到过。ip_rcv 函数地址就是存在这个 hash table中的。

deliver_ptype_list_skb
  deliver_skb
static inline int deliver_skb(struct sk_buff *skb,
			      struct packet_type *pt_prev,
			      struct net_device *orig_dev)
{
	if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
		return -ENOMEM;
	refcount_inc(&skb->users);
	return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">pt_prev->func</font>这一行就调用到了协议层注册的处理函数了。对于ip包来将,就会进入到<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_rcv</font>(如果是arp包的话,会进入到arp_rcv)。

3 IP层处理
我们再来大致看一下linux在ip协议层都做了什么,包又是怎么样进一步被送到udp或tcp协议处理函数中的。
//net\ipv4\ip_input.c
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
	   struct net_device *orig_dev)
{
	struct net *net = dev_net(dev);

	skb = ip_rcv_core(skb, net);
	if (skb == NULL)
		return NET_RX_DROP;
	return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
		       net, NULL, skb, dev, NULL,
		       ip_rcv_finish);
}


这里<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">NF_HOOK</font>是一个钩子函数,当执行完注册的钩子后就会执行到最后一个参数指向的函数<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_rcv_finish</font>

ip_rcv_finish
  ip_rcv_finish_core
    
static int ip_rcv_finish_core(struct net *net, struct sock *sk,
			      struct sk_buff *skb, struct net_device *dev)
{
    ......

    if (!skb_dst(skb)) {
        int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
                           iph->tos, skb->dev);
        ...
    }

    ......

    return dst_input(skb);
}

跟踪<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_route_input_noref</font> 后看到它又调用了 <font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_route_input_mc</font>。 在<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_route_input_mc</font>中,函数<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_local_deliver</font>被赋值给了<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">dst.input</font>, 如下:

ip_route_input_mc
  rt_dst_alloc
  struct rtable *rt_dst_alloc(struct net_device *dev,
			    unsigned int flags, u16 type,
			    bool nopolicy, bool noxfrm, bool will_cache)
{
      ...
	if (rt) {
		if (flags & RTCF_LOCAL)
			rt->dst.input = ip_local_deliver;
	}
...
	return rt;
}

所以回到<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">ip_rcv_finish</font>中的<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">return dst_input(skb);</font>

/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{
    return skb_dst(skb)->input(skb);
}

<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">skb_dst(skb)->input</font>调用的input方法就是路由子系统赋的ip_local_deliver。

/*
 * 将 IP 数据包传递给上层协议(如 TCP/UDP/ICMP)
 * 此函数在数据包被路由判定为发往本机后调用
 */
int ip_local_deliver(struct sk_buff *skb)
{
    /* 获取数据包所属的网络命名空间 */
    struct net *net = dev_net(skb->dev);

    /*
     * 重组 IP 分片(若当前数据包是分片)
     * - ip_is_fragment(): 检查 IP 头部的分片标志(MF 或 Fragment Offset ≠ 0)
     * - ip_defrag(): 尝试重组分片,返回 0 表示重组未完成(需等待更多分片)
     * - IP_DEFRAG_LOCAL_DELIVER: 指定重组目的为本地交付
     */
    if (ip_is_fragment(ip_hdr(skb))) {
        if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
            return 0;  // 重组未完成,暂不继续处理
    }

    /*
     * 通过 Netfilter 的 LOCAL_IN 钩子点(NF_INET_LOCAL_IN)
     * 此处进行防火墙过滤(如 iptables INPUT 规则)
     *
     * 参数详解:
     * - NFPROTO_IPV4: IPv4 协议族
     * - NF_INET_LOCAL_IN: 钩子点(数据包进入本机上层协议前)
     * - net: 网络命名空间
     * - NULL: 未使用的套接字参数
     * - skb: 待处理的数据包缓冲区
     * - skb->dev: 接收数据的网络设备
     * - NULL: 输出设备(此处为 NULL)
     * - ip_local_deliver_finish: 钩子处理后调用的回调函数
     *
     * 返回值说明:
     * - 若钩子链返回 NF_DROP (0),则丢弃数据包
     * - 若返回 NF_ACCEPT (1),则调用 ip_local_deliver_finish()
     */
    return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
               net, NULL, skb, skb->dev, NULL,
               ip_local_deliver_finish);
}
/* 
 * IP 本地交付的最终处理阶段
 * 将数据包传递给传输层协议(TCP/UDP/ICMP)或原始套接字
 */
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    /* 移除网络层头部:将 data 指针指向传输层头部(如 TCP/UDP 头部)*/
    __skb_pull(skb, skb_network_header_len(skb));

    /* 进入 RCU 读临界区,安全访问协议处理函数表 */
    rcu_read_lock();
    {
        /* 从 IP 头部获取上层协议类型(如 TCP=6, UDP=17, ICMP=1) */
        int protocol = ip_hdr(skb)->protocol;
        const struct net_protocol *ipprot;  // 传输层协议处理函数
        int raw;                            // 标记是否被原始套接字处理

    resubmit:
        /* 处理原始套接字(RAW socket):
         * 1. 复制数据包给所有匹配的原始套接字
         * 2. 返回非零值表示至少有一个原始套接字处理了数据包
         */
        raw = raw_local_deliver(skb, protocol);

        /* 获取对应协议的处理函数(通过 inet_add_protocol() 注册) */
        ipprot = rcu_dereference(inet_protos[protocol]);
        if (ipprot) {
            /* 找到注册的传输层协议处理器 */
            int ret;

            /* 检查是否需要执行安全策略(XFRM/IPsec) */
            if (!ipprot->no_policy) {
                /* 执行 XFRM 策略检查(输入方向) */
                if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    kfree_skb(skb);  // 策略检查失败,丢弃数据包
                    goto out;
                }
                nf_reset(skb);  // 重置 Netfilter 相关状态
            }

            /* 调用传输层协议处理函数(如 tcp_v4_rcv/udp_rcv/icmp_rcv) */
            ret = ipprot->handler(skb);
            if (ret < 0) {
                /* 协议处理要求重新提交(通常用于 ICMP 错误处理):
                 * - 返回负值表示新协议号(如 -IPPROTO_ICMP)
                 */
                protocol = -ret;  // 转换为正数协议号
                goto resubmit;    // 重新处理(注意:最多嵌套一次)
            }
            
            /* 成功交付到传输层,更新统计计数器 */
            __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
        } else {
            /* 没有注册的协议处理器 */
            if (!raw) {
                /* 无原始套接字处理 */
                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    /* 更新未知协议统计 */
                    __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
                    /* 发送 ICMP 协议不可达错误 */
                    icmp_send(skb, ICMP_DEST_UNREACH, 
                              ICMP_PROT_UNREACH, 0);
                }
                kfree_skb(skb);  // 释放数据包
            } else {
                /* 数据包已被原始套接字处理 */
                __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
                consume_skb(skb);  // 减少引用计数(原始套接字已消费)
            }
        }
    }
 out:
    /* 退出 RCU 读临界区 */
    rcu_read_unlock();

    return 0;  // 始终返回 0(实际状态通过 skb 处理体现)
}

如协议注册小节看到inet_protos中保存着tcp_rcv()和udp_rcv()的函数地址。这里将会根据包中的协议类型选择进行分发,在这里skb包将会进一步被派送到更上层的协议中,udp和tcp。

4 udp层处理
在上小节的时候我们说过,udp协议的处理函数是` udp_rcv` 。
int udp_rcv(struct sk_buff *skb)
{
	return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}

int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
           int proto)
{
    sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);

    if (sk)
        return udp_unicast_rcv_skb(sk, skb, uh);  // 找到套接字则交付
    
     /* 发送 ICMP 端口不可达错误(Type=3, Code=3) */
    icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
}

<font style="color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">__udp4_lib_lookup_skb</font>是根据skb来寻找对应的socket,当找到以后将数据包放到socket的缓存队列里。如果没有找到,则发送一个目标不可达的icmp包。

//file: net/ipv4/udp.c
udp_unicast_rcv_skb
  udp_queue_rcv_skb
int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{   
    ......

    if (sk_rcvqueues_full(sk, skb, sk->sk_rcvbuf))
        goto drop;

        rc = 0;

    ipv4_pktinfo_prepare(skb);
    bh_lock_sock(sk);
    if (!sock_owned_by_user(sk))
        rc = __udp_queue_rcv_skb(sk, skb);
    else if (sk_add_backlog(sk, skb, sk->sk_rcvbuf)) {
        bh_unlock_sock(sk);
        goto drop;
    }
    bh_unlock_sock(sk);

    return rc;
}

sock_owned_by_user判断的是用户是不是正在这个socker上进行系统调用(socket被占用),如果没有,那就可以直接放到socket的接收队列中。如果有,那就通过sk_add_backlog把数据包添加到backlog队列。 当用户释放的socket的时候,内核会检查backlog队列,如果有数据再移动到接收队列中。

sk_rcvqueues_full接收队列如果满了的话,将直接把包丢弃。接收队列大小受内核参数net.core.rmem_max和net.core.rmem_default影响。

__udp_queue_rcv_skb
    __udp_enqueue_schedule_skb
    /**
 * 将接收到的 UDP 数据包加入套接字接收队列
 * 参数:
 *   sk:  目标套接字
 *   skb: 待入队的数据包
 * 返回值:
 *   0 表示成功,负数错误码表示失败
 */
int __udp_enqueue_schedule_skb(struct sock *sk, struct sk_buff *skb)
{
    /* 获取套接字接收队列指针 */
    //recv_from 从sk_receive_queue获取数据
    struct sk_buff_head *list = &sk->sk_receive_queue;
    int rmem, delta, amt, err = -ENOMEM;
    spinlock_t *busy = NULL;  // 用于异步接收的忙锁指针
    int size;

    /* 检查接收内存是否超过缓冲区限制(快速路径) 
     * sk_rmem_alloc:已分配接收内存,sk_rcvbuf:接收缓冲区上限
     */
    rmem = atomic_read(&sk->sk_rmem_alloc);
    if (rmem > sk->sk_rcvbuf)
        goto drop;  // 缓冲区满,丢弃数据包 

    /* 【内存压力优化】当已用内存 > 50% 缓冲区时:
     *   1. 压缩 skb 减少内存碎片(合并非线性区)
     *   2. 减少后续复制时的缓存未命中
     *   3. 降低释放时的页面操作开销
     */
    if (rmem > (sk->sk_rcvbuf >> 1)) {
        skb_condense(skb);          // 压缩 skb
        busy = busylock_acquire(sk); // 获取忙锁(支持异步接收)
    }
    size = skb->truesize;           // 获取 skb 实际占用内存(含元数据)
    udp_set_dev_scratch(skb);        // 设置设备专用存储区(用于 GRO)

    /* 原子增加已分配内存,并检查是否超出硬限制 */
    rmem = atomic_add_return(size, &sk->sk_rmem_alloc);
    if (rmem > (size + sk->sk_rcvbuf))
        goto uncharge_drop;  // 超出硬限制,回退并丢弃 

    /* === 临界区开始:操作接收队列需加锁 === */
    spin_lock(&list->lock);

    /* 检查前向分配内存是否不足(动态扩展机制)*/
    if (size >= sk->sk_forward_alloc) {
        amt = sk_mem_pages(size);                          // 计算所需内存页数
        delta = amt << SK_MEM_QUANTUM_SHIFT;               // 转换为字节数
        /* 尝试扩展接收内存(失败则返回 -ENOBUFS) */
        if (!__sk_mem_raise_allocated(sk, delta, amt, SK_MEM_RECV)) {
            err = -ENOBUFS;
            spin_unlock(&list->lock);
            goto uncharge_drop;  // 内存不足 
        }
        sk->sk_forward_alloc += delta;  // 更新前向分配内存
    }

    sk->sk_forward_alloc -= size;  // 扣除本次分配的内存

    /* 显式管理内存释放(无需设置 destructor) */
    sock_skb_set_dropcount(sk, skb);

    /* 将 skb 加入接收队列尾部 */
    __skb_queue_tail(list, skb);
    spin_unlock(&list->lock);  // 临界区结束

    /* 唤醒套接字的等待进程(若套接字未关闭) */
    if (!sock_flag(sk, SOCK_DEAD))
        sk->sk_data_ready(sk);  // 触发数据就绪回调(如唤醒阻塞的 recvmsg)

    busylock_release(busy);  // 释放忙锁
    return 0;  // 成功入队

/* === 错误处理路径 === */
uncharge_drop:
    atomic_sub(skb->truesize, &sk->sk_rmem_alloc);  // 回滚内存计数
drop:
    atomic_inc(&sk->sk_drops);  // 更新丢包统计 
    busylock_release(busy);
    return err;  // 返回错误码
}
5 用户程序获取数据
见文章:[https://blog.csdn.net/zhangyanfei01/article/details/110621887](https://blog.csdn.net/zhangyanfei01/article/details/110621887)
6 总结
参考:

https://zhuanlan.zhihu.com/p/397983142

https://zhuanlan.zhihu.com/p/373060740

https://zhuanlan.zhihu.com/p/256428917

https://cloud.tencent.com/developer/article/1964476


网站公告

今日签到

点亮在社区的每一天
去签到