2020-10-18 17:18:12

by Douglas Gilbert

[permalink] [raw]
Subject: [PATCH v2 0/4] scatterlist: add new capabilities

Scatter-gather lists (sgl_s) are frequently used as data carriers in
the block layer. For example the SCSI and NVMe subsystems interchange
data with the block layer using sgl_s. The sgl API is declared in
<linux/scatterlist.h>

The author has extended these transient sgl use cases to a store (i.e.
a ramdisk) in the scsi_debug driver. Other new potential uses of sgl_s
could be for caches. When this extra step is taken, the need to copy
between sgl_s becomes apparent. The patchset adds sgl_copy_sgl() and
two other sgl operations.

The existing sgl_alloc_order() function can be seen as a replacement
for vmalloc() for large, long-term allocations. For what seems like
no good reason, sgl_alloc_order() currently restricts its total
allocation to less than or equal to 4 GiB. vmalloc() has no such
restriction.

Changes since v1 [posted 20201016]:
- Bodo Stroesser pointed out a problem with the nesting of
kmap_atomic() [called via sg_miter_next()] and kunmap_atomic()
calls [called via sg_miter_stop()] and proposed a solution that
simplifies the previous code.

- the new implementation of the three functions has shorter periods
when pre-emption is disabled (but has more them). This should
make operations on large sgl_s more pre-emption "friendly" with
a relatively small performance hit.

- sgl_memset return type changed from void to size_t and is the
number of bytes actually (over)written. That number is needed
anyway internally so may as well return it as it may be useful to
the caller.

This patchset is against lk 5.9.0

Douglas Gilbert (4):
sgl_alloc_order: remove 4 GiB limit, sgl_free() warning
scatterlist: add sgl_copy_sgl() function
scatterlist: add sgl_compare_sgl() function
scatterlist: add sgl_memset()

include/linux/scatterlist.h | 12 +++
lib/scatterlist.c | 204 +++++++++++++++++++++++++++++++++++-
2 files changed, 213 insertions(+), 3 deletions(-)

--
2.25.1


*** BLURB HERE ***

Douglas Gilbert (4):
sgl_alloc_order: remove 4 GiB limit, sgl_free() warning
scatterlist: add sgl_copy_sgl() function
scatterlist: add sgl_compare_sgl() function
scatterlist: add sgl_memset()

include/linux/scatterlist.h | 12 +++
lib/scatterlist.c | 185 +++++++++++++++++++++++++++++++++++-
2 files changed, 194 insertions(+), 3 deletions(-)

--
2.25.1


2020-10-18 18:20:45

by Douglas Gilbert

[permalink] [raw]
Subject: [PATCH v2 4/4] scatterlist: add sgl_memset()

The existing sg_zero_buffer() function is a bit restrictive.
For example protection information (PI) blocks are usually
initialized to 0xff bytes. As its name suggests sgl_memset()
is modelled on memset(). One difference is the type of the
val argument which is u8 rather than int. Plus it returns
the number of bytes (over)written.

Signed-off-by: Douglas Gilbert <[email protected]>
---
include/linux/scatterlist.h | 3 +++
lib/scatterlist.c | 54 ++++++++++++++++++++++++++++++++++---
2 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
index ae260dc5fedb..a40012c8a4e6 100644
--- a/include/linux/scatterlist.h
+++ b/include/linux/scatterlist.h
@@ -329,6 +329,9 @@ bool sgl_compare_sgl(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_sk
struct scatterlist *y_sgl, unsigned int y_nents, off_t y_skip,
size_t n_bytes);

