网络子系统学习2:套接字缓冲区

套接字缓冲区

目录

套接字缓冲区

套接字缓冲区整体结构

使用套接字缓冲区管理数据

管理套接字缓冲区数据


套接字缓冲区整体结构

在内核分析(收到的)网络分组时,底层协议的数据将传递到更高的层。发送数据时顺序相反,各种协议产生的数据(首部和净荷)依次向更低的层传递,直至最终发送。这些操作的速度对网络子系统的性能有决定性的影响,因此内核使用了一种特殊的结构,称为套接字缓冲区 (socket buffer),定义如下:

//<skbuff.h>
struct sk_buff {
    /* 这两个成员必须在最前面*/
    struct sk_buff *next;
    struct sk_buff *prev;
    struct sock *sk;
    ktime_t tstamp;
    struct net_device *dev;
    struct dst_entry *dst;
    char cb[48];
    unsigned int len,
    data_len;
    __u16 mac_len,
    hdr_len;
    union {
        __wsum csum;
        struct {
            __u16 csum_start;
            __u16 csum_offset;
            };
    };
    __u32 priority;
    __u8 local_df:1,
    cloned:1,
    ip_summed:2,
    nohdr:1,
    nfctinfo:3;
    __u8 pkt_type:3,
    fclone:2,
    ipvs_property:1;
    nf_trace:1;__be16 protocol;
...
    void (*destructor)(struct sk_buff *skb);
...
    int iif;
...
    sk_buff_data_t transport_header;
    sk_buff_data_t network_header;
    sk_buff_data_t mac_header;
/* 这些成员必须在末尾,详见alloc_skb()*/
    sk_buff_data_t tail;
    sk_buff_data_t end;
    unsigned char *head, *data;
    unsigned int truesize;
    atomic_t users;
};

        套接字缓冲区用于在网络实现的各个层次之间交换数据,而无须来回复制分组数据,对性能的提高很可观。

使用套接字缓冲区管理数据
 

套接字缓冲区通过其中包含的各种指针与一个内存区域相关联,网络分组的数据就位于该区域中,如图所示。图中假定我们使用的是32位系统。
 套接字缓冲区的基本思想是,通过操作指针来增删协议首部。

  • head 和end 指向数据在内存中的起始和结束位置。
  • data 和tail 指向协议数据区域的起始和结束位置。
  • mac_header 指向MAC协议首部的起始,而network_header和transport_header 分别指向网络层和传输层协议首部的起始。在字长32位的系统上,数据类型sk_buff_data_t 用来表示各种类型为简单指针的数据。

在一个新分组产生时,TCP层首先在用户空间中分配内存来容纳该分组数据(首部和净荷)。分配的空间大于数据实际需要的长度,因此较低的协议层可以进一步增加首部。

在套接字缓冲区传递到互联网络层时,必须增加一个新层。只需要向已经分配但尚未占用的那部分内存空间写入数据即可,除了data 之外所有的指针都不变,data 现在指向IP首部的起始处。下面的各层会重复同样的操作,直至分组完成,即将通过网络发送。在不同的协议层,data指针会变化,指向不同层的网络。

对接收的分组进行分析的过程是类似的。分组数据复制到内核分配的一个内存区中,并在整个分析期间一直处于该内存区中。与该分组相关联的套接字缓冲区在各层之间顺序传递,各层依次将其中的各个指针设置为正确值。

套接字缓冲区需要很多指针来表示缓冲区中内容的不同部分。由于网络子系统必须保证较低的内存占用和较高的处理速度,因而对struct sk_buff 来说,我们需要保持该结构的长度尽可能小。在64位 CPU上,可使用一点小技巧来节省一些空间。sk_buff_data_t 的定义改为整型变量:
 

//<skbuff.h>
typedef unsigned int sk_buff_data_t;

由于在此类体系结构上,整型变量占用的内存只有指针变量的一半(前者是4字节,后者是8字节),该结构的长度缩减了20字节。但套接字缓冲区中包含的信息仍然是同样的。data 和head 仍然是常规的指针,而所有sk_buff_data_t 类型的成员现在都解释为相对于前两者的偏移量。指向传输层首部的指针现在计算如下:
 

//<skbuff.h>
static inline unsigned char *skb_transport_header(const struct
sk_buff *skb)
{
return skb->head + skb->transport_header;
}

由于假定套接字缓冲区的内部表示对通用网络代码是不可见的,所以提供了如下几个辅助函数来访问struct sk_buff 的成员。这些函数都定义在<skbuff.h> 中,编译时会自动选择其中适当的变体使用。

  • skb_transport_header(const struct sk_buff *skb)从给定的套接字缓冲区获取传输层首部的地址。
  • skb_reset_transport_header(struct sk_buff *skb)将传输层首部重置为数据部分的起始位置。
  • skb_set_transport_header(struct sk_buff *skb,const int offset) 根据数据部分中给定的偏移量来设置传输层首部的起始地址。

对MAC层和网络层首部来说,也有同样一组函数可用,只需将transport 分别替换为mac 或network 即可。


管理套接字缓冲区数据
 

套接字缓冲区结构不仅包含上述指针,还包括用于处理相关的数据和管理套接字缓冲区自身的其他成员。
其中不常见的成员在本章中遇到时才会讨论。下面列出的是一些最重要的成员。

  • tstamp 保存了分组到达的时间。
  • dev 指定了处理分组的网络设备。dev 在处理分组的过程中可能会改变,例如,在未来某个时候,分组可能通过计算机的另一个设备发出。
  • 输入设备的接口索引号总是保存在iif 中。
  • sk 是一个指针,指向用于处理该分组的套接字对应的socket 实例(。
  • dst表示接下来该分组通过内核网络实现的路由。这里使用了一个特殊的格式,将在12.8.5节讨论。
  • next 和prev 用于将套接字缓冲区保存到一个双链表中。这里没有使用内核的标准链表实现,而是使用了一个手工实现的版本。
  • 使用了一个表头来实现套接字缓冲区的等待队列。其结构定义如下:
struct sk_buff_head {
    /* 这两个成员必须在最前面*/
    struct sk_buff *next;
    struct sk_buff *prev;
    __u32 qlen;
    spinlock_t lock;
};

qlen 指定了等待队列的长度,即队列中成员的数目。sk_buff_head 和sk_buff 的next 和prev 用于创建一个循环双链表,套接字缓冲区的list 成员指回到表头。分组通常放置在等待队列中,例如分组等待处理时,或需要重新组合已经分析过的分组时。
 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Finish_all

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值