内核通用的套接口(不包括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);
}
}