+size_t sgl_memset(struct scatterlist *sgl, unsigned int nents, off_t skip,
+ u8 val, size_t n_bytes);
+
/*
* Maximum number of entries that will be allocated in one piece, if
* a list larger than this is required then chaining will be utilized.
diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index d910776a4c96..a704039ab54d 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -985,7 +985,8 @@ EXPORT_SYMBOL(sg_zero_buffer);
* @s_skip: Number of bytes to skip in source before starting
* @n_bytes: The (maximum) number of bytes to copy
*
- * Returns the number of copied bytes.
+ * Returns:
+ * The number of copied bytes.
*
* Notes:
* Destination arguments appear before the source arguments, as with memcpy().
@@ -1058,8 +1059,9 @@ EXPORT_SYMBOL(sgl_copy_sgl);
* @y_skip: Number of bytes to skip in y (right) before starting
* @n_bytes: The (maximum) number of bytes to compare
*
- * Returns true if x and y compare equal before x, y or n_bytes is exhausted.
- * Otherwise on a miscompare, returns false (and stops comparing).
+ * Returns:
+ * true if x and y compare equal before x, y or n_bytes is exhausted.
+ * Otherwise on a miscompare, returns false (and stops comparing).
*
* Notes:
* x and y are symmetrical: they can be swapped and the result is the same.
@@ -1108,3 +1110,49 @@ bool sgl_compare_sgl(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_sk
return equ;
}
EXPORT_SYMBOL(sgl_compare_sgl);
+
+/**
+ * sgl_memset - set byte 'val' up to n_bytes times on SG list
+ * @sgl: The SG list
+ * @nents: Number of SG entries in sgl
+ * @skip: Number of bytes to skip before starting
+ * @val: byte value to write to sgl
+ * @n_bytes: The (maximum) number of bytes to modify
+ *
+ * Returns:
+ * The number of bytes written.
+ *
+ * Notes:
+ * Stops writing if either sgl or n_bytes is exhausted. If n_bytes is
+ * set SIZE_MAX then val will be written to each byte until the end
+ * of sgl.
+ *
+ * The notes in sgl_copy_sgl() about large sgl_s _applies here as well.
+ *
+ **/
+size_t sgl_memset(struct scatterlist *sgl, unsigned int nents, off_t skip,
+ u8 val, size_t n_bytes)
+{
+ size_t offset = 0;
+ size_t len;
+ struct sg_mapping_iter miter;
+
+ if (n_bytes == 0)
+ return 0;
+ sg_miter_start(&miter, sgl, nents, SG_MITER_ATOMIC | SG_MITER_TO_SG);
+ if (!sg_miter_skip(&miter, skip))
+ goto fini;
+
+ while ((offset < n_bytes) && sg_miter_next(&miter)) {
+ len = min(miter.length, n_bytes - offset);
+ memset(miter.addr, val, len);
+ offset += len;
+ miter.consumed = len;
+ sg_miter_stop(&miter);
+ }
+fini:
+ sg_miter_stop(&miter);
+ return offset;
+}
+EXPORT_SYMBOL(sgl_memset);
+
--
2.25.1

2020-10-18 18:31:32

by Douglas Gilbert

[permalink] [raw]
Subject: [PATCH v2 1/4] sgl_alloc_order: remove 4 GiB limit, sgl_free() warning

This patch removes a check done by sgl_alloc_order() before it starts
any allocations. The comment before the removed code says: "Check for
integer overflow" arguably gives a false sense of security. The right
hand side of the expression in the condition is resolved as u32 so
cannot exceed UINT32_MAX (4 GiB) which means 'length' cannot exceed
that amount. If that was the intention then the comment above it
could be dropped and the condition rewritten more clearly as:
if (length > UINT32_MAX) <<failure path >>;

The author's intention is to use sgl_alloc_order() to replace
vmalloc(unsigned long) for a large allocation (debug ramdisk).
vmalloc has no limit at 4 GiB so its seems unreasonable that:
sgl_alloc_order(unsigned long long length, ....)
does. sgl_s made with sgl_alloc_order(chainable=false) have equally
sized segments placed in a scatter gather array. That allows O(1)
navigation around a big sgl using some simple integer maths.

Having previously sent a patch to fix a memory leak in
sg_alloc_order() take the opportunity to put a one line comment above
sgl_free()'s declaration that it is not suitable when order > 0 . The
mis-use of sgl_free() when order > 0 was the reason for the memory
leak. The other users of sgl_alloc_order() in the kernel where
checked and found to handle free-ing properly.

