UDP之收发内存管理


套接字建立后就会收发数据包。对于发送,数据包是先由应用发给协议栈,由协议栈缓存后发出去的;对于接收,也是先由协议栈将输入数据包缓存,然后才由应用读取走的,这种缓存数据包的行为必然会占用内存。比如应用迟迟不读取数据,那么协议栈就会不停的缓存数据,如果不对套接字占用的内存进行限制,那么很容易会吃光系统内存,内核当然不会让这种事情发生,这篇笔记介绍了内核是对套接字的内存使用进行管理的。

整体策略

无论是接收还是发送,linux内核对内存的使用限制都是在两个层面上进行管控:1) 整个传输层层面的限制;2) 具体某个传输控制块层面的限制。为了实现在两个层面上的内存用量控制,内核定义了一系列的变量,理解这些变量的含义是理解内核相关代码实现的关键。

变量说明

UDP层面的控制变量

UDP层面的内存控制变量全部都定义在了UDP协议结构中,相关字段如下:

/* Networking protocol blocks we attach to sockets.
 * socket layer -> transport layer interface
 * transport -> network interface is defined by struct inet_proto
 */
struct proto {
   
...
	/* Memory pressure */
	void (*enter_memory_pressure)(struct sock *sk);
	atomic_t *memory_allocated;	/* Current allocated memory. */
	struct percpu_counter *sockets_allocated;	/* Current number of sockets. */
	/*
	 * Pressure flag: try to collapse.
	 * Technical note: it is used by multiple contexts non atomically.
	 * All the __sk_mem_schedule() is of this nature: accounting
	 * is strict, actions are advisory and have some latency.
	 */
	// 这些变量之所以是指针,是因为协议栈通用代码在某些上下文会更新这些值
	int	*memory_pressure;
	int	*sysctl_mem;
	int	*sysctl_wmem;
	int	*sysctl_rmem;
...
};

当然,每个传输层协议也并非要提供所有的字段,按照实际需求提供即可,这些字段的使用见下文代码分析。UDP协议对该结构的实例化如下:

atomic_t udp_memory_allocated;
int sysctl_udp_mem[3] __read_mostly;
int sysctl_udp_wmem_min __read_mostly;
int sysctl_udp_rmem_min __read_mostly;

struct proto udp_prot = {
   
...
	.memory_allocated = &udp_memory_allocated,
	.sysctl_mem	= sysctl_udp_mem,
	.sysctl_wmem = &sysctl_udp_wmem_min, // UDP并未使用该机制
	.sysctl_rmem = &sysctl_udp_rmem_min,
...
};

sysctl_mem

和文件/proc/sys/net/ipv4/udp_mem对应的系统参数。该系统参数包含三个值,如上,内核中用数组实现。配置时,这三个成员值应依次增大。如此可以根据占用内存的大小将内存使用情况分成4个等级,如下:

  • 已分配用量< sysctl_mem[0]: 内存使用量非常低,没有任何压力;
  • sysctl_mem[0] < 已分配用量 < sysctl_mem[1]: 内存使用量还行,没有超过压力值sysctl_mem[1],这时可能会抑制接收;
  • sysctl_memp[1] < 已分配用量 < sysctl_mem[2]: 使用量已经超过了压力值,需要重点处理下;
  • 已分配用量 > sysctl_mem[2]: 使用量已经超过了硬性限制,此时抑制分配,所有数据包都会被丢弃;

关于这三个门限值的使用细节见下文代码分析。

sysctl_rmem/sysctl_wmem

UDP层面为单个TCB指定的最小内存可用量。当UDP层面的内存用量处于[sysctl_mem[0], sysctl_mem[2]范围时,如果TCB的内存用量没有超过该最小值,那么它也是被允许处理数据包的。UDP只是用了接收方向的内存调度,具体见下面的__sk_mem_schedule()。

memory_allocated

在UDP层面记录已经消耗的系统内存大小,该变量以物理页为单位统计,使用方式见下面的__sk_mem_schedule()。

初始化

上面sysctl_udp_mem、sysctl_udp_rmem_min、sysctl_udp_wmem_min几个变量的初始化如下:

#define SK_MEM_QUANTUM ((int)PAGE_SIZE)

void __init udp_init(void)
{
   
...
	/* Set the pressure threshold up by the same strategy of TCP. It is a
	 * fraction of global memory that is up to 1/2 at 256 MB, decreasing
	 * toward zero with the amount of memory, with a floor of 128 pages.
	 */
	// 根据系统可用物理内存页设置三个门限值
	nr_pages = totalram_pages - totalhigh_pages;
	limit = min(nr_pages, 1UL<<(28-PAGE_SHIFT)) >> (20-PAGE_SHIFT);
	limit = (limit * (nr_pages >> (20-PAGE_SHIFT))) >> (PAGE_SHIFT-11);
	limit = max(limit, 128UL);
	sysctl_udp_mem[0] = limit / 4 * 3;
	sysctl_udp_mem[1] = limit;
	sysctl_udp_mem[2] = sysctl_udp_mem[0] * 2;

	// 这两个变量都设定为一个物理内存页
	sysctl_udp_rmem_min = SK_MEM_QUANTUM;
	sysctl_udp_wmem_min = SK_MEM_QUANTUM;
}

传输控制块层面的限制

在传输控制块层面,同样定义了一些变量用于控制单个传输控制块的接收内存使用量,如下:

struct sock {
   
...
    int	sk_rcvbuf;
    atomic_t sk_rmem_alloc;
    int	sk_forward_alloc;

    int	sk_sndbuf;
    atomic_t sk_wmem_alloc;
...
};

sk_rcv_buf/sk_sndbuf

这两个参数分别代表TCB的收发缓冲区大小,缓冲区中的数据不能超过这两个限定值。在sock_init_data()中这两个会被分别初始化为系统参数sysctl_rmem_default和sysctl_wmem_default,之后,应用程序还可以通过setsockopt(2)的SO_SNDBUF和SO_RCVBUF设置它们。

void sock_init_data(struct socket *sock, struct sock *sk)
{
   
...
	sk->sk_rcvbuf =	sysctl_rmem_default;
	sk->sk_sndbuf =	sysctl_wmem_default;
...
}

setsockopt()的内核实现如下:

#define SOCK_MIN_SNDBUF 2048
#define SOCK_MIN_RCVBUF 256

int sock_setsockopt(struct socket *sock, int level, int optname,
		    char
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值