Skip to content

Commit a3e4aba

Browse files
committed
Merge branch 'SO_RESEVED_MEM'
Wei Wang says: ==================== net: add new socket option SO_RESERVE_MEM This patch series introduces a new socket option SO_RESERVE_MEM. This socket option provides a mechanism for users to reserve a certain amount of memory for the socket to use. When this option is set, kernel charges the user specified amount of memory to memcg, as well as sk_forward_alloc. This amount of memory is not reclaimable and is available in sk_forward_alloc for this socket. With this socket option set, the networking stack spends less cycles doing forward alloc and reclaim, which should lead to better system performance, with the cost of an amount of pre-allocated and unreclaimable memory, even under memory pressure. With a tcp_stream test with 10 flows running on a simulated 100ms RTT link, I can see the cycles spent in __sk_mem_raise_allocated() dropping by ~0.02%. Not a whole lot, since we already have logic in sk_mem_uncharge() to only reclaim 1MB when sk_forward_alloc has more than 2MB free space. But on a system suffering memory pressure constently, the savings should be more. The first patch is the implementation of this socket option. The following 2 patches change the tcp stack to make use of this reserved memory when under memory pressure. This makes the tcp stack behavior more flexible when under memory pressure, and provides a way for user to control the distribution of the memory among its sockets. With a TCP connection on a simulated 100ms RTT link, the default throughput under memory pressure is ~500Kbps. With SO_RESERVE_MEM set to 100KB, the throughput under memory pressure goes up to ~3.5Mbps. Change since v2: - Added description for new field added in struct sock in patch 1 Change since v1: - Added performance stats in cover letter and rebased ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
2 parents 4075a6a + 053f368 commit a3e4aba

8 files changed

Lines changed: 147 additions & 13 deletions

File tree

include/net/sock.h

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ struct bpf_local_storage;
269269
* @sk_omem_alloc: "o" is "option" or "other"
270270
* @sk_wmem_queued: persistent queue size
271271
* @sk_forward_alloc: space allocated forward
272+
* @sk_reserved_mem: space reserved and non-reclaimable for the socket
272273
* @sk_napi_id: id of the last napi context to receive data for sk
273274
* @sk_ll_usec: usecs to busypoll when there is no data
274275
* @sk_allocation: allocation mode
@@ -409,6 +410,7 @@ struct sock {
409410
#define sk_rmem_alloc sk_backlog.rmem_alloc
410411

411412
int sk_forward_alloc;
413+
u32 sk_reserved_mem;
412414
#ifdef CONFIG_NET_RX_BUSY_POLL
413415
unsigned int sk_ll_usec;
414416
/* ===== mostly read cache line ===== */
@@ -1511,20 +1513,49 @@ sk_rmem_schedule(struct sock *sk, struct sk_buff *skb, int size)
15111513
skb_pfmemalloc(skb);
15121514
}
15131515

1516+
static inline int sk_unused_reserved_mem(const struct sock *sk)
1517+
{
1518+
int unused_mem;
1519+
1520+
if (likely(!sk->sk_reserved_mem))
1521+
return 0;
1522+
1523+
unused_mem = sk->sk_reserved_mem - sk->sk_wmem_queued -
1524+
atomic_read(&sk->sk_rmem_alloc);
1525+
1526+
return unused_mem > 0 ? unused_mem : 0;
1527+
}
1528+
15141529
static inline void sk_mem_reclaim(struct sock *sk)
15151530
{
1531+
int reclaimable;
1532+
15161533
if (!sk_has_account(sk))
15171534
return;
1518-
if (sk->sk_forward_alloc >= SK_MEM_QUANTUM)
1519-
__sk_mem_reclaim(sk, sk->sk_forward_alloc);
1535+
1536+
reclaimable = sk->sk_forward_alloc - sk_unused_reserved_mem(sk);
1537+
1538+
if (reclaimable >= SK_MEM_QUANTUM)
1539+
__sk_mem_reclaim(sk, reclaimable);
1540+
}
1541+
1542+
static inline void sk_mem_reclaim_final(struct sock *sk)
1543+
{
1544+
sk->sk_reserved_mem = 0;
1545+
sk_mem_reclaim(sk);
15201546
}
15211547

