首页 / 操作系统 / Linux / Linux网络协议栈之套接字缓冲区
Linux网络协议栈是内核中最大的组件之一,由于网络部分应用的范围很广,也相对较热,该部分现有的资料很多,学起来也比较容易。首先,我们看看贯穿网络协议栈各层的一个最关键数据结构——套接字缓冲区(sk_buff结构)。一个封包就存储在这个数据结构中。所有网络分层都会使用这个结构来存储其报头、有关数据的信息,以及用来协调工作的其他内部信息。在内核的进化历程中,这个结构经历多次变动,本文及后面的文章都是基于2.6.20版本,在2.6.32中该结构又变化了很多。该结构字段可粗略划分为集中类型:布局、通用、专用、可选(可用宏开关)。SKB在不同网络层之间传递,可用于不同的网络协议。协议栈中的每一层往下一层传递SKB之前,首先就是调用skb_reserve函数在数据缓存区头部预留出的一定空间以保证每一层都能把本层的协议首部添加到数据缓冲区中。如果是向上层协议传递skb,则下层协议层的首部信息就没有用了,内核实现上用指针改变指向来实现。下面看看该结构体中的字段,大部分都给了注释,后面的方法与实现中我们将看到他的各个字段的应用与实际意义。struct sk_buff { /* These two members must be first. */ /*链表,放在结构头,用于强制转化得到,链表头为skb_buff_head*/ struct sk_buff *next; struct sk_buff *prev; /*指向拥有此缓冲区的sock数据结构,当数据在本地 产生或者正由本地进程接收时,就需要这个指针 因为该数据以及套接字相关的信息会由L4 以及用户应用程序使用。当缓冲去只是被 转发时,该指针为NULL*/ struct sock *sk; /*通常只对一个以及接收的封包才有意义 这是一个时间戳记,用于表示封包何时被 接收,或者有时用于表示封包预定传输时间 */ struct skb_timeval tstamp; /*此字段描述一个网络设备,该字段的作用与该SKB是发送包还是 接受包有关*/ struct net_device *dev; /*接收报文的原始网络设备,主要用于流量控制*/ struct net_device *input_dev; union { struct tcphdr *th; struct udphdr *uh; struct icmphdr *icmph; struct igmphdr *igmph; struct iphdr *ipiph; struct ipv6hdr *ipv6h; unsigned char *raw; } h;/*四层协议首部*/ union { struct iphdr *iph; struct ipv6hdr *ipv6h; struct arphdr *arph; unsigned char *raw; } nh;/*三层协议首部*/ union { unsigned char *raw; } mac;/*二层协议首部*/ /*目的路由缓存项*/ struct dst_entry *dst; /*IPSec协议用来跟踪传输的信息*/ struct sec_path *sp; /* * 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. */ /*SKB信息控制块,是每层协议的私有信息存储空间,由每层协议自己使用并维护,并只有在本层有效*/ char cb[48]; /*数据总长度,包括线性缓冲区数据长度(data指向),SG类型的聚合分散I/O的数据 以及FRAGLIST类型的聚合分散I/O的数据长度,也包括协议首部的长度*/ unsigned int len, /*SG类型和FRAGLIST类型聚合分散I/O存储区中的数据长度*/ data_len, /*二层首部长度。实际长度与网络介质有关,在以太网中为以太网桢首部的长度*/ mac_len; union { __wsum csum; __u32 csum_offset; }; /*发送或转发QOS类别。*/ __u32 priority; /*表示此skb在本地允许分片*/ __u8 local_df:1, /*标记所属SKB是否已经克隆*/ cloned:1, /*标记传输层校验和的状态*/ ip_summed:2, /*标识payload是否被单独引用,不存在协议首部*/ nohdr:1, /*防火墙使用*/ nfctinfo:3; /*桢类型,分类是由二层目的地址来决定的,对于以太网设备 来说,该字段由eth_type_trans()初始化*/ __u8 pkt_type:3, /*当前克隆状态*/ fclone:2, ipvs_property:1; /*从二层设备角度看到的上层协议,即链路层承载的三层协议类型*/ __be16 protocol; /*skb析构函数指针,在释放skb时被调用,完成某些必要的工作*/ void (*destructor)(struct sk_buff *skb); /*在数据结构中定义的宏不能编译成模块;原因在于内核编译之后,开启该选项所得的多数结果为不可逆的,一般而言,任何引起内核数据结构改变的选项,都不适合编译成一个模块,编译选项和特定的#ifdef符号相配,以了解一个代码区块什么时候会包含到内核中,这种关联在源码树的Kconfig文件中*/#ifdef CONFIG_NETFILTER /*防火墙使用*/ struct nf_conntrack *nfct;#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) struct sk_buff *nfct_reasm;#endif#ifdef CONFIG_BRIDGE_NETFILTER /*防火墙使用*/ struct nf_bridge_info *nf_bridge;#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#ifdef CONFIG_NET_DMA dma_cookie_t dma_cookie;#endif#ifdef CONFIG_NETWORK_SECMARK __u32 secmark;#endif __u32 mark; /* These elements must be at the end, see alloc_skb() for details. */ /*全部数据的长度+该结构的长度,如果申请了一个len字节的缓冲区 该字段为len+sizeof(sk_buff)*/ unsigned int truesize; /*引用计数,使用这个缓冲区的实例的数目*/ atomic_t users; /*head和end指向数据在内存中的起始和结束位置,即 已经分配的缓冲区空间的开端和尾端 data和tail指向数据区域的起始和结束位置,即 实际数据的开端和尾端*/ unsigned char *head, *data, *tail, *end;};