2. Layout Fields 有些sk_buff成员变量的作用是方便查找或者是连接数据结构本身。内核可以把sk_buff组织成一个双向链表。当然,这个链表的结构要比常见的双向链表的结构复杂一点。 就像任何一个双向链表一样,sk_buff中有两个指针next和prev,其中,next指向下一个节点,而 prev指向上一个节点。但是,这个链表还有另一个需求:每个sk_buff结构都必须能够很快找到链表头节点。为了满足这个需求,在第一个节点前面会插入另一个结构sk_buff_head,这是一个辅助节点,它的定义如下: struct sk_buff_head { /* These two members must be first. */ struct sk_buff * next; struct sk_buff * prev; _ _u32 qlen; spinlock_t lock; }; qlen代表链表元素的个数。 lock用于防止对链表的并发访问。 sk_buff和sk_buff_head的前两个元素是一样的:next和prev指针。这使得它们可以放到同一个链表中,尽管sk_buff_head要比sk_buff小得多。另外,相同的函数可以同样应用于sk_buff和sk_buff_head。 为了使这个数据结构更灵活,每个sk_buff结构都包含一个指向sk_buff_head的指针。这个指针的名字是list(资料显示2.6.11-2.6.13.5有此变量,2.6.11之前的版本不晓得有没有这个变量,2.6.14之后的版本没有这个变量)。图1会帮助你理解它们之间的关系。 Figure 1. List of sk_buff elements
其他有趣的成员变量如下: struct sock *sk 这是一个指向拥有这个sk_buff的sock结构的指针。这个指针在网络包由本机发出或者由本机进程接收时有效,因为插口相关的信息被L4(TCP或UDP)或者用户空间程序使用。如果sk_buff只在转发中使用(这意味着,源地址和目的地址都不是本机地址),这个指针是NULL。 unsigned int len 这是缓冲区中数据部分的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。它的值在缓冲区从一个层向另一个层传递时改变,因为往上层传递,旧的头部就没有用了,而往下层传递,需要添加本层的头部。len同样包含了协议头的长度。 unsigned int data_len 和len不同,data_len只计算分片中数据的长度。
struct net_device *dev 这个变量的类型是net_device,net_device它代表一个网络设备。dev的作用与这个包是准备发出的包还是刚接收的包有关。当收到一个包时,设备驱动会把sk_buff的dev指针指向收到这个包的设备的数据结构,就像下面的vortex_rx里的一段代码所做的一样,这个函数属于 3c59x系列以太网卡驱动,用于接收一个帧。(drivers/net/3c59x.c): static int vortex_rx(struct net_device *dev) { ... ... ... skb->dev = dev; ... ... ... skb->protocol = eth_type_trans(skb, dev); netif_rx(skb); /* Pass the packet to the higher layer */ ... ... ... } 当一个包被发送时,这个变量代表将要发送这个包的设备。在发送网络包时设置这个值的代码要比接收网络包时设置这个值的代码复杂。有些网络功能可以把多个网络设备组成一个虚拟的网络设备(也就是说,这些设备没有和物理设备直接关联),并由一个虚拟网络设备驱动管理。当虚拟设备被使用时,dev指针指向虚拟设备的net_device结构。而虚拟设备驱动会在一组设备中选择一个设备并把dev指针修改为这个设备的net_device结构。因此,在某些情况下, 指向传输设备的指针会在包处理过程中被改变。 struct net_device *input_dev 这是收到包的网络设备的指针。如果包是本地生成的,这个值为NULL。对以太网设备来说,这个值由eth_type_trans初始化,它主要被流量控制代码使用。 struct net_device *real_dev 这个变量只对虚拟设备有意义,它代表与虚拟设备关联的真实设备。例如,Bonding和VLAN设备都使用它来指向收到包的真实设备。 union {...} h union {...} nh union {...} mac 在内核2.6.35.13中这三个成员演变为: sk_buff_data_t transport_header; sk_buff_data_t network_header; sk_buff_data_t mac_header;
这些是指向TCP/IP各层协议头的指针:h指向L4,nh指向L3,mac指向L2。每个指针的类型都是一个联合,包含多个数据结构,每一个数据结构都表示内核在这一层可以解析的协议。例如,h是一个包含内核所能解析的L4协议的数据结构的联合。每一个联合都有一个raw变量用于初始化,后续的访问都是通过协议相关的变量进行的。 当接收一个包时,处理n层协议头的函数从n-1层收到一个缓冲区,它的skb->data指向n层协议的头。处理n层协议的函数把本层的指针(例如,L3对应的是skb->nh指针)初始化为skb->data,因为这个指针的值会在处理下一层协议时改变(skb->data将被初始化成缓冲区里的其他地址)。在处理n层协议的函数结束时,在把包传递给n+1层的处理函数前,它会把skb->data指针指向n层协议头的末尾,这正好是n+1层协议的协议头(参见图3)。 发送包的过程与此相反,但是由于要为每一层添加新的协议头,这个过程要比接收包的过程复杂。 Figure 3. Header's pointer initializations while moving from layer two to layer three
dev_alloc_skb也是一个缓冲区分配函数,它主要被设备驱动使用,通常用在中断上下文中。这是一个alloc_skb函数的包装函数,它会在请求分配的大小上增加16字节的空间以优化缓冲区的读写效率,它的分配要求使用原子操作 (GFP_ATOMIC),这是因为它是在中断处理函数中被调用的。 static inline struct sk_buff *dev_alloc_skb(unsigned int length) { return _ _dev_alloc_skb(length, GFP_ATOMIC); } static inline struct sk_buff *_ _dev_alloc_skb(unsigned int length, int gfp_mask) { struct sk_buff *skb = alloc_skb(length + 16, gfp_mask); if (likely(skb)) skb_reserve(skb, 16); return skb; } 如果没有体系架构相关的实现,缺省使用__dev_alloc_skb的实现。 5.2. Freeing memory: kfree_skb and dev_kfree_skb 这两个函数释放缓冲区,并把它返回给缓冲池(缓存)。 kfree_skb可以直接调用,也可以通过包装函数 dev_kfree_skb调用。后面这个函数一般被设备驱动使用,与之功能相反的函数是dev_alloc_skb。dev_kfree_skb仅是一 个简单的宏,它什么都不做,只简单地调用kfree_skb。这些函数只有在skb->users为1地情况下才释放内存(没有人引用这个结构)。 否则,它只是简单地减小 skb->users。如果缓冲区有三个引用者,那么只有第三次调用dev_kfree_skb或kfree_skb时才释放内存。 图6中的流程图显示了分配一个缓冲区所需要的步骤。当sk_buff释放后,dst_release同样会被调用以减小相关dst_entry数据结构的引用计数。 如果destructor被初始化过,相应的函数会在此时被调用. 在图5中,我们看到,一个简单的场景是:一个sk_buff结构与另一个内存块相关,这个内存块里存储的是真正的数据。 当然,内存块底部的skb_shared_info数据结构可以包含指向其他分片的指针(参见图5)。如果存在分片,kfree_skb同样会释放这些分 片所占用的内存。最后,kfree_skb 把sk_buff结构返回给skbuff_head_cache缓存。 5.3. Data reservation and alignment: skb_reserve, skb_put, skb_push, and skb_pull skb_reserve可以在缓冲区的头部预留一定的空间,它通常被用来在缓冲区中插入协议头或者在某个边界上对齐。这个函数改变data和tail指针,而data和tail指针分别指向负载的开头和结尾,图4(d)展示了调用skb_reserve(skb,n)的结果。这个函数通常在分配缓冲区之后就调用,此时的 data和tail指针还是指向同一个地方。 如果你查看某个以太网设备驱动的收包函数(例如,drivers/net/3c59x.c中的vortex_rx), 你就会发现它在分配缓冲区之后,在向缓冲区中填充数据之前,会调用下面的函数: skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ Figure 6. kfree_skb function
由于以太网帧的头部长度是14个八位组,这个函数把缓冲区的头部指针向后移动了2个字节。这样,紧跟在以太网头部之后的IP头部在缓冲区中存储时就可以在16字节的边界上对齐。如图7所示。 Figure 7. (a) before skb_reserve, (b) after skb_reserve, and (c) after copying the frame on the buffer
图8展示了一个在发送过程中使用skb_reserve的例子。 Figure 8. Buffer that is filled in while traversing the stack from the TCP layer down to the link layer
TCP在缓冲区的头部预留足够的空间(用skb_reserve)用于填充各层的头部(如TCP,IP,链路层等)。MAX_TCP_HEADER参数是各层头部长度的总和,它考虑了最坏的情况:由于tcp层不知道将要用哪个接口发送包,它为每一层预留了最大的头部长度。它甚至考虑了出现多个IP头的可能性(如果内核编译支持IP over IP, 我们就会遇到多个IP头的情况)。