15221548
static inline void sk_mem_reclaim_partial(struct sock *sk)
15231549
{
1550+
int reclaimable;
1551+
15241552
if (!sk_has_account(sk))
15251553
return;
1526-
if (sk->sk_forward_alloc > SK_MEM_QUANTUM)
1527-
__sk_mem_reclaim(sk, sk->sk_forward_alloc - 1);
1554+
1555+
reclaimable = sk->sk_forward_alloc - sk_unused_reserved_mem(sk);
1556+
1557+
if (reclaimable > SK_MEM_QUANTUM)
1558+
__sk_mem_reclaim(sk, reclaimable - 1);
15281559
}
15291560

15301561
static inline void sk_mem_charge(struct sock *sk, int size)
@@ -1536,9 +1567,12 @@ static inline void sk_mem_charge(struct sock *sk, int size)
15361567

15371568
static inline void sk_mem_uncharge(struct sock *sk, int size)
15381569
{
1570+
int reclaimable;
1571+
15391572
if (!sk_has_account(sk))
15401573
return;
15411574
sk->sk_forward_alloc += size;
1575+
reclaimable = sk->sk_forward_alloc - sk_unused_reserved_mem(sk);
15421576

15431577
/* Avoid a possible overflow.
15441578
* TCP send queues can make this happen, if sk_mem_reclaim()
@@ -1547,7 +1581,7 @@ static inline void sk_mem_uncharge(struct sock *sk, int size)
15471581
* If we reach 2 MBytes, reclaim 1 MBytes right now, there is
15481582
* no need to hold that much forward allocation anyway.
15491583
*/
1550-
if (unlikely(sk->sk_forward_alloc >= 1 << 21))
1584+
if (unlikely(reclaimable >= 1 << 21))
15511585
__sk_mem_reclaim(sk, 1 << 20);
15521586
}
15531587

@@ -2344,6 +2378,7 @@ static inline void sk_stream_moderate_sndbuf(struct sock *sk)
23442378
return;
23452379

23462380
val = min(sk->sk_sndbuf, sk->sk_wmem_queued >> 1);
2381+
val = max_t(u32, val, sk_unused_reserved_mem(sk));
23472382

23482383
WRITE_ONCE(sk->sk_sndbuf, max_t(u32, val, SOCK_MIN_SNDBUF));
23492384
}

include/net/tcp.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,17 @@ static inline int tcp_full_space(const struct sock *sk)
14211421
return tcp_win_from_space(sk, READ_ONCE(sk->sk_rcvbuf));
14221422
}
14231423

1424+
static inline void tcp_adjust_rcv_ssthresh(struct sock *sk)
1425+
{
1426+
int unused_mem = sk_unused_reserved_mem(sk);
1427+
struct tcp_sock *tp = tcp_sk(sk);
1428+
1429+
tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U * tp->advmss);
1430+
if (unused_mem)
1431+
tp->rcv_ssthresh = max_t(u32, tp->rcv_ssthresh,
1432+
tcp_win_from_space(sk, unused_mem));
1433+
}
1434+
14241435
void tcp_cleanup_rbuf(struct sock *sk, int copied);
14251436

