This series extends zoned support for Zoned Namespace (ZNS) SSDs [1].
[1] https://zonedstorage.io/introduction/zns/
This series is available on GitHub at
v1 https://github.com/naota/linux/tree/btrfs-zns-v1
HEAD https://github.com/naota/linux/tree/btrfs-zns
The ZNS specification introduces extra functionalities listed below.
- No conventional zones
- Zone Append write command
- Zone Capacity
- Active Zones
The first two functionalities are already addressed in the current
zoned support on btrfs. We do not rely on conventional zones, and we
use the zone append write command to write data IOs.
This series implements support for the other ones.
While userland tool needs some tweaks (e.g. using capactiy instead of
the length) to be precise, but it still works fine as it is.
* Changes in this version
- Fix btrfs_zone_finish_endio()'s argument on compress writes
- Convert btrfs_zone_finish_endio to void and drop a unnecessary variable
- Do not zone finish if reserved bytes exist
* Zone Capacity Support
A zone capacity is an additional per-zone attribute that indicates the
number of usable logical blocks within each zone, starting from the
first logical block of each zone. It is always smaller or equal to the
zone size.
We can naturally map the capacity to the newly introduced
"zone_capacity" of a block group. Allocations are limited under the
zone capacity instead of the block group's length.
* Active Zones Tracking
The ZNS specification defines a limit on the number of zones that can
be in the implicit open, explicit open or closed conditions. Any zone
with such condition is defined as an active zone and correspond to any
zone that is being written or that has been only partially written. If
the maximum number of active zones is reached, we must either reset or
finish some active zones before being able to chose other zones for
storing data.
In order to not exceed the number of max active zones, we need to
track which zones are active and how the active zones are related to
the block groups. We mark a block group as "active" if the
corresponding device zones are all active. Allocating an extent will
activate a block group, and allocation from an inactive block group is
prohibited. Such active block groups are tracked in a list. Once a
block group is fully written, we deactivate it and remove it from the
list.
* Active Zone Aware Sequential Allocator
Handling the active zones will make the allocator complex. Here is a
summary of how find_free_extent_update_loop() behave.
1. If enough space is available in an active block group
- allocate from it (end, success)
2. If we can activate another zone on a device
2.1 Try to allocate a new block group and activate it
2.2 If the activation succeeds
- allocation will be satisfied from it in the next iteration
2.3 If the activation failed
- Try the next cycle. Some writes may free up an active block group
3. If we cannot activate any zones
3.1 Try to allocate in a small size by checking min_alloc_size
- btrfs_reserve_extent() will halve the allocation size and
restart the loop
3.2 Nothing can be done anymore. Give up. ENOSPC
* Patch series organization
Note: patches 2 and 14 are preparation patches and can be merged
independently.
Patches 1-6 implement zone capacity support.
Patch 7 implements finishing a superblock zone once there is no space
left for new superblock.
Patches 8-13 implement the activation side of the active zone
tracking.
Patches 14 and 15 tweak the allocator to retry with a smaller size if
possible (step 3.1 in the above list)
Patches 16 and 17 implement the deactivation side of the active zone
tracking.
* Changes
- v2
- Fix btrfs_zone_finish_endio()'s argument on compress writes
- Convert btrfs_zone_finish_endio to void and drop a unnecessary variable
- Do not zone finish if reserved bytes exist
Naohiro Aota (17):
btrfs: zoned: load zone capacity information from devices
btrfs: zoned: move btrfs_free_excluded_extents out from
btrfs_calc_zone_unusable
btrfs: zoned: calculate free space from zone capacity
btrfs: zoned: tweak reclaim threshold for zone capacity
btrfs: zoned: consider zone as full when no more SB can be written
btrfs: zoned: locate superblock position using zone capacity
btrfs: zoned: finish superblock zone once no space left for new SB
btrfs: zoned: load active zone information from devices
btrfs: zoned: introduce physical_map to btrfs_block_group
btrfs: zoned: implement active zone tracking
btrfs: zoned: load active zone info for block group
btrfs: zoned: activate block group on allocation
btrfs: zoned: activate new block group
btrfs: move ffe_ctl one level up
btrfs: zoned: avoid chunk allocation if active block group has enough
space
btrfs: zoned: finish fully written block group
btrfs: zoned: finish relocating block group
fs/btrfs/block-group.c | 29 ++-
fs/btrfs/block-group.h | 4 +
fs/btrfs/ctree.h | 3 +
fs/btrfs/disk-io.c | 6 +-
fs/btrfs/extent-tree.c | 204 +++++++++------
fs/btrfs/extent_io.c | 11 +-
fs/btrfs/extent_io.h | 1 +
fs/btrfs/free-space-cache.c | 19 +-
fs/btrfs/inode.c | 6 +-
fs/btrfs/relocation.c | 4 +
fs/btrfs/zoned.c | 501 +++++++++++++++++++++++++++++++++---
fs/btrfs/zoned.h | 36 ++-
12 files changed, 695 insertions(+), 129 deletions(-)
--
2.33.0
The ZNS specification introduces the concept of a Zone Capacity. A zone
capacity is an additional per-zone attribute that indicates the number of
usable logical blocks within each zone, starting from the first logical
block of each zone. It is always smaller or equal to the zone size.
With the SINGLE profile, we can set a block group's "capacity" as the same
as the underlying zone's Zone Capacity. We will limit the allocation not
to exceed in a following commit.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/block-group.h | 1 +
fs/btrfs/zoned.c | 24 +++++++++++++++++++++++-
2 files changed, 24 insertions(+), 1 deletion(-)
@@ -1039,6 +1039,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)inti;unsignedintnofs_flag;u64*alloc_offsets=NULL;+u64*caps=NULL;u64last_alloc=0;u32num_sequential=0,num_conventional=0;
@@ -1069,6 +1070,12 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)return-ENOMEM;}+caps=kcalloc(map->num_stripes,sizeof(*caps),GFP_NOFS);+if(!caps){+ret=-ENOMEM;+gotoout;+}+for(i=0;i<map->num_stripes;i++){boolis_sequential;structblk_zonezone;
@@ -1131,6 +1138,8 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)gotoout;}+caps[i]=zone.capacity<<SECTOR_SHIFT;+switch(zone.cond){caseBLK_ZONE_COND_OFFLINE:caseBLK_ZONE_COND_READONLY:
@@ -1144,7 +1153,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)alloc_offsets[i]=0;break;caseBLK_ZONE_COND_FULL:-alloc_offsets[i]=fs_info->zone_size;+alloc_offsets[i]=caps[i];break;default:/* Partially used zone */
@@ -1169,6 +1178,9 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)*calculate_alloc_pointer()whichtakesextentbuffer*lockstoavoiddeadlock.*/++/* Zone capacity is always zone size in emulation */+cache->zone_capacity=cache->length;if(new){cache->alloc_offset=0;gotoout;
@@ -1195,6 +1207,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)gotoout;}cache->alloc_offset=alloc_offsets[0];+cache->zone_capacity=caps[0];break;caseBTRFS_BLOCK_GROUP_DUP:caseBTRFS_BLOCK_GROUP_RAID1:
@@ -1218,6 +1231,14 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)ret=-EIO;}+if(cache->alloc_offset>cache->zone_capacity){+btrfs_err(fs_info,+"zoned: invalid write pointer %llu (larger than zone capacity %llu) in block group %llu",+cache->alloc_offset,cache->zone_capacity,+cache->start);+ret=-EIO;+}+/* An extent is allocated after the write pointer */if(!ret&&num_conventional&&last_alloc>cache->alloc_offset){btrfs_err(fs_info,
@@ -1229,6 +1250,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)if(!ret)cache->meta_write_pointer=cache->alloc_offset+cache->start;+kfree(caps);kfree(alloc_offsets);free_extent_map(em);
btrfs_free_excluded_extents() is not nccessary for
btrfs_calc_zone_unusable() and it makes btrfs_calc_zone_unusable()
difficult to reuse. Move it out and call btrfs_free_excluded_extents() in
the proper context.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/block-group.c | 5 +++++
fs/btrfs/zoned.c | 3 ---
2 files changed, 5 insertions(+), 3 deletions(-)
@@ -1273,9 +1273,6 @@ void btrfs_calc_zone_unusable(struct btrfs_block_group *cache)cache->cached=BTRFS_CACHE_FINISHED;cache->free_space_ctl->free_space=free;cache->zone_unusable=unusable;--/* Should not have any excluded extents. Just in case, though */-btrfs_free_excluded_extents(cache);}voidbtrfs_redirty_list_add(structbtrfs_transaction*trans,
Now that we introduced capacity in a block group, we need to calculate free
space using the capacity instead of the length. Thus, bytes we account
capacity - alloc_pointer as free, and account bytes [capacity, length] as
zone unusable.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/block-group.c | 6 ++++--
fs/btrfs/extent-tree.c | 3 ++-
fs/btrfs/free-space-cache.c | 8 +++++++-
fs/btrfs/zoned.c | 5 +++--
4 files changed, 16 insertions(+), 6 deletions(-)
@@ -1265,8 +1265,9 @@ void btrfs_calc_zone_unusable(struct btrfs_block_group *cache)return;WARN_ON(cache->bytes_super!=0);-unusable=cache->alloc_offset-cache->used;-free=cache->length-cache->alloc_offset;+unusable=(cache->alloc_offset-cache->used)++(cache->length-cache->zone_capacity);+free=cache->zone_capacity-cache->alloc_offset;/* We only need ->free_space in ALLOC_SEQ block groups */cache->last_byte_to_unpin=(u64)-1;
With the introduction of zone capacity, bytes [capacity, length] are always
zone unusable. Counting this region as a reclaim target will cause
reclaiming too early. Reclaim block groups based on bytes that can be
usable after resetting.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/free-space-cache.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
@@ -2540,6 +2540,7 @@ static int __btrfs_add_free_space_zoned(struct btrfs_block_group *block_group,u64to_free,to_unusable;constintbg_reclaim_threshold=READ_ONCE(fs_info->bg_reclaim_threshold);boolinitial=(size==block_group->length);+u64reclaimable_unusable;WARN_ON(!initial&&offset+size>block_group->zone_capacity);
@@ -2570,12 +2571,15 @@ static int __btrfs_add_free_space_zoned(struct btrfs_block_group *block_group,spin_unlock(&block_group->lock);}+reclaimable_unusable=block_group->zone_unusable-+(block_group->length-block_group->zone_capacity);/* All the region is now unusable. Mark it as unused and reclaim */if(block_group->zone_unusable==block_group->length){btrfs_mark_bg_unused(block_group);}elseif(bg_reclaim_threshold&&-block_group->zone_unusable>=-div_factor_fine(block_group->length,bg_reclaim_threshold)){+reclaimable_unusable>=+div_factor_fine(block_group->zone_capacity,+bg_reclaim_threshold)){btrfs_mark_bg_to_reclaim(block_group);}
We cannot write beyond zone capacity. So, we should consider a zone as
"full" when the write pointer goes beyond capacity - the size of super
info.
Also, take this opportunity to replace a subtle duplicated code with a loop
and fix a typo in comment.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/zoned.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
sb_write_pointer() returns the write position of next superblock. For READ,
we need a previous location. When the pointer is at the head, the previous
one is the last one of the other zone. Calculate the last one's position
from zone capacity.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/zoned.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
@@ -683,9 +683,20 @@ static int sb_log_location(struct block_device *bdev, struct blk_zone *zones,reset->wp=reset->start;}}elseif(ret!=-ENOENT){-/* For READ, we want the precious one */+/*+*ForREAD,wewantthepreviousone.Movewritepointer+*totheendofazone,ifitisattheheadofazone.+*/+u64zone_end=0;+if(wp==zones[0].start<<SECTOR_SHIFT)-wp=(zones[1].start+zones[1].len)<<SECTOR_SHIFT;+zone_end=zones[1].start+zones[1].capacity;+elseif(wp==zones[1].start<<SECTOR_SHIFT)+zone_end=zones[0].start+zones[0].capacity;+if(zone_end)+wp=ALIGN_DOWN(zone_end<<SECTOR_SHIFT,+BTRFS_SUPER_INFO_SIZE);+wp-=BTRFS_SUPER_INFO_SIZE;}
If there is no more space left for a new superblock in a superblock zone,
then it is better to ZONE_FINISH the zone and frees up the active zone
count.
Since btrfs_advance_sb_log() can now issue REQ_OP_ZONE_FINISH, we also need
to convert it to return int for the error case.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/disk-io.c | 4 +++-
fs/btrfs/zoned.c | 53 ++++++++++++++++++++++++++++++++--------------
fs/btrfs/zoned.h | 8 ++++---
3 files changed, 45 insertions(+), 20 deletions(-)
@@ -789,36 +789,57 @@ static inline bool is_sb_log_zone(struct btrfs_zoned_device_info *zinfo,returntrue;}-voidbtrfs_advance_sb_log(structbtrfs_device*device,intmirror)+intbtrfs_advance_sb_log(structbtrfs_device*device,intmirror){structbtrfs_zoned_device_info*zinfo=device->zone_info;structblk_zone*zone;+inti;if(!is_sb_log_zone(zinfo,mirror))-return;+return0;zone=&zinfo->sb_zones[BTRFS_NR_SB_LOG_ZONES*mirror];-if(zone->cond!=BLK_ZONE_COND_FULL){+for(i=0;i<BTRFS_NR_SB_LOG_ZONES;i++){+/* Advance the next zone */+if(zone->cond==BLK_ZONE_COND_FULL){+zone++;+continue;+}+if(zone->cond==BLK_ZONE_COND_EMPTY)zone->cond=BLK_ZONE_COND_IMP_OPEN;-zone->wp+=(BTRFS_SUPER_INFO_SIZE>>SECTOR_SHIFT);+zone->wp+=SUPER_INFO_SECTORS;++if(sb_zone_is_full(zone)){+/*+*Noroomlefttowritenewsuperblock.Since+*superblockiswrittenwithREQ_SYNC,itissafe+*tofinishthezonenow.+*+*Ifthewritepointerisexactlyatthecapacity,+*explicitZONE_FINISHisnotnecessary.+*/+if(zone->wp!=zone->start+zone->capacity){+intret;++ret=blkdev_zone_mgmt(device->bdev,+REQ_OP_ZONE_FINISH,+zone->start,zone->len,+GFP_NOFS);+if(ret)+returnret;+}-if(zone->wp==zone->start+zone->len)+zone->wp=zone->start+zone->len;zone->cond=BLK_ZONE_COND_FULL;--return;+}+return0;}-zone++;-ASSERT(zone->cond!=BLK_ZONE_COND_FULL);-if(zone->cond==BLK_ZONE_COND_EMPTY)-zone->cond=BLK_ZONE_COND_IMP_OPEN;--zone->wp+=(BTRFS_SUPER_INFO_SIZE>>SECTOR_SHIFT);--if(zone->wp==zone->start+zone->len)-zone->cond=BLK_ZONE_COND_FULL;+/* All the zones are FULL. Should not reach here. */+ASSERT(0);+return-EIO;}intbtrfs_reset_sb_log_zones(structblock_device*bdev,intmirror)
@@ -40,7 +40,7 @@ int btrfs_sb_log_location_bdev(struct block_device *bdev, int mirror, int rw,u64*bytenr_ret);intbtrfs_sb_log_location(structbtrfs_device*device,intmirror,intrw,u64*bytenr_ret);-voidbtrfs_advance_sb_log(structbtrfs_device*device,intmirror);+intbtrfs_advance_sb_log(structbtrfs_device*device,intmirror);intbtrfs_reset_sb_log_zones(structblock_device*bdev,intmirror);u64btrfs_find_allocatable_zones(structbtrfs_device*device,u64hole_start,u64hole_end,u64num_bytes);
@@ -113,8 +113,10 @@ static inline int btrfs_sb_log_location(struct btrfs_device *device, int mirror,return0;}-staticinlinevoidbtrfs_advance_sb_log(structbtrfs_device*device,intmirror)-{}+staticinlineintbtrfs_advance_sb_log(structbtrfs_device*device,intmirror)+{+return0;+}staticinlineintbtrfs_reset_sb_log_zones(structblock_device*bdev,intmirror){
The ZNS specification defines a limit on the number of zones that can be in
the implicit open, explicit open or closed conditions. Any zone with such
condition is defined as an active zone and correspond to any zone that is
being written or that has been only partially written. If the maximum
number of active zones is reached, we must either reset or finish some
active zones before being able to chose other zones for storing data.
Load queue_max_active_zones() and track the number of active zones left on
the device.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/zoned.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++-
fs/btrfs/zoned.h | 3 +++
2 files changed, 59 insertions(+), 1 deletion(-)
@@ -38,6 +39,15 @@/* Number of superblock log zones */#define BTRFS_NR_SB_LOG_ZONES 2+/* Number of minimal activate zones we want.+*+*-BTRFS_SUPER_MIRROR_MAXzonesforsuperblockmirrors+*-3zonestoensureatleastonezoneperSYSTEM,METAandDATAblockgroup+*-1zonefortree-logdedicatedblockgroup+*-1zoneforrelocation+*/+#define BTRFS_MIN_ACTIVE_ZONES (BTRFS_SUPER_MIRROR_MAX + 5)+/**Maximumsupportedzonesize.Currently,SMRdiskshaveazonesizeof*256MiB,andweareexpectingZNSdrivestobeinthe1-4GiBrange.Wedonot
@@ -303,6 +313,9 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)structbtrfs_fs_info*fs_info=device->fs_info;structbtrfs_zoned_device_info*zone_info=NULL;structblock_device*bdev=device->bdev;+structrequest_queue*queue=bdev_get_queue(bdev);+unsignedintmax_active_zones;+unsignedintnactive;sector_tnr_sectors;sector_tsector=0;structblk_zone*zones=NULL;
@@ -358,6 +371,17 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)if(!IS_ALIGNED(nr_sectors,zone_sectors))zone_info->nr_zones++;+max_active_zones=queue_max_active_zones(queue);+if(max_active_zones&&max_active_zones<BTRFS_MIN_ACTIVE_ZONES){+btrfs_err_in_rcu(fs_info,+"zoned: %s: max active zones %u is too small. Need at least %u active zones",+rcu_str_deref(device->name),max_active_zones,+BTRFS_MIN_ACTIVE_ZONES);+ret=-EINVAL;+gotoout;+}+zone_info->max_active_zones=max_active_zones;+zone_info->seq_zones=bitmap_zalloc(zone_info->nr_zones,GFP_KERNEL);if(!zone_info->seq_zones){ret=-ENOMEM;
@@ -370,6 +394,12 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)gotoout;}+zone_info->active_zones=bitmap_zalloc(zone_info->nr_zones,GFP_KERNEL);+if(!zone_info->active_zones){+ret=-ENOMEM;+gotoout;+}+zones=kcalloc(BTRFS_REPORT_NR_ZONES,sizeof(structblk_zone),GFP_KERNEL);if(!zones){ret=-ENOMEM;
@@ -377,6 +407,7 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)}/* Get zones type */+nactive=0;while(sector<nr_sectors){nr_zones=BTRFS_REPORT_NR_ZONES;ret=btrfs_get_dev_zones(device,sector<<SECTOR_SHIFT,zones,
@@ -387,8 +418,17 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)for(i=0;i<nr_zones;i++){if(zones[i].type==BLK_ZONE_TYPE_SEQWRITE_REQ)__set_bit(nreported,zone_info->seq_zones);-if(zones[i].cond==BLK_ZONE_COND_EMPTY)+switch(zones[i].cond){+caseBLK_ZONE_COND_EMPTY:__set_bit(nreported,zone_info->empty_zones);+break;+caseBLK_ZONE_COND_IMP_OPEN:+caseBLK_ZONE_COND_EXP_OPEN:+caseBLK_ZONE_COND_CLOSED:+__set_bit(nreported,zone_info->active_zones);+nactive++;+break;+}nreported++;}sector=zones[nr_zones-1].start+zones[nr_zones-1].len;
@@ -403,6 +443,19 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)gotoout;}+if(max_active_zones){+if(nactive>max_active_zones){+btrfs_err_in_rcu(device->fs_info,+"zoned: %d active zones on %s exceeds max_active_zones %d",+nactive,rcu_str_deref(device->name),+max_active_zones);+ret=-EIO;+gotoout;+}+atomic_set(&zone_info->active_zones_left,+max_active_zones-nactive);+}+/* Validate superblock log */nr_zones=BTRFS_NR_SB_LOG_ZONES;for(i=0;i<BTRFS_SUPER_MIRROR_MAX;i++){
@@ -485,6 +538,7 @@ int btrfs_get_dev_zone_info(struct btrfs_device *device)out:kfree(zones);out_free_zone_info:+bitmap_free(zone_info->active_zones);bitmap_free(zone_info->empty_zones);bitmap_free(zone_info->seq_zones);kfree(zone_info);
We will use a block group's physical location to track active zones and
finish fully written zones in the following commits. Since the zone
activation is done in the extent allocation context which already holding
the tree locks, we can't query the chunk tree for the physical locations.
So, copy the location info into a block group and use it for activation.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/block-group.c | 1 +
fs/btrfs/block-group.h | 1 +
fs/btrfs/zoned.c | 17 +++++++++++++++--
3 files changed, 17 insertions(+), 2 deletions(-)
Add zone_is_active flag to btrfs_block_group. This flag indicates the
underlying zones are all active. Such zone active block groups are tracked
by fs_info->active_bg_list.
btrfs_dev_{set,clear}_active_zone() take responsibility for the underlying
device part. They set/clear the bitmap to indicate zone activeness and
count the number of zones we can activate left.
btrfs_zone_{activate,finish}() take responsibility for the logical part and
the list management. In addition, btrfs_zone_finish() wait for any writes
on it and send REQ_OP_ZONE_FINISH to the zone.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/block-group.c | 11 ++
fs/btrfs/block-group.h | 2 +
fs/btrfs/ctree.h | 3 +
fs/btrfs/disk-io.c | 2 +
fs/btrfs/free-space-cache.c | 5 +-
fs/btrfs/zoned.c | 196 ++++++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 12 +++
7 files changed, 229 insertions(+), 2 deletions(-)
@@ -2763,9 +2763,10 @@ void btrfs_dump_free_space(struct btrfs_block_group *block_group,*outthefreespaceaftertheallocationoffset.*/if(btrfs_is_zoned(fs_info)){-btrfs_info(fs_info,"free space %llu",+btrfs_info(fs_info,"free space %llu active %d",block_group->zone_capacity--block_group->alloc_offset);+block_group->alloc_offset,+block_group->zone_is_active);return;}
@@ -989,6 +989,41 @@ u64 btrfs_find_allocatable_zones(struct btrfs_device *device, u64 hole_start,returnpos;}+staticboolbtrfs_dev_set_active_zone(structbtrfs_device*device,u64pos)+{+structbtrfs_zoned_device_info*zone_info=device->zone_info;+unsignedintzno=pos>>zone_info->zone_size_shift;++/* We can use any number of zones */+if(!zone_info->max_active_zones)+returntrue;++if(!test_bit(zno,zone_info->active_zones)){+/* Active zone left? */+if(atomic_dec_if_positive(&zone_info->active_zones_left)<0)+returnfalse;+if(test_and_set_bit(zno,zone_info->active_zones)){+/* Someone already set the bit */+atomic_inc(&zone_info->active_zones_left);+}+}++returntrue;+}++staticvoidbtrfs_dev_clear_active_zone(structbtrfs_device*device,u64pos)+{+structbtrfs_zoned_device_info*zone_info=device->zone_info;+unsignedintzno=pos>>zone_info->zone_size_shift;++/* We can use any number of zones */+if(!zone_info->max_active_zones)+return;++if(test_and_clear_bit(zno,zone_info->active_zones))+atomic_inc(&zone_info->active_zones_left);+}+intbtrfs_reset_device_zone(structbtrfs_device*device,u64physical,u64length,u64*bytes){
@@ -1004,6 +1039,7 @@ int btrfs_reset_device_zone(struct btrfs_device *device, u64 physical,*bytes=length;while(length){btrfs_dev_set_zone_empty(device,physical);+btrfs_dev_clear_active_zone(device,physical);physical+=device->zone_info->zone_size;length-=device->zone_info->zone_size;}
@@ -1657,3 +1693,163 @@ struct btrfs_device *btrfs_zoned_get_device(struct btrfs_fs_info *fs_info,returndevice;}++/**+*btrfs_zone_activate-activateblockgroupandunderlyingdevicezones+*+*@block_group:theblockgrouptoactivate+*+*@return:trueonsuccess,falseotherwise+*/+boolbtrfs_zone_activate(structbtrfs_block_group*block_group)+{+structbtrfs_fs_info*fs_info=block_group->fs_info;+structmap_lookup*map;+structbtrfs_device*device;+u64physical;+boolret;++if(!btrfs_is_zoned(block_group->fs_info))+returntrue;++map=block_group->physical_map;+/* Currently support SINGLE profile only */+ASSERT(map->num_stripes==1);+device=map->stripes[0].dev;+physical=map->stripes[0].physical;++if(!device->zone_info->max_active_zones)+returntrue;++spin_lock(&block_group->lock);++if(block_group->zone_is_active){+ret=true;+gotoout_unlock;+}++/* No space left */+if(block_group->alloc_offset==block_group->zone_capacity){+ret=false;+gotoout_unlock;+}++if(!btrfs_dev_set_active_zone(device,physical)){+/* Cannot activate the zone */+ret=false;+gotoout_unlock;+}++/* Successfully activated all the zones */+block_group->zone_is_active=1;++spin_unlock(&block_group->lock);++/* for the active BG list */+btrfs_get_block_group(block_group);++spin_lock(&fs_info->zone_active_bgs_lock);+ASSERT(list_empty(&block_group->active_bg_list));+list_add_tail(&block_group->active_bg_list,&fs_info->zone_active_bgs);+spin_unlock(&fs_info->zone_active_bgs_lock);++returntrue;++out_unlock:+spin_unlock(&block_group->lock);+returnret;+}++intbtrfs_zone_finish(structbtrfs_block_group*block_group)+{+structbtrfs_fs_info*fs_info=block_group->fs_info;+structmap_lookup*map;+structbtrfs_device*device;+u64physical;+intret=0;++if(!btrfs_is_zoned(fs_info))+return0;++map=block_group->physical_map;+/* Currently support SINGLE profile only */+ASSERT(map->num_stripes==1);++device=map->stripes[0].dev;+physical=map->stripes[0].physical;++if(!device->zone_info->max_active_zones)+return0;++spin_lock(&block_group->lock);+if(!block_group->zone_is_active){+spin_unlock(&block_group->lock);+return0;+}++/* Check if we have unwritten allocated space */+if((block_group->flags&+(BTRFS_BLOCK_GROUP_METADATA|BTRFS_BLOCK_GROUP_SYSTEM))&&+block_group->alloc_offset>block_group->meta_write_pointer){+spin_unlock(&block_group->lock);+return-EAGAIN;+}+spin_unlock(&block_group->lock);++ret=btrfs_inc_block_group_ro(block_group,false);+if(ret)+returnret;++/* Ensure all writes in this block group finish */+btrfs_wait_block_group_reservations(block_group);+/*+*Noneedtowaitnocowwriters.Zonedbtrfsdoesnotallow+*nocowanyway.+*/+btrfs_wait_ordered_roots(fs_info,U64_MAX,block_group->start,+block_group->length);++spin_lock(&block_group->lock);++/*+*Bailoutifsomeonealreadydeactivatedtheblockgroup,or+*allocatedspaceisleftintheblockgroup.+*/+if(!block_group->zone_is_active){+spin_unlock(&block_group->lock);+btrfs_dec_block_group_ro(block_group);+return0;+}++if(block_group->reserved){+spin_unlock(&block_group->lock);+btrfs_dec_block_group_ro(block_group);+return-EAGAIN;+}++block_group->zone_is_active=0;+block_group->alloc_offset=block_group->zone_capacity;+block_group->free_space_ctl->free_space=0;+btrfs_clear_treelog_bg(block_group);+spin_unlock(&block_group->lock);++ret=blkdev_zone_mgmt(device->bdev,REQ_OP_ZONE_FINISH,+physical>>SECTOR_SHIFT,+device->zone_info->zone_size>>SECTOR_SHIFT,+GFP_NOFS);+btrfs_dec_block_group_ro(block_group);++if(!ret){+btrfs_dev_clear_active_zone(device,physical);++spin_lock(&fs_info->zone_active_bgs_lock);+ASSERT(!list_empty(&block_group->active_bg_list));+list_del_init(&block_group->active_bg_list);+spin_unlock(&fs_info->zone_active_bgs_lock);++/* for active_bg_list */+btrfs_put_block_group(block_group);+}++returnret;+}
Activate a block group when trying to allocate an extent from it. We check
read-only case and no space left case before trying to activate a block
group not to consume the number of active zones uselessly.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/extent-tree.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
@@ -3773,6 +3773,18 @@ static int do_allocation_zoned(struct btrfs_block_group *block_group,if(skip)return1;+/* Check RO and no space case before trying to activate it */+spin_lock(&block_group->lock);+if(block_group->ro||+block_group->alloc_offset==block_group->zone_capacity){+spin_unlock(&block_group->lock);+return1;+}+spin_unlock(&block_group->lock);++if(!btrfs_zone_activate(block_group))+return1;+spin_lock(&space_info->lock);spin_lock(&block_group->lock);spin_lock(&fs_info->treelog_bg_lock);
Load activeness of underlying zones of a block group. When underlying zones
are active, we add the block group to the fs_info->zone_active_bgs list.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/zoned.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
@@ -1170,6 +1170,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)unsignedintnofs_flag;u64*alloc_offsets=NULL;u64*caps=NULL;+unsignedlong*active=NULL;u64last_alloc=0;u32num_sequential=0,num_conventional=0;
@@ -1215,6 +1216,12 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)gotoout;}+active=bitmap_zalloc(map->num_stripes,GFP_NOFS);+if(!active){+ret=-ENOMEM;+gotoout;+}+for(i=0;i<map->num_stripes;i++){boolis_sequential;structblk_zonezone;
@@ -1298,8 +1305,16 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)/* Partially used zone */alloc_offsets[i]=((zone.wp-zone.start)<<SECTOR_SHIFT);+__set_bit(i,active);break;}++/*+*Considerazoneasactiveifwecanallowanynumberof+*activezones.+*/+if(!device->zone_info->max_active_zones)+__set_bit(i,active);}if(num_sequential>0)
@@ -1347,6 +1362,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)}cache->alloc_offset=alloc_offsets[0];cache->zone_capacity=caps[0];+cache->zone_is_active=test_bit(0,active);break;caseBTRFS_BLOCK_GROUP_DUP:caseBTRFS_BLOCK_GROUP_RAID1:
@@ -1362,6 +1378,14 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)gotoout;}+if(cache->zone_is_active){+btrfs_get_block_group(cache);+spin_lock(&fs_info->zone_active_bgs_lock);+list_add_tail(&cache->active_bg_list,+&fs_info->zone_active_bgs);+spin_unlock(&fs_info->zone_active_bgs_lock);+}+out:if(cache->alloc_offset>fs_info->zone_size){btrfs_err(fs_info,
@@ -1393,6 +1417,7 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new)kfree(cache->physical_map);cache->physical_map=NULL;}+bitmap_free(active);kfree(caps);kfree(alloc_offsets);free_extent_map(em);
Activate new block group at btrfs_make_block_group(). We do not check the
return value. If failed, we can try again later at the actual extent
allocation phase.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/block-group.c | 6 ++++++
1 file changed, 6 insertions(+)
@@ -2447,6 +2447,12 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tranreturnERR_PTR(ret);}+/*+*Newblockgroupislikelytobeusedsoon.Trytoactivateitnow.+*FailureisOKfornow.+*/+btrfs_zone_activate(cache);+ret=exclude_super_stripes(cache);if(ret){/* We may have excluded something, so call this just in case */
We are passing too many variables as it is from btrfs_reserve_extent() to
find_free_extent(). The next commit will add min_alloc_size to ffe_ctl, and
that means another pass-through argument. Take this opportunity to move
ffe_ctl one level up and drop the redundant arguments.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/extent-tree.c | 162 ++++++++++++++++++++++-------------------
1 file changed, 88 insertions(+), 74 deletions(-)
@@ -4130,65 +4131,62 @@ static int prepare_allocation(struct btrfs_fs_info *fs_info,*|-Ifnotfound,re-iterateallblockgroups*/staticnoinlineintfind_free_extent(structbtrfs_root*root,-u64ram_bytes,u64num_bytes,u64empty_size,-u64hint_byte_orig,structbtrfs_key*ins,-u64flags,intdelalloc)+structbtrfs_key*ins,+structfind_free_extent_ctl*ffe_ctl){structbtrfs_fs_info*fs_info=root->fs_info;intret=0;intcache_block_group_error=0;structbtrfs_block_group*block_group=NULL;-structfind_free_extent_ctlffe_ctl={0};structbtrfs_space_info*space_info;boolfull_search=false;-boolfor_treelog=(root->root_key.objectid==BTRFS_TREE_LOG_OBJECTID);-WARN_ON(num_bytes<fs_info->sectorsize);--ffe_ctl.num_bytes=num_bytes;-ffe_ctl.empty_size=empty_size;-ffe_ctl.flags=flags;-ffe_ctl.search_start=0;-ffe_ctl.delalloc=delalloc;-ffe_ctl.index=btrfs_bg_flags_to_raid_index(flags);-ffe_ctl.have_caching_bg=false;-ffe_ctl.orig_have_caching_bg=false;-ffe_ctl.found_offset=0;-ffe_ctl.hint_byte=hint_byte_orig;-ffe_ctl.for_treelog=for_treelog;-ffe_ctl.policy=BTRFS_EXTENT_ALLOC_CLUSTERED;+WARN_ON(ffe_ctl->num_bytes<fs_info->sectorsize);+ffe_ctl->search_start=0;+/* For clustered allocation */+ffe_ctl->empty_cluster=0;+ffe_ctl->last_ptr=NULL;+ffe_ctl->use_cluster=true;+ffe_ctl->have_caching_bg=false;+ffe_ctl->orig_have_caching_bg=false;+ffe_ctl->index=btrfs_bg_flags_to_raid_index(ffe_ctl->flags);+ffe_ctl->loop=0;/* For clustered allocation */-ffe_ctl.retry_clustered=false;-ffe_ctl.retry_unclustered=false;-ffe_ctl.last_ptr=NULL;-ffe_ctl.use_cluster=true;+ffe_ctl->retry_clustered=false;+ffe_ctl->retry_unclustered=false;+ffe_ctl->cached=0;+ffe_ctl->max_extent_size=0;+ffe_ctl->total_free_space=0;+ffe_ctl->found_offset=0;+ffe_ctl->policy=BTRFS_EXTENT_ALLOC_CLUSTERED;if(btrfs_is_zoned(fs_info))-ffe_ctl.policy=BTRFS_EXTENT_ALLOC_ZONED;+ffe_ctl->policy=BTRFS_EXTENT_ALLOC_ZONED;ins->type=BTRFS_EXTENT_ITEM_KEY;ins->objectid=0;ins->offset=0;-trace_find_free_extent(root,num_bytes,empty_size,flags);+trace_find_free_extent(root,ffe_ctl->num_bytes,ffe_ctl->empty_size,+ffe_ctl->flags);-space_info=btrfs_find_space_info(fs_info,flags);+space_info=btrfs_find_space_info(fs_info,ffe_ctl->flags);if(!space_info){-btrfs_err(fs_info,"No space info for %llu",flags);+btrfs_err(fs_info,"No space info for %llu",ffe_ctl->flags);return-ENOSPC;}-ret=prepare_allocation(fs_info,&ffe_ctl,space_info,ins);+ret=prepare_allocation(fs_info,ffe_ctl,space_info,ins);if(ret<0)returnret;-ffe_ctl.search_start=max(ffe_ctl.search_start,-first_logical_byte(fs_info,0));-ffe_ctl.search_start=max(ffe_ctl.search_start,ffe_ctl.hint_byte);-if(ffe_ctl.search_start==ffe_ctl.hint_byte){+ffe_ctl->search_start=max(ffe_ctl->search_start,+first_logical_byte(fs_info,0));+ffe_ctl->search_start=max(ffe_ctl->search_start,ffe_ctl->hint_byte);+if(ffe_ctl->search_start==ffe_ctl->hint_byte){block_group=btrfs_lookup_block_group(fs_info,-ffe_ctl.search_start);+ffe_ctl->search_start);/**wedon'twanttousetheblockgroupifitdoesn'tmatchour*allocationbits,orifitsnotcached.
@@ -4196,7 +4194,7 @@ static noinline int find_free_extent(struct btrfs_root *root,*Howeverifwearere-searchingwithanidealblockgroup*pickedoutthenwedon'tcarethattheblockgroupiscached.*/-if(block_group&&block_group_bits(block_group,flags)&&+if(block_group&&block_group_bits(block_group,ffe_ctl->flags)&&block_group->cached!=BTRFS_CACHE_NO){down_read(&space_info->groups_sem);if(list_empty(&block_group->list)||
@@ -4210,9 +4208,10 @@ static noinline int find_free_extent(struct btrfs_root *root,btrfs_put_block_group(block_group);up_read(&space_info->groups_sem);}else{-ffe_ctl.index=btrfs_bg_flags_to_raid_index(+ffe_ctl->index=btrfs_bg_flags_to_raid_index(block_group->flags);-btrfs_lock_block_group(block_group,delalloc);+btrfs_lock_block_group(block_group,+ffe_ctl->delalloc);gotohave_block_group;}}elseif(block_group){
@@ -4220,31 +4219,31 @@ static noinline int find_free_extent(struct btrfs_root *root,}}search:-ffe_ctl.have_caching_bg=false;-if(ffe_ctl.index==btrfs_bg_flags_to_raid_index(flags)||-ffe_ctl.index==0)+ffe_ctl->have_caching_bg=false;+if(ffe_ctl->index==btrfs_bg_flags_to_raid_index(ffe_ctl->flags)||+ffe_ctl->index==0)full_search=true;down_read(&space_info->groups_sem);list_for_each_entry(block_group,-&space_info->block_groups[ffe_ctl.index],list){+&space_info->block_groups[ffe_ctl->index],list){structbtrfs_block_group*bg_ret;/* If the block group is read-only, we can skip it entirely. */if(unlikely(block_group->ro)){-if(for_treelog)+if(ffe_ctl->for_treelog)btrfs_clear_treelog_bg(block_group);continue;}-btrfs_grab_block_group(block_group,delalloc);-ffe_ctl.search_start=block_group->start;+btrfs_grab_block_group(block_group,ffe_ctl->delalloc);+ffe_ctl->search_start=block_group->start;/**thiscanhappenifweendupcyclingthroughallthe*raidtypes,butwewanttomakesureweonlyallocate*forthepropertype.*/-if(!block_group_bits(block_group,flags)){+if(!block_group_bits(block_group,ffe_ctl->flags)){u64extra=BTRFS_BLOCK_GROUP_DUP|BTRFS_BLOCK_GROUP_RAID1_MASK|BTRFS_BLOCK_GROUP_RAID56_MASK|
@@ -4255,7 +4254,8 @@ static noinline int find_free_extent(struct btrfs_root *root,*doesn'tprovidethem,bail.Thisdoesallowusto*fillraid0fromraid1.*/-if((flags&extra)&&!(block_group->flags&extra))+if((ffe_ctl->flags&extra)&&+!(block_group->flags&extra))gotoloop;/*
@@ -4263,14 +4263,15 @@ static noinline int find_free_extent(struct btrfs_root *root,*It'spossiblethatwehaveMIXED_GROUPflagbutno*blockgroupismixed.Justskipsuchblockgroup.*/-btrfs_release_block_group(block_group,delalloc);+btrfs_release_block_group(block_group,+ffe_ctl->delalloc);continue;}have_block_group:-ffe_ctl.cached=btrfs_block_group_done(block_group);-if(unlikely(!ffe_ctl.cached)){-ffe_ctl.have_caching_bg=true;+ffe_ctl->cached=btrfs_block_group_done(block_group);+if(unlikely(!ffe_ctl->cached)){+ffe_ctl->have_caching_bg=true;ret=btrfs_cache_block_group(block_group,0);/*
@@ -4293,10 +4294,11 @@ static noinline int find_free_extent(struct btrfs_root *root,gotoloop;bg_ret=NULL;-ret=do_allocation(block_group,&ffe_ctl,&bg_ret);+ret=do_allocation(block_group,ffe_ctl,&bg_ret);if(ret==0){if(bg_ret&&bg_ret!=block_group){-btrfs_release_block_group(block_group,delalloc);+btrfs_release_block_group(block_group,+ffe_ctl->delalloc);block_group=bg_ret;}}elseif(ret==-EAGAIN){
@@ -4306,46 +4308,49 @@ static noinline int find_free_extent(struct btrfs_root *root,}/* Checks */-ffe_ctl.search_start=round_up(ffe_ctl.found_offset,-fs_info->stripesize);+ffe_ctl->search_start=round_up(ffe_ctl->found_offset,+fs_info->stripesize);/* move on to the next group */-if(ffe_ctl.search_start+num_bytes>+if(ffe_ctl->search_start+ffe_ctl->num_bytes>block_group->start+block_group->length){btrfs_add_free_space_unused(block_group,-ffe_ctl.found_offset,num_bytes);+ffe_ctl->found_offset,+ffe_ctl->num_bytes);gotoloop;}-if(ffe_ctl.found_offset<ffe_ctl.search_start)+if(ffe_ctl->found_offset<ffe_ctl->search_start)btrfs_add_free_space_unused(block_group,-ffe_ctl.found_offset,-ffe_ctl.search_start-ffe_ctl.found_offset);+ffe_ctl->found_offset,+ffe_ctl->search_start-ffe_ctl->found_offset);-ret=btrfs_add_reserved_bytes(block_group,ram_bytes,-num_bytes,delalloc);+ret=btrfs_add_reserved_bytes(block_group,ffe_ctl->ram_bytes,+ffe_ctl->num_bytes,+ffe_ctl->delalloc);if(ret==-EAGAIN){btrfs_add_free_space_unused(block_group,-ffe_ctl.found_offset,num_bytes);+ffe_ctl->found_offset,+ffe_ctl->num_bytes);gotoloop;}btrfs_inc_block_group_reservations(block_group);/* we are all good, lets return */-ins->objectid=ffe_ctl.search_start;-ins->offset=num_bytes;+ins->objectid=ffe_ctl->search_start;+ins->offset=ffe_ctl->num_bytes;-trace_btrfs_reserve_extent(block_group,ffe_ctl.search_start,-num_bytes);-btrfs_release_block_group(block_group,delalloc);+trace_btrfs_reserve_extent(block_group,ffe_ctl->search_start,+ffe_ctl->num_bytes);+btrfs_release_block_group(block_group,ffe_ctl->delalloc);break;loop:-release_block_group(block_group,&ffe_ctl,delalloc);+release_block_group(block_group,ffe_ctl,ffe_ctl->delalloc);cond_resched();}up_read(&space_info->groups_sem);-ret=find_free_extent_update_loop(fs_info,ins,&ffe_ctl,full_search);+ret=find_free_extent_update_loop(fs_info,ins,ffe_ctl,full_search);if(ret>0)gotosearch;
@@ -4354,12 +4359,12 @@ static noinline int find_free_extent(struct btrfs_root *root,*Useffe_ctl->total_free_spaceasfallbackifwecan'tfind*anycontiguoushole.*/-if(!ffe_ctl.max_extent_size)-ffe_ctl.max_extent_size=ffe_ctl.total_free_space;+if(!ffe_ctl->max_extent_size)+ffe_ctl->max_extent_size=ffe_ctl->total_free_space;spin_lock(&space_info->lock);-space_info->max_extent_size=ffe_ctl.max_extent_size;+space_info->max_extent_size=ffe_ctl->max_extent_size;spin_unlock(&space_info->lock);-ins->offset=ffe_ctl.max_extent_size;+ins->offset=ffe_ctl->max_extent_size;}elseif(ret==-ENOSPC){ret=cache_block_group_error;}
@@ -4417,6 +4422,7 @@ int btrfs_reserve_extent(struct btrfs_root *root, u64 ram_bytes,structbtrfs_key*ins,intis_data,intdelalloc){structbtrfs_fs_info*fs_info=root->fs_info;+structfind_free_extent_ctlffe_ctl={0};boolfinal_tried=num_bytes==min_alloc_size;u64flags;intret;
@@ -4425,8 +4431,16 @@ int btrfs_reserve_extent(struct btrfs_root *root, u64 ram_bytes,flags=get_alloc_profile_by_root(root,is_data);again:WARN_ON(num_bytes<fs_info->sectorsize);-ret=find_free_extent(root,ram_bytes,num_bytes,empty_size,-hint_byte,ins,flags,delalloc);++ffe_ctl.ram_bytes=ram_bytes;+ffe_ctl.num_bytes=num_bytes;+ffe_ctl.empty_size=empty_size;+ffe_ctl.flags=flags;+ffe_ctl.delalloc=delalloc;+ffe_ctl.hint_byte=hint_byte;+ffe_ctl.for_treelog=for_treelog;++ret=find_free_extent(root,ins,&ffe_ctl);if(!ret&&!is_data){btrfs_dec_block_group_reservations(fs_info,ins->objectid);}elseif(ret==-ENOSPC){
The current extent allocator tries to allocate a new block group when the
existing block groups do not have enough space. On a ZNS device, a new
block group means a new active zone. If the number of active zones has
already reached the max_active_zones, activating a new zone needs to finish
an existing zone, leading to wasting the free space there.
So, instead, it should reuse the existing active block groups as much as
possible when we can't activate any other zones without sacrificing an
already activated block group.
While at it, I converted find_free_extent_update_loop() to check the
found_extent() case early and made the other conditions simpler.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/extent-tree.c | 27 ++++++++++++++++++++-------
fs/btrfs/zoned.c | 32 ++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 8 ++++++++
3 files changed, 60 insertions(+), 7 deletions(-)
@@ -1878,3 +1878,35 @@ int btrfs_zone_finish(struct btrfs_block_group *block_group)returnret;}++boolbtrfs_can_activate_zone(structbtrfs_fs_devices*fs_devices,+intraid_index)+{+structbtrfs_device*device;+boolret=false;++if(!btrfs_is_zoned(fs_devices->fs_info))+returntrue;++/* Non-single profiles are not supported yet */+if(raid_index!=BTRFS_RAID_SINGLE)+returnfalse;++/* Check if there is a device with active zones left */+mutex_lock(&fs_devices->device_list_mutex);+list_for_each_entry(device,&fs_devices->devices,dev_list){+structbtrfs_zoned_device_info*zinfo=device->zone_info;++if(!device->bdev)+continue;++if(!zinfo->max_active_zones||+atomic_read(&zinfo->active_zones_left)){+ret=true;+break;+}+}+mutex_unlock(&fs_devices->device_list_mutex);++returnret;+}
If we have written to the zone capacity, the device automatically
deactivates the zone. Sync up block group side (the active BG list and
zone_is_active flag) with it.
We need to do it both on data BGs and metadata BGs. On data side, we add a
hook to btrfs_finish_ordered_io(). On metadata side, we use
end_extent_buffer_writeback().
To reduce excess lookup of a block group, we mark the last extent buffer in
a block group with EXTENT_BUFFER_ZONE_FINISH flag. This cannot be done for
data (ordered_extent), because the address may change due to
REQ_OP_ZONE_APPEND.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/extent_io.c | 11 +++++++++-
fs/btrfs/extent_io.h | 1 +
fs/btrfs/inode.c | 6 +++++-
fs/btrfs/zoned.c | 51 ++++++++++++++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 5 +++++
5 files changed, 72 insertions(+), 2 deletions(-)
@@ -4756,8 +4759,14 @@ static int submit_eb_page(struct page *page, struct writeback_control *wbc,free_extent_buffer(eb);returnret;}-if(cache)+if(cache){+/* Impiles write in zoned btrfs*/btrfs_put_block_group(cache);+/* Mark the last eb in a block group */+if(cache->seq_zone&&+eb->start+eb->len==cache->zone_capacity)+set_bit(EXTENT_BUFFER_ZONE_FINISH,&eb->bflags);+}ret=write_one_eb(eb,wbc,epd);free_extent_buffer(eb);if(ret<0)
@@ -3010,8 +3010,12 @@ static int btrfs_finish_ordered_io(struct btrfs_ordered_extent *ordered_extent)gotoout;}-if(ordered_extent->bdev)+/* Non-null bdev implies a write on a sequential zone */+if(ordered_extent->bdev){btrfs_rewrite_logical_zoned(ordered_extent);+btrfs_zone_finish_endio(fs_info,ordered_extent->disk_bytenr,+ordered_extent->disk_num_bytes);+}btrfs_free_io_failure_record(inode,start,end);
@@ -1910,3 +1910,54 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices,returnret;}++voidbtrfs_zone_finish_endio(structbtrfs_fs_info*fs_info,u64logical,+u64length)+{+structbtrfs_block_group*block_group;+structmap_lookup*map;+structbtrfs_device*device;+u64physical;++if(!btrfs_is_zoned(fs_info))+return;++block_group=btrfs_lookup_block_group(fs_info,logical);+ASSERT(block_group);++if(logical+length<block_group->start+block_group->zone_capacity)+gotoout;++spin_lock(&block_group->lock);++if(!block_group->zone_is_active){+spin_unlock(&block_group->lock);+gotoout;+}++block_group->zone_is_active=0;+/* We should have consumed all the free space */+ASSERT(block_group->alloc_offset==block_group->zone_capacity);+ASSERT(block_group->free_space_ctl->free_space==0);+btrfs_clear_treelog_bg(block_group);+spin_unlock(&block_group->lock);++map=block_group->physical_map;+device=map->stripes[0].dev;+physical=map->stripes[0].physical;++if(!device->zone_info->max_active_zones)+gotoout;++btrfs_dev_clear_active_zone(device,physical);++spin_lock(&fs_info->zone_active_bgs_lock);+ASSERT(!list_empty(&block_group->active_bg_list));+list_del_init(&block_group->active_bg_list);+spin_unlock(&fs_info->zone_active_bgs_lock);++btrfs_put_block_group(block_group);++out:+btrfs_put_block_group(block_group);+}
We will no longer write to a relocating block group. So, we can finish it
now.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
fs/btrfs/relocation.c | 4 ++++
1 file changed, 4 insertions(+)
From: Johannes Thumshirn <hidden> Date: 2021-08-24 07:52:11
On 19/08/2021 14:27, Naohiro Aota wrote:
The ZNS specification introduces the concept of a Zone Capacity. A zone
capacity is an additional per-zone attribute that indicates the number of
usable logical blocks within each zone, starting from the first logical
block of each zone. It is always smaller or equal to the zone size.
With the SINGLE profile, we can set a block group's "capacity" as the same
as the underlying zone's Zone Capacity. We will limit the allocation not
to exceed in a following commit.
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
---
Looks good,
Reviewed-by: Johannes Thumshirn <redacted>
From: David Sterba <hidden> Date: 2021-08-27 16:28:28
On Thu, Aug 19, 2021 at 09:19:07PM +0900, Naohiro Aota wrote:
This series extends zoned support for Zoned Namespace (ZNS) SSDs [1].
[1] https://zonedstorage.io/introduction/zns/
This series is available on GitHub at
v1 https://github.com/naota/linux/tree/btrfs-zns-v1
HEAD https://github.com/naota/linux/tree/btrfs-zns
The ZNS specification introduces extra functionalities listed below.
- No conventional zones
- Zone Append write command
- Zone Capacity
- Active Zones
Naohiro Aota (17):
btrfs: zoned: load zone capacity information from devices
btrfs: zoned: move btrfs_free_excluded_extents out from
btrfs_calc_zone_unusable
btrfs: zoned: calculate free space from zone capacity
btrfs: zoned: tweak reclaim threshold for zone capacity
btrfs: zoned: consider zone as full when no more SB can be written
btrfs: zoned: locate superblock position using zone capacity
btrfs: zoned: finish superblock zone once no space left for new SB
btrfs: zoned: load active zone information from devices
btrfs: zoned: introduce physical_map to btrfs_block_group
btrfs: zoned: implement active zone tracking
btrfs: zoned: load active zone info for block group
btrfs: zoned: activate block group on allocation
btrfs: zoned: activate new block group
btrfs: move ffe_ctl one level up
btrfs: zoned: avoid chunk allocation if active block group has enough
space
btrfs: zoned: finish fully written block group
btrfs: zoned: finish relocating block group
This is contained in the zoned mode and I don't see much reason to hold
it back, so it's in misc-next now. I've fixed some minor style issues.
In case there are small fixups worth folding please let me know,
otherwise please send separate patches. Thanks.