一、整体框架图
其中网络层向上提供网络协议接口,向下提供网络设备接口,在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_XFRMstruct 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)
#endifunion {__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_SCHEDhash_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_OFFLOADskb_queue_head_init(&sd->xfrm_backlog);
#endifINIT_LIST_HEAD(&sd->poll_list);sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPSsd->csd.func = rps_trigger_softirq;sd->csd.info = sd;sd->cpu = i;
#endifinit_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_MULTICASTif (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;elsereturn 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 驱动初始化
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);
#endifndev->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);// 使能napihigmac_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系统调用
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找到socketsock = 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)
{//获取设备的回调函数集合 opsconst 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++; // 发送包数+1dev->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 Bufferstruct 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]; // 获取描述符关联的skbif (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 硬中断处理
注意:当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); // 默认队列:回收已发送的SKBnum = 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队列描述符elsedesc = 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_skbhigmac_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); // 立即释放异常SKBreturn; // 终止处理流程}// 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 网络协议栈处理
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,继续执行PASScase 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); // 通用丢弃计数elseatomic_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_skbdeliver_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_finiship_rcv_finish_corestatic 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_mcrt_dst_allocstruct 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_skbudp_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); // 压缩 skbbusy = 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