[PATCH 3/6] fs: Convert block_read_full_page to be synchronous

From: Matthew Wilcox (Oracle)
Date: Thu Oct 22 2020 - 17:22:40 EST


Use the new blk_completion infrastructure to wait for multiple I/Os.
Also coalesce adjacent buffer heads into a single BIO instead of
submitting one BIO per buffer head. This doesn't work for fscrypt yet,
so keep the old code around for now.

Signed-off-by: Matthew Wilcox (Oracle) <willy@xxxxxxxxxxxxx>
---
fs/buffer.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 90 insertions(+)

diff --git a/fs/buffer.c b/fs/buffer.c
index 1b0ba1d59966..ccb90081117c 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -2249,6 +2249,87 @@ int block_is_partially_uptodate(struct page *page, unsigned long from,
}
EXPORT_SYMBOL(block_is_partially_uptodate);

+static void readpage_end_bio(struct bio *bio)
+{
+ struct bio_vec *bvec;
+ struct page *page;
+ struct buffer_head *bh;
+ int i, nr = 0;
+
+ bio_for_each_bvec_all(bvec, bio, i) {
+ size_t offset = 0;
+ size_t max = bvec->bv_offset + bvec->bv_len;
+
+ page = bvec->bv_page;
+ bh = page_buffers(page);
+
+ for (offset = 0; offset < max; offset += bh->b_size,
+ bh = bh->b_this_page) {
+ if (offset < bvec->bv_offset)
+ continue;
+ BUG_ON(bh_offset(bh) != offset);
+ nr++;
+ if (unlikely(bio_flagged(bio, BIO_QUIET)))
+ set_bit(BH_Quiet, &bh->b_state);
+ if (bio->bi_status == BLK_STS_OK)
+ set_buffer_uptodate(bh);
+ else
+ buffer_io_error(bh, ", async page read");
+ unlock_buffer(bh);
+ }
+ }
+
+ if (blk_completion_sub(bio->bi_private, bio->bi_status, nr) < 0)
+ unlock_page(page);
+ bio_put(bio);
+}
+
+static int readpage_submit_bhs(struct page *page, struct blk_completion *cmpl,
+ unsigned int nr, struct buffer_head **bhs)
+{
+ struct bio *bio = NULL;
+ unsigned int i;
+ int err;
+
+ blk_completion_init(cmpl, nr);
+
+ for (i = 0; i < nr; i++) {
+ struct buffer_head *bh = bhs[i];
+ sector_t sector = bh->b_blocknr * (bh->b_size >> 9);
+ bool same_page;
+
+ if (buffer_uptodate(bh)) {
+ end_buffer_async_read(bh, 1);
+ blk_completion_sub(cmpl, BLK_STS_OK, 1);
+ continue;
+ }
+ if (bio) {
+ if (bio_end_sector(bio) == sector &&
+ __bio_try_merge_page(bio, bh->b_page, bh->b_size,
+ bh_offset(bh), &same_page))
+ continue;
+ submit_bio(bio);
+ }
+ bio = bio_alloc(GFP_NOIO, 1);
+ bio_set_dev(bio, bh->b_bdev);
+ bio->bi_iter.bi_sector = sector;
+ bio_add_page(bio, bh->b_page, bh->b_size, bh_offset(bh));
+ bio->bi_end_io = readpage_end_bio;
+ bio->bi_private = cmpl;
+ /* Take care of bh's that straddle the end of the device */
+ guard_bio_eod(bio);
+ }
+
+ if (bio)
+ submit_bio(bio);
+
+ err = blk_completion_wait_killable(cmpl);
+ if (!err)
+ return AOP_UPDATED_PAGE;
+ unlock_page(page);
+ return err;
+}
+
/*
* Generic "read page" function for block devices that have the normal
* get_block functionality. This is most of the block device filesystems.
@@ -2258,6 +2339,7 @@ EXPORT_SYMBOL(block_is_partially_uptodate);
*/
int block_read_full_page(struct page *page, get_block_t *get_block)
{
+ struct blk_completion *cmpl = kmalloc(sizeof(*cmpl), GFP_NOIO);
struct inode *inode = page->mapping->host;
sector_t iblock, lblock;
struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE];
@@ -2265,6 +2347,9 @@ int block_read_full_page(struct page *page, get_block_t *get_block)
int nr, i, err = 0;
int fully_mapped = 1;

+ if (!cmpl)
+ return -ENOMEM;
+
head = create_page_buffers(page, inode, 0);
blocksize = head->b_size;
bbits = block_size_bits(blocksize);
@@ -2303,6 +2388,7 @@ int block_read_full_page(struct page *page, get_block_t *get_block)
} while (i++, iblock++, (bh = bh->b_this_page) != head);

if (err) {
+ kfree(cmpl);
unlock_page(page);
return err;
}
@@ -2322,6 +2408,10 @@ int block_read_full_page(struct page *page, get_block_t *get_block)
mark_buffer_async_read(bh);
}

+ if (!fscrypt_inode_uses_fs_layer_crypto(inode))
+ return readpage_submit_bhs(page, cmpl, nr, arr);
+ kfree(cmpl);
+
/*
* Stage 3: start the IO. Check for uptodateness
* inside the buffer lock in case another process reading
--
2.28.0