btrfs: subpage: make reader lock utilize bitmap
authorQu Wenruo <wqu@suse.com>
Sat, 17 Feb 2024 06:29:49 +0000 (16:59 +1030)
committerDavid Sterba <dsterba@suse.com>
Tue, 5 Mar 2024 16:13:23 +0000 (17:13 +0100)
Currently btrfs_subpage utilizes its atomic member @reader to manage the
reader counter.  However it is only utilized to prevent the page to be
released/unlocked when we still have reads underway.

In that use case, we don't really allow multiple readers on the same
subpage sector.  So here we can introduce a new locked bitmap to
represent exactly which subpage range is locked for read.

In theory we can remove btrfs_subpage::reader as it's just the set bits
of the new locked bitmap.  But unfortunately bitmap doesn't provide such
handy API yet, so we still keep the reader counter.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/subpage.c
fs/btrfs/subpage.h

index 24f8be5..eae9e8a 100644 (file)
@@ -111,6 +111,9 @@ void btrfs_init_subpage_info(struct btrfs_subpage_info *subpage_info, u32 sector
        subpage_info->checked_offset = cur;
        cur += nr_bits;
 
+       subpage_info->locked_offset = cur;
+       cur += nr_bits;
+
        subpage_info->total_nr_bits = cur;
 }
 
@@ -237,28 +240,58 @@ static void btrfs_subpage_assert(const struct btrfs_fs_info *fs_info,
                       start + len <= folio_pos(folio) + PAGE_SIZE);
 }
 
+#define subpage_calc_start_bit(fs_info, folio, name, start, len)       \
+({                                                                     \
+       unsigned int start_bit;                                         \
+                                                                       \
+       btrfs_subpage_assert(fs_info, folio, start, len);               \
+       start_bit = offset_in_page(start) >> fs_info->sectorsize_bits;  \
+       start_bit += fs_info->subpage_info->name##_offset;              \
+       start_bit;                                                      \
+})
+
 void btrfs_subpage_start_reader(const struct btrfs_fs_info *fs_info,
                                struct folio *folio, u64 start, u32 len)
 {
        struct btrfs_subpage *subpage = folio_get_private(folio);
+       const int start_bit = subpage_calc_start_bit(fs_info, folio, locked, start, len);
        const int nbits = len >> fs_info->sectorsize_bits;
+       unsigned long flags;
+
 
        btrfs_subpage_assert(fs_info, folio, start, len);
 
+       spin_lock_irqsave(&subpage->lock, flags);
+       /*
+        * Even though it's just for reading the page, no one should have
+        * locked the subpage range.
+        */
+       ASSERT(bitmap_test_range_all_zero(subpage->bitmaps, start_bit, nbits));
+       bitmap_set(subpage->bitmaps, start_bit, nbits);
        atomic_add(nbits, &subpage->readers);
+       spin_unlock_irqrestore(&subpage->lock, flags);
 }
 
 void btrfs_subpage_end_reader(const struct btrfs_fs_info *fs_info,
                              struct folio *folio, u64 start, u32 len)
 {
        struct btrfs_subpage *subpage = folio_get_private(folio);
+       const int start_bit = subpage_calc_start_bit(fs_info, folio, locked, start, len);
        const int nbits = len >> fs_info->sectorsize_bits;
+       unsigned long flags;
        bool is_data;
        bool last;
 
        btrfs_subpage_assert(fs_info, folio, start, len);
        is_data = is_data_inode(folio->mapping->host);
+
+       spin_lock_irqsave(&subpage->lock, flags);
+
+       /* The range should have already been locked. */
+       ASSERT(bitmap_test_range_all_set(subpage->bitmaps, start_bit, nbits));
        ASSERT(atomic_read(&subpage->readers) >= nbits);
+
+       bitmap_clear(subpage->bitmaps, start_bit, nbits);
        last = atomic_sub_and_test(nbits, &subpage->readers);
 
        /*
@@ -270,6 +303,7 @@ void btrfs_subpage_end_reader(const struct btrfs_fs_info *fs_info,
         */
        if (is_data && last)
                folio_unlock(folio);
+       spin_unlock_irqrestore(&subpage->lock, flags);
 }
 
 static void btrfs_subpage_clamp_range(struct folio *folio, u64 *start, u32 *len)
@@ -365,16 +399,6 @@ void btrfs_folio_end_writer_lock(const struct btrfs_fs_info *fs_info,
                folio_unlock(folio);
 }
 
-#define subpage_calc_start_bit(fs_info, folio, name, start, len)       \
-({                                                                     \
-       unsigned int start_bit;                                         \
-                                                                       \
-       btrfs_subpage_assert(fs_info, folio, start, len);               \
-       start_bit = offset_in_page(start) >> fs_info->sectorsize_bits;  \
-       start_bit += fs_info->subpage_info->name##_offset;              \
-       start_bit;                                                      \
-})
-
 #define subpage_test_bitmap_all_set(fs_info, subpage, name)            \
        bitmap_test_range_all_set(subpage->bitmaps,                     \
                        fs_info->subpage_info->name##_offset,           \
@@ -751,6 +775,7 @@ void __cold btrfs_subpage_dump_bitmap(const struct btrfs_fs_info *fs_info,
        GET_SUBPAGE_BITMAP(subpage, subpage_info, writeback, &writeback_bitmap);
        GET_SUBPAGE_BITMAP(subpage, subpage_info, ordered, &ordered_bitmap);
        GET_SUBPAGE_BITMAP(subpage, subpage_info, checked, &checked_bitmap);
+       GET_SUBPAGE_BITMAP(subpage, subpage_info, locked, &checked_bitmap);
        spin_unlock_irqrestore(&subpage->lock, flags);
 
        dump_page(folio_page(folio, 0), "btrfs subpage dump");
index 97ba2c1..b6dc013 100644 (file)
@@ -33,7 +33,7 @@ struct btrfs_subpage_info {
        unsigned int total_nr_bits;
 
        /*
-        * *_start indicates where the bitmap starts, the length is always
+        * *_offset indicates where the bitmap starts, the length is always
         * @bitmap_size, which is calculated from PAGE_SIZE / sectorsize.
         */
        unsigned int uptodate_offset;
@@ -41,6 +41,16 @@ struct btrfs_subpage_info {
        unsigned int writeback_offset;
        unsigned int ordered_offset;
        unsigned int checked_offset;
+
+       /*
+        * For locked bitmaps, normally it's subpage representation for folio
+        * Locked flag, but metadata is different:
+        *
+        * - Metadata doesn't really lock the folio
+        *   It's just to prevent page::private get cleared before the last
+        *   end_page_read().
+        */
+       unsigned int locked_offset;
 };
 
 /*