bio_map: avoid splitting abd pages across bios

If we encounter a split page, we add two iovecs to the bio, one for the
fragment of the buffer on each side of the split. In order to do this
safely, we must be sure that we always have room for both fragments.

Its possible for a linear abd to have multiple pages, in which case we
want to add the "left" fragment, then a run of proper 4K pages. then
then "right" fragment. In this way we can keep whole pages together as
much possible.

This change handles both cases by noticing a split page. If we don't
have at least two iovecs remaining in the bio, then we abort outright
(allowing the caller to allocate a new bio and try again). We add the
"left" fragment, and note how big we expect the right fragment to be.
Then we load in as many full pages as are available.

When we reach the last iovec, we close out the bio by taking as uch as
is necessary to restore alignment.

Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
(cherry picked from commit 173cafcc3d8b6c94c61844c705d7a410f412a18e)
This commit is contained in:
Rob Norris 2023-07-21 16:05:41 +10:00 committed by Geoff Amey
parent 98bcc390e8
commit 1bd475d6c6
1 changed files with 30 additions and 3 deletions

View File

@ -1034,16 +1034,43 @@ abd_nr_pages_off(abd_t *abd, unsigned int size, size_t off)
static unsigned int static unsigned int
bio_map(struct bio *bio, void *buf_ptr, unsigned int bio_size) bio_map(struct bio *bio, void *buf_ptr, unsigned int bio_size)
{ {
unsigned int offset, size, i; unsigned int offset, size;
struct page *page; struct page *page;
offset = offset_in_page(buf_ptr); offset = offset_in_page(buf_ptr);
for (i = 0; i < bio->bi_max_vecs; i++) {
size = PAGE_SIZE - offset;
boolean_t is_split = B_FALSE;
unsigned int split_rem = 0;
for (int i = bio->bi_vcnt; i < bio->bi_max_vecs; i++) {
if (bio_size <= 0) if (bio_size <= 0)
break; break;
if (is_split &&
(bio_size <= split_rem || ((i + 1) == bio->bi_max_vecs))) {
/*
* Last segment and we're split, so we have to add just
* enough to restore alignment.
*/
size = split_rem;
is_split = B_FALSE;
}
else if (offset != 0) {
/*
* This is a split page, so we need to ensure that we
* have room for the tail within this bio.
*/
if ((i + 1) == bio->bi_max_vecs)
break;
/* Take up to the end of the page */
size = PAGE_SIZE - offset;
is_split = B_TRUE;
split_rem = offset;
}
else
size = PAGE_SIZE;
if (size > bio_size) if (size > bio_size)
size = bio_size; size = bio_size;