Signed-off-by: Douglas Gilbert <[email protected]>
---
include/linux/scatterlist.h | 1 +
lib/scatterlist.c | 3 ---
2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
index 45cf7b69d852..80178afc2a4a 100644
--- a/include/linux/scatterlist.h
+++ b/include/linux/scatterlist.h
@@ -302,6 +302,7 @@ struct scatterlist *sgl_alloc(unsigned long long length, gfp_t gfp,
unsigned int *nent_p);
void sgl_free_n_order(struct scatterlist *sgl, int nents, int order);
void sgl_free_order(struct scatterlist *sgl, int order);
+/* Only use sgl_free() when order is 0 */
void sgl_free(struct scatterlist *sgl);
#endif /* CONFIG_SGL_ALLOC */

diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index c448642e0f78..d5770e7f1030 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -493,9 +493,6 @@ struct scatterlist *sgl_alloc_order(unsigned long long length,
u32 elem_len;

nent = round_up(length, PAGE_SIZE << order) >> (PAGE_SHIFT + order);
- /* Check for integer overflow */
- if (length > (nent << (PAGE_SHIFT + order)))
- return NULL;
nalloc = nent;
if (chainable) {
/* Check for integer overflow */
--
2.25.1

2020-10-19 11:54:53

by Bodo Stroesser

[permalink] [raw]
Subject: Re: [PATCH v2 4/4] scatterlist: add sgl_memset()

AFAICS, there are 2 unneeded lines in the new implementation
of sgl_memset. Please see details below.


Am 18.10.20 um 19:13 schrieb Douglas Gilbert:
> The existing sg_zero_buffer() function is a bit restrictive.
> For example protection information (PI) blocks are usually
> initialized to 0xff bytes. As its name suggests sgl_memset()
> is modelled on memset(). One difference is the type of the
> val argument which is u8 rather than int. Plus it returns
> the number of bytes (over)written.
>
> Signed-off-by: Douglas Gilbert <[email protected]>
> ---

...

> +
> +/**
> + * sgl_memset - set byte 'val' up to n_bytes times on SG list
> + * @sgl: The SG list
> + * @nents: Number of SG entries in sgl
> + * @skip: Number of bytes to skip before starting
> + * @val: byte value to write to sgl
> + * @n_bytes: The (maximum) number of bytes to modify
> + *
> + * Returns:
> + * The number of bytes written.
> + *
> + * Notes:
> + * Stops writing if either sgl or n_bytes is exhausted. If n_bytes is
> + * set SIZE_MAX then val will be written to each byte until the end
> + * of sgl.
> + *
> + * The notes in sgl_copy_sgl() about large sgl_s _applies here as well.
> + *
> + **/
> +size_t sgl_memset(struct scatterlist *sgl, unsigned int nents, off_t skip,
> + u8 val, size_t n_bytes)
> +{
> + size_t offset = 0;
> + size_t len;
> + struct sg_mapping_iter miter;
> +
> + if (n_bytes == 0)
> + return 0;
> + sg_miter_start(&miter, sgl, nents, SG_MITER_ATOMIC | SG_MITER_TO_SG);
> + if (!sg_miter_skip(&miter, skip))
> + goto fini;
> +
> + while ((offset < n_bytes) && sg_miter_next(&miter)) {
> + len = min(miter.length, n_bytes - offset);
> + memset(miter.addr, val, len);
> + offset += len;
> + miter.consumed = len;

The above line will not change miter.consumed in all loop cycles but the
last, since len will be miter.length for all loop cycles but the last
and sg_miter_next initializes miter.consumed to contain miter.length.
In the last loop cycle it does not harm if miter.consumed stays bigger
than len. So this line is not needed and can be removed.

> + sg_miter_stop(&miter);

Since the code does not use nested sg_miter, the sg_miter_stop() here is
not needed, you can remove that line.

Either the next call to sg_miter_next will call sg_miter_stop before
preparing next chunk of mem, or sg_miter_stop is called behind the loop.

> + }
> +fini:
> + sg_miter_stop(&miter);
> + return offset;
> +}
> +EXPORT_SYMBOL(sgl_memset);
> +
>