套接字缓存之sk_buff结构
https://www.cnblogs.com/wanpengcoder/p/7529486.html 来此此处
sk_buff结构用来描述已接收或者待发送的数据报文信息;skb在不同网络协议层之间传递,可被用于不同网络协议,如二层的以太网协议,三层的ip协议,四层的tcp或者udp协议,其中某些成员变量会在该结构从一层向另一层传递时发生改变,从上层向下层传递需要添加首部,从下层向上层传递需要移除首部;
多个skb通过sk_buff_head表头部结构的next和prev指针连接成双向链表;头部还包含了链表中skb节点的总数量;
/* skb头结构 */ struct sk_buff_head { /* These two members must be first. */ /* 通过下面两个指针成员将skb连接成双向链表 */ struct sk_buff *next; /* 指向后一个skb */ struct sk_buff *prev; /* 指向前一个skb */ __u32 qlen; /* 链表中元素个数 */ spinlock_t lock; /* 自旋锁 */ };
2/
/* skb结构 */ struct sk_buff { union { struct { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; /* 报文到达或者离开的时间戳 */ union { ktime_t tstamp; struct skb_mstamp skb_mstamp; }; }; /* 红黑树的节点,用在netem和tcp协议栈 */ struct rb_node rbnode; /* used in netem & tcp stack */ }; /* 指向缓冲区的套接字sock数据结构。当数据在本地产生或者正由本地进程接收时, 该数据以及套接字相关信息会被L4(tcp或者udp)以及用户应用程序使用 当缓冲区只是被转发时(本地机器不是来源也不是目的地),该指针为NULL */ struct sock *sk; union { /* 报文到达或者离开时的网络设备 */ struct net_device *dev; /* Some protocols might use this space to store information, * while device pointer would be NULL. * UDP receive path is one user. */ unsigned long dev_scratch; }; /* * This is the control buffer. It is free to use for every * layer. Please put your private variables there. If you * want to keep them across layers you have to do a skb_clone() * first. This is owned by whoever has the skb queued ATM. */ /* 控制缓冲区,用于存储私有信息,每层协议自己维护并使用, 并且只在本层有有效 */ char cb[48] __aligned(8); /* 路由缓存,输入或者输出报文都要查询到目的路由缓存项,才能确定流向 */ unsigned long _skb_refdst; /* 当缓冲区被删除时,可以完成某些清理工作 当缓冲区不属于一个套接字时,该函数通常不被初始化 属于一个套接字时,通常设置为sock_rfree或sock_wfree sock_xxx函数用于更新套接字队列中所持有的内存 */ void (*destructor)(struct sk_buff *skb); #ifdef CONFIG_XFRM /* ipsec用于跟踪传输信息 */ struct sec_path *sp; #endif #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) /* 连接跟踪 */ unsigned long _nfct; #endif #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) /* 桥接帧的相关信息 */ struct nf_bridge_info *nf_bridge; #endif /* 缓冲区的数据区块大小,该长度包括主缓冲区(head指针指向)的数据 以及一些片段(fragment)的数据,当缓冲区从一个网络分层移动到下一个 网络分层时,该值会发生变化,因为在协议栈中向上层移动时报头会被丢弃 向下层移动时报头会添加,len也会把协议报头算在内,与"数据预留和对齐"操作 */ unsigned int len, /* 片段(fragment)中的数据大小 */ data_len; /* mac报头大小 */ __u16 mac_len, /* 克隆skb时可写报文头部长度 */ hdr_len; /* Following fields are _not_ copied in __copy_skb_header() * Note that queue_mapping is here mostly to fill a hole. */ kmemcheck_bitfield_begin(flags1); /* 多设备的队列映射 */ __u16 queue_mapping; /* if you move cloned around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define CLONED_MASK (1 << 7) #else #define CLONED_MASK 1 #endif #define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset) __u8 __cloned_offset[0]; /* 表示该skb是另外一个skb的克隆 */ __u8 cloned:1, /* payload是否被单独引用,不存在协议首部,如果被引用,则不能修改协议首部, 也不能通过skb->data来访问协议首部 */ nohdr:1, /* 当前克隆状态 SKB_FCLONE_UNAVAILABLE-skb未被克隆 SKB_FCLONE_ORIG-在skbuff_fclone_cache分配的父skb,可以被克隆 SKB_FCLONE_CLONE-在skbuff_fclone_cache分配的子skb,从父skb克隆得到 */ fclone:2, peeked:1, /* 通过page_fragment_alloc分配内存 */ head_frag:1, xmit_more:1, __unused:1; /* one bit hole */ kmemcheck_bitfield_end(flags1); /* fields enclosed in headers_start/headers_end are copied * using a single memcpy() in __copy_skb_header() */ /* private: */ __u32 headers_start[0]; /* public: */ /* if you move pkt_type around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define PKT_TYPE_MAX (7 << 5) #else #define PKT_TYPE_MAX 7 #endif #define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset) __u8 __pkt_type_offset[0]; /* 此字段根据l2的目的地址进行划分 PACKET_HOST-mac地址与接收设备mac地址相等,说明是发给该主机的 PACKET_BROADCAST-mac地址是接收设备的广播地址 PACKET_MULTICAST-mac地址接收改设备注册的多播地址之一 PACKET_OTHERHOST-mac地址不属于接收设备的地址,启用转发则转发,否则丢弃 PACKET_OUTGOING-数据包将被发出,用到这个标记的功能包括decnet,或者为每个 网络tab都复制一份发出包的函数 PACKET_LOOPBACK-数据包发往回环设备,有此标识,处理回环设备时, 可以跳过一些真实设备所需的操作 PACKET_USER-发送到用户空间,netlink使用 PACKET_KERNEL-发送到内核空间,netlink使用 PACKET_FASTROUTE-未使用 */ __u8 pkt_type:3; /* PFMEMALLOC内存分配标记 */ __u8 pfmemalloc:1; __u8 ignore_df:1; __u8 nf_trace:1; /* CHECKSUM_NONE-硬件不支持,完全由软件执行校验和 CHECKSUM_PARTIAL-由硬件来执行校验和 CHECKSUM_UNNECESSARY-没必要执行校验和 CHECKSUM_COMPLETE-已完成执行校验和 */ __u8 ip_summed:2; __u8 ooo_okay:1; __u8 l4_hash:1; __u8 sw_hash:1; __u8 wifi_acked_valid:1; __u8 wifi_acked:1; __u8 no_fcs:1; /* Indicates the inner headers are valid in the skbuff. */ __u8 encapsulation:1; __u8 encap_hdr_csum:1; __u8 csum_valid:1; __u8 csum_complete_sw:1; __u8 csum_level:2; __u8 csum_bad:1; __u8 dst_pending_confirm:1; #ifdef CONFIG_IPV6_NDISC_NODETYPE __u8 ndisc_nodetype:2; #endif __u8 ipvs_property:1; __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; __u8 tc_from_ingress:1; #endif #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #endif union { /* 校验和,必须包含csum_start和csum_offset */ __wsum csum; struct { /* 校验开始位置,相对于header */ __u16 csum_start; /* 校验和存储位置,相对于csum_start */ __u16 csum_offset; }; }; /* 正在被传输的数据包QoS等级 数据包由本地产生,套接字会定义优先级的值 数据包在被转发,则在调用ip_forward函数时,会根据 ip头本身的ToS字段定义该值 */ __u32 priority; /* 数据包接收时的网络设备索引号 */ int skb_iif; /* 数据包的hash值 */ __u32 hash; /* vlan封装协议 */ __be16 vlan_proto; /* vlan标签控制信息 */ __u16 vlan_tci; #if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS) union { unsigned int napi_id; unsigned int sender_cpu; }; #endif #ifdef CONFIG_NETWORK_SECMARK __u32 secmark; #endif union { __u32 mark; __u32 reserved_tailroom; }; /* 封装的协议 */ union { __be16 inner_protocol; __u8 inner_ipproto; }; /* 封装的传输层头部相对于head的偏移 */ __u16 inner_transport_header; /* 封装的网络层头部相对于head的偏移 */ __u16 inner_network_header; /* 封装的链路层头部相对于head的偏移 */ __u16 inner_mac_header; /* l3层协议值 如ETH_P_IP-ipv4报文 ETH_P_ARP-arp报文等 */ __be16 protocol; /* 传输层头部相对于head的偏移 */ __u16 transport_header; /* 网络层头部相对于head的偏移 */ __u16 network_header; /* 链路层头部相对于head的偏移 */ __u16 mac_header; /* private: */ __u32 headers_end[0]; /* public: */ /* These elements must be at the end, see alloc_skb() for details. */ /* 实际数据的尾部 */ sk_buff_data_t tail; /* 缓冲区的尾部 */ sk_buff_data_t end; /* 缓冲区的头部 */ unsigned char *head, /* 实际数据的头部 */ *data; /* 缓冲区的总大小,包括skb本身和实际数据len大小,alloc_skb函数将 该字段设置为len+sizeof(sk_buff) 每当len值更新,该值也要对应更新 */ unsigned int truesize; /* 引用计数,在使用该skb缓冲区的实例个数,当引用计数为0时,skb才能被释放 skb_get()获取操作中会增加引用计数,kfree_skb释放过程中检查引用计数, 引用计数为0时,才真正释放skb 该计数器只计算sk_buff结构引用计数,缓冲区包含的实际数据由 skb_shared_info->dataref字段记录 */ atomic_t users; };