一、整体框架图
其中网络层向上提供网络协议接口,向下提供网络设备接口,在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 驱动初始化
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系统调用
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 硬中断处理
注意:当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 网络协议栈处理
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