1.关键数据结构
套接字缓冲区:sk_buff结构

为了很快地找到sk_buff链表中的表头,每个链表中都有一个这样的字段: struct sk_buff_head *list; struct sk_buff_head { /* These two members must be first. */ struct sk_buff *next; //###指向sk_buff;指向链表表头 struct sk_buff *prev; //###指向sk_buff;指向连表表尾 __u32 qlen; spinlock_t lock; };

套接字缓冲区:sk_buff结构,代表已接收或正要传输的数据的报头 struct sk_buff { /* These two members must be first. */ struct sk_buff *next; //###指向链表的下一个表项 struct sk_buff *prev; //###指向链表的上一个表项 //###每个sk_buff结构都包含了一个指向struct sk_buff_head结构的指针,这个指针的字段名list(struct sk_buff没找到这个字段???) struct sock *sk; //###指向拥有此缓冲区的套接字的sock数据结构 struct skb_timeval tstamp; //###书上未struct timeval stamp,猜测是这个字段被更新了。这是一个时间戳,由netif_rx函数用net_timestamp设置。 struct net_device *dev; //###指向一个网络设备;对于接收的数据包,这个字段代表接收数据包的网络设备;对于发送的数据包,这个字段代表数据包由这个网络设备发出 struct net_device *input_dev;//###接收报文时,这个字段表示源设备的信息;当数据包是由本地产生时,这个字段为NULL //###书上还有一个字段struct net_device *real_dev; 这个字段只对虚拟设备有意义 union { struct tcphdr *th; struct udphdr *uh; struct icmphdr *icmph; struct igmphdr *igmph; struct iphdr *ipiph; struct ipv6hdr *ipv6h; unsigned char *raw; } h; //###TCP/IP协议栈协议报头的指针,h指向L4;在网络分层间移动时,该union包含的指针是不会变化的 union { struct iphdr *iph; struct ipv6hdr *ipv6h; struct arphdr *arph; unsigned char *raw; } nh; //###TCP/IP协议栈协议报头的指针,h指向L3;在网络分层间移动时,该union包含的指针是不会变化的 union { unsigned char *raw; } mac; //###TCP/IP协议栈协议报头的指针,h指向L2;在网络分层间移动时,该union包含的指针是不会变化的 struct dst_entry *dst; //###路由子系统 struct sec_path *sp; //###由IPsec协议组使用,以记录转换信息 /* * 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]; //###“控制缓冲区”or私有信息的存储空间。该空间足以容纳每个层所需的私有数据。例如TCP使用该空间储存一个tcp_skb_cb数据结构 unsigned int len, //###缓冲区块的大小;这个长度包括主要缓冲区(由head所指)的数据以及一些片段的数据。当缓冲区从一个网络分层移动到另一个网络分层时,值会发生变化。 //###len包含了协议报头的长度 data_len, //###只计算数据大小 mac_len, //###mac报头的大小 csum; //###校验和(checksum)相关 __u32 priority; //### QoS相关 __u8 local_df:1, cloned:1, //###当一个boolean标识置位时,标识该结构是另一个sk_buff的缓冲区的克隆 ip_summed:2, //###校验和(checksum)相关 nohdr:1, nfctinfo:3; //###nfxxx netfilter相关 __u8 pkt_type:3, //###此字段会根据L2目的地址进行类型划分 fclone:2, ipvs_property:1; __be16 protocol; //###这个字段类型和书上的不同;对于L2层的设备驱动程序而言,驱动程序使用这个字段通知其上层使用哪里协议进行处理。 void (*destructor)(struct sk_buff *skb); //###此函数指针可以被初始化为一个函数,当次缓冲区被删除时,可以完成某些工作。此缓冲区不属于一个套接字时,destructor通常不会被初始化 #ifdef CONFIG_NETFILTER __u32 nfmark; //###nfxxx netfilter相关 struct nf_conntrack *nfct; //###nfxxx netfilter相关 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) struct sk_buff *nfct_reasm; //###nfxxx netfilter相关 #endif #ifdef CONFIG_BRIDGE_NETFILTER struct nf_bridge_info *nf_bridge; //###nfxxx netfilter相关 #endif #endif /* CONFIG_NETFILTER */ #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ //###流控相关 #ifdef CONFIG_NET_CLS_ACT __u16 tc_verd; /* traffic control verdict */ //###流控相关 #endif #endif /* These elements must be at the end, see alloc_skb() for details. */ unsigned int truesize; //###缓冲区总长度(总大小),包括sk_buff结构本身 atomic_t users; //###引用计数 unsigned char *head, //###head/end指向已分配缓冲区的开端和尾端; *data, //###data/tail指向数据(包含报头)的开端和尾端;(也就是说head/end指针才是固定的;data/tail指针随着网络分层的移动,是会变化的) *tail, *end; };

数据生成过程;套接字缓存之skb_put、skb_push、skb_pull、skb_reserve ========================================================================= 原话:数据缓冲区和报头(sk_buff数据结构)是两种不同的实例;建立一个缓冲区会涉及到两次内存分配:1.分配一个sk_buff结构;2.分配缓冲区 alloc_skb通过调用kmem_cache_alloc函数,从一个缓存中取得一个sk_buff数据结构,然后再调用kmalloc驱动一个数据缓冲区 skb操作中的预留和对齐操作主要由skb_put、skb_push、skb_pull、skb_reserve完成; 注意:skb_reserve只能操作空skb,即在分配了空间,尚未填充数据时调用; 本地产生数据的场景(接收数据的场景也是这样的吧。。。): P45-48 1.分配一个sk_buff结构 2.分配缓冲区 3.分配缓冲区之后马上调用skb_reserve函数,sk_buff的*data、*tail会被移动(*data不再等于*head),但*data、*tail的值仍然相同(表示数据还未写入到缓冲区中) 4.数据拷贝入缓冲区之后,*data、*tail中的一个指针会被移动 ###本地产生数据和本地接收数据,指针的逻辑略有不同: 因为接收的数据大小的已知的; 但是本地产生的数据,由上至下进行封装时,上层对于下层的报头大小是未知的,所以指针的逻辑略有不同,*head和*data之间会预留出足够大的空间以应付各种情况 ------------------------------------------------------------------------- skb_put 看起来是向报文尾部插入数据,skb->tail会移动 skb_push 看起来是向报文头部插入数据,skb->data会移动 skb_pull 看起来是剥离报头,露出内层数据,skb->data会移动 skb_reserve 看起来做的是指针的初始化工作。

//###skb_shared_info结构体储存在数据缓冲区尾端 struct skb_shared_info { atomic_t dataref; // 对象被引用次数 unsigned short nr_frags; // 分页段数目,即frags数组元素个数 unsigned short tso_size; unsigned short tso_segs; unsigned short ufo_size; unsigned int ip6_frag_id; struct sk_buff *frag_list; // 一般用于分段(还没有非常清楚的理解) skb_frag_t frags[MAX_SKB_FRAGS]; // 保存分页数据(skb->data_len=所有的数组数据长度之和) };

缓冲区的克隆和复制:skb_clone函数、skb_share_check函数 ======================================================== 缓冲区的克隆和复制 一个网络数据包含了2个部分:数据缓冲区+报头(sk_buff数据结构) 多个消费者使用同一个缓冲区时,并不一定总是要复制整个数据(数据缓冲区+报头(sk_buff数据结构));可以仅复制sk_buff数据结构,同时使用引用计数(避免过早释放该缓存区域) 缓冲区的克隆由skb_clone函数实现;当一个缓冲区被克隆时,数据区块的内容不能修改。 skb_share_check函数用于检查sk_buff数据结构的引用计数skb->users
net_device结构
#看来这个结构发生了很大的变化

net_device结构 net_device数据结构储存着特定网络设备的所有信息。每个设备都有一个这种结构。 所有设备的net_device结构都放在一个由全局变量dev_base所指的全局列表中。 #define IFNAMSIZ 32 struct net_device { //用于存放网络设备的设备名称; char name[IFNAMSIZ]; //网络设备的别名; char *ifalias; int ifindex; //###网络设备的接口索引值,独一无二的网络设备标识符; //int iflink; //###这个字段由虚拟隧道设备使用 //unsigend short dev_id;//###IPv6使用,用于区别可由不同OS同时共享的同一种设备的诸多虚拟实例 //这个字段用于构建网络设备名的哈希散列表,而struct net中的 //name_hlist就指向每个哈希散列表的链表头; struct hlist_node name_hlist; //用于构建网络设备的接口索引值哈希散列表,在struct net中的 //index_hlist用于指向接口索引值哈希散列表的链表头; struct hlist_node index_hlist; //用于将每一个网络设备加入到一个网络命名空间中的网络设备双链表中 struct list_head dev_list; //网络设备接口的标识符,其状态类型被定义在<linux/if.h>之中; unsigned int flags; //网络设备接口的标识符,但对用户空间不可见; unsigned short priv_flags; //接口硬件类型,在<if_arp.h>中定义了每一个接口硬件类型; unsigned short type; //网络设备接口的最大传输单元; unsigned mtu; //硬件接口头长度; unsigned short hard_header_len; //网络设备接口的MAC地址; unsigned char *dev_addr; //网络设备接口的单播模式 int uc_promisc; //网络设备接口的混杂模式; unsigned int promiscuity; //网络设备接口的全组播模式; unsigend int allmulti; //secondary unicast mac address struct netdev_hw_addr_list uc; //list of device hw address; struct netdev_hw_addr_list dev_addrs; //hw broadcast address; unsigned char broadcast[MAX_ADDR_LEN]; //multicast mac address; struct dev_addr_list *mac_list; //网络设备接口的数据包接收队列; struct netdev_queue rx_queue; //网络设备接口的数据包发送队列; struct netdev_queue *tx; //Number of TX queues allocated at alloc_netdev_mq() time unsigned int num_tx_queues; //Number of TX queues currently active in device; unsigned int real_num_tx_queues; //Max frame per queue allowned; unsigned long tx_queue_len; //网络设备接口的状态; unsigned long state; //网络设备接口的统计情况; struct net_device_state states; //用于执行网络设备所在的命名空间; struct net *nd_net; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!