History

fuse: add io-uring buffer pools

Right now, ents and buffers are tightly coupled in fuse io-uring where each entry has its own dedicated payload buffer, requiring N buffers for N entries where each buffer must be large enough to accomodate the maximum payload size. This is suboptimal as most request types (lookup, open, release, getattr, etc) require vastly less bytes than the maximum payload size and some requests (unlink, rmdir, fsync, flush, etc) do not require payload buffers at all. Instead of requiring a 1:1 coupling between ents and payload buffers, allow the server to pass in a buffer pool (a contiguous chunk of memory) that the kernel will use as it wishes for servicing ents/requests. Entries only reserve a "buffer" from the pool while actively processing a request that requires a payload buffer. This decoupling and letting the kernel delegate memory from the pool for requests allows the kernel to optimize memory usage and reduces the memory usage requirements needed to use fuse-over-io-uring. A pool is registered per queue with the new FUSE_IO_URING_CMD_ADD_BUFPOOL command. The server passes the pool's base address and length in fuse_uring_cmd_req.bufpool.{uaddr,len}. Internally, the kernel splits the region into buffers of ring->max_payload_sz bytes each (nr_bufs = pool len / max_payload_sz). A queue commits to a payload mode on first use: registering an entry that carries its own payload selects the legacy per-entry mode, while ADD_BUFPOOL selects pool mode. The two are mutually exclusive, so ADD_BUFPOOL must be issued before any payload-carrying entries are registered on that queue. The queue must have been created before the bufpool is added, through the FUSE_IO_URING_CMD_ADD_QUEUE command. The kernel tracks free buffers with a bitmap (a set bit marks a free buffer). On dispatch, a request that needs a payload claims a free buffer (find_first_bit + clear). A request that needs none claims nothing. The buffer's byte offset within the pool is reported to the server in the new fuse_uring_ent_in_out.offset field so that the server can locate the payload. On completion the buffer is returned to the pool or reused directly if the next request on that entry also has a payload. The FUSE_HAS_IO_URING_BUFPOOL flag advertises kernel support to the server for bufpools. Buffer pool request flow ~~~~~~~~~~~~~~~~~~~~~~~~ | Kernel | FUSE daemon | | | [request arrives] | | [claim a free pool buffer] | | >fuse_uring_select_buffer() | | [copy headers to ring] | | [copy payload to buffer] | | [report buffer offset in ent_in_out] | | >io_uring_cmd_done() | | | [read headers] | | [read/write payload at offset] | | [process request] | | >io_uring_submit() | | COMMIT_AND_FETCH | >fuse_uring_commit_fetch() | | [copy reply from ring] | | [return buffer to the pool] | | >fuse_uring_recycle_buffer() |

Joanne Koong committed 2 months ago

Commit b45aaab1 parent ebed9ea4 files · +269 −30Browse files

Showing the changes to include/uapi (1 of 4 files). Show all

Unified · Split

include/uapi/linux/fuse.h+20−1
@@ -243,6 +243,9 @@ · 242 unmodified lines
243 *243 *
244 * 7.46244 * 7.46
245 * - add FUSE_IO_URING_CMD_ADD_QUEUE245 * - add FUSE_IO_URING_CMD_ADD_QUEUE
246 * - add FUSE_HAS_IO_URING_BUFPOOL
247 * - add fuse_uring_cmd_req bufpool struct
248 * - add bufpool offset field to fuse_uring_ent_in_out struct
246 */249 */
247 250
248#ifndef _LINUX_FUSE_H251#ifndef _LINUX_FUSE_H
@@ -451,6 +454,7 @@ struct fuse_file_lock { · 202 unmodified lines
451 * FUSE_OVER_IO_URING: Indicate that client supports io-uring454 * FUSE_OVER_IO_URING: Indicate that client supports io-uring
452 * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests.455 * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests.
453 * init_out.request_timeout contains the timeout (in secs)456 * init_out.request_timeout contains the timeout (in secs)
457 * FUSE_HAS_IO_URING_BUFPOOL: kernel supports io-uring buffer pools
454 */458 */
455#define FUSE_ASYNC_READ (1 << 0)459#define FUSE_ASYNC_READ (1 << 0)
456#define FUSE_POSIX_LOCKS (1 << 1)460#define FUSE_POSIX_LOCKS (1 << 1)
@@ -498,6 +502,7 @@ struct fuse_file_lock { · 41 unmodified lines
498#define FUSE_ALLOW_IDMAP (1ULL << 40)502#define FUSE_ALLOW_IDMAP (1ULL << 40)
499#define FUSE_OVER_IO_URING (1ULL << 41)503#define FUSE_OVER_IO_URING (1ULL << 41)
500#define FUSE_REQUEST_TIMEOUT (1ULL << 42)504#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
505#define FUSE_HAS_IO_URING_BUFPOOL (1ULL << 43)
501 506
502/**507/**
503 * CUSE INIT request/reply flags508 * CUSE INIT request/reply flags
@@ -1266,7 +1271,9 @@ struct fuse_uring_ent_in_out { · 762 unmodified lines
1266 1271
1267 /* size of user payload buffer */1272 /* size of user payload buffer */
1268 uint32_t payload_sz;1273 uint32_t payload_sz;
1269 uint32_t padding;1274
1275 /* Offset into the bufpool, if bufpools are used */
1276 uint32_t offset;
1270 1277
1271 uint64_t reserved;1278 uint64_t reserved;
1272};1279};
@@ -1298,6 +1305,9 @@ enum fuse_uring_cmd { · 25 unmodified lines
1298 1305
1299 /* add a queue */1306 /* add a queue */
1300 FUSE_IO_URING_CMD_ADD_QUEUE = 3,1307 FUSE_IO_URING_CMD_ADD_QUEUE = 3,
1308
1309 /* add a bufpool to a queue */
1310 FUSE_IO_URING_CMD_ADD_BUFPOOL = 4,
1301};1311};
1302 1312
1303/**1313/**
@@ -1312,6 +1322,15 @@ struct fuse_uring_cmd_req { · 8 unmodified lines
1312 /* queue the command is for (queue index) */1322 /* queue the command is for (queue index) */
1313 uint16_t qid;1323 uint16_t qid;
1314 uint8_t padding[6];1324 uint8_t padding[6];
1325
1326 union {
1327 struct {
1328 /* base address of bufpool */
1329 uint64_t uaddr;
1330 uint32_t len;
1331 uint32_t reserved;
1332 } bufpool;
1333 };
1315};1334};
1316 1335
1317#endif /* _LINUX_FUSE_H */1336#endif /* _LINUX_FUSE_H */