BUFFERIO(9)
π BUFFERIO(9): block I/O buffer transfers, bestie π
okay so welcome to BUFFERIO(9) from the Kernel Developer's Manual β this is the whole page for the BUFFERIO subsystem and its besties biodone, biowait, getiobuf, putiobuf, nestiobuf_setup, and nestiobuf_done, and it's literally all about block I/O buffer transfers β¨
π NAME π
BUFFERIO, biodone, biowait, getiobuf, putiobuf, nestiobuf_setup, nestiobuf_done β block I/O buffer transfers π
β¨ SYNOPSIS β¨
no cap, here's the exact shape of it:
#include <sys/buf.h>
void
biodone(buf_t *bp);
int
biowait(buf_t *bp);
buf_t *
getiobuf(struct vnode *vp, bool waitok);
void
putiobuf(buf_t *bp);
void
nestiobuf_setup(buf_t *mbp, buf_t *bp, int offset, size_t size);
void
nestiobuf_done(buf_t *mbp, int donebytes, int error);
π¦ DESCRIPTION π¦
The BUFFERIO subsystem manages block I/O buffer transfers, described by the struct buf structure, which serves multiple purposes between users in BUFFERIO, users in buffercache(9), and users in block device drivers to execute transfers to physical disks. it's giving shared-struct main character energy fr fr π«Ά
π BLOCK DEVICE USERS π
Users of BUFFERIO wishing to submit a buffer for block I/O transfer must obtain a struct buf, e.g. via getiobuf(), fill its parameters, and submit it to a block device with bdev_strategy(9), usually via VOP_STRATEGY(9). that's the whole flow, bestie β¨
The parameters to an I/O transfer described by bp are specified by the following struct buf fields:
bp->b_flagsβ flags specifying the type of transfer.B_READβ transfer is read from device. If it's not set, transfer is write to device.B_ASYNCβ asynchronous I/O. Caller must not providebp->b_iodoneand must not callbiowait(bp).
bp->b_dataβ pointer to kernel virtual address of source/target for transfer.bp->b_bcountβ nonnegative number of bytes requested for transfer.bp->b_blknoβ block number at which to do transfer.bp->b_iodoneβ I/O completion callback.B_ASYNCmust not be set inbp->b_flags.
For legibility, callers should indicate writes by passing the pseudo-flag B_WRITE, which is zero. yes bestie, zero, it's a whole vibe π
Additionally, if the I/O transfer is a write associated with a vnode(9) vp, then before the user submits it to a block device, the user must increment vp->v_numoutput. The user must not acquire vp's vnode lock between incrementing vp->v_numoutput and submitting bp to a block device β doing so will likely cause deadlock with the syncer. like, do not do that, bestie π
Block I/O transfer completion may be notified by the bp->b_iodone callback, by signalling biowait() waiters, or not at all in the B_ASYNC case.
- If the user sets the
bp->b_iodonecallback to a non-NULL function pointer, it will be called in soft interrupt context when the I/O transfer is complete. The user may not callbiowait(bp)in this case. - If
B_ASYNCis set, then the I/O transfer is asynchronous and the user will not be notified when it is completed. The user may not callbiowait(bp)in this case. - Otherwise, if
bp->b_iodoneis NULL andB_ASYNCis not specified, the user may wait for the I/O transfer to complete withbiowait(bp).
Once an I/O transfer has completed, its struct buf may be reused, but the user must first clear the BO_DONE flag of bp->b_oflags before reusing it. don't skip that part, okay? π
π§ NESTED I/O TRANSFERS π§
Sometimes an I/O transfer from a single buffer in memory cannot go to a single location on a block device: it must be split up into smaller transfers for each segment of the memory buffer. it's giving "one buffer, many little errands" energy πΈ
After initializing the b_flags, b_data, and b_bcount parameters of an I/O transfer for the buffer, called the master buffer, the user can issue smaller transfers for segments of the buffer using nestiobuf_setup(). When nested I/O transfers complete, in any order, they debit from the amount of work left to be done in the master buffer. If any segments of the buffer were skipped, the user can report this with nestiobuf_done() to debit the skipped part of the work.
The master buffer's I/O transfer is completed when all nested buffers' I/O transfers are completed, and if nestiobuf_done() is called in the case of skipped segments.
For writes associated with a vnode vp, nestiobuf_setup() accounts for vp->v_numoutput, so the caller is not allowed to acquire vp's vnode lock before submitting the nested I/O transfer to a block device. However, the caller is responsible for accounting the master buffer in vp->v_numoutput. This must be done very carefully because after incrementing vp->v_numoutput, the caller is not allowed to acquire vp's vnode lock before either calling nestiobuf_done() or submitting the last nested I/O transfer to a block device. this is the careful girlboss part, do not fumble it π
For example:
struct buf *mbp, *bp;
size_t skipped = 0;
unsigned i;
int error = 0;
mbp = getiobuf(vp, true);
mbp->b_data = data;
mbp->b_resid = mbp->b_bcount = datalen;
mbp->b_flags = B_WRITE;
KASSERT(0 < nsegs);
KASSERT(datalen == nsegs*segsz);
for (i = 0; i < nsegs; i++) {
struct vnode *devvp;
daddr_t blkno;
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
error = VOP_BMAP(vp, i*segsz, &devvp, &blkno, NULL);
VOP_UNLOCK(vp);
if (error == 0 && blkno == -1)
error = EIO;
if (error) {
/* Give up early, don't try to handle holes. */
skipped += datalen - i*segsz;
break;
}
bp = getiobuf(vp, true);
nestiobuf_setup(bp, mbp, i*segsz, segsz);
bp->b_blkno = blkno;
if (i == nsegs - 1) /* Last segment. */
break;
VOP_STRATEGY(devvp, bp);
}
/*
* Account v_numoutput for master write.
* (Must not vn_lock before last VOP_STRATEGY!)
*/
mutex_enter(&vp->v_interlock);
vp->v_numoutput++;
mutex_exit(&vp->v_interlock);
if (skipped)
nestiobuf_done(mbp, skipped, error);
else
VOP_STRATEGY(devvp, bp);
π BLOCK DEVICE DRIVERS π
Block device drivers implement a βstrategyβ method, in the d_strategy member of struct bdevsw (driver(9)), to queue a buffer for disk I/O. The inputs to the strategy method are:
bp->b_flagsβ flags specifying the type of transfer.B_READβ transfer is read from device. If it's not set, transfer is write to device.
bp->b_dataβ pointer to kernel virtual address of source/target for transfer.bp->b_bcountβ nonnegative number of bytes requested for transfer.bp->b_blknoβ block number at which to do transfer, relative to partition start.
If the strategy method uses bufq(9), it must additionally initialize the following fields before queueing bp with bufq_put(9):
bp->b_rawblknoβ block number relative to volume start.
When the I/O transfer is complete, whether it succeeded or failed, the strategy method must:
- Set
bp->b_errorto zero on success, or to anerrno(2)error code on failure. - Set
bp->b_residto the number of bytes remaining to transfer, whether on success or on failure. If no bytes were transferred, this must be set tobp->b_bcount. - Call
biodone(bp).
π FUNCTIONS π
π©· biodone(bp)
Notify that the I/O transfer described by bp has completed.
To be called by a block device driver. Caller must first set bp->b_error to an error code and bp->b_resid to the number of bytes remaining to transfer. slay responsibly π
π©΅ biowait(bp)
Wait for the synchronous I/O transfer described by bp to complete. Returns the value of bp->b_error.
To be called by a user requesting the I/O transfer.
May not be called if bp has a callback or is asynchronous β that is, if bp->b_iodone is set, or if B_ASYNC is set in bp->b_flags. do not call it in those cases, bestie π
π getiobuf(vp, waitok)
Allocate a struct buf for an I/O transfer. If vp is non-NULL, the transfer is associated with it. If waitok is false, returns NULL if none can be allocated immediately.
The resulting struct buf pointer must eventually be passed to putiobuf() to release it. Do not use brelse(9).
The buffer may not be used for an asynchronous I/O transfer, because there is no way to know when it is completed and may be safely passed to putiobuf(). Asynchronous I/O transfers are allowed only for buffers in the buffercache(9).
May sleep if waitok is true.
π putiobuf(bp)
Free bp, which must have been allocated by getiobuf(). Either bp must never have been submitted to a block device, or the I/O transfer must have completed. no freeing mid-flight, okay? π«Ά
β CODE REFERENCES β
The BUFFERIO subsystem is implemented in sys/kern/vfs_bio.c. that's the file, bestie π
πΈ SEE ALSO πΈ
buffercache(9), bufq(9) β go read these too, iykyk π«Ά
π BUGS π
- The BUFFERIO abstraction provides no way to cancel an I/O transfer once it has been submitted to a block device.
- The BUFFERIO abstraction provides no way to do I/O transfers with non-kernel pages, e.g. directly to buffers in userland without copying into the kernel first.
- The
struct buftype is all mixed up with thebuffercache(9). - The BUFFERIO abstraction is a totally idiotic API design.
- The
v_numoutputaccounting required of BUFFERIO callers is asinine.
mwah this iconic little manual page comes from Debian, dated September 12, 2019, and it is officially BUFFERIO(9) β periodt ππ«Ά
π¦ where this page comes from
- nixpkgs unstable: netbsd-source 11.0
- Copyright (c) 2015 The NetBSD Foundation, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms