套接口通用发送缓存区限定

发布于:2023-01-22 ⋅ 阅读:(14) ⋅ 点赞:(0) ⋅ 评论:(0)

内核通用的套接口(不包括TCP套接口)发送缓冲区大小可由PROC文件wmem_default获得,其最大值可由wmem_max文件得到。默认情况下,两者值相同,如下所示。

$ cat /proc/sys/net/core/wmem_max
212992
$
$ cat /proc/sys/net/core/wmem_default 
212992
两者的值在net_core_table结构中初始化,wmem_max的初始化为sysctl_wmem_max变量的值,wmem_default初始化为sysctl_wmem_default变量的值。在用户通过PROC文件配置这两者值的时候,将最小值限定在min_sndbuf的值之上。最小的发送缓冲区大小由宏SOCK_MIN_SNDBUF定义,其为sk_buff结构体大小与2048的和,之后在乘以2的所得到的值,意味着最小的发送缓冲区能够容纳2个数据包。

#define TCP_SKB_MIN_TRUESIZE    (2048 + SKB_DATA_ALIGN(sizeof(struct sk_buff)))
#define SOCK_MIN_SNDBUF     (TCP_SKB_MIN_TRUESIZE * 2)
static int min_sndbuf = SOCK_MIN_SNDBUF;
 
static struct ctl_table net_core_table[] = {
    {
        .procname   = "wmem_max",
        .data       = &sysctl_wmem_max,
        .extra1     = &min_sndbuf,
    },
    {
        .procname   = "wmem_default",
        .data       = &sysctl_wmem_default,
        .extra1     = &min_sndbuf,
    },
}

默认和最大的发送缓冲区的值由宏SK_WMEM_MAX定义,其值为256个大小为256字节的数据包及相应sk_buff所占用的内存空间。

__u32 sysctl_wmem_max __read_mostly = SK_WMEM_MAX;
__u32 sysctl_wmem_default __read_mostly = SK_WMEM_MAX;
 
#define SKB_TRUESIZE(X) ((X) + SKB_DATA_ALIGN(sizeof(struct sk_buff)) + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
 
#define _SK_MEM_PACKETS     256
#define _SK_MEM_OVERHEAD    SKB_TRUESIZE(256)
#define SK_WMEM_MAX     (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)

一、初始化发送缓冲区大小

套接口创建时(inet_create),调用sock_init_data初始化发送缓冲的大小。初始值为默认的sysctl_wmem_default变量的值,即SK_WMEM_MAX。

static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
    sock_init_data(sock, sk);
    if (sk->sk_prot->init)
        err = sk->sk_prot->init(sk);
}
void sock_init_data(struct socket *sock, struct sock *sk)
{
    sk->sk_sndbuf       =   sysctl_wmem_default;
}
套接口创建函数,最后会调用具体协议的初始化回调函数,例如TCP协议的初始化函数tcp_init_sock,注意TCP会重新初始化发送缓冲的长度。UDP协议的套接口初始化函数udp_init_sock,不改变发送缓存大小,直接使用协议通用的初始配置。

struct proto udp_prot = {
    .name          = "UDP",
    .init          = udp_init_sock,
}
int udp_init_sock(struct sock *sk)
{
    skb_queue_head_init(&udp_sk(sk)->reader_queue);
    sk->sk_destruct = udp_destruct_sock;
}

二、套接口发送缓存的最大值

默认情况下套接口发送缓冲区最大值与缺省值相同。用户层可通过setsockopt系统调用,修改系统缺省的发送缓冲区大小值,但是设置值不能大于内核限定的最大值sysctl_wmem_max。并且用户设置的值不能小于最小值的一半(SOCK_MIN_SNDBUF表示2个数据包)。

int sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
{
    struct sock *sk = sock->sk;
 
    switch (optname) {
    case SO_SNDBUF:
        val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
        sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
        sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
        break;
}
当用户设置新值,内核增加一个SOCK_SNDBUF_LOCK的标志在套接口结构的成员sk_userlocks中。在TCP三次握手完成之后,如果用户没有锁定发送缓冲区的大小,内核可根据双方协商的MSS值重设发送缓冲区大小值,反之,使用用户的设定值。再者,当接收到对端的ACK报文,确认了发送缓冲区中的报文之后,也进行一次发送缓冲区的检查,看是否可以扩展其大小。

void tcp_init_buffer_space(struct sock *sk)
{
    if (!(sk->sk_userlocks & SOCK_SNDBUF_LOCK))
        tcp_sndbuf_expand(sk);
}
static void tcp_new_space(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
 
    if (tcp_should_expand_sndbuf(sk))
        tcp_sndbuf_expand(sk);
}
另外,内核中如果锁定了发送缓冲区的大小,试图减小其空间的操作都不会被执行,如函数sk_stream_moderate_sndbuf,在套接口发送缓冲空间进入压力状态后,也不会减小其大小。

static inline void sk_stream_moderate_sndbuf(struct sock *sk)
{
    if (!(sk->sk_userlocks & SOCK_SNDBUF_LOCK)) {
        sk->sk_sndbuf = min(sk->sk_sndbuf, sk->sk_wmem_queued >> 1);
        sk->sk_sndbuf = max_t(u32, sk->sk_sndbuf, SOCK_MIN_SNDBUF);
    }
}