14261437
/* We provision sk_rcvbuf around 200% of sk_rcvlowat.

include/uapi/asm-generic/socket.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@
126126

127127
#define SO_BUF_LOCK 72
128128

129+
#define SO_RESERVE_MEM 73
130+
129131
#if !defined(__KERNEL__)
130132

131133
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))

net/core/sock.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,53 @@ void sock_set_mark(struct sock *sk, u32 val)
947947
}
948948
EXPORT_SYMBOL(sock_set_mark);
949949

950+
static void sock_release_reserved_memory(struct sock *sk, int bytes)
951+
{
952+
/* Round down bytes to multiple of pages */
953+
bytes &= ~(SK_MEM_QUANTUM - 1);
954+
955+
WARN_ON(bytes > sk->sk_reserved_mem);
956+
sk->sk_reserved_mem -= bytes;
957+
sk_mem_reclaim(sk);
958+
}
959+
960+
static int sock_reserve_memory(struct sock *sk, int bytes)
961+
{
962+
long allocated;
963+
bool charged;
964+
int pages;
965+
966+
if (!mem_cgroup_sockets_enabled || !sk->sk_memcg)
967+
return -EOPNOTSUPP;
968+
969+
if (!bytes)
970+
return 0;
971+
972+
pages = sk_mem_pages(bytes);
973+
974+
/* pre-charge to memcg */
975+
charged = mem_cgroup_charge_skmem(sk->sk_memcg, pages,
976+
GFP_KERNEL | __GFP_RETRY_MAYFAIL);
977+
if (!charged)
978+
return -ENOMEM;
979+
980+
/* pre-charge to forward_alloc */
981+
allocated = sk_memory_allocated_add(sk, pages);
982+
/* If the system goes into memory pressure with this
983+
* precharge, give up and return error.
984+
*/
985+
if (allocated > sk_prot_mem_limits(sk, 1)) {
986+
sk_memory_allocated_sub(sk, pages);
987+
mem_cgroup_uncharge_skmem(sk->sk_memcg, pages);
988+
return -ENOMEM;
989+
}
990+
sk->sk_forward_alloc += pages << SK_MEM_QUANTUM_SHIFT;
991+
992+
sk->sk_reserved_mem += pages << SK_MEM_QUANTUM_SHIFT;
993+
994+
return 0;
995+
}
996+
950997
/*
951998
* This is meant for all protocols to use and covers goings on
952999
* at the socket level. Everything here is generic.
@@ -1367,6 +1414,23 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
13671414
~SOCK_BUF_LOCK_MASK);
13681415
break;
13691416

1417+
case SO_RESERVE_MEM:
1418+
{
1419+
int delta;
1420+
1421+
if (val < 0) {
1422+
ret = -EINVAL;
1423+
break;
1424+
}
1425+
1426+
delta = val - sk->sk_reserved_mem;
1427+
if (delta < 0)
1428+
sock_release_reserved_memory(sk, -delta);
1429+
else
1430+
ret = sock_reserve_memory(sk, delta);
1431+
break;
1432+
}
1433+
13701434
default:
13711435
ret = -ENOPROTOOPT;
13721436
break;
@@ -1733,6 +1797,10 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
17331797
v.val = sk->sk_userlocks & SOCK_BUF_LOCK_MASK;
17341798
break;
17351799

1800+
case SO_RESERVE_MEM:
1801+
v.val = sk->sk_reserved_mem;
1802+
break;
1803+
17361804
default:
17371805
/* We implement the SO_SNDLOWAT etc to not be settable
17381806
* (1003.1g 7).
@@ -2045,6 +2113,7 @@ struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)
20452113
newsk->sk_dst_pending_confirm = 0;
20462114
newsk->sk_wmem_queued = 0;
20472115
newsk->sk_forward_alloc = 0;
2116+
newsk->sk_reserved_mem = 0;
20482117
atomic_set(&newsk->sk_drops, 0);
20492118
newsk->sk_send_head = NULL;
20502119
newsk->sk_userlocks = sk->sk_userlocks & ~SOCK_BINDPORT_LOCK;

net/core/stream.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ void sk_stream_kill_queues(struct sock *sk)
202202
WARN_ON(!skb_queue_empty(&sk->sk_write_queue));
203203

204204
/* Account for returned memory. */
205-
sk_mem_reclaim(sk);
205+
sk_mem_reclaim_final(sk);
206206

207207
WARN_ON(sk->sk_wmem_queued);
208208
WARN_ON(sk->sk_forward_alloc);

net/ipv4/af_inet.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ void inet_sock_destruct(struct sock *sk)
135135
__skb_queue_purge(&sk->sk_receive_queue);
136136
__skb_queue_purge(&sk->sk_error_queue);
137137

138-
sk_mem_reclaim(sk);
138+
sk_mem_reclaim_final(sk);
139139

140140
if (sk->sk_type == SOCK_STREAM && sk->sk_state != TCP_CLOSE) {
141141
pr_err("Attempt to release TCP socket in state %d %p\n",

net/ipv4/tcp_input.c

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,11 @@ static void tcp_grow_window(struct sock *sk, const struct sk_buff *skb,
500500

501501
room = min_t(int, tp->window_clamp, tcp_space(sk)) - tp->rcv_ssthresh;
502502

503+
if (room <= 0)
504+
return;
505+
503506
/* Check #1 */
504-
if (room > 0 && !tcp_under_memory_pressure(sk)) {
507+
if (!tcp_under_memory_pressure(sk)) {
505508
unsigned int truesize = truesize_adjust(adjust, skb);
506509
int incr;
507510

@@ -518,6 +521,11 @@ static void tcp_grow_window(struct sock *sk, const struct sk_buff *skb,
518521
tp->rcv_ssthresh += min(room, incr);
519522
inet_csk(sk)->icsk_ack.quick |= 1;
520523
}
524+
} else {
525+
/* Under pressure:
526+
* Adjust rcv_ssthresh according to reserved mem
527+
*/
528+
tcp_adjust_rcv_ssthresh(sk);
521529
}
522530
}
523531

@@ -5345,7 +5353,7 @@ static int tcp_prune_queue(struct sock *sk)
53455353
if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf)
53465354
tcp_clamp_window(sk);
53475355
else if (tcp_under_memory_pressure(sk))
5348-
tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U * tp->advmss);
5356+
tcp_adjust_rcv_ssthresh(sk);
53495357

53505358
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf)
53515359
return 0;
@@ -5380,7 +5388,7 @@ static int tcp_prune_queue(struct sock *sk)
53805388
return -1;
53815389
}
53825390

5383-
static bool tcp_should_expand_sndbuf(const struct sock *sk)
5391+
static bool tcp_should_expand_sndbuf(struct sock *sk)
53845392
{
53855393
const struct tcp_sock *tp = tcp_sk(sk);
53865394

@@ -5391,8 +5399,18 @@ static bool tcp_should_expand_sndbuf(const struct sock *sk)
53915399
return false;
53925400

53935401
/* If we are under global TCP memory pressure, do not expand. */
5394-
if (tcp_under_memory_pressure(sk))
5402+
if (tcp_under_memory_pressure(sk)) {
5403+
int unused_mem = sk_unused_reserved_mem(sk);
5404+
5405+
/* Adjust sndbuf according to reserved mem. But make sure
5406+
* it never goes below SOCK_MIN_SNDBUF.
5407+
* See sk_stream_moderate_sndbuf() for more details.
5408+
*/
5409+
if (unused_mem > SOCK_MIN_SNDBUF)
5410+
WRITE_ONCE(sk->sk_sndbuf, unused_mem);
5411+
53955412
return false;
5413+
}
53965414

53975415
/* If we are under soft global TCP memory pressure, do not expand. */
53985416
if (sk_memory_allocated(sk) >= sk_prot_mem_limits(sk, 0))

net/ipv4/tcp_output.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,8 +2967,7 @@ u32 __tcp_select_window(struct sock *sk)
29672967
icsk->icsk_ack.quick = 0;
29682968

29692969
if (tcp_under_memory_pressure(sk))
2970-
tp->rcv_ssthresh = min(tp->rcv_ssthresh,
2971-
4U * tp->advmss);
2970+
tcp_adjust_rcv_ssthresh(sk);
29722971

29732972
/* free_space might become our new window, make sure we don't
29742973
* increase it due to wscale.

0 commit comments

Comments
 (0)