From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:28:30
This series introduces a proposal to implementing atomic writes in the
kernel for torn-write protection.
This series takes the approach of adding a new "atomic" flag to each of
pwritev2() and iocb->ki_flags - RWF_ATOMIC and IOCB_ATOMIC, respectively.
When set, these indicate that we want the write issued "atomically".
Only direct IO is supported and for block devices and XFS.
The atomic writes feature requires dedicated HW support, like
SCSI WRITE_ATOMIC_16 command.
man pages update has been posted at:
https://lore.kernel.org/linux-api/20230929093717.2972367-1-john.g.garry@oracle.com/T/#t
The goal here is to provide an interface that allow applications use
application-specific block sizes larger than logical block size
reported by the storage device or larger than filesystem block size as
reported by stat().
With this new interface, application blocks will never be torn or
fractured when written. For a power fail, for each individual application
block, all or none of the data to be written. A racing atomic write and
read will mean that the read sees all the old data or all the new data,
but never a mix of old and new.
Two new fields are added to struct statx - atomic_write_unit_min and
atomic_write_unit_max. For each atomic individual write, the total length
of a write must be a between atomic_write_unit_min and
atomic_write_unit_max, inclusive, and a power-of-2. The write must also be
at a natural offset in the file wrt the write length.
For XFS, we must ensure extent alignment with the userspace block size.
XFS supports an extent size hint. However, it must be ensured that the
hint is honoured. For this, a new flag is added - forcealign - to
instruct the XFS block allocator to always honour the extent size hint.
The user would typically set the extent size hint at the userspace
block size to support atomic writes.
The atomic_write_unit_{min, max} values from statx on an XFS file will
consider both the backing bdev atomic_write_unit_{min, max} values and
the extent alignment for the file.
SCSI sd.c and scsi_debug and NVMe kernel support is added.
xfsprogs update for forcealign is at:
https://lore.kernel.org/linux-xfs/20230929095342.2976587-1-john.g.garry@oracle.com/T/#t
This series is based on v6.6-rc3.
Major changes since RFC (https://lore.kernel.org/linux-scsi/20230503183821.1473305-1-john.g.garry@oracle.com/):
- Add XFS forcealign feature
- Only allow writing a single userspace block
Alan Adamson (1):
nvme: Support atomic writes
Darrick J. Wong (3):
fs: xfs: Introduce FORCEALIGN inode flag
fs: xfs: Make file data allocations observe the 'forcealign' flag
fs: xfs: Enable file data forcealign feature
Himanshu Madhani (2):
block: Add atomic write operations to request_queue limits
block: Add REQ_ATOMIC flag
John Garry (13):
block: Limit atomic writes according to bio and queue limits
block: Pass blk_queue_get_max_sectors() a request pointer
block: Limit atomic write IO size according to
atomic_write_max_sectors
block: Error an attempt to split an atomic write bio
block: Add checks to merging of atomic writes
block: Add fops atomic write support
fs: xfs: Don't use low-space allocator for alignment > 1
fs: xfs: Support atomic write for statx
fs: iomap: Atomic write support
fs: xfs: iomap atomic write support
scsi: sd: Support reading atomic properties from block limits VPD
scsi: sd: Add WRITE_ATOMIC_16 support
scsi: scsi_debug: Atomic write support
Prasad Singamsetty (2):
fs/bdev: Add atomic write support info to statx
fs: Add RWF_ATOMIC and IOCB_ATOMIC flags for atomic write support
Documentation/ABI/stable/sysfs-block | 42 ++
block/bdev.c | 33 +-
block/blk-merge.c | 92 ++++-
block/blk-mq.c | 2 +-
block/blk-settings.c | 76 ++++
block/blk-sysfs.c | 33 ++
block/blk.h | 9 +-
block/fops.c | 42 +-
drivers/nvme/host/core.c | 29 ++
drivers/scsi/scsi_debug.c | 587 +++++++++++++++++++++------
drivers/scsi/scsi_trace.c | 22 +
drivers/scsi/sd.c | 57 ++-
drivers/scsi/sd.h | 7 +
fs/iomap/direct-io.c | 26 +-
fs/iomap/trace.h | 3 +-
fs/stat.c | 15 +-
fs/xfs/libxfs/xfs_bmap.c | 26 +-
fs/xfs/libxfs/xfs_format.h | 9 +-
fs/xfs/libxfs/xfs_inode_buf.c | 40 ++
fs/xfs/libxfs/xfs_inode_buf.h | 3 +
fs/xfs/libxfs/xfs_sb.c | 3 +
fs/xfs/xfs_inode.c | 12 +
fs/xfs/xfs_inode.h | 5 +
fs/xfs/xfs_ioctl.c | 18 +
fs/xfs/xfs_iomap.c | 40 +-
fs/xfs/xfs_iops.c | 51 +++
fs/xfs/xfs_iops.h | 4 +
fs/xfs/xfs_mount.h | 2 +
fs/xfs/xfs_super.c | 4 +
include/linux/blk_types.h | 2 +
include/linux/blkdev.h | 37 +-
include/linux/fs.h | 1 +
include/linux/iomap.h | 1 +
include/linux/stat.h | 2 +
include/scsi/scsi_proto.h | 1 +
include/trace/events/scsi.h | 1 +
include/uapi/linux/fs.h | 7 +-
include/uapi/linux/stat.h | 7 +-
38 files changed, 1179 insertions(+), 172 deletions(-)
--
2.31.1
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:28:28
We rely the block layer always being able to send a bio of size
atomic_write_unit_max without being required to split it due to request
queue or other bio limits.
A bio may contain min(BIO_MAX_VECS, limits->max_segments) vectors,
and each vector is at worst case the device logical block size from
direct IO alignment requirement.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/blk-settings.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
@@ -213,6 +213,18 @@ void blk_queue_atomic_write_boundary_bytes(struct request_queue *q,}EXPORT_SYMBOL(blk_queue_atomic_write_boundary_bytes);+staticunsignedintblk_queue_max_guaranteed_bio_size_sectors(+structrequest_queue*q)+{+structqueue_limits*limits=&q->limits;+unsignedintmax_segments=min_t(unsignedint,BIO_MAX_VECS,+limits->max_segments);+/* Limit according to dev sector size as we only support direct-io */+unsignedintlimit=max_segments*queue_logical_block_size(q);++returnrounddown_pow_of_two(limit>>SECTOR_SHIFT);+}+/***blk_queue_atomic_write_unit_min_sectors-smallestunitthatcanbewritten*atomicallytothedevice.
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:28:48
Currently an IO size is limited to the request_queue limits max_sectors.
Limit the size for an atomic write to queue limit atomic_write_max_sectors
value.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
note: atomic_write_max_sectors should prob be limited to max_sectors,
which it isn't
block/blk-merge.c | 12 +++++++++++-
block/blk.h | 3 +++
2 files changed, 14 insertions(+), 1 deletion(-)
@@ -21,6 +21,48 @@ Description: device is offset from the internal allocation unit's natural alignment.+What: /sys/block/<disk>/atomic_write_max_bytes+Date: May 2023+Contact: Himanshu Madhani <himanshu.madhani@oracle.com>+Description:+ [RO] This parameter specifies the maximum atomic write+ size reported by the device. An atomic write operation+ must not exceed this number of bytes.+++What: /sys/block/<disk>/atomic_write_unit_min_bytes+Date: May 2023+Contact: Himanshu Madhani <himanshu.madhani@oracle.com>+Description:+ [RO] This parameter specifies the smallest block which can+ be written atomically with an atomic write operation. All+ atomic write operations must begin at a+ atomic_write_unit_min boundary and must be multiples of+ atomic_write_unit_min. This value must be a power-of-two.+++What: /sys/block/<disk>/atomic_write_unit_max_bytes+Date: January 2023+Contact: Himanshu Madhani <himanshu.madhani@oracle.com>+Description:+ [RO] This parameter defines the largest block which can be+ written atomically with an atomic write operation. This+ value must be a multiple of atomic_write_unit_min and must+ be a power-of-two.+++What: /sys/block/<disk>/atomic_write_boundary_bytes+Date: May 2023+Contact: Himanshu Madhani <himanshu.madhani@oracle.com>+Description:+ [RO] A device may need to internally split I/Os which+ straddle a given logical block address boundary. In that+ case a single atomic write operation will be processed as+ one of more sub-operations which each complete atomically.+ This parameter specifies the size in bytes of the atomic+ boundary if one is reported by the device. This value must+ be a power-of-two.+ What: /sys/block/<disk>/diskseq Date: February 2021
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:29:08
From: Prasad Singamsetty <redacted>
Userspace may add flag RWF_ATOMIC to pwritev2() to indicate that the
write is to be issued with torn write prevention, according to special
alignment and length rules.
Torn write prevention means that for a power or any other HW failure, all
or none of the data will be committed to storage, but never a mix of old
and new.
For any syscall interface utilizing struct iocb, add IOCB_ATOMIC for
iocb->ki_flags field to indicate the same.
A call to statx will give the relevant atomic write info:
- atomic_write_unit_min
- atomic_write_unit_max
Both values are a power-of-2.
Applications can avail of atomic write feature by ensuring that the total
length of a write is a power-of-2 in size and also sized between
atomic_write_unit_min and atomic_write_unit_max, inclusive. Applications
must ensure that the write is at a naturally-aligned offset in the file
wrt the total write length.
Signed-off-by: Prasad Singamsetty <redacted>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/fs.h | 1 +
include/uapi/linux/fs.h | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:29:08
From: Prasad Singamsetty <redacted>
Extend statx system call to return additional info for atomic write support
support if the specified file is a block device.
Add initial support for a block device.
Signed-off-by: Prasad Singamsetty <redacted>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/bdev.c | 33 +++++++++++++++++++++++----------
fs/stat.c | 15 ++++++++-------
include/linux/blkdev.h | 4 ++--
include/linux/stat.h | 2 ++
include/uapi/linux/stat.h | 7 ++++++-
5 files changed, 41 insertions(+), 20 deletions(-)
@@ -250,13 +250,12 @@ static int vfs_statx(int dfd, struct filename *filename, int flags,stat->attributes|=STATX_ATTR_MOUNT_ROOT;stat->attributes_mask|=STATX_ATTR_MOUNT_ROOT;-/* Handle STATX_DIOALIGN for block devices. */-if(request_mask&STATX_DIOALIGN){-structinode*inode=d_backing_inode(path.dentry);--if(S_ISBLK(inode->i_mode))-bdev_statx_dioalign(inode,stat);-}+/* If this is a block device inode, override the filesystem+*attributeswiththeblockdevicespecificparameters+*thatneedtobeobtainedfromthebdevbackinginode+*/+if(S_ISBLK(d_backing_inode(path.dentry)->i_mode))+bdev_statx(path.dentry,stat,request_mask);path_put(&path);if(retry_estale(error,lookup_flags)){
@@ -53,6 +53,8 @@ struct kstat {u32dio_mem_align;u32dio_offset_align;u64change_cookie;+u32atomic_write_unit_max;+u32atomic_write_unit_min;};/* These definitions are internal to the kernel for now. Mainly used by nfsd. */
@@ -127,7 +127,10 @@ struct statx {__u32stx_dio_mem_align;/* Memory buffer alignment for direct I/O */__u32stx_dio_offset_align;/* File offset alignment for direct I/O *//* 0xa0 */-__u64__spare3[12];/* Spare space for future expansion */+__u32stx_atomic_write_unit_max;+__u32stx_atomic_write_unit_min;+/* 0xb0 */+__u64__spare3[11];/* Spare space for future expansion *//* 0x100 */};
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:29:46
Support providing info on atomic write unit min and max for an inode.
For simplicity, currently we limit the min at the FS block size, but a
lower limit could be supported in future.
The atomic write unit min and max is limited by the guaranteed extent
alignment for the inode.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/xfs_iops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_iops.h | 4 ++++
2 files changed, 55 insertions(+)
@@ -546,6 +546,46 @@ xfs_stat_blksize(returnPAGE_SIZE;}+voidxfs_ip_atomic_write_attr(structxfs_inode*ip,+xfs_filblks_t*unit_min_fsb,+xfs_filblks_t*unit_max_fsb)+{+xfs_extlen_textsz_hint=xfs_get_extsz_hint(ip);+structxfs_buftarg*target=xfs_inode_buftarg(ip);+structblock_device*bdev=target->bt_bdev;+structxfs_mount*mp=ip->i_mount;+xfs_filblks_tatomic_write_unit_min,+atomic_write_unit_max,+align;++atomic_write_unit_min=XFS_B_TO_FSB(mp,+queue_atomic_write_unit_min_bytes(bdev->bd_queue));+atomic_write_unit_max=XFS_B_TO_FSB(mp,+queue_atomic_write_unit_max_bytes(bdev->bd_queue));++/* for RT, unset extsize gives hint of 1 */+/* for !RT, unset extsize gives hint of 0 */+if(extsz_hint&&(XFS_IS_REALTIME_INODE(ip)||+(ip->i_diflags2&XFS_DIFLAG2_FORCEALIGN)))+align=extsz_hint;+else+align=1;++if(atomic_write_unit_max==0){+*unit_min_fsb=0;+*unit_max_fsb=0;+}elseif(atomic_write_unit_min==0){+*unit_min_fsb=1;+*unit_max_fsb=min_t(xfs_filblks_t,atomic_write_unit_max,+align);+}else{+*unit_min_fsb=min_t(xfs_filblks_t,atomic_write_unit_min,+align);+*unit_max_fsb=min_t(xfs_filblks_t,atomic_write_unit_max,+align);+}+}+STATICintxfs_vn_getattr(structmnt_idmap*idmap,
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:29:47
From: "Darrick J. Wong" <djwong@kernel.org>
The existing extsize hint code already did the work of expanding file
range mapping requests so that the range is aligned to the hint value.
Now add the code we need to guarantee that the space allocations are
also always aligned.
XXX: still need to check all this with reflink
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Co-developed-by: John Garry <john.g.garry@oracle.com>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 22 +++++++++++++++++-----
fs/xfs/xfs_iomap.c | 4 +++-
2 files changed, 20 insertions(+), 6 deletions(-)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:30:07
Ensure that when creating a mapping that we adhere to all the atomic
write rules.
We check that the mapping covers the complete range of the write to ensure
that we'll be just creating a single mapping.
Currently minimum granularity is the FS block size, but it should be
possibly to support lower in future.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/xfs_iomap.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
@@ -814,6 +815,41 @@ xfs_direct_write_iomap_begin(if(error)gotoout_unlock;+if(flags&IOMAP_ATOMIC_WRITE){+xfs_filblks_tunit_min_fsb,unit_max_fsb;++xfs_ip_atomic_write_attr(ip,&unit_min_fsb,&unit_max_fsb);++if(!imap_spans_range(&imap,offset_fsb,end_fsb)){+error=-EIO;+gotoout_unlock;+}++if(offset%m_sb->sb_blocksize||+length%m_sb->sb_blocksize){+error=-EIO;+gotoout_unlock;+}++if(imap.br_blockcount==unit_min_fsb||+imap.br_blockcount==unit_max_fsb){+/* min and max must be a power-of-2 */+}elseif(imap.br_blockcount<unit_min_fsb||+imap.br_blockcount>unit_max_fsb){+error=-EIO;+gotoout_unlock;+}elseif(!is_power_of_2(imap.br_blockcount)){+error=-EIO;+gotoout_unlock;+}++if(imap.br_startoff&&+imap.br_startoff%imap.br_blockcount){+error=-EIO;+gotoout_unlock;+}+}+if(imap_needs_cow(ip,flags,&imap,nimaps)){error=-EAGAIN;if(flags&IOMAP_NOWAIT)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:32:38
As the name suggests, we should not be splitting these.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/blk-merge.c | 5 +++++
1 file changed, 5 insertions(+)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:33:08
Add flag IOMAP_ATOMIC_WRITE to indicate to the FS that an atomic write
bio is being created and all the rules there need to be followed.
It is the task of the FS iomap iter callbacks to ensure that the mapping
created adheres to those rules, like size is power-of-2, is at a
naturally-aligned offset, etc.
In iomap_dio_bio_iter(), ensure that for a non-dsync iocb that the mapping
is not dirty nor unmapped.
A write should only produce a single bio, so error when it doesn't.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/iomap/direct-io.c | 26 ++++++++++++++++++++++++--
fs/iomap/trace.h | 3 ++-
include/linux/iomap.h | 1 +
3 files changed, 27 insertions(+), 3 deletions(-)
@@ -397,6 +408,12 @@ static loff_t iomap_dio_bio_iter(const struct iomap_iter *iter,}n=bio->bi_iter.bi_size;+if(atomic_write&&n!=length){+/* This bio should have covered the complete length */+ret=-EINVAL;+bio_put(bio);+gotoout;+}if(dio->flags&IOMAP_DIO_WRITE){task_io_account_write(n);}else{
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:33:31
From: Himanshu Madhani <redacted>
Add flag REQ_ATOMIC, meaning an atomic operation. This should only be
used in conjunction with REQ_OP_WRITE.
We will not add a special "request atomic write" operation, as to try to
avoid maintenance effort for an operation which is almost the same as
REQ_OP_WRITE.
Signed-off-by: Himanshu Madhani <redacted>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/blk_types.h | 2 ++
1 file changed, 2 insertions(+)
@@ -422,6 +422,7 @@ enum req_flag_bits {__REQ_DRV,/* for driver use */__REQ_FS_PRIVATE,/* for file system (submitter) use */+__REQ_ATOMIC,/* for atomic write operations *//**Commandspecificflags,keeplast:*/
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:33:33
For atomic writes we allow merging, but we must adhere to some additional
rules:
- Only allow merging of atomic writes with other atomic writes
- Ensure that the merged IO would not cross an atomic write boundary, if
any
We already ensure that we don't exceed the atomic writes size limit in
get_max_io_size().
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/blk-merge.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
@@ -664,6 +681,18 @@ int ll_back_merge_fn(struct request *req, struct bio *bio, unsigned int nr_segs)return0;}+if(req->cmd_flags&REQ_ATOMIC){+unsignedintatomic_write_boundary_bytes=+queue_atomic_write_boundary_bytes(req->q);++if(atomic_write_boundary_bytes&&+bio_straddles_atomic_write_boundary(req->__sector,+bio->bi_iter.bi_size+blk_rq_bytes(req),+atomic_write_boundary_bytes)){+return0;+}+}+returnll_new_hw_segment(req,bio,nr_segs);}
@@ -683,6 +712,19 @@ static int ll_front_merge_fn(struct request *req, struct bio *bio,return0;}+if(req->cmd_flags&REQ_ATOMIC){+unsignedintatomic_write_boundary_bytes=+queue_atomic_write_boundary_bytes(req->q);++if(atomic_write_boundary_bytes&&+bio_straddles_atomic_write_boundary(+bio->bi_iter.bi_sector,+bio->bi_iter.bi_size+blk_rq_bytes(req),+atomic_write_boundary_bytes)){+return0;+}+}+returnll_new_hw_segment(req,bio,nr_segs);}
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:33:55
The low-space allocator doesn't honour the alignment requirement, so don't
attempt to even use it (when we have an alignment requirement).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 4 ++++
1 file changed, 4 insertions(+)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:34:05
Add support for atomic writes, as follows:
- Ensure that the IO follows all the atomic writes rules, like must be
naturally aligned
- Set REQ_ATOMIC
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/fops.c | 42 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:34:08
Add function sd_setup_atomic_cmnd() to setup an WRITE_ATOMIC_16
CDB for when REQ_ATOMIC flag is set for the request.
Also add trace info.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_trace.c | 22 ++++++++++++++++++++++
drivers/scsi/sd.c | 20 ++++++++++++++++++++
include/scsi/scsi_proto.h | 1 +
include/trace/events/scsi.h | 1 +
4 files changed, 44 insertions(+)
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:34:14
From: "Darrick J. Wong" <djwong@kernel.org>
Add a new inode flag to require that all file data extent mappings must
be aligned (both the file offset range and the allocated space itself)
to the extent size hint. Having a separate COW extent size hint is no
longer allowed.
The goal here is to enable sysadmins and users to mandate that all space
mappings in a file must have a startoff/blockcount that are aligned to
(say) a 2MB alignment and that the startblock/blockcount will follow the
same alignment.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Co-developed-by: John Garry <john.g.garry@oracle.com>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_format.h | 6 +++++-
fs/xfs/libxfs/xfs_inode_buf.c | 40 +++++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_inode_buf.h | 3 +++
fs/xfs/libxfs/xfs_sb.c | 3 +++
fs/xfs/xfs_inode.c | 12 +++++++++++
fs/xfs/xfs_inode.h | 5 +++++
fs/xfs/xfs_ioctl.c | 18 ++++++++++++++++
fs/xfs/xfs_mount.h | 2 ++
fs/xfs/xfs_super.c | 4 ++++
include/uapi/linux/fs.h | 2 ++
10 files changed, 94 insertions(+), 1 deletion(-)
@@ -772,3 +780,35 @@ xfs_inode_validate_cowextsize(returnNULL;}++/* Validate the forcealign inode flag */+xfs_failaddr_t+xfs_inode_validate_forcealign(+structxfs_mount*mp,+uint16_tmode,+uint16_tflags,+uint32_textsize,+uint32_tcowextsize)+{+/* superblock rocompat feature flag */+if(!xfs_has_forcealign(mp))+return__this_address;++/* Only regular files and directories */+if(!S_ISDIR(mode)&&!S_ISREG(mode))+return__this_address;++/* Doesn't apply to realtime files */+if(flags&XFS_DIFLAG_REALTIME)+return__this_address;++/* Requires a nonzero extent size hint */+if(extsize==0)+return__this_address;++/* Requires no cow extent size hint */+if(cowextsize!=0)+return__this_address;++returnNULL;+}
@@ -1703,6 +1703,10 @@ xfs_fs_fill_super(mp->m_features&=~XFS_FEAT_DISCARD;}+if(xfs_has_forcealign(mp))+xfs_warn(mp,+"EXPERIMENTAL forced data extent alignment feature in use. Use at your own risk!");+if(xfs_has_reflink(mp)){if(mp->m_sb.sb_rblocks){xfs_alert(mp,
@@ -140,6 +140,8 @@ struct fsxattr {#define FS_XFLAG_FILESTREAM 0x00004000 /* use filestream allocator */#define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */#define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */+/* data extent mappings for regular files must be aligned to extent size hint */+#define FS_XFLAG_FORCEALIGN 0x00020000#define FS_XFLAG_HASATTR 0x80000000 /* no DIFLAG for this *//* the read-only stuff doesn't really belong here, but any other place is
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:34:46
Add initial support for atomic writes.
As is standard method, feed device properties via modules param, those
being:
- atomic_max_size_blks
- atomic_alignment_blks
- atomic_granularity_blks
- atomic_max_size_with_boundary_blks
- atomic_max_boundary_blks
These just match sbc4r22 section 6.6.4 - Block limits VPD page.
We just support ATOMIC_WRITE_16.
The major change in the driver is how we lock the device for RW accesses.
Currently the driver uses a per-device lock for accessing device metadata
and "media" data (calls to do_device_access()) atomically for the duration
of the whole read/write command.
This should not suit verifying atomic writes. Reason being that currently
all reads/writes are atomic, so using atomic writes does not prove
anything.
Change device access model to basis that regular writes only atomic on a
per-sector basis, while reads and atomic writes are fully atomic.
As mentioned, since accessing metadata and device media is atomic,
continue to have regular writes involving metadata - like discard or PI -
as atomic. We can improve this later.
Currently we only support model where overlapping going reads or writes
wait for current access to complete before commencing an atomic write.
This is described in 4.29.3.2 section of the SBC. However, we simplify,
things and wait for all accesses to complete (when issuing an atomic
write).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_debug.c | 587 +++++++++++++++++++++++++++++---------
1 file changed, 454 insertions(+), 133 deletions(-)
@@ -322,7 +331,9 @@ struct sdebug_host_info {/* There is an xarray of pointers to this struct's objects, one per host */structsdeb_store_info{-rwlock_tmacc_lck;/* for atomic media access on this store */+rwlock_tmacc_data_lck;/* for media data access on this store */+rwlock_tmacc_meta_lck;/* for atomic media meta access on this store */+rwlock_tmacc_sector_lck;/* per-sector media data access on this store */u8*storep;/* user data storage (ram) */structt10_pi_tuple*dif_storep;/* protection info */void*map_storep;/* provisioning map */
@@ -346,12 +357,20 @@ struct sdebug_defer {enumsdeb_defer_typedefer_t;};+structsdebug_device_access_info{+boolatomic_write;+u64lba;+u32num;+structscsi_cmnd*self;+};+structsdebug_queued_cmd{/* corresponding bit set in in_use_bm[] in owning struct sdebug_queue*instanceindicatesthisslotisinuse.*/structsdebug_defersd_dp;structscsi_cmnd*scmd;+structsdebug_device_access_info*i;};structsdebug_scsi_cmd{
@@ -411,7 +430,8 @@ enum sdeb_opcode_index {SDEB_I_PRE_FETCH=29,/* 10, 16 */SDEB_I_ZONE_OUT=30,/* 0x94+SA; includes no data xfer */SDEB_I_ZONE_IN=31,/* 0x95+SA; all have data-in */-SDEB_I_LAST_ELEM_P1=32,/* keep this last (previous + 1) */+SDEB_I_ATOMIC_WRITE_16=32,/* keep this last (previous + 1) */+SDEB_I_LAST_ELEM_P1=33,/* keep this last (previous + 1) */};
@@ -1500,6 +1538,14 @@ static int inquiry_vpd_b0(unsigned char *arr)/* Maximum WRITE SAME Length */put_unaligned_be64(sdebug_write_same_length,&arr[32]);+if(sdebug_atomic_write){+put_unaligned_be32(sdebug_atomic_max_size_blks,&arr[40]);+put_unaligned_be32(sdebug_atomic_alignment_blks,&arr[44]);+put_unaligned_be32(sdebug_atomic_granularity_blks,&arr[48]);+put_unaligned_be32(sdebug_atomic_max_size_with_boundary_blks,&arr[52]);+put_unaligned_be32(sdebug_atomic_max_boundary_blks,&arr[56]);+}+return0x3c;/* Mandatory page length for Logical Block Provisioning */}
@@ -3001,15 +3047,240 @@ static inline struct sdeb_store_info *devip2sip(struct sdebug_dev_info *devip,returnxa_load(per_store_ap,devip->sdbg_host->si_idx);}++staticinlinevoid+sdeb_read_lock(rwlock_t*lock)+{+if(sdebug_no_rwlock)+__acquire(lock);+else+read_lock(lock);+}++staticinlinevoid+sdeb_read_unlock(rwlock_t*lock)+{+if(sdebug_no_rwlock)+__release(lock);+else+read_unlock(lock);+}++staticinlinevoid+sdeb_write_lock(rwlock_t*lock)+{+if(sdebug_no_rwlock)+__acquire(lock);+else+write_lock(lock);+}++staticinlinevoid+sdeb_write_unlock(rwlock_t*lock)+{+if(sdebug_no_rwlock)+__release(lock);+else+write_unlock(lock);+}++staticinlinevoid+sdeb_data_read_lock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_read_lock(&sip->macc_data_lck);+}++staticinlinevoid+sdeb_data_read_unlock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_read_unlock(&sip->macc_data_lck);+}++staticinlinevoid+sdeb_data_write_lock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_write_lock(&sip->macc_data_lck);+}++staticinlinevoid+sdeb_data_write_unlock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_write_unlock(&sip->macc_data_lck);+}++staticinlinevoid+sdeb_data_sector_read_lock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_read_lock(&sip->macc_sector_lck);+}++staticinlinevoid+sdeb_data_sector_read_unlock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_read_unlock(&sip->macc_sector_lck);+}++staticinlinevoid+sdeb_data_sector_write_lock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_write_lock(&sip->macc_sector_lck);+}++staticinlinevoid+sdeb_data_sector_write_unlock(structsdeb_store_info*sip)+{+BUG_ON(!sip);++sdeb_write_unlock(&sip->macc_sector_lck);+}++/*+Atomiclocking:+Wesimplifytheatomicmodeltoallowonly1xatomic+writeandmanynon-atomicreadsorwritesforall+LBAs.++ARWlockhasasimilarbahaviour:+Only1xwriterandmanyreaders.++SouseaRWlockforper-devicereadandwritelocking:+Anatomicaccessgrabsthelockasawriterand+non-atomicgrabsthelockasareader.+*/++staticinlinevoid+sdeb_data_lock(structsdeb_store_info*sip,boolatomic_write)+{+if(atomic_write)+sdeb_data_write_lock(sip);+else+sdeb_data_read_lock(sip);+}++staticinlinevoid+sdeb_data_unlock(structsdeb_store_info*sip,boolatomic_write)+{+if(atomic_write)+sdeb_data_write_unlock(sip);+else+sdeb_data_read_unlock(sip);+}++/* Allow many reads but only 1x write per sector */+staticinlinevoid+sdeb_data_sector_lock(structsdeb_store_info*sip,booldo_write)+{+if(do_write)+sdeb_data_sector_write_lock(sip);+else+sdeb_data_sector_read_lock(sip);+}++staticinlinevoid+sdeb_data_sector_unlock(structsdeb_store_info*sip,booldo_write)+{+if(do_write)+sdeb_data_sector_write_unlock(sip);+else+sdeb_data_sector_read_unlock(sip);+}++staticinlinevoid+sdeb_meta_read_lock(structsdeb_store_info*sip)+{+if(sdebug_no_rwlock){+if(sip)+__acquire(&sip->macc_meta_lck);+else+__acquire(&sdeb_fake_rw_lck);+}else{+if(sip)+read_lock(&sip->macc_meta_lck);+else+read_lock(&sdeb_fake_rw_lck);+}+}++staticinlinevoid+sdeb_meta_read_unlock(structsdeb_store_info*sip)+{+if(sdebug_no_rwlock){+if(sip)+__release(&sip->macc_meta_lck);+else+__release(&sdeb_fake_rw_lck);+}else{+if(sip)+read_unlock(&sip->macc_meta_lck);+else+read_unlock(&sdeb_fake_rw_lck);+}+}++staticinlinevoid+sdeb_meta_write_lock(structsdeb_store_info*sip)+{+if(sdebug_no_rwlock){+if(sip)+__acquire(&sip->macc_meta_lck);+else+__acquire(&sdeb_fake_rw_lck);+}else{+if(sip)+write_lock(&sip->macc_meta_lck);+else+write_lock(&sdeb_fake_rw_lck);+}+}++staticinlinevoid+sdeb_meta_write_unlock(structsdeb_store_info*sip)+{+if(sdebug_no_rwlock){+if(sip)+__release(&sip->macc_meta_lck);+else+__release(&sdeb_fake_rw_lck);+}else{+if(sip)+write_unlock(&sip->macc_meta_lck);+else+write_unlock(&sdeb_fake_rw_lck);+}+}+/* Returns number of bytes copied or -1 if error. */staticintdo_device_access(structsdeb_store_info*sip,structscsi_cmnd*scp,-u32sg_skip,u64lba,u32num,booldo_write)+u32sg_skip,u64lba,u32num,booldo_write,+boolatomic_write){intret;-u64block,rest=0;+u64block;enumdma_data_directiondir;structscsi_data_buffer*sdb=&scp->sdb;u8*fsp;+inti;++/*+*Eventhoughreadsareinherentlyatomic(inthisdriver),weexpect+*theatomicflagonlyforwrites.+*/+if(!do_write&&atomic_write)+return-1;if(do_write){dir=DMA_TO_DEVICE;
@@ -3025,21 +3296,26 @@ static int do_device_access(struct sdeb_store_info *sip, struct scsi_cmnd *scp,fsp=sip->storep;block=do_div(lba,sdebug_store_sectors);-if(block+num>sdebug_store_sectors)-rest=block+num-sdebug_store_sectors;-ret=sg_copy_buffer(sdb->table.sgl,sdb->table.nents,+/* Only allow 1x atomic write or multiple non-atomic writes at any given time */+sdeb_data_lock(sip,atomic_write);+for(i=0;i<num;i++){+/* We shouldn't need to lock for atomic writes, but do it anyway */+sdeb_data_sector_lock(sip,do_write);+ret=sg_copy_buffer(sdb->table.sgl,sdb->table.nents,fsp+(block*sdebug_sector_size),-(num-rest)*sdebug_sector_size,sg_skip,do_write);-if(ret!=(num-rest)*sdebug_sector_size)-returnret;--if(rest){-ret+=sg_copy_buffer(sdb->table.sgl,sdb->table.nents,-fsp,rest*sdebug_sector_size,-sg_skip+((num-rest)*sdebug_sector_size),-do_write);+sdebug_sector_size,sg_skip,do_write);+sdeb_data_sector_unlock(sip,do_write);+if(ret!=sdebug_sector_size){+ret+=(i*sdebug_sector_size);+break;+}+sg_skip+=sdebug_sector_size;+if(++block>=sdebug_store_sectors)+block=0;}+ret=num*sdebug_sector_size;+sdeb_data_unlock(sip,atomic_write);returnret;}
@@ -3650,22 +3880,22 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)switch(prot_verify_write(scp,lba,num,ei_lba)){case1:/* Guard tag error */if(scp->prot_flags&SCSI_PROT_GUARD_CHECK){-sdeb_write_unlock(sip);+sdeb_meta_write_unlock(sip);mk_sense_buffer(scp,ILLEGAL_REQUEST,0x10,1);returnillegal_condition_result;}elseif(scp->cmnd[1]>>5!=3){/* WRPROTECT != 3 */-sdeb_write_unlock(sip);+sdeb_meta_write_unlock(sip);mk_sense_buffer(scp,ABORTED_COMMAND,0x10,1);returncheck_condition_result;}break;case3:/* Reference tag error */if(scp->prot_flags&SCSI_PROT_REF_CHECK){-sdeb_write_unlock(sip);+sdeb_meta_write_unlock(sip);mk_sense_buffer(scp,ILLEGAL_REQUEST,0x10,3);returnillegal_condition_result;}elseif(scp->cmnd[1]>>5!=3){/* WRPROTECT != 3 */-sdeb_write_unlock(sip);+sdeb_meta_write_unlock(sip);mk_sense_buffer(scp,ABORTED_COMMAND,0x10,3);returncheck_condition_result;}
@@ -3673,13 +3903,16 @@ static int resp_write_dt0(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)}}-ret=do_device_access(sip,scp,0,lba,num,true);+ret=do_device_access(sip,scp,0,lba,num,true,false);if(unlikely(scsi_debug_lbp()))map_region(sip,lba,num);+/* If ZBC zone then bump its write pointer */if(sdebug_dev_is_zoned(devip))zbc_inc_wp(devip,lba,num);-sdeb_write_unlock(sip);+if(meta_data_locked)+sdeb_meta_write_unlock(sip);+if(unlikely(-1==ret))returnDID_ERROR<<16;elseif(unlikely(sdebug_verbose&&
@@ -3786,7 +4019,8 @@ static int resp_write_scat(struct scsi_cmnd *scp,gotoerr_out;}-sdeb_write_lock(sip);+/* Just keep it simple and always lock for now */+sdeb_meta_write_lock(sip);sg_off=lbdof_blen;/* Spec says Buffer xfer Length field in number of LBs in dout */cum_lb=0;
@@ -3829,7 +4063,11 @@ static int resp_write_scat(struct scsi_cmnd *scp,}}-ret=do_device_access(sip,scp,sg_off,lba,num,true);+/*+*Writerangesatomicallytokeepasclosetopre-atomic+*writesbehaviouraspossible.+*/+ret=do_device_access(sip,scp,sg_off,lba,num,true,true);/* If ZBC zone then bump its write pointer */if(sdebug_dev_is_zoned(devip))zbc_inc_wp(devip,lba,num);
@@ -3868,7 +4106,7 @@ static int resp_write_scat(struct scsi_cmnd *scp,}ret=0;err_out_unlock:-sdeb_write_unlock(sip);+sdeb_meta_write_unlock(sip);err_out:kfree(lrdp);returnret;
@@ -3930,10 +4171,12 @@ static int resp_write_same(struct scsi_cmnd *scp, u64 lba, u32 num,/* If ZBC zone then bump its write pointer */if(sdebug_dev_is_zoned(devip))zbc_inc_wp(devip,lba,num);+sdeb_data_write_unlock(sip);+ret=0;out:-sdeb_write_unlock(sip);--return0;+if(meta_data_locked)+sdeb_meta_write_unlock(sip);+returnret;}staticintresp_write_same_10(structscsi_cmnd*scp,
@@ -4076,25 +4319,30 @@ static int resp_comp_write(struct scsi_cmnd *scp,returncheck_condition_result;}-sdeb_write_lock(sip);-ret=do_dout_fetch(scp,dnum,arr);if(ret==-1){retval=DID_ERROR<<16;-gotocleanup;+gotocleanup_free;}elseif(sdebug_verbose&&(ret<(dnum*lb_size)))sdev_printk(KERN_INFO,scp->device,"%s: compare_write: cdb ""indicated=%u, IO sent=%d bytes\n",my_name,dnum*lb_size,ret);++sdeb_data_write_lock(sip);+sdeb_meta_write_lock(sip);if(!comp_write_worker(sip,lba,num,arr,false)){mk_sense_buffer(scp,MISCOMPARE,MISCOMPARE_VERIFY_ASC,0);retval=check_condition_result;-gotocleanup;+gotocleanup_unlock;}++/* Cover sip->map_storep (which map_region()) sets with data lock */if(scsi_debug_lbp())map_region(sip,lba,num);-cleanup:-sdeb_write_unlock(sip);+cleanup_unlock:+sdeb_meta_write_unlock(sip);+sdeb_data_write_unlock(sip);+cleanup_free:kfree(arr);returnretval;}
@@ -4267,12 +4515,13 @@ static int resp_pre_fetch(struct scsi_cmnd *scp,rest=block+nblks-sdebug_store_sectors;/* Try to bring the PRE-FETCH range into CPU's cache */-sdeb_read_lock(sip);+sdeb_data_read_lock(sip);prefetch_range(fsp+(sdebug_sector_size*block),(nblks-rest)*sdebug_sector_size);if(rest)prefetch_range(fsp,rest*sdebug_sector_size);-sdeb_read_unlock(sip);++sdeb_data_read_unlock(sip);fini:if(cmd[1]&0x2)res=SDEG_RES_IMMED_MASK;
@@ -4431,7 +4680,7 @@ static int resp_verify(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)returncheck_condition_result;}/* Not changing store, so only need read access */-sdeb_read_lock(sip);+sdeb_data_read_lock(sip);ret=do_dout_fetch(scp,a_num,arr);if(ret==-1){
@@ -4499,7 +4748,7 @@ static int resp_report_zones(struct scsi_cmnd *scp,returncheck_condition_result;}-sdeb_read_lock(sip);+sdeb_meta_read_lock(sip);desc=arr+64;for(lba=zs_lba;lba<sdebug_capacity;
@@ -4597,11 +4846,68 @@ static int resp_report_zones(struct scsi_cmnd *scp,ret=fill_from_dev_buffer(scp,arr,min_t(u32,alloc_len,rep_len));fini:-sdeb_read_unlock(sip);+sdeb_meta_read_unlock(sip);kfree(arr);returnret;}+staticintresp_atomic_write(structscsi_cmnd*scp,+structsdebug_dev_info*devip)+{+structsdeb_store_info*sip;+u8*cmd=scp->cmnd;+u16boundary,len;+u64lba;+intret;++if(!scsi_debug_atomic_write()){+mk_sense_invalid_opcode(scp);+returncheck_condition_result;+}++sip=devip2sip(devip,true);++lba=get_unaligned_be64(cmd+2);+boundary=get_unaligned_be16(cmd+10);+len=get_unaligned_be16(cmd+12);++if(sdebug_atomic_alignment_blks&&lba%sdebug_atomic_alignment_blks){+/* Does not meet alignment requirement */+mk_sense_buffer(scp,ILLEGAL_REQUEST,INVALID_FIELD_IN_CDB,0);+returncheck_condition_result;+}++if(sdebug_atomic_granularity_blks&&len%sdebug_atomic_granularity_blks){+/* Does not meet alignment requirement */+mk_sense_buffer(scp,ILLEGAL_REQUEST,INVALID_FIELD_IN_CDB,0);+returncheck_condition_result;+}++if(boundary>0){+if(boundary>sdebug_atomic_max_boundary_blks){+mk_sense_invalid_fld(scp,SDEB_IN_CDB,12,-1);+returncheck_condition_result;+}++if(len>sdebug_atomic_max_size_with_boundary_blks){+mk_sense_invalid_fld(scp,SDEB_IN_CDB,12,-1);+returncheck_condition_result;+}+}else{+if(len>sdebug_atomic_max_size_blks){+mk_sense_invalid_fld(scp,SDEB_IN_CDB,12,-1);+returncheck_condition_result;+}+}++ret=do_device_access(sip,scp,0,lba,len,true,true);+if(unlikely(ret==-1))+returnDID_ERROR<<16;+if(unlikely(ret!=len*sdebug_sector_size))+returnDID_ERROR<<16;+return0;+}+/* Logic transplanted from tcmu-runner, file_zbc.c */staticvoidzbc_open_all(structsdebug_dev_info*devip){
@@ -4628,8 +4934,7 @@ static int resp_open_zone(struct scsi_cmnd *scp, struct sdebug_dev_info *devip)mk_sense_invalid_opcode(scp);returncheck_condition_result;}--sdeb_write_lock(sip);+sdeb_meta_write_lock(sip);if(all){/* Check if all closed zones can be open */
@@ -5802,6 +6114,7 @@ MODULE_PARM_DESC(lbprz,MODULE_PARM_DESC(lbpu,"enable LBP, support UNMAP command (def=0)");MODULE_PARM_DESC(lbpws,"enable LBP, support WRITE SAME(16) with UNMAP bit (def=0)");MODULE_PARM_DESC(lbpws10,"enable LBP, support WRITE SAME(10) with UNMAP bit (def=0)");+MODULE_PARM_DESC(atomic_write,"enable ATOMIC WRITE support, support WRITE ATOMIC(16) (def=1)");MODULE_PARM_DESC(lowest_aligned,"lowest aligned lba (def=0)");MODULE_PARM_DESC(lun_format,"LUN format: 0->peripheral (def); 1 --> flat address method");MODULE_PARM_DESC(max_luns,"number of LUNs per target to simulate(def=1)");
@@ -5833,6 +6146,11 @@ MODULE_PARM_DESC(unmap_alignment, "lowest aligned thin provisioning lba (def=0)"MODULE_PARM_DESC(unmap_granularity,"thin provisioning granularity in blocks (def=1)");MODULE_PARM_DESC(unmap_max_blocks,"max # of blocks can be unmapped in one cmd (def=0xffffffff)");MODULE_PARM_DESC(unmap_max_desc,"max # of ranges that can be unmapped in one cmd (def=256)");+MODULE_PARM_DESC(atomic_max_size_blks,"max # of blocks can be atomically written in one cmd (def=0xff)");+MODULE_PARM_DESC(atomic_alignment_blks,"minimum alignment of atomic write in blocks (def=2)");+MODULE_PARM_DESC(atomic_granularity_blks,"minimum granularity of atomic write in blocks (def=2)");+MODULE_PARM_DESC(atomic_max_size_with_boundary_blks,"max # of blocks can be atomically written in one cmd with boundary set (def=0xff)");+MODULE_PARM_DESC(atomic_boundary_blks,"max # boundaries per atomic write (def=0)");MODULE_PARM_DESC(uuid_ctl,"1->use uuid for lu name, 0->don't, 2->all use same (def=0)");MODULE_PARM_DESC(virtual_gb,"virtual gigabyte (GiB) size (def=0 -> use dev_size_mb)");
@@ -6978,6 +7296,7 @@ static int __init scsi_debug_init(void)return-EINVAL;}}+xa_init_flags(per_store_ap,XA_FLAGS_ALLOC|XA_FLAGS_LOCK_IRQ);if(want_store){idx=sdebug_add_store();
@@ -7180,7 +7499,9 @@ static int sdebug_add_store(void)map_region(sip,0,2);}-rwlock_init(&sip->macc_lck);+rwlock_init(&sip->macc_data_lck);+rwlock_init(&sip->macc_meta_lck);+rwlock_init(&sip->macc_sector_lck);return(int)n_idx;err:sdebug_erase_store((int)n_idx,sip);
From: John Garry <john.g.garry@oracle.com> Date: 2023-09-29 10:44:05
Currently blk_queue_get_max_sectors() is passed a enum req_op, which does
not work for atomic writes. This is because an atomic write has a different
max sectors values to a regular write, and we need the rq->cmd_flags
to know that we have an atomic write, so pass the request pointer, which
has all information available.
Also use rq->cmd_flags instead of rq->bio->bi_opf when possible.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
block/blk-merge.c | 3 ++-
block/blk-mq.c | 2 +-
block/blk.h | 6 ++++--
3 files changed, 7 insertions(+), 4 deletions(-)
From: Bart Van Assche <bvanassche@acm.org> Date: 2023-09-29 14:58:29
On 9/29/23 03:27, John Garry wrote:
The atomic writes feature requires dedicated HW support, like
SCSI WRITE_ATOMIC_16 command.
This is not correct. Log-structured filesystems can implement atomic
writes without support for atomic writes in the block device(s) used
by the filesystem. See also the F2FS_IOC_*_ATOMIC_WRITE ioctls. This
being said, I hope that atomic write support will be added in the
block layer and also that a single interface will be supported by all
filesystems.
Thanks,
Bart.
@@ -127,7 +127,10 @@ struct statx {__u32stx_dio_mem_align;/* Memory buffer alignment for direct I/O */__u32stx_dio_offset_align;/* File offset alignment for direct I/O *//* 0xa0 */-__u64__spare3[12];/* Spare space for future expansion */+__u32stx_atomic_write_unit_max;+__u32stx_atomic_write_unit_min;
Maybe min first and then max? That seems a bit more natural, and a lot of the
code you've written handle them in that order.
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(ll_back_merge_fn) in archive vmlinux.a
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(ll_back_merge_fn) in archive vmlinux.a
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(bio_attempt_front_merge) in archive vmlinux.a
>>> referenced 3 more times
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
@@ -127,7 +127,10 @@ struct statx {__u32stx_dio_mem_align;/* Memory buffer alignment for direct I/O */__u32stx_dio_offset_align;/* File offset alignment for direct I/O *//* 0xa0 */-__u64__spare3[12];/* Spare space for future expansion */+__u32stx_atomic_write_unit_max;+__u32stx_atomic_write_unit_min;
Maybe min first and then max? That seems a bit more natural, and a lot of the
code you've written handle them in that order.
How would this differ from stx_atomic_write_unit_min != 0?
Is it even possible that stx_atomic_write_unit_min == 0? My understanding
is that all Linux filesystems rely on the assumption that writing a single
logical block either succeeds or does not happen, even if a power failure
occurs between writing and reading a logical block.
Thanks,
Bart.
How would this differ from stx_atomic_write_unit_min != 0?
Yeah, I suppose that we can just not set this for the case of
stx_atomic_write_unit_min == 0.
Is it even possible that stx_atomic_write_unit_min == 0? My understanding
is that all Linux filesystems rely on the assumption that writing a single
logical block either succeeds or does not happen, even if a power failure
occurs between writing and reading a logical block.
Maybe they do rely on this, but is it particularly interesting?
BTW, I would not like to provide assurances that every storage media
produced writes logical blocks atomically.
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-02 10:11:33
On 29/09/2023 18:51, Bart Van Assche wrote:
On 9/29/23 03:27, John Garry wrote:
quoted
+ if (pos % atomic_write_unit_min_bytes)
+ return false;
+ if (iov_iter_count(iter) % atomic_write_unit_min_bytes)
+ return false;
+ if (!is_power_of_2(iov_iter_count(iter)))
+ return false;
[ ... ]
quoted
+ if (pos % iov_iter_count(iter))
+ return false;
Where do these rules come from? Is there any standard that requires
any of the above?
SCSI and NVMe have slightly different atomic writes semantics, and the
rules are created to work for both.
In addition, the rules are related to FS extent alignment.
Note that for simplicity and consistency we use the same rules for
regular files as for bdev's.
This is the coding for the rules and where they come from:
> + if (!atomic_write_unit_min_bytes)
> + return false;
If atomic_write_unit_min_bytes == 0, then we just don't support atomic
writes.
> + if (pos % atomic_write_unit_min_bytes)
> + return false;
See later rules.
> + if (iov_iter_count(iter) % atomic_write_unit_min_bytes)
> + return false;
For SCSI, there is an atomic write granularity, which dictates
atomic_write_unit_min_bytes. So here we need to ensure that the length
is a multiple of this value.
> + if (!is_power_of_2(iov_iter_count(iter)))
> + return false;
This rule comes from FS block alignment and NVMe atomic boundary.
FSes (XFS) have discontiguous extents. We need to ensure that an atomic
write does not cross discontiguous extents. To do this we ensure extent
length and alignment and limit atomic_write_unit_max_bytes to that.
For NVMe, an atomic write boundary is a boundary in LBA space which an
atomic write should not cross. We limit atomic_write_unit_max_bytes such
that it is evenly divisible into this atomic write boundary.
To ensure that the write does not cross these alignment boundaries we
say that it must be naturally aligned and a power-of-2 in length.
We may be able to relax this rule but I am not sure it buys us anything
- typically we want to be writing a 64KB block aligned to 64KB, for example.
> + if (iov_iter_count(iter) > atomic_write_unit_max_bytes)
> + return false;
We just can't exceed this length.
> + if (pos % iov_iter_count(iter))
> + return false;
As above, ensure naturally aligned.
Thanks,
John
Why does "rounddown_pow_of_two()" occur in the above code?
I assume that you are talking about all the code above to calculate
atomic write values for the device.
The reason is that atomic write unit min and max are always a power-of-2
- see rules described earlier - as so that we why we rounddown to a
power-of-2.
Thanks,
John
From: Bart Van Assche <bvanassche@acm.org> Date: 2023-10-02 18:39:12
On 10/2/23 02:51, John Garry wrote:
On 01/10/2023 14:23, Bart Van Assche wrote:
quoted
Is it even possible that stx_atomic_write_unit_min == 0? My
understanding is that all Linux filesystems rely on the assumption
that writing a single logical block either succeeds or does not
happen, even if a power failure occurs between writing and reading
a logical block.
Maybe they do rely on this, but is it particularly interesting?
BTW, I would not like to provide assurances that every storage media
produced writes logical blocks atomically.
Neither the SCSI SBC standard nor the NVMe standard defines a "minimum
atomic write unit". So why to introduce something in the Linux kernel
that is not defined in common storage standards?
I propose to leave out stx_atomic_write_unit_min from
struct statx and also to leave out atomic_write_unit_min_sectors from
struct queue_limits. My opinion is that we should not support block
devices in the Linux kernel that do not write logical blocks atomically.
Block devices that do not write logical blocks atomically are not
compatible with Linux kernel journaling filesystems. Additionally, I'm
not sure it's even possible to write a journaling filesystem for such
block devices.
Thanks,
Bart.
From: Bart Van Assche <bvanassche@acm.org> Date: 2023-10-02 19:12:56
On 10/2/23 03:10, John Garry wrote:
On 29/09/2023 18:51, Bart Van Assche wrote:
quoted
On 9/29/23 03:27, John Garry wrote:
> + if (pos % atomic_write_unit_min_bytes)
> + return false;
See later rules.
Is atomic_write_unit_min_bytes always equal to the logical block size?
If so, can the above test be left out?
> + if (iov_iter_count(iter) % atomic_write_unit_min_bytes)
> + return false;
For SCSI, there is an atomic write granularity, which dictates
atomic_write_unit_min_bytes. So here we need to ensure that the length
is a multiple of this value.
Are there any SCSI devices that we care about that report an ATOMIC
TRANSFER LENGTH GRANULARITY that is larger than a single logical block?
I'm wondering whether we really have to support such devices.
> + if (!is_power_of_2(iov_iter_count(iter)))
> + return false;
This rule comes from FS block alignment and NVMe atomic boundary.
FSes (XFS) have discontiguous extents. We need to ensure that an atomic
write does not cross discontiguous extents. To do this we ensure extent
length and alignment and limit atomic_write_unit_max_bytes to that.
For NVMe, an atomic write boundary is a boundary in LBA space which an
atomic write should not cross. We limit atomic_write_unit_max_bytes such
that it is evenly divisible into this atomic write boundary.
To ensure that the write does not cross these alignment boundaries we
say that it must be naturally aligned and a power-of-2 in length.
We may be able to relax this rule but I am not sure it buys us anything
- typically we want to be writing a 64KB block aligned to 64KB, for
example.
It seems to me that the requirement is_power_of_2(iov_iter_count(iter))
is necessary for some filesystems but not for all filesystems.
Restrictions that are specific to a single filesystem (XFS) should not
occur in code that is intended to be used by all filesystems
(blkdev_atomic_write_valid()).
Thanks,
Bart.
Please store the 'dld' value in the GROUP NUMBER field. See e.g.
sd_setup_rw16_cmnd().
Are you sure that WRITE ATOMIC (16) supports dld?
Hi John,
I was assuming that DLD would be supported by the WRITE ATOMIC(16)
command. After having taken another look at the latest SBC-5 draft
I see that the DLD2/DLD1/DLD0 bits are not present in the WRITE
ATOMIC(16) command. So please ignore my comment above.
Thanks,
Bart.
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(ll_back_merge_fn) in archive vmlinux.a
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(ll_back_merge_fn) in archive vmlinux.a
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(bio_attempt_front_merge) in archive vmlinux.a
>>> referenced 3 more times
This does not appear to be clang specific, I can reproduce it with GCC
12.3.0 and the same configuration target.
Cheers,
Nathan
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-03 01:16:35
On Fri, Sep 29, 2023 at 10:27:16AM +0000, John Garry wrote:
quoted hunk
The low-space allocator doesn't honour the alignment requirement, so don't
attempt to even use it (when we have an alignment requirement).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 4 ++++
1 file changed, 4 insertions(+)
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-03 01:43:01
On Fri, Sep 29, 2023 at 10:27:18AM +0000, John Garry wrote:
quoted hunk
From: "Darrick J. Wong" <djwong@kernel.org>
The existing extsize hint code already did the work of expanding file
range mapping requests so that the range is aligned to the hint value.
Now add the code we need to guarantee that the space allocations are
also always aligned.
XXX: still need to check all this with reflink
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Co-developed-by: John Garry <john.g.garry@oracle.com>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 22 +++++++++++++++++-----
fs/xfs/xfs_iomap.c | 4 +++-
2 files changed, 20 insertions(+), 6 deletions(-)
This smells wrong.
If a filesystem has a stripe unit set (hence stripe_align is
non-zero) then any IO that crosses stripe unit boundaries will not
be atomic - they will require multiple IOs to different devices.
Hence if the filesystem has a stripe unit set, then all forced
alignment hints for atomic IO *must* be an exact integer divider
of the stripe unit. hence when an atomic IO bundle is aligned, the
atomic boundaries within the bundle always fall on a stripe unit
boundary and never cross devices.
IOWs, for a striped filesystem, the maximum size/alignment for a
single atomic IO unit is the stripe unit.
This should be enforced when the forced align flag is set on the
inode (i.e. from the ioctl)
quoted hunk
+
if (align) {
if (xfs_bmap_extsize_align(mp, &ap->got, &ap->prev, align, 0,
ap->eof, 0, ap->conv, &ap->offset,
@@ -3543,10 +3556,10 @@ xfs_bmap_btalloc_at_eof( /* * Allocation failed, so turn return the allocation args to their- * original non-aligned state so the caller can proceed on allocation- * failure as if this function was never called.+ * original state so the caller can proceed on allocation failure as+ * if this function was never called. */- args->alignment = 1;+ args->alignment = orig_alignment; return 0; }
Urk. Not sure that is right, it's certainly a change of behaviour.
Ah. Now I see. This abuses the stripe alignment code to try to
implement this new inode allocation alignment restriction, rather
than just making the extent size hint alignment mandatory....
Yeah, this can be done better... :)
As it is, I have been working on a series that reworks all this
allocator code to separate out the aligned IO from the exact EOF
allocation case to help clean this up for better perag selection
during allocation. I think that needs to be done first before we go
making the alignment code more intricate like this....
-Dave.
--
Dave Chinner
david@fromorbit.com
How would this differ from stx_atomic_write_unit_min != 0?
Yeah, I suppose that we can just not set this for the case of
stx_atomic_write_unit_min == 0.
Please use the STATX_ATTR_WRITE_ATOMIC flag to indicate that the
filesystem, file and underlying device support atomic writes when
the values are non-zero. The whole point of the attribute mask is
that the caller can check the mask for supported functionality
without having to read every field in the statx structure to
determine if the functionality it wants is present.
-Dave.
--
Dave Chinner
david@fromorbit.com
How would this differ from stx_atomic_write_unit_min != 0?
Yeah, I suppose that we can just not set this for the case of
stx_atomic_write_unit_min == 0.
Please use the STATX_ATTR_WRITE_ATOMIC flag to indicate that the
filesystem, file and underlying device support atomic writes when
the values are non-zero. The whole point of the attribute mask is
that the caller can check the mask for supported functionality
without having to read every field in the statx structure to
determine if the functionality it wants is present.
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2023-10-03 03:00:14
On Tue, Oct 03, 2023 at 12:16:26PM +1100, Dave Chinner wrote:
On Fri, Sep 29, 2023 at 10:27:16AM +0000, John Garry wrote:
quoted
The low-space allocator doesn't honour the alignment requirement, so don't
attempt to even use it (when we have an alignment requirement).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 4 ++++
1 file changed, 4 insertions(+)
How does this happen?
The earlier failing aligned allocations will clear alignment before
we get here....
I was thinking the predicate should be xfs_inode_force_align(ip) to save
me/us from thinking about all the other weird ways args->alignment could
end up 1.
/* forced-alignment means we don't use low mode */
if (xfs_inode_force_align(ip))
return -ENOSPC;
--D
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-03 03:32:26
On Fri, Sep 29, 2023 at 10:27:20AM +0000, John Garry wrote:
quoted hunk
Support providing info on atomic write unit min and max for an inode.
For simplicity, currently we limit the min at the FS block size, but a
lower limit could be supported in future.
The atomic write unit min and max is limited by the guaranteed extent
alignment for the inode.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/xfs_iops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_iops.h | 4 ++++
2 files changed, 55 insertions(+)
Formatting.
Also, we don't use variable name shorthand for function names -
xfs_get_atomic_write_hint(ip) to match xfs_get_extsz_hint(ip)
would be appropriate, right?
These should be set in the buftarg at mount time, like we do with
sector size masks. Then we don't need to convert them to fsbs on
every single lookup.
+ /* for RT, unset extsize gives hint of 1 */
+ /* for !RT, unset extsize gives hint of 0 */
+ if (extsz_hint && (XFS_IS_REALTIME_INODE(ip) ||
+ (ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN)))
Logic is non-obvious. The compound is (rt || force), not
(extsz && rt), so it took me a while to actually realise I read this
incorrectly.
if (extsz_hint &&
(XFS_IS_REALTIME_INODE(ip) ||
(ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN))) {
+ align = extsz_hint;
+ else
+ align = 1;
And now the logic looks wrong to me. We don't want to use extsz hint
for RT inodes if force align is not set, this will always use it
regardless of the fact it has nothing to do with force alignment.
Indeed, if XFS_DIFLAG2_FORCEALIGN is not set, then shouldn't this
always return min/max = 0 because atomic alignments are not in us on
this inode?
i.e. the first thing this code should do is:
*unit_min_fsb = 0;
*unit_max_fsb = 0;
if (!(ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN))
return;
Then we can check device support:
if (!buftarg->bt_atomic_write_max)
return;
Then we can check for extent size hints. If that's not set:
align = xfs_get_extsz_hint(ip);
if (align <= 1) {
unit_min_fsb = 1;
unit_max_fsb = 1;
return;
}
And finally, if there is an extent size hint, we can return that.
Why is it valid for a device to have a zero minimum size? If it can
set a maximum, it should -always- set a minimum size as logical
sector size is a valid lower bound, yes?
Nothing here guarantees the power-of-2 sizes that the RWF_ATOMIC
user interface requires....
It also doesn't check that the extent size hint is aligned with
atomic write units.
It also doesn't check either against stripe unit alignment....
quoted hunk
+}
+
STATIC int
xfs_vn_getattr(
struct mnt_idmap *idmap,
That's just nasty. We pull byte units from the bdev, convert them to
fsb to round them, then convert them back to byte counts. We should
be doing all the work in one set of units....
If the min/max are zero, then atomic writes are not supported on
this inode, right? Why would we set any of the attributes or result
mask to say it is supported on this file?
-Dave.
--
Dave Chinner
david@fromorbit.com
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-03 04:24:32
On Fri, Sep 29, 2023 at 10:27:21AM +0000, John Garry wrote:
Add flag IOMAP_ATOMIC_WRITE to indicate to the FS that an atomic write
bio is being created and all the rules there need to be followed.
It is the task of the FS iomap iter callbacks to ensure that the mapping
created adheres to those rules, like size is power-of-2, is at a
naturally-aligned offset, etc.
The mapping being returned by the filesystem can span a much greater
range than the actual IO needs - the iomap itself is not guaranteed
to be aligned to anything in particular, but the IO location within
that map can still conform to atomic IO constraints. See how
iomap_sector() calculates the actual LBA address of the IO from
the iomap and the current file position the IO is being done at.
hence I think saying "the filesysetm should make sure all IO
alignment adheres to atomic IO rules is probably wrong. The iomap
layer doesn't care what the filesystem does, all it cares about is
whether the IO can be done given the extent map that was returned to
it.
Indeed, iomap_dio_bio_iter() is doing all these alignment checks for
normal DIO reads and writes which must be logical block sized
aligned. i.e. this check:
if ((pos | length) & (bdev_logical_block_size(iomap->bdev) - 1) ||
!bdev_iter_is_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
Hence I think that atomic IO units, which are similarly defined by
the bdev, should be checked at the iomap layer, too. e.g, by
following up with:
if ((dio->iocb->ki_flags & IOCB_ATOMIC) &&
((pos | length) & (bdev_atomic_unit_min(iomap->bdev) - 1) ||
!bdev_iter_is_atomic_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
At this point, filesystems don't really need to know anything about
atomic IO - if they've allocated a large contiguous extent (e.g. via
fallocate()), then RWF_ATOMIC will just work for the cases where the
block device supports it...
This then means that stuff like XFS extent size hints only need to
check when the hint is set that it is aligned to the underlying
device atomic IO constraints. Then when it sees the IOMAP_ATOMIC
modifier, it can fail allocation if it can't get extent size hint
aligned allocation.
IOWs, I'm starting to think this doesn't need any change to the
on-disk format for XFS - it can be driven entirely through two
dynamic mechanisms:
1. (IOMAP_WRITE | IOMAP_ATOMIC) requests from the direct IO layer
which causes mapping/allocation to fail if it can't allocate (or
map) atomic IO compatible extents for the IO.
2. FALLOC_FL_ATOMIC preallocation flag modifier to tell fallocate()
to force alignment of all preallocated extents to atomic IO
constraints.
This doesn't require extent size hints at all. The filesystem can
query the bdev at mount time, store the min/max atomic write sizes,
and then use them for all requests that have _ATOMIC modifiers set
on them.
With iomap doing the same "get the atomic constraints from the bdev"
style lookups for per-IO file offset and size checking, I don't
think we actually need extent size hints or an on-disk flag to force
extent size hint alignment.
That doesn't mean extent size hints can't be used - it just means
that extent size hints have to be constrained to being aligned to
atomic IOs (e.g. extent size hint must be an integer multiple of the
max atomic IO size). This then acts as a modifier for _ATOMIC
context allocations, much like it is a modifier for normal
allocations now.
In iomap_dio_bio_iter(), ensure that for a non-dsync iocb that the mapping
is not dirty nor unmapped.
A write should only produce a single bio, so error when it doesn't.
How do we get here without space having been allocated for the
write?
Perhaps what this is trying to do is make RWF_ATOMIC only be valid
into written space? I mean, this will fail with preallocated space
(IOMAP_UNWRITTEN) even though we still have exactly the RWF_ATOMIC
all-or-nothing behaviour guaranteed after a crash because of journal
recovery behaviour. i.e. if the unwritten conversion gets written to
the journal, the data will be there. If it isn't written to the
journal, then the space remains unwritten and there's no data across
that entire range....
So I'm not really sure that either of these checks are valid or why
they are actually needed....
@@ -397,6 +408,12 @@ static loff_t iomap_dio_bio_iter(const struct iomap_iter *iter, } n = bio->bi_iter.bi_size;+ if (atomic_write && n != length) {+ /* This bio should have covered the complete length */+ ret = -EINVAL;+ bio_put(bio);+ goto out;
Why? The actual bio can be any length that meets the aligned
criteria between min and max, yes? So it's valid to split a
RWF_ATOMIC write request up into multiple min unit sized bios, is it
not? I mean, that's the whole point of the min/max unit setup, isn't
it? That the max sized write only guarantees that it will tear at
min unit boundaries, not within those min unit boundaries? If
I've understood this correctly, then why does this "single bio for
large atomic write" constraint need to exist?
We already have an IOMAP_WRITE flag, so IOMAP_ATOMIC is the modifier
for the write IO behaviour (like NOWAIT), not a replacement write
flag.
-Dave.
--
Dave Chinner
david@fromorbit.com
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-03 04:34:26
On Mon, Oct 02, 2023 at 08:00:10PM -0700, Darrick J. Wong wrote:
On Tue, Oct 03, 2023 at 12:16:26PM +1100, Dave Chinner wrote:
quoted
On Fri, Sep 29, 2023 at 10:27:16AM +0000, John Garry wrote:
quoted
The low-space allocator doesn't honour the alignment requirement, so don't
attempt to even use it (when we have an alignment requirement).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 4 ++++
1 file changed, 4 insertions(+)
How does this happen?
The earlier failing aligned allocations will clear alignment before
we get here....
I was thinking the predicate should be xfs_inode_force_align(ip) to save
me/us from thinking about all the other weird ways args->alignment could
end up 1.
/* forced-alignment means we don't use low mode */
if (xfs_inode_force_align(ip))
return -ENOSPC;
See the email I just wrote about not needing per-inode on-disk state
or even extent size hints for doing allocation for atomic IO. Atomic
write unit alignment is a device parameter (similar to stripe unit)
that applies to context specific allocation requests - it's not an
inode property as such....
Cheers,
Dave.
--
Dave Chinner
david@fromorbit.com
How would this differ from stx_atomic_write_unit_min != 0?
Yeah, I suppose that we can just not set this for the case of
stx_atomic_write_unit_min == 0.
Please use the STATX_ATTR_WRITE_ATOMIC flag to indicate that the
filesystem, file and underlying device support atomic writes when
the values are non-zero. The whole point of the attribute mask is
that the caller can check the mask for supported functionality
without having to read every field in the statx structure to
determine if the functionality it wants is present.
Sure, but again that would be just checking atomic_write_unit_min_bytes
or another atomic write block setting as that is the only way to tell
from the block layer (if atomic writes are supported), so it will be
something like:
if (request_mask & STATX_WRITE_ATOMIC &&
queue_atomic_write_unit_min_bytes(bdev->bd_queue)) {
stat->atomic_write_unit_min =
queue_atomic_write_unit_min_bytes(bdev->bd_queue);
stat->atomic_write_unit_max =
queue_atomic_write_unit_max_bytes(bdev->bd_queue);
stat->attributes |= STATX_ATTR_WRITE_ATOMIC;
stat->attributes_mask |= STATX_ATTR_WRITE_ATOMIC;
stat->result_mask |= STATX_WRITE_ATOMIC;
}
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-03 08:37:47
On 02/10/2023 20:12, Bart Van Assche wrote:
quoted
> + if (!is_power_of_2(iov_iter_count(iter)))
> + return false;
This rule comes from FS block alignment and NVMe atomic boundary.
FSes (XFS) have discontiguous extents. We need to ensure that an
atomic write does not cross discontiguous extents. To do this we
ensure extent length and alignment and limit
atomic_write_unit_max_bytes to that.
For NVMe, an atomic write boundary is a boundary in LBA space which an
atomic write should not cross. We limit atomic_write_unit_max_bytes
such that it is evenly divisible into this atomic write boundary.
To ensure that the write does not cross these alignment boundaries we
say that it must be naturally aligned and a power-of-2 in length.
We may be able to relax this rule but I am not sure it buys us
anything - typically we want to be writing a 64KB block aligned to
64KB, for example.
It seems to me that the requirement is_power_of_2(iov_iter_count(iter))
is necessary for some filesystems but not for all filesystems.
Restrictions that are specific to a single filesystem (XFS) should not
occur in code that is intended to be used by all filesystems
(blkdev_atomic_write_valid()).
I don't think that is_power_of_2(write length) is specific to XFS. It is
just a simple mathematical method to ensure we obey length and alignment
requirement always.
Furthermore, if ext4 wants to support atomic writes, for example, then
it will probably base that on bigalloc. And bigalloc is power-of-2 based.
As for the rules, current proposal is:
- atomic_write_unit_min and atomic_write_unit_max are power-of-2
- write needs to be at a naturally aligned file offset
- write length needs to be a power-of-2 between atomic_write_unit_min
and atomic_write_unit_max, inclusive
Those could be relaxed to:
- atomic_write_unit_min and atomic_write_unit_max are power-of-2
- write length needs to be a multiple of atomic_write_unit_min and a max
of atomic_write_unit_max
- write needs to be at an offset aligned to atomic_write_unit_min
- write cannot cross atomic_write_unit_max boundary within the file
Are the relaxed rules better? I don't think so, and I don't like "write
cannot cross atomic_write_unit_max boundary" in terms of wording.
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-03 10:14:40
On 03/10/2023 02:42, Dave Chinner wrote:
On Fri, Sep 29, 2023 at 10:27:18AM +0000, John Garry wrote:
quoted
From: "Darrick J. Wong" <djwong@kernel.org>
The existing extsize hint code already did the work of expanding file
range mapping requests so that the range is aligned to the hint value.
Now add the code we need to guarantee that the space allocations are
also always aligned.
XXX: still need to check all this with reflink
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Co-developed-by: John Garry <john.g.garry@oracle.com>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/libxfs/xfs_bmap.c | 22 +++++++++++++++++-----
fs/xfs/xfs_iomap.c | 4 +++-
2 files changed, 20 insertions(+), 6 deletions(-)
This smells wrong.
If a filesystem has a stripe unit set (hence stripe_align is
non-zero) then any IO that crosses stripe unit boundaries will not
be atomic - they will require multiple IOs to different devices.
Hence if the filesystem has a stripe unit set, then all forced
alignment hints for atomic IO *must* be an exact integer divider
of the stripe unit. hence when an atomic IO bundle is aligned, the
atomic boundaries within the bundle always fall on a stripe unit
boundary and never cross devices.
IOWs, for a striped filesystem, the maximum size/alignment for a
single atomic IO unit is the stripe unit.
ok, when I added this I was looking at being robust against wacky
scenarios when that is not true, like forcealign = stripe alignment * 2.
Please note that this forcealign feature is being added with the view
that it can be useful for other scenarios, and not just atomic writes.
This should be enforced when the forced align flag is set on the
inode (i.e. from the ioctl)
ok, fine.
quoted
+
if (align) {
if (xfs_bmap_extsize_align(mp, &ap->got, &ap->prev, align, 0,
ap->eof, 0, ap->conv, &ap->offset,
@@ -3543,10 +3556,10 @@ xfs_bmap_btalloc_at_eof( /* * Allocation failed, so turn return the allocation args to their- * original non-aligned state so the caller can proceed on allocation- * failure as if this function was never called.+ * original state so the caller can proceed on allocation failure as+ * if this function was never called. */- args->alignment = 1;+ args->alignment = orig_alignment; return 0; }
Urk. Not sure that is right, it's certainly a change of behaviour.
Is it really a change in behaviour? We just restore the args->alignment
value, which was originally always 1.
As described in the comment, above, args->alignment is temporarily set
to the stripe align to try to align a new alloc on a stripe boundary.
Ah. Now I see. This abuses the stripe alignment code to try to
implement this new inode allocation alignment restriction, rather
than just making the extent size hint alignment mandatory....
Yeah, this can be done better... :)
As it is, I have been working on a series that reworks all this
allocator code to separate out the aligned IO from the exact EOF
allocation case to help clean this up for better perag selection
during allocation. I think that needs to be done first before we go
making the alignment code more intricate like this....
-Dave.
ok, fine. I think that we'll just keep this code as is until that code
you mention appears, apart from enforcing stripe alignment % forcealign
== 0.
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-03 10:24:07
On 03/10/2023 04:00, Darrick J. Wong wrote:
quoted
How does this happen?
The earlier failing aligned allocations will clear alignment before
we get here....
I was thinking the predicate should be xfs_inode_force_align(ip) to save
me/us from thinking about all the other weird ways args->alignment could
end up 1.
/* forced-alignment means we don't use low mode */
if (xfs_inode_force_align(ip))
My idea was that if we add another feature which requires
args->alignment > 1 be honoured, then we would need to change this code
to cover both features, so better just check args->alignment > 1.
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-03 10:57:41
On 03/10/2023 04:32, Dave Chinner wrote:
On Fri, Sep 29, 2023 at 10:27:20AM +0000, John Garry wrote:
quoted
Support providing info on atomic write unit min and max for an inode.
For simplicity, currently we limit the min at the FS block size, but a
lower limit could be supported in future.
The atomic write unit min and max is limited by the guaranteed extent
alignment for the inode.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/xfs_iops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_iops.h | 4 ++++
2 files changed, 55 insertions(+)
Also, we don't use variable name shorthand for function names -
xfs_get_atomic_write_hint(ip) to match xfs_get_extsz_hint(ip)
would be appropriate, right?
Changing the name format would be ok. However we are not returning a
hint, but rather the inode atomic write unit min and max values in FS
blocks. Anyway, I'll look to rework the name.
These should be set in the buftarg at mount time, like we do with
sector size masks. Then we don't need to convert them to fsbs on
every single lookup.
ok, fine. However I do still have a doubt on whether these values should
be changeable - please see (small) comment about
atomic_write_max_sectors in patch 7/21
quoted
+ /* for RT, unset extsize gives hint of 1 */
+ /* for !RT, unset extsize gives hint of 0 */
+ if (extsz_hint && (XFS_IS_REALTIME_INODE(ip) ||
+ (ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN)))
Logic is non-obvious. The compound is (rt || force), not
(extsz && rt), so it took me a while to actually realise I read this
incorrectly.
if (extsz_hint &&
(XFS_IS_REALTIME_INODE(ip) ||
(ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN))) {
quoted
+ align = extsz_hint;
+ else
+ align = 1;
And now the logic looks wrong to me. We don't want to use extsz hint
for RT inodes if force align is not set, this will always use it
regardless of the fact it has nothing to do with force alignment.
extsz_hint comes from xfs_get_extsz_hint(), which gives us the SB
extsize for the RT inode and this alignment is guaranteed, no?
Indeed, if XFS_DIFLAG2_FORCEALIGN is not set, then shouldn't this
always return min/max = 0 because atomic alignments are not in us on
this inode?
As above, for RT I thought that extsize alignment was guaranteed and we
don't need to bother with XFS_DIFLAG2_FORCEALIGN there.
i.e. the first thing this code should do is:
*unit_min_fsb = 0;
*unit_max_fsb = 0;
if (!(ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN))
return;
Then we can check device support:
if (!buftarg->bt_atomic_write_max)
return;
Then we can check for extent size hints. If that's not set:
align = xfs_get_extsz_hint(ip);
if (align <= 1) {
unit_min_fsb = 1;
unit_max_fsb = 1;
return;
}
And finally, if there is an extent size hint, we can return that.
Why is it valid for a device to have a zero minimum size?
It's not valid. Local variables atomic_write_unit_max and
atomic_write_unit_min unit here is FS blocks - maybe I should change names.
The idea is that for simplicity we won't support atomic writes for XFS
of size less than 1x FS block initially. So if the bdev has - for
example - queue_atomic_write_unit_min_bytes() == 2K and
queue_atomic_write_unit_max_bytes() == 64K, then (ignoring alignment) we
say that unit_min_fsb = 1 and unit_max_fsb = 16 (for 4K FS blocks).
If it can
set a maximum, it should -always- set a minimum size as logical
sector size is a valid lower bound, yes?
Nothing here guarantees the power-of-2 sizes that the RWF_ATOMIC
user interface requires....
atomic_write_unit_min and atomic_write_unit_max will be powers-of-2 (or 0).
But, you are right, we don't check align is a power-of-2 - that can be
added.
It also doesn't check that the extent size hint is aligned with
atomic write units.
If we add a check for align being a power-of-2 and atomic_write_unit_min
and atomic_write_unit_max are already powers-of-2, then this can be
relied on, right?
It also doesn't check either against stripe unit alignment....
As mentioned in earlier response, this could be enforced.
quoted
+}
+
STATIC int
xfs_vn_getattr(
struct mnt_idmap *idmap,
That's just nasty. We pull byte units from the bdev, convert them to
fsb to round them, then convert them back to byte counts. We should
be doing all the work in one set of units....
If the min/max are zero, then atomic writes are not supported on
this inode, right? Why would we set any of the attributes or result
mask to say it is supported on this file?
ok, we won't set STATX_ATTR_WRITE_ATOMIC for min/max are zero
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-03 12:56:15
On 03/10/2023 05:24, Dave Chinner wrote:
On Fri, Sep 29, 2023 at 10:27:21AM +0000, John Garry wrote:
quoted
Add flag IOMAP_ATOMIC_WRITE to indicate to the FS that an atomic write
bio is being created and all the rules there need to be followed.
It is the task of the FS iomap iter callbacks to ensure that the mapping
created adheres to those rules, like size is power-of-2, is at a
naturally-aligned offset, etc.
The mapping being returned by the filesystem can span a much greater
range than the actual IO needs - the iomap itself is not guaranteed
to be aligned to anything in particular, but the IO location within
that map can still conform to atomic IO constraints. See how
iomap_sector() calculates the actual LBA address of the IO from
the iomap and the current file position the IO is being done at.
I see, but I was working on the basis that the filesystem produces an
iomap which itself conforms to all the rules. And that is because the
atomic write unit min and max for the file depend on the extent
alignment, which only the filesystem is aware of.
hence I think saying "the filesysetm should make sure all IO
alignment adheres to atomic IO rules is probably wrong. The iomap
layer doesn't care what the filesystem does, all it cares about is
whether the IO can be done given the extent map that was returned to
it.
Indeed, iomap_dio_bio_iter() is doing all these alignment checks for
normal DIO reads and writes which must be logical block sized
aligned. i.e. this check:
if ((pos | length) & (bdev_logical_block_size(iomap->bdev) - 1) ||
!bdev_iter_is_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
Hence I think that atomic IO units, which are similarly defined by
the bdev, should be checked at the iomap layer, too. e.g, by
following up with:
if ((dio->iocb->ki_flags & IOCB_ATOMIC) &&
((pos | length) & (bdev_atomic_unit_min(iomap->bdev) - 1) ||
!bdev_iter_is_atomic_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
Seems ok for at least enforcing alignment for the bdev. Again,
filesystem extent alignment is my concern.
At this point, filesystems don't really need to know anything about
atomic IO - if they've allocated a large contiguous extent (e.g. via
fallocate()), then RWF_ATOMIC will just work for the cases where the
block device supports it...
This then means that stuff like XFS extent size hints only need to
check when the hint is set that it is aligned to the underlying
device atomic IO constraints. Then when it sees the IOMAP_ATOMIC
modifier, it can fail allocation if it can't get extent size hint
aligned allocation.
I am not sure what you mean by allocation in this context. I assume that
fallocate allocates the extents, but they remain unwritten. So if we
then dd into that file to zero it or init it any other way, they become
written and the extent size hint or bdev atomic write constraints would
be just ignored then.
BTW, if you remember, we did propose an XFS fallocate extension for
extent alignment in the initial RFC, but decided to drop it.
IOWs, I'm starting to think this doesn't need any change to the
on-disk format for XFS - it can be driven entirely through two
dynamic mechanisms:
1. (IOMAP_WRITE | IOMAP_ATOMIC) requests from the direct IO layer
which causes mapping/allocation to fail if it can't allocate (or
map) atomic IO compatible extents for the IO.
2. FALLOC_FL_ATOMIC preallocation flag modifier to tell fallocate()
to force alignment of all preallocated extents to atomic IO
constraints.
Would that be a sticky flag? What stops the extents mutating before the
atomic write?
This doesn't require extent size hints at all. The filesystem can
query the bdev at mount time, store the min/max atomic write sizes,
and then use them for all requests that have _ATOMIC modifiers set
on them.
A drawback is that the storage device may support atomic write unit max
much bigger than the user requires and cause inefficient alignment, e.g.
bdev atomic write unit max = 1M, and we only ever want 8KB atomic
writes. But you are mentioning extent size hints can be paid attention
to, below.
With iomap doing the same "get the atomic constraints from the bdev"
style lookups for per-IO file offset and size checking, I don't
think we actually need extent size hints or an on-disk flag to force
extent size hint alignment.
That doesn't mean extent size hints can't be used - it just means
that extent size hints have to be constrained to being aligned to
atomic IOs (e.g. extent size hint must be an integer multiple of the
max atomic IO size).
Yeah, well I think that we already agreed something like this.
This then acts as a modifier for _ATOMIC
context allocations, much like it is a modifier for normal
allocations now.
quoted
In iomap_dio_bio_iter(), ensure that for a non-dsync iocb that the mapping
is not dirty nor unmapped.
A write should only produce a single bio, so error when it doesn't.
How do we get here without space having been allocated for the
write?
I don't think that we can, but we are checking that the space is also
written.
Perhaps what this is trying to do is make RWF_ATOMIC only be valid
into written space?
Yes, and we now detail this in the man pages.
I mean, this will fail with preallocated space
(IOMAP_UNWRITTEN) even though we still have exactly the RWF_ATOMIC
all-or-nothing behaviour guaranteed after a crash because of journal
recovery behaviour. i.e. if the unwritten conversion gets written to
the journal, the data will be there. If it isn't written to the
journal, then the space remains unwritten and there's no data across
that entire range....
So I'm not really sure that either of these checks are valid or why
they are actually needed....
I think that the idea is that the space is already written and the
metadata for the space is persisted or going to be. Darrick guided me on
this, so hopefully can comment more.
@@ -397,6 +408,12 @@ static loff_t iomap_dio_bio_iter(const struct iomap_iter *iter, } n = bio->bi_iter.bi_size;+ if (atomic_write && n != length) {+ /* This bio should have covered the complete length */+ ret = -EINVAL;+ bio_put(bio);+ goto out;
Why? The actual bio can be any length that meets the aligned
criteria between min and max, yes?
The write also needs to be a power-of-2 in length. atomic write min and
max will always be a power-of-2.
So it's valid to split a
RWF_ATOMIC write request up into multiple min unit sized bios, is it
not?
It is not. In the RFC we sent in May there was a scheme to break up the
atomic write into multiple userspace block-sized bios, but that is no
longer supported.
Now an atomic write only produces a single bio. So userspace may do a
16KB atomic write, for example, and we only ever issue that as a single
16KB operation to the storage device.
I mean, that's the whole point of the min/max unit setup, isn't
it?
The point of min/max is to ensure that userspace executes an atomic
write which is guaranteed to be only ever issued as a single write to
the storage device. In addition, the length and position for that write
conforms to the storage device atomic write constraints.
That the max sized write only guarantees that it will tear at
min unit boundaries, not within those min unit boundaries?
There is no tearing. As mentioned, the RFC in May did support some
splitting but we decided to drop it.
If
I've understood this correctly, then why does this "single bio for
large atomic write" constraint need to exist?
atomic write means that a write will never we torn.
How would this differ from stx_atomic_write_unit_min != 0?
Yeah, I suppose that we can just not set this for the case of
stx_atomic_write_unit_min == 0.
Please use the STATX_ATTR_WRITE_ATOMIC flag to indicate that the
filesystem, file and underlying device support atomic writes when
the values are non-zero. The whole point of the attribute mask is
that the caller can check the mask for supported functionality
without having to read every field in the statx structure to
determine if the functionality it wants is present.
Sure, but again that would be just checking atomic_write_unit_min_bytes or
another atomic write block setting as that is the only way to tell from the
block layer (if atomic writes are supported), so it will be something like:
if (request_mask & STATX_WRITE_ATOMIC &&
queue_atomic_write_unit_min_bytes(bdev->bd_queue)) {
stat->atomic_write_unit_min =
queue_atomic_write_unit_min_bytes(bdev->bd_queue);
stat->atomic_write_unit_max =
queue_atomic_write_unit_max_bytes(bdev->bd_queue);
stat->attributes |= STATX_ATTR_WRITE_ATOMIC;
stat->attributes_mask |= STATX_ATTR_WRITE_ATOMIC;
stat->result_mask |= STATX_WRITE_ATOMIC;
The result_mask (which becomes the statx stx_mask) needs to have
STATX_WRITE_ATOMIC set any time a filesystem responds to
STATX_WRITE_ATOMIC being set in the request_mask, even if the response
is "not supported".
The attributes_mask also needs to have STATX_ATTR_WRITE_ATOMIC set if
the filesystem+file can support the flag, even if it's not currently set
for that file. This should get turned into a generic vfs helper for the
next fs that wants to support atomic write units:
static void generic_fill_statx_atomic_writes(struct kstat *stat,
struct block_device *bdev)
{
u64 min_bytes;
/* Confirm that the fs driver knows about this statx request */
stat->result_mask |= STATX_WRITE_ATOMIC;
/* Confirm that the file attribute is known to the fs. */
stat->attributes_mask |= STATX_ATTR_WRITE_ATOMIC;
/* Fill out the rest of the atomic write fields if supported */
min_bytes = queue_atomic_write_unit_min_bytes(bdev->bd_queue);
if (min_bytes == 0)
return;
stat->atomic_write_unit_min = min_bytes;
stat->atomic_write_unit_max =
queue_atomic_write_unit_max_bytes(bdev->bd_queue);
/* Atomic writes actually supported on this file. */
stat->attributes |= STATX_ATTR_WRITE_ATOMIC;
}
and then:
if (request_mask & STATX_WRITE_ATOMIC)
generic_fill_statx_atomic_writes(stat, bdev);
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2023-10-03 16:10:33
On Tue, Oct 03, 2023 at 11:56:52AM +0100, John Garry wrote:
On 03/10/2023 04:32, Dave Chinner wrote:
quoted
On Fri, Sep 29, 2023 at 10:27:20AM +0000, John Garry wrote:
quoted
Support providing info on atomic write unit min and max for an inode.
For simplicity, currently we limit the min at the FS block size, but a
lower limit could be supported in future.
The atomic write unit min and max is limited by the guaranteed extent
alignment for the inode.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/xfs_iops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_iops.h | 4 ++++
2 files changed, 55 insertions(+)
Also, we don't use variable name shorthand for function names -
xfs_get_atomic_write_hint(ip) to match xfs_get_extsz_hint(ip)
would be appropriate, right?
Changing the name format would be ok. However we are not returning a hint,
but rather the inode atomic write unit min and max values in FS blocks.
Anyway, I'll look to rework the name.
These should be set in the buftarg at mount time, like we do with
sector size masks. Then we don't need to convert them to fsbs on
every single lookup.
ok, fine. However I do still have a doubt on whether these values should be
changeable - please see (small) comment about atomic_write_max_sectors in
patch 7/21
No, this /does/ have to be looked up every time, because the geometry of
the device can change underneath the fs without us knowing about it. If
someone snapshots an LV with different (or no) atomic write abilities
then we'll be doing the wrong checks.
And yes, it's true that this is a benign problem because we don't lock
anything in the bdev here and the block device driver will eventually
have to catch that anyway.
quoted
quoted
+ /* for RT, unset extsize gives hint of 1 */
+ /* for !RT, unset extsize gives hint of 0 */
+ if (extsz_hint && (XFS_IS_REALTIME_INODE(ip) ||
+ (ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN)))
Logic is non-obvious. The compound is (rt || force), not
(extsz && rt), so it took me a while to actually realise I read this
incorrectly.
if (extsz_hint &&
(XFS_IS_REALTIME_INODE(ip) ||
(ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN))) {
quoted
+ align = extsz_hint;
+ else
+ align = 1;
And now the logic looks wrong to me. We don't want to use extsz hint
for RT inodes if force align is not set, this will always use it
regardless of the fact it has nothing to do with force alignment.
extsz_hint comes from xfs_get_extsz_hint(), which gives us the SB extsize
for the RT inode and this alignment is guaranteed, no?
One can also set an extent size hint on realtime files that is a
multiple of the realtime extent size. IOWs, I can decide that space on
the rt volume should be given out in 32k chunks, and then later decide
that a specific rt file should actually try for 64k chunks.
quoted
Indeed, if XFS_DIFLAG2_FORCEALIGN is not set, then shouldn't this
always return min/max = 0 because atomic alignments are not in us on
this inode?
As above, for RT I thought that extsize alignment was guaranteed and we
don't need to bother with XFS_DIFLAG2_FORCEALIGN there.
quoted
i.e. the first thing this code should do is:
*unit_min_fsb = 0;
*unit_max_fsb = 0;
if (!(ip->i_diflags2 & XFS_DIFLAG2_FORCEALIGN))
return;
Then we can check device support:
if (!buftarg->bt_atomic_write_max)
return;
Then we can check for extent size hints. If that's not set:
align = xfs_get_extsz_hint(ip);
if (align <= 1) {
unit_min_fsb = 1;
unit_max_fsb = 1;
return;
}
And finally, if there is an extent size hint, we can return that.
Why is it valid for a device to have a zero minimum size?
It's not valid. Local variables atomic_write_unit_max and
atomic_write_unit_min unit here is FS blocks - maybe I should change names.
Yes, please, the variable names throughout are long enough to make for
ugly code.
/* "awu" = atomic write unit */
xfs_filblks_t awu_min_fsb, align;
u64 awu_min_bytes;
awu_min_bytes = queue_atomic_write_unit_min_bytes(bdev->bd_queue);
if (!awu_min_bytes) {
/* Not supported at all. */
*unit_min_fsb = 0;
return;
}
awu_min_fsb = XFS_B_TO_FSBT(mp, awu_min_bytes);
if (awu_min_fsb < 1) {
/* Don't allow smaller than fsb atomic writes */
*unit_min_fsb = 1;
return;
}
*unit_min_fsb = min(awu_min_fsb, align);
--D
The idea is that for simplicity we won't support atomic writes for XFS of
size less than 1x FS block initially. So if the bdev has - for example -
queue_atomic_write_unit_min_bytes() == 2K and
queue_atomic_write_unit_max_bytes() == 64K, then (ignoring alignment) we say
that unit_min_fsb = 1 and unit_max_fsb = 16 (for 4K FS blocks).
quoted
If it can
set a maximum, it should -always- set a minimum size as logical
sector size is a valid lower bound, yes?
Nothing here guarantees the power-of-2 sizes that the RWF_ATOMIC
user interface requires....
atomic_write_unit_min and atomic_write_unit_max will be powers-of-2 (or 0).
But, you are right, we don't check align is a power-of-2 - that can be
added.
quoted
It also doesn't check that the extent size hint is aligned with
atomic write units.
If we add a check for align being a power-of-2 and atomic_write_unit_min and
atomic_write_unit_max are already powers-of-2, then this can be relied on,
right?
quoted
It also doesn't check either against stripe unit alignment....
As mentioned in earlier response, this could be enforced.
quoted
quoted
+}
+
STATIC int
xfs_vn_getattr(
struct mnt_idmap *idmap,
That's just nasty. We pull byte units from the bdev, convert them to
fsb to round them, then convert them back to byte counts. We should
be doing all the work in one set of units....
If the min/max are zero, then atomic writes are not supported on
this inode, right? Why would we set any of the attributes or result
mask to say it is supported on this file?
ok, we won't set STATX_ATTR_WRITE_ATOMIC for min/max are zero
Thanks,
John
From: Bart Van Assche <bvanassche@acm.org> Date: 2023-10-03 16:40:52
On 9/29/23 03:27, John Garry wrote:
+What: /sys/block/<disk>/atomic_write_unit_min_bytes
+Date: May 2023
+Contact: Himanshu Madhani [off-list ref]
+Description:
+ [RO] This parameter specifies the smallest block which can
+ be written atomically with an atomic write operation. All
+ atomic write operations must begin at a
+ atomic_write_unit_min boundary and must be multiples of
+ atomic_write_unit_min. This value must be a power-of-two.
I have two comments about these descriptions:
- Referring to "atomic writes" only is not sufficient. It should be
explained that in this context "atomic" means "indivisible" only and
also that there are no guarantees that the data written by an atomic
write will survive a power failure. See also the difference between
the NVMe parameters AWUN and AWUPF.
- atomic_write_unit_min_bytes will always be the logical block size so I
don't think it is useful to make the block layer track this value nor
to export this value through sysfs.
Thanks,
Bart.
From: Bart Van Assche <bvanassche@acm.org> Date: 2023-10-03 16:45:35
On 10/3/23 01:37, John Garry wrote:
I don't think that is_power_of_2(write length) is specific to XFS.
I think this is specific to XFS. Can you show me the F2FS code that
restricts the length of an atomic write to a power of two? I haven't
found it. The only power-of-two check that I found in F2FS is the
following (maybe I overlooked something):
$ git grep -nH is_power fs/f2fs
fs/f2fs/super.c:3914: if (!is_power_of_2(zone_sectors)) {
Thanks,
Bart.
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2023-10-03 16:47:58
On Tue, Oct 03, 2023 at 03:24:23PM +1100, Dave Chinner wrote:
On Fri, Sep 29, 2023 at 10:27:21AM +0000, John Garry wrote:
quoted
Add flag IOMAP_ATOMIC_WRITE to indicate to the FS that an atomic write
bio is being created and all the rules there need to be followed.
It is the task of the FS iomap iter callbacks to ensure that the mapping
created adheres to those rules, like size is power-of-2, is at a
naturally-aligned offset, etc.
The mapping being returned by the filesystem can span a much greater
range than the actual IO needs - the iomap itself is not guaranteed
to be aligned to anything in particular, but the IO location within
that map can still conform to atomic IO constraints. See how
iomap_sector() calculates the actual LBA address of the IO from
the iomap and the current file position the IO is being done at.
hence I think saying "the filesysetm should make sure all IO
alignment adheres to atomic IO rules is probably wrong. The iomap
layer doesn't care what the filesystem does, all it cares about is
whether the IO can be done given the extent map that was returned to
it.
Indeed, iomap_dio_bio_iter() is doing all these alignment checks for
normal DIO reads and writes which must be logical block sized
aligned. i.e. this check:
if ((pos | length) & (bdev_logical_block_size(iomap->bdev) - 1) ||
!bdev_iter_is_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
Hence I think that atomic IO units, which are similarly defined by
the bdev, should be checked at the iomap layer, too. e.g, by
following up with:
if ((dio->iocb->ki_flags & IOCB_ATOMIC) &&
((pos | length) & (bdev_atomic_unit_min(iomap->bdev) - 1) ||
!bdev_iter_is_atomic_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
At this point, filesystems don't really need to know anything about
atomic IO - if they've allocated a large contiguous extent (e.g. via
fallocate()), then RWF_ATOMIC will just work for the cases where the
block device supports it...
This then means that stuff like XFS extent size hints only need to
check when the hint is set that it is aligned to the underlying
device atomic IO constraints. Then when it sees the IOMAP_ATOMIC
modifier, it can fail allocation if it can't get extent size hint
aligned allocation.
IOWs, I'm starting to think this doesn't need any change to the
on-disk format for XFS - it can be driven entirely through two
dynamic mechanisms:
1. (IOMAP_WRITE | IOMAP_ATOMIC) requests from the direct IO layer
which causes mapping/allocation to fail if it can't allocate (or
map) atomic IO compatible extents for the IO.
2. FALLOC_FL_ATOMIC preallocation flag modifier to tell fallocate()
to force alignment of all preallocated extents to atomic IO
constraints.
Ugh, let's not relitigate problems that you (Dave) and I have already
solved.
Back in 2018, our internal proto-users of pmem asked for aligned
allocations so they could use PMD mappings to reduce TLB pressure. At
the time, you and I talked on IRC about whether that should be done via
fallocate flag or setting extszinherit+sunit at mkfs time. We decided
against adding fallocate flags because linux-api bikeshed hell.
Ever since, we've been shipping UEK with a mkfs.xmem scripts that
automates computing the mkfs.xfs geometry CLI options. It works,
mostly, except for the unaligned allocations that one gets when the free
space gets fragmented. The xfsprogs side of the forcealign patchset
moves most of the mkfs.xmem cli option setting logic into mkfs itself,
and the kernel side shuts off the lowspace allocator to fix the
fragmentation problem.
I'd rather fix the remaining quirks and not reinvent solved solutions,
as popular as that is in programming circles.
Why is mandatory allocation alignment for atomic writes different?
Forcealign solves the problem for NVME/SCSI AWU and pmem PMD in the same
way with the same control knobs for sysadmins. I don't want to have
totally separate playbooks for accomplishing nearly the same things.
I don't like encoding hardware details in the fallocate uapi either.
That implies adding FALLOC_FL_HUGEPAGE for pmem, and possibly
FALLOC_FL_{SUNIT,SWIDTH} for users with RAIDs.
This doesn't require extent size hints at all. The filesystem can
query the bdev at mount time, store the min/max atomic write sizes,
and then use them for all requests that have _ATOMIC modifiers set
on them.
With iomap doing the same "get the atomic constraints from the bdev"
style lookups for per-IO file offset and size checking, I don't
think we actually need extent size hints or an on-disk flag to force
extent size hint alignment.
That doesn't mean extent size hints can't be used - it just means
that extent size hints have to be constrained to being aligned to
atomic IOs (e.g. extent size hint must be an integer multiple of the
max atomic IO size). This then acts as a modifier for _ATOMIC
context allocations, much like it is a modifier for normal
allocations now.
(One behavior change that comes with FORCEALIGN is that without it,
extent size hints affect only the alignment of the file range mappings.
With FORCEALIGN, the space allocation itself *and* the mapping are
aligned.)
The one big downside of FORCEALIGN is that the extent size hint can
become misaligned with the AWU (or pagetable) geometry if the fs is
moved to a different computing environment. I prefer not to couple the
interface to the hardware because that leaves open the possibility for
users to discover more use cases.
quoted
In iomap_dio_bio_iter(), ensure that for a non-dsync iocb that the mapping
is not dirty nor unmapped.
A write should only produce a single bio, so error when it doesn't.
How do we get here without space having been allocated for the
write?
Perhaps what this is trying to do is make RWF_ATOMIC only be valid
into written space? I mean, this will fail with preallocated space
(IOMAP_UNWRITTEN) even though we still have exactly the RWF_ATOMIC
all-or-nothing behaviour guaranteed after a crash because of journal
recovery behaviour. i.e. if the unwritten conversion gets written to
the journal, the data will be there. If it isn't written to the
journal, then the space remains unwritten and there's no data across
that entire range....
So I'm not really sure that either of these checks are valid or why
they are actually needed....
This requires O_DSYNC (or RWF_DSYNC) for atomic writes to unwritten or
COW space. We want failures in forcing the log transactions for the
endio processing to be reported to the pwrite caller as EIO, right?
--D
@@ -397,6 +408,12 @@ static loff_t iomap_dio_bio_iter(const struct iomap_iter *iter, } n = bio->bi_iter.bi_size;+ if (atomic_write && n != length) {+ /* This bio should have covered the complete length */+ ret = -EINVAL;+ bio_put(bio);+ goto out;
Why? The actual bio can be any length that meets the aligned
criteria between min and max, yes? So it's valid to split a
RWF_ATOMIC write request up into multiple min unit sized bios, is it
not? I mean, that's the whole point of the min/max unit setup, isn't
it? That the max sized write only guarantees that it will tear at
min unit boundaries, not within those min unit boundaries? If
I've understood this correctly, then why does this "single bio for
large atomic write" constraint need to exist?
We already have an IOMAP_WRITE flag, so IOMAP_ATOMIC is the modifier
for the write IO behaviour (like NOWAIT), not a replacement write
flag.
-Dave.
--
Dave Chinner
david@fromorbit.com
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-04 01:16:32
On Tue, Oct 03, 2023 at 09:47:49AM -0700, Darrick J. Wong wrote:
On Tue, Oct 03, 2023 at 03:24:23PM +1100, Dave Chinner wrote:
quoted
On Fri, Sep 29, 2023 at 10:27:21AM +0000, John Garry wrote:
quoted
Add flag IOMAP_ATOMIC_WRITE to indicate to the FS that an atomic write
bio is being created and all the rules there need to be followed.
It is the task of the FS iomap iter callbacks to ensure that the mapping
created adheres to those rules, like size is power-of-2, is at a
naturally-aligned offset, etc.
The mapping being returned by the filesystem can span a much greater
range than the actual IO needs - the iomap itself is not guaranteed
to be aligned to anything in particular, but the IO location within
that map can still conform to atomic IO constraints. See how
iomap_sector() calculates the actual LBA address of the IO from
the iomap and the current file position the IO is being done at.
hence I think saying "the filesysetm should make sure all IO
alignment adheres to atomic IO rules is probably wrong. The iomap
layer doesn't care what the filesystem does, all it cares about is
whether the IO can be done given the extent map that was returned to
it.
Indeed, iomap_dio_bio_iter() is doing all these alignment checks for
normal DIO reads and writes which must be logical block sized
aligned. i.e. this check:
if ((pos | length) & (bdev_logical_block_size(iomap->bdev) - 1) ||
!bdev_iter_is_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
Hence I think that atomic IO units, which are similarly defined by
the bdev, should be checked at the iomap layer, too. e.g, by
following up with:
if ((dio->iocb->ki_flags & IOCB_ATOMIC) &&
((pos | length) & (bdev_atomic_unit_min(iomap->bdev) - 1) ||
!bdev_iter_is_atomic_aligned(iomap->bdev, dio->submit.iter))
return -EINVAL;
At this point, filesystems don't really need to know anything about
atomic IO - if they've allocated a large contiguous extent (e.g. via
fallocate()), then RWF_ATOMIC will just work for the cases where the
block device supports it...
This then means that stuff like XFS extent size hints only need to
check when the hint is set that it is aligned to the underlying
device atomic IO constraints. Then when it sees the IOMAP_ATOMIC
modifier, it can fail allocation if it can't get extent size hint
aligned allocation.
IOWs, I'm starting to think this doesn't need any change to the
on-disk format for XFS - it can be driven entirely through two
dynamic mechanisms:
1. (IOMAP_WRITE | IOMAP_ATOMIC) requests from the direct IO layer
which causes mapping/allocation to fail if it can't allocate (or
map) atomic IO compatible extents for the IO.
2. FALLOC_FL_ATOMIC preallocation flag modifier to tell fallocate()
to force alignment of all preallocated extents to atomic IO
constraints.
Ugh, let's not relitigate problems that you (Dave) and I have already
solved.
Back in 2018, our internal proto-users of pmem asked for aligned
allocations so they could use PMD mappings to reduce TLB pressure. At
the time, you and I talked on IRC about whether that should be done via
fallocate flag or setting extszinherit+sunit at mkfs time. We decided
against adding fallocate flags because linux-api bikeshed hell.
Ok, but I don't see how I'm supposed to correlate a discussion from
5 years ago on a different topic with this one. I can only comment
on what I see in front of me. And what is in front of me is
something that doesn't need on-disk changes to implement....
Ever since, we've been shipping UEK with a mkfs.xmem scripts that
automates computing the mkfs.xfs geometry CLI options. It works,
mostly, except for the unaligned allocations that one gets when the free
space gets fragmented. The xfsprogs side of the forcealign patchset
moves most of the mkfs.xmem cli option setting logic into mkfs itself,
and the kernel side shuts off the lowspace allocator to fix the
fragmentation problem.
I'd rather fix the remaining quirks and not reinvent solved solutions,
as popular as that is in programming circles.
Why is mandatory allocation alignment for atomic writes different?
Forcealign solves the problem for NVME/SCSI AWU and pmem PMD in the same
way with the same control knobs for sysadmins. I don't want to have
totally separate playbooks for accomplishing nearly the same things.
Which is fair enough, but that's not the context under which this
has been presented.
Can we please get the forced-align stuff separated from atomic write
support - the atomic write requirements completely overwhelms small
amount of change needed to support physical file offset
alignment....
I don't like encoding hardware details in the fallocate uapi either.
That implies adding FALLOC_FL_HUGEPAGE for pmem, and possibly
FALLOC_FL_{SUNIT,SWIDTH} for users with RAIDs.
No, that's reading way too much into it. FALLOC_FL_ATOMIC would mean
"ensure preallocation is valid for RWF_ATOMIC based IO contrainsts",
nothing more, nothing less. This isn't -hardware specific-, it's
simply a flag to tell the filesystem to align file offsets to
physical storage constraints so the allocated space works works
appropriately for a specific IO API.
IOWs, it is little different from the FALLOC_FL_NOHIDE_STALE flag
for modifying fallocate() behaviour...
quoted
This doesn't require extent size hints at all. The filesystem can
query the bdev at mount time, store the min/max atomic write sizes,
and then use them for all requests that have _ATOMIC modifiers set
on them.
With iomap doing the same "get the atomic constraints from the bdev"
style lookups for per-IO file offset and size checking, I don't
think we actually need extent size hints or an on-disk flag to force
extent size hint alignment.
That doesn't mean extent size hints can't be used - it just means
that extent size hints have to be constrained to being aligned to
atomic IOs (e.g. extent size hint must be an integer multiple of the
max atomic IO size). This then acts as a modifier for _ATOMIC
context allocations, much like it is a modifier for normal
allocations now.
(One behavior change that comes with FORCEALIGN is that without it,
extent size hints affect only the alignment of the file range mappings.
With FORCEALIGN, the space allocation itself *and* the mapping are
aligned.)
The one big downside of FORCEALIGN is that the extent size hint can
become misaligned with the AWU (or pagetable) geometry if the fs is
moved to a different computing environment. I prefer not to couple the
interface to the hardware because that leaves open the possibility for
users to discover more use cases.
Sure, but this isn't really a "forced" alignment. This is a feature
that is providing "file offset is physically aligned to an
underlying hardware address space" instead of doing the normal thing
of abstracting file data away from the physical layout of the
storage.
If we can have user APIs that say "file data should be physically
aligned to storage" then we don't need on-disk flags to implement
this. Extent size hints could still be used to indicate the required
alignment, but we could also pull it straight from the hardware if
those aren't set. AFAICT only fallocate() and pwritev2() need these
flags for IO, but we could add a fadvise() command to set it on a
struct file, if mmap()/madvise is told to use hugepages we can use
PMD alignment rather than storage hardware alignment, etc.
IOWs actually having APIs that simply say "use physical offset
alignment" without actually saying exactly which hardware alignment
they want allows the filesystem to dynamically select the optimal
alignment for the given application use case rather than requiring
the admin to set up specific configuration at mkfs time....
How do we get here without space having been allocated for the
write?
Perhaps what this is trying to do is make RWF_ATOMIC only be valid
into written space? I mean, this will fail with preallocated space
(IOMAP_UNWRITTEN) even though we still have exactly the RWF_ATOMIC
all-or-nothing behaviour guaranteed after a crash because of journal
recovery behaviour. i.e. if the unwritten conversion gets written to
the journal, the data will be there. If it isn't written to the
journal, then the space remains unwritten and there's no data across
that entire range....
So I'm not really sure that either of these checks are valid or why
they are actually needed....
This requires O_DSYNC (or RWF_DSYNC) for atomic writes to unwritten or
COW space.
COW, maybe - I haven't thought that far through it.
However, for unwritten extents we just don't need O_DSYNC to
guarantee all or nothing writes. The application still has to use
fdatasync() to determine if the IO succeeded, but the actual IO and
unwritten conversion transaction ordering guarantee the
"all-or-nothing" behaviour of a RWF_ATOMIC write that is not using
O_DSYNC.
i.e. It just doesn't matter when the conversion transaction hits
the journal. If it doesn't hit the journal before the crash, the
write never happened. If it does hit the journal, then the cache
flush before the journal write ensures all the data from the
RWF_ATOMIC write is present on disk before the unwritten conversion
hits the journal.
We want failures in forcing the log transactions for the
endio processing to be reported to the pwrite caller as EIO, right?
A failure to force the log will result in a filesystem shutdown. It
doesn't matter if that happens during IO completion or sometime
before or during the fdatasync() call the application would still
need to use to guarantee data integrity.
RWF_ATOMIC implies FUA semantics, right? i.e. if the RWF_ATOMIC
write is a pure overwrite, there are no journal or cache flushes
needed to complete the write. If so, batching up all the metadata
updates between data integrity checkpoints can still make
performance much better. If the filesystem flushes the journal
itself, it's no different from an application crash recovery
perspective to using RWF_DSYNC|RWF_ATOMIC and failing in the middle
of a multi-IO update....
Hence I just don't see why RWF_ATOMIC requires O_DSYNC semantics at
all; all RWF_ATOMIC provides is larger "non-tearing" IO granularity
and this doesn't change filesystem data integrity semantics at all.
-Dave.
--
Dave Chinner
david@fromorbit.com
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-04 09:15:30
On 03/10/2023 17:45, Bart Van Assche wrote:
On 10/3/23 01:37, John Garry wrote:
quoted
I don't think that is_power_of_2(write length) is specific to XFS.
I think this is specific to XFS. Can you show me the F2FS code that
restricts the length of an atomic write to a power of two? I haven't
found it. The only power-of-two check that I found in F2FS is the
following (maybe I overlooked something):
$ git grep -nH is_power fs/f2fs
fs/f2fs/super.c:3914: if (!is_power_of_2(zone_sectors)) {
Any usecases which we know of requires a power-of-2 block size.
Do you know of a requirement for other sizes? Or are you concerned that
it is unnecessarily restrictive?
We have to deal with HW features like atomic write boundary and FS
restrictions like extent and stripe alignment transparent, which are
almost always powers-of-2, so naturally we would want to work with
powers-of-2 for atomic write sizes.
The power-of-2 stuff could be dropped if that is what people want.
However we still want to provide a set of rules to the user to make
those HW and FS features mentioned transparent to the user.
Thanks,
John
blk_queue_atomic_write_unit_[min| max]_sectors expects sectors (512 bytes unit)
as input but no conversion is done here from device logical block size
to SECTORs.
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-04 11:40:53
On 02/10/2023 23:50, Nathan Chancellor wrote:
quoted
quoted
quoted
ld.lld: error: undefined symbol: __moddi3
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(ll_back_merge_fn) in archive vmlinux.a
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(ll_back_merge_fn) in archive vmlinux.a
>>> referenced by blk-merge.c
>>> block/blk-merge.o:(bio_attempt_front_merge) in archive vmlinux.a
>>> referenced 3 more times
This does not appear to be clang specific, I can reproduce it with GCC
12.3.0 and the same configuration target.
Yeah, I just need to stop using the modulo operator for 64b values,
which I had already been advised to :|
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-04 14:20:36
On 03/10/2023 16:46, Darrick J. Wong wrote:
quoted
stat->result_mask |= STATX_WRITE_ATOMIC;
The result_mask (which becomes the statx stx_mask) needs to have
STATX_WRITE_ATOMIC set any time a filesystem responds to
STATX_WRITE_ATOMIC being set in the request_mask, even if the response
is "not supported".
The attributes_mask also needs to have STATX_ATTR_WRITE_ATOMIC set if
the filesystem+file can support the flag, even if it's not currently set
for that file. This should get turned into a generic vfs helper for the
next fs that wants to support atomic write units:
static void generic_fill_statx_atomic_writes(struct kstat *stat,
struct block_device *bdev)
{
u64 min_bytes;
/* Confirm that the fs driver knows about this statx request */
stat->result_mask |= STATX_WRITE_ATOMIC;
/* Confirm that the file attribute is known to the fs. */
stat->attributes_mask |= STATX_ATTR_WRITE_ATOMIC;
/* Fill out the rest of the atomic write fields if supported */
min_bytes = queue_atomic_write_unit_min_bytes(bdev->bd_queue);
if (min_bytes == 0)
return;
stat->atomic_write_unit_min = min_bytes;
stat->atomic_write_unit_max =
queue_atomic_write_unit_max_bytes(bdev->bd_queue);
/* Atomic writes actually supported on this file. */
stat->attributes |= STATX_ATTR_WRITE_ATOMIC;
}
and then:
if (request_mask & STATX_WRITE_ATOMIC)
generic_fill_statx_atomic_writes(stat, bdev);
That looks sensible, but, if used by an FS, we would still need a method
to include extra FS restrictions, like extent alignment as in 15/21.
Thanks,
John
From: Bart Van Assche <bvanassche@acm.org> Date: 2023-10-04 17:34:20
On 10/4/23 02:14, John Garry wrote:
On 03/10/2023 17:45, Bart Van Assche wrote:
quoted
On 10/3/23 01:37, John Garry wrote:
quoted
I don't think that is_power_of_2(write length) is specific to XFS.
I think this is specific to XFS. Can you show me the F2FS code that
restricts the length of an atomic write to a power of two? I haven't
found it. The only power-of-two check that I found in F2FS is the
following (maybe I overlooked something):
$ git grep -nH is_power fs/f2fs
fs/f2fs/super.c:3914: if (!is_power_of_2(zone_sectors)) {
Any usecases which we know of requires a power-of-2 block size.
Do you know of a requirement for other sizes? Or are you concerned that
it is unnecessarily restrictive?
We have to deal with HW features like atomic write boundary and FS
restrictions like extent and stripe alignment transparent, which are
almost always powers-of-2, so naturally we would want to work with
powers-of-2 for atomic write sizes.
The power-of-2 stuff could be dropped if that is what people want.
However we still want to provide a set of rules to the user to make
those HW and FS features mentioned transparent to the user.
Hi John,
My concern is that the power-of-2 requirements are only needed for
traditional filesystems and not for log-structured filesystems (BTRFS,
F2FS, BCACHEFS).
What I'd like to see is that each filesystem declares its atomic write
requirements (in struct address_space_operations?) and that
blkdev_atomic_write_valid() checks the filesystem-specific atomic write
requirements.
Thanks,
Bart.
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-04 22:00:04
On Wed, Oct 04, 2023 at 10:34:13AM -0700, Bart Van Assche wrote:
On 10/4/23 02:14, John Garry wrote:
quoted
On 03/10/2023 17:45, Bart Van Assche wrote:
quoted
On 10/3/23 01:37, John Garry wrote:
quoted
I don't think that is_power_of_2(write length) is specific to XFS.
I think this is specific to XFS. Can you show me the F2FS code that
restricts the length of an atomic write to a power of two? I haven't
found it. The only power-of-two check that I found in F2FS is the
following (maybe I overlooked something):
$ git grep -nH is_power fs/f2fs
fs/f2fs/super.c:3914: if (!is_power_of_2(zone_sectors)) {
Any usecases which we know of requires a power-of-2 block size.
Do you know of a requirement for other sizes? Or are you concerned that
it is unnecessarily restrictive?
We have to deal with HW features like atomic write boundary and FS
restrictions like extent and stripe alignment transparent, which are
almost always powers-of-2, so naturally we would want to work with
powers-of-2 for atomic write sizes.
The power-of-2 stuff could be dropped if that is what people want.
However we still want to provide a set of rules to the user to make
those HW and FS features mentioned transparent to the user.
Hi John,
My concern is that the power-of-2 requirements are only needed for
traditional filesystems and not for log-structured filesystems (BTRFS,
F2FS, BCACHEFS).
Filesystems that support copy-on-write data (needed for arbitrary
filesystem block aligned RWF_ATOMIC support) are not necessarily log
structured. For example: XFS.
All three of the filesystems you list above still use power-of-2
block sizes for most of their metadata structures and for large data
extents. Hence once you go above a certain file size they are going
to be doing full power-of-2 block size aligned IO anyway. hence the
constraint of atomic writes needing to be power-of-2 block size
aligned to avoid RMW cycles doesn't really change for these
filesystems.
In which case, they can just set their minimum atomic IO size to be
the same as their block size (e.g. 4kB) and set the maximum to
something they can guarantee gets COW'd in a single atomic
transaction. What the hardware can do with REQ_ATOMIC IO is
completely irrelevant at this point....
What I'd like to see is that each filesystem declares its atomic write
requirements (in struct address_space_operations?) and that
blkdev_atomic_write_valid() checks the filesystem-specific atomic write
requirements.
That seems unworkable to me - IO constraints propagate from the
bottom up, not from the top down.
Consider multi-device filesystems (btrfs and XFS), where different
devices might have different atomic write parameters. Which
set of bdev parameters does the filesystem report to the querying
bdev? (And doesn't that question just sound completely wrong?)
It also doesn't work for filesystems that can configure extent
allocation alignment at an individual inode level (like XFS) - what
does the filesystem report to the device when it doesn't know what
alignment constraints individual on-disk inodes might be using?
That's why statx() vectors through filesystems to all them to set
their own parameters based on the inode statx() is being called on.
If the filesystem has a native RWF_ATOMIC implementation, it can put
it's own parameters in the statx min/max atomic write size fields.
If the fs doesn't have it's own native support, but can do physical
file offset/LBA alignment, then it publishes the block device atomic
support parameters or overrides them with it's internal allocation
alignment constraints. If the bdev doesn't support REQ_ATOMIC, the
filesystem says "atomic writes are not supported".
-Dave.
--
Dave Chinner
david@fromorbit.com
blk_queue_atomic_write_unit_[min| max]_sectors expects sectors (512 bytes unit)
as input but no conversion is done here from device logical block size
to SECTORs.
Yeah, you are right. I think that we can just use:
blk_queue_atomic_write_unit_max_sectors(disk->queue,
atomic_bs >> SECTOR_SHIFT);
Makes sense.
I still don't grok the difference between max_bytes and unit_max_sectors here.
(Maybe NVMe spec does not differentiate it?)
I assume min_sectors should be as follows instead of setting it to 1 (512 bytes)?
blk_queue_atomic_write_unit_min_sectors(disk->queue, bs >> SECTORS_SHIFT);
blk_queue_atomic_write_unit_[min| max]_sectors expects sectors (512 bytes unit)
as input but no conversion is done here from device logical block size
to SECTORs.
Yeah, you are right. I think that we can just use:
blk_queue_atomic_write_unit_max_sectors(disk->queue,
atomic_bs >> SECTOR_SHIFT);
Thanks,
John
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-05 16:19:04
On 05/10/2023 14:32, Pankaj Raghav wrote:
quoted
quoted
te_unit_[min| max]_sectors expects sectors (512 bytes unit)
as input but no conversion is done here from device logical block size
to SECTORs.
Yeah, you are right. I think that we can just use:
blk_queue_atomic_write_unit_max_sectors(disk->queue,
atomic_bs >> SECTOR_SHIFT);
Makes sense.
I still don't grok the difference between max_bytes and unit_max_sectors here.
(Maybe NVMe spec does not differentiate it?)
I think that max_bytes does not need to be a power-of-2 and could be
relaxed.
Having said that, max_bytes comes into play for merging of bios - so if
we are in a scenario with no merging, then may a well leave
atomic_write_max_bytes == atomic_write_unit_max.
But let us check this proposal to relax.
I assume min_sectors should be as follows instead of setting it to 1 (512 bytes)?
blk_queue_atomic_write_unit_min_sectors(disk->queue, bs >> SECTORS_SHIFT);
Yeah, right, we want unit_min to be the logical block size.
Thanks,
John
Why does "rounddown_pow_of_two()" occur in the above code?
I assume that you are talking about all the code above to calculate
atomic write values for the device.
The reason is that atomic write unit min and max are always a power-of-2
- see rules described earlier - as so that we why we rounddown to a
power-of-2.
From SBC-5: "The ATOMIC ALIGNMENT field indicates the required alignment
of the starting LBA in an atomic write command. If the ATOMIC ALIGNMENT
field is set to 0000_0000h, then there is no alignment requirement for
atomic write commands.
The ATOMIC TRANSFER LENGTH GRANULARITY field indicates the minimum
transfer length for an atomic write command. Atomic write operations are
required to have a transfer length that is a multiple of the atomic
transfer length granularity. An ATOMIC TRANSFER LENGTH GRANULARITY field
set to 0000_0000h indicates that there is no atomic transfer length
granularity requirement."
I think the above means that it is wrong to round down the ATOMIC
TRANSFER LENGTH GRANULARITY or the ATOMIC BOUNDARY values.
Thanks,
Bart.
From: Jeremy Bongio <hidden> Date: 2023-10-06 18:15:27
What is the advantage of using write flags instead of using an atomic
open flag (O_ATOMIC)? With an open flag, write, writev, pwritev would
all be supported for atomic writes. And this would potentially require
less application changes to take advantage of atomic writes.
On Fri, Sep 29, 2023 at 3:28 AM John Garry [off-list ref] wrote:
quoted hunk
From: Prasad Singamsetty <redacted>
Userspace may add flag RWF_ATOMIC to pwritev2() to indicate that the
write is to be issued with torn write prevention, according to special
alignment and length rules.
Torn write prevention means that for a power or any other HW failure, all
or none of the data will be committed to storage, but never a mix of old
and new.
For any syscall interface utilizing struct iocb, add IOCB_ATOMIC for
iocb->ki_flags field to indicate the same.
A call to statx will give the relevant atomic write info:
- atomic_write_unit_min
- atomic_write_unit_max
Both values are a power-of-2.
Applications can avail of atomic write feature by ensuring that the total
length of a write is a power-of-2 in size and also sized between
atomic_write_unit_min and atomic_write_unit_max, inclusive. Applications
must ensure that the write is at a naturally-aligned offset in the file
wrt the total write length.
Signed-off-by: Prasad Singamsetty <redacted>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/fs.h | 1 +
include/uapi/linux/fs.h | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
From: Dave Chinner <david@fromorbit.com> Date: 2023-10-09 22:02:58
On Fri, Oct 06, 2023 at 11:15:11AM -0700, Jeremy Bongio wrote:
What is the advantage of using write flags instead of using an atomic
open flag (O_ATOMIC)? With an open flag, write, writev, pwritev would
all be supported for atomic writes. And this would potentially require
less application changes to take advantage of atomic writes.
Atomic writes are not a property of the file or even the inode
itself, they are an attribute of the specific IO being issued by
the application.
Most applications that want atomic writes are using it as a
performance optimisation. They are likely already using DIO with
either AIO, pwritev2 or io_uring and so are already using the
interfaces that support per-IO attributes. Not every IO to every
file needs to be atomic, so a per-IO attribute makes a lot of sense
for these applications.
Add to that that implementing atomic IO semantics in the generic IO
paths (e.g. for buffered writes) is much more difficult. It's
not an unsolvable problem (especially now with high-order folio
support in the page cache), it's just way outside the scope of this
patchset.
-Dave.
--
Dave Chinner
david@fromorbit.com
From: John Garry <john.g.garry@oracle.com> Date: 2023-10-24 13:00:59
On 03/10/2023 05:24, Dave Chinner wrote:
I don't think that this was ever responded to - apologies for that.
quoted
n = bio->bi_iter.bi_size;
+ if (atomic_write && n != length) {
+ /* This bio should have covered the complete length */
+ ret = -EINVAL;
+ bio_put(bio);
+ goto out;
Why? The actual bio can be any length that meets the aligned
criteria between min and max, yes?
So it's valid to split a
RWF_ATOMIC write request up into multiple min unit sized bios, is it
not?
It is not.
I mean, that's the whole point of the min/max unit setup, isn't
it?
atomic write unit min/max are lower and upper limits for the atomic
write length only.
That the max sized write only guarantees that it will tear at
min unit boundaries, not within those min unit boundaries?
We will never split an atomic write nor create multiple bios for an
atomic write. unit min is the minimum size supported for an atomic write
length. It is not also a boundary size which we may split a write. An
atomic write will only ever produce a maximum for a single IO operation.
We do support merging of atomic writes in the block layer, but this is
transparent to the user.
Please let me know if
https://lore.kernel.org/linux-api/20230929093717.2972367-1-john.g.garry@oracle.com/T/#mb48328cf84b1643b651b5f1293f443e26f18fbb5
needs to be improved to make this clear.
If
I've understood this correctly, then why does this "single bio for
large atomic write" constraint need to exist?
@@ -21,6 +21,48 @@ Description: device is offset from the internal allocation unit's natural alignment.+What: /sys/block/<disk>/atomic_write_max_bytes+Date: May 2023+Contact: Himanshu Madhani <himanshu.madhani@oracle.com>+Description:+ [RO] This parameter specifies the maximum atomic write+ size reported by the device. An atomic write operation+ must not exceed this number of bytes.
+What: /sys/block/<disk>/atomic_write_unit_max_bytes
+Date: January 2023
+Contact: Himanshu Madhani [off-list ref]
+Description:
+ [RO] This parameter defines the largest block which can be
+ written atomically with an atomic write operation. This
+ value must be a multiple of atomic_write_unit_min and must
+ be a power-of-two.
What is the difference between these two values?
+Date: May 2023
+Contact: Himanshu Madhani [off-list ref]
+Description:
+ [RO] This parameter specifies the smallest block which can
+ be written atomically with an atomic write operation. All
+ atomic write operations must begin at a
+ atomic_write_unit_min boundary and must be multiples of
+ atomic_write_unit_min. This value must be a power-of-two.
How can the minimum unit be anythіng but one logical block?
+extern void blk_queue_atomic_write_max_bytes(struct request_queue *q,
+ unsigned int bytes);
Please don't add pointless externs to prototypes in headers.
+static inline unsigned int queue_atomic_write_unit_max_bytes(const struct request_queue *q)
From: Christoph Hellwig <hch@lst.de> Date: 2023-11-09 15:13:48
On Fri, Sep 29, 2023 at 10:27:07AM +0000, John Garry wrote:
We rely the block layer always being able to send a bio of size
atomic_write_unit_max without being required to split it due to request
queue or other bio limits.
A bio may contain min(BIO_MAX_VECS, limits->max_segments) vectors,
and each vector is at worst case the device logical block size from
direct IO alignment requirement.
A bio can have more than BIO_MAX_VECS if you use bio_init.
+static unsigned int blk_queue_max_guaranteed_bio_size_sectors(
+ struct request_queue *q)
+{
+ struct queue_limits *limits = &q->limits;
+ unsigned int max_segments = min_t(unsigned int, BIO_MAX_VECS,
+ limits->max_segments);
+ /* Limit according to dev sector size as we only support direct-io */
Who is "we", and how tells the caller to only ever use direct I/O?
And how would a type of userspace I/O even matter for low-level
block code. What if I wanted to use this for file system metadata?
From: Christoph Hellwig <hch@lst.de> Date: 2023-11-09 15:24:19
On Fri, Sep 29, 2023 at 10:27:17AM +0000, John Garry wrote:
From: "Darrick J. Wong" <djwong@kernel.org>
Add a new inode flag to require that all file data extent mappings must
be aligned (both the file offset range and the allocated space itself)
to the extent size hint. Having a separate COW extent size hint is no
longer allowed.
The goal here is to enable sysadmins and users to mandate that all space
mappings in a file must have a startoff/blockcount that are aligned to
(say) a 2MB alignment and that the startblock/blockcount will follow the
same alignment.
This needs a good explanation of why someone would want this.
From: Christoph Hellwig <hch@lst.de> Date: 2023-11-09 15:26:20
On Fri, Sep 29, 2023 at 10:27:22AM +0000, John Garry wrote:
Ensure that when creating a mapping that we adhere to all the atomic
write rules.
We check that the mapping covers the complete range of the write to ensure
that we'll be just creating a single mapping.
Currently minimum granularity is the FS block size, but it should be
possibly to support lower in future.
I really dislike how this forces aligned allocations. Aligned
allocations are a nice optimization to offload some of the work
to the storage hard/firmware, but we need to support it in general.
And I think with out of place writes into the COW fork, and atomic
transactions to swap it in we can do that pretty easily.
That should also allow to get rid of the horrible forcealign mode,
as we can still try align if possible and just fall back to the
out of place writes.
Please figure out a way to split the atomic configuration into a
helper and avoid all those crazy long lines, preferable also avoid
the double calls to the block helpers as well while you're at it.
Also I really want a check in the NVMe I/O path that any request
with the atomic flag set actually adhers to the limits to at least
partially paper over the annoying lack of a separate write atomic
command in nvme.