[Devel] [PATCH vz7 v3] net/sctp: Replace in/out stream arrays with flex_array

Konstantin Khorenko khorenko at virtuozzo.com
Fri Aug 17 14:32:02 MSK 2018


From: Oleg Babin <obabin at virtuozzo.com>

SCTP protocol allocates TCB on receiving INIT and COOKIE ECHO chunks
which specify input and output stream count. As the total count of
those streams can be up to (2^16 - 1) of each type, it is possible
to occupy a fifth order of memory for only one type of streams. As
allocation can happen in a softirq contex with GFP_ATOMIC flag set
and we actually do not need the memory to be physically contiguous,
for performance reasons we use flex_array which allocates memory on
a per-page basis.

v2: Use flex_array instead of allocating pages manually.
v3: Initialize stream->len on stream allocation.

Mainstream code is different, so patches do differ significantly.
  0d493b4d0be3 ("net/sctp: Replace in/out stream arrays with flex_array")
  05364ca03cfd ("net/sctp: Make wrappers for accessing in/out streams")

https://jira.sw.ru/browse/PSBM-82552

Signed-off-by: Oleg Babin <obabin at virtuozzo.com>
Acked-by: Konstantin Khorenko <khorenko at virtuozzo.com>

Tested:
  run netperf (1 stream) and sctp_test (65535 streams)
  on an empty node and on node with memory highly fragmented.

---
 include/net/sctp/structs.h |  26 ++++++++----
 net/sctp/ssnmap.c          | 100 ++++++++++++++++++++++-----------------------
 2 files changed, 66 insertions(+), 60 deletions(-)

diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h
index 6bcd9d18b751..432affdacd7a 100644
--- a/include/net/sctp/structs.h
+++ b/include/net/sctp/structs.h
@@ -64,6 +64,7 @@
 #include <linux/atomic.h>		/* This gets us atomic counters.  */
 #include <linux/skbuff.h>	/* We need sk_buff_head. */
 #include <linux/workqueue.h>	/* We need tq_struct.	 */
+#include <linux/flex_array.h>	/* We need flex_array.   */
 #include <linux/sctp.h>		/* We need sctp* header structs.  */
 #include <net/sctp/auth.h>	/* We need auth specific structs */
 #include <net/ip.h>		/* For inet_skb_parm */
@@ -396,10 +397,17 @@ typedef struct sctp_sender_hb_info {
  *
  *  This is the structure we use to track both our outbound and inbound
  *  SSN, or Stream Sequence Numbers.
+ *
+ *  As the total count of input or output streams can be up to (2^16 - 1)
+ *  of each type, it is possible to occupy a fifth order of memory for
+ *  only one type of streams. As allocation can happen in a softirq
+ *  contex with GFP_ATOMIC flag set and we actually do not need the memory
+ *  to be physically contiguous, for performance reasons we use flex_array
+ *  which allocates memory on a per-page basis.
  */
 
 struct sctp_stream {
-	__u16 *ssn;
+	struct flex_array *ssn;
 	unsigned int len;
 };
 
@@ -408,30 +416,32 @@ struct sctp_ssnmap {
 	struct sctp_stream out;
 };
 
-struct sctp_ssnmap *sctp_ssnmap_new(__u16 in, __u16 out,
-				    gfp_t gfp);
+struct sctp_ssnmap *sctp_ssnmap_new(__u16 in, __u16 out, gfp_t gfp);
 void sctp_ssnmap_free(struct sctp_ssnmap *map);
 void sctp_ssnmap_clear(struct sctp_ssnmap *map);
 
 /* What is the current SSN number for this stream? */
 static inline __u16 sctp_ssn_peek(struct sctp_stream *stream, __u16 id)
 {
-	return stream->ssn[id];
+	__u16 *ssnp = flex_array_get(stream->ssn, id);
+	return *ssnp;
 }
 
 /* Return the next SSN number for this stream.	*/
 static inline __u16 sctp_ssn_next(struct sctp_stream *stream, __u16 id)
 {
-	return stream->ssn[id]++;
+	__u16 *ssnp = flex_array_get(stream->ssn, id);
+	return (*ssnp)++;
 }
 
 /* Skip over this ssn and all below. */
-static inline void sctp_ssn_skip(struct sctp_stream *stream, __u16 id, 
+static inline void sctp_ssn_skip(struct sctp_stream *stream, __u16 id,
 				 __u16 ssn)
 {
-	stream->ssn[id] = ssn+1;
+	__u16 *ssnp = flex_array_get(stream->ssn, id);
+	*ssnp = ssn + 1;
 }
-              
+
 /*
  * Pointers to address related SCTP functions.
  * (i.e. things that depend on the address family.)
diff --git a/net/sctp/ssnmap.c b/net/sctp/ssnmap.c
index da8603523808..2a6f38bed1db 100644
--- a/net/sctp/ssnmap.c
+++ b/net/sctp/ssnmap.c
@@ -41,37 +41,58 @@
 #include <net/sctp/sctp.h>
 #include <net/sctp/sm.h>
 
-static struct sctp_ssnmap *sctp_ssnmap_init(struct sctp_ssnmap *map, __u16 in,
-					    __u16 out);
+/* Allocate memory pages for one type of stream (in or out). */
+static int sctp_stream_alloc(struct sctp_stream *stream, __u16 len, gfp_t gfp)
+{
+	int err = -ENOMEM;
+
+	stream->ssn = flex_array_alloc(sizeof(__u16), len, gfp);
+	if (stream->ssn) {
+		err = flex_array_prealloc(stream->ssn, 0, len, gfp);
+		if (err) {
+			flex_array_free(stream->ssn);
+			stream->ssn = NULL;
+		}
+		stream->len = len;
+	}
+
+	return err;
+}
 
-/* Storage size needed for map includes 2 headers and then the
- * specific needs of in or out streams.
- */
-static inline size_t sctp_ssnmap_size(__u16 in, __u16 out)
+/* Free memory pages for one type of stream (in or out). */
+static void sctp_stream_free(struct sctp_stream *stream)
 {
-	return sizeof(struct sctp_ssnmap) + (in + out) * sizeof(__u16);
+	if (stream->ssn)
+		flex_array_free(stream->ssn);
 }
 
+/* Clear all SSNs for one type of stream (in or out). */
+static void sctp_stream_clear(struct sctp_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->len; i++)
+		flex_array_clear(stream->ssn, i);
+}
 
 /* Create a new sctp_ssnmap.
- * Allocate room to store at least 'len' contiguous TSNs.
+ * Allocate room to store at least 'in' + 'out' SSNs.
  */
-struct sctp_ssnmap *sctp_ssnmap_new(__u16 in, __u16 out,
-				    gfp_t gfp)
+struct sctp_ssnmap *sctp_ssnmap_new(__u16 in, __u16 out, gfp_t gfp)
 {
 	struct sctp_ssnmap *retval;
-	int size;
-
-	size = sctp_ssnmap_size(in, out);
-	if (size <= KMALLOC_MAX_SIZE)
-		retval = kmalloc(size, gfp);
-	else
-		retval = (struct sctp_ssnmap *)
-			  __get_free_pages(gfp, get_order(size));
+	int err;
+
+	retval = (struct sctp_ssnmap *)kzalloc(sizeof(struct sctp_ssnmap), gfp);
 	if (!retval)
 		goto fail;
 
-	if (!sctp_ssnmap_init(retval, in, out))
+	err = sctp_stream_alloc(&retval->in, in, gfp);
+	if (err)
+		goto fail_map;
+
+	err = sctp_stream_alloc(&retval->out, out, gfp);
+	if (err)
 		goto fail_map;
 
 	SCTP_DBG_OBJCNT_INC(ssnmap);
@@ -79,54 +100,29 @@ struct sctp_ssnmap *sctp_ssnmap_new(__u16 in, __u16 out,
 	return retval;
 
 fail_map:
-	if (size <= KMALLOC_MAX_SIZE)
-		kfree(retval);
-	else
-		free_pages((unsigned long)retval, get_order(size));
+	sctp_stream_free(&retval->in);
+	sctp_stream_free(&retval->out);
+	kfree(retval);
 fail:
 	return NULL;
 }
 
-
-/* Initialize a block of memory as a ssnmap.  */
-static struct sctp_ssnmap *sctp_ssnmap_init(struct sctp_ssnmap *map, __u16 in,
-					    __u16 out)
-{
-	memset(map, 0x00, sctp_ssnmap_size(in, out));
-
-	/* Start 'in' stream just after the map header. */
-	map->in.ssn = (__u16 *)&map[1];
-	map->in.len = in;
-
-	/* Start 'out' stream just after 'in'. */
-	map->out.ssn = &map->in.ssn[in];
-	map->out.len = out;
-
-	return map;
-}
-
 /* Clear out the ssnmap streams.  */
 void sctp_ssnmap_clear(struct sctp_ssnmap *map)
 {
-	size_t size;
-
-	size = (map->in.len + map->out.len) * sizeof(__u16);
-	memset(map->in.ssn, 0x00, size);
+	sctp_stream_clear(&map->in);
+	sctp_stream_clear(&map->out);
 }
 
 /* Dispose of a ssnmap.  */
 void sctp_ssnmap_free(struct sctp_ssnmap *map)
 {
-	int size;
-
 	if (unlikely(!map))
 		return;
 
-	size = sctp_ssnmap_size(map->in.len, map->out.len);
-	if (size <= KMALLOC_MAX_SIZE)
-		kfree(map);
-	else
-		free_pages((unsigned long)map, get_order(size));
+	sctp_stream_free(&map->in);
+	sctp_stream_free(&map->out);
+	kfree(map);
 
 	SCTP_DBG_OBJCNT_DEC(ssnmap);
 }
-- 
2.15.1



More information about the Devel mailing list