Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

selftests: ublk: support arbitrary threads/queues combination

Enable flexible thread-to-queue mapping in batch I/O mode to support
arbitrary combinations of threads and queues, improving resource
utilization and scalability.

Key improvements:
- Support N:M thread-to-queue mapping (previously limited to 1:1)
- Dynamic buffer allocation based on actual queue assignment per thread
- Thread-safe queue preparation with spinlock protection
- Intelligent buffer index calculation for multi-queue scenarios
- Enhanced validation for thread/queue combination constraints

Implementation details:
- Add q_thread_map matrix to track queue-to-thread assignments
- Dynamic allocation of commit and fetch buffers per thread
- Round-robin queue assignment algorithm for load balancing
- Per-queue spinlock to prevent race conditions during prep
- Updated buffer index calculation using queue position within thread

This enables efficient configurations like:
- Any other N:M combinations for optimal resource matching

Testing:
- Added test_batch_02.sh: 4 threads vs 1 queue
- Added test_batch_03.sh: 1 thread vs 4 queues
- Validates correctness across different mapping scenarios

Signed-off-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>

authored by

Ming Lei and committed by
Jens Axboe
e8cd481c 20aeab0b

+302 -48
+2
tools/testing/selftests/ublk/Makefile
··· 26 26 TEST_PROGS += test_generic_16.sh 27 27 28 28 TEST_PROGS += test_batch_01.sh 29 + TEST_PROGS += test_batch_02.sh 30 + TEST_PROGS += test_batch_03.sh 29 31 30 32 TEST_PROGS += test_null_01.sh 31 33 TEST_PROGS += test_null_02.sh
+170 -29
tools/testing/selftests/ublk/batch.c
··· 76 76 free(t->commit_buf); 77 77 } 78 78 allocator_deinit(&t->commit_buf_alloc); 79 + free(t->commit); 79 80 } 80 81 81 82 static int alloc_batch_commit_buf(struct ublk_thread *t) ··· 85 84 unsigned int total = buf_size * t->nr_commit_buf; 86 85 unsigned int page_sz = getpagesize(); 87 86 void *buf = NULL; 88 - int ret; 87 + int i, ret, j = 0; 88 + 89 + t->commit = calloc(t->nr_queues, sizeof(*t->commit)); 90 + for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) { 91 + if (t->q_map[i]) 92 + t->commit[j++].q_id = i; 93 + } 89 94 90 95 allocator_init(&t->commit_buf_alloc, t->nr_commit_buf); 91 96 ··· 114 107 return ret; 115 108 } 116 109 110 + static unsigned int ublk_thread_nr_queues(const struct ublk_thread *t) 111 + { 112 + int i; 113 + int ret = 0; 114 + 115 + for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) 116 + ret += !!t->q_map[i]; 117 + 118 + return ret; 119 + } 120 + 117 121 void ublk_batch_prepare(struct ublk_thread *t) 118 122 { 119 123 /* ··· 137 119 */ 138 120 struct ublk_queue *q = &t->dev->q[0]; 139 121 122 + /* cache nr_queues because we don't support dynamic load-balance yet */ 123 + t->nr_queues = ublk_thread_nr_queues(t); 124 + 140 125 t->commit_buf_elem_size = ublk_commit_elem_buf_size(t->dev); 141 126 t->commit_buf_size = ublk_commit_buf_size(t); 142 127 t->commit_buf_start = t->nr_bufs; 143 - t->nr_commit_buf = 2; 128 + t->nr_commit_buf = 2 * t->nr_queues; 144 129 t->nr_bufs += t->nr_commit_buf; 145 130 146 131 t->cmd_flags = 0; ··· 165 144 { 166 145 int i; 167 146 168 - for (i = 0; i < UBLKS_T_NR_FETCH_BUF; i++) { 147 + for (i = 0; i < t->nr_fetch_bufs; i++) { 169 148 io_uring_free_buf_ring(&t->ring, t->fetch[i].br, 1, i); 170 149 munlock(t->fetch[i].fetch_buf, t->fetch[i].fetch_buf_size); 171 150 free(t->fetch[i].fetch_buf); 172 151 } 152 + free(t->fetch); 173 153 } 174 154 175 155 static int alloc_batch_fetch_buf(struct ublk_thread *t) ··· 181 159 int ret; 182 160 int i = 0; 183 161 184 - for (i = 0; i < UBLKS_T_NR_FETCH_BUF; i++) { 162 + /* double fetch buffer for each queue */ 163 + t->nr_fetch_bufs = t->nr_queues * 2; 164 + t->fetch = calloc(t->nr_fetch_bufs, sizeof(*t->fetch)); 165 + 166 + /* allocate one buffer for each queue */ 167 + for (i = 0; i < t->nr_fetch_bufs; i++) { 185 168 t->fetch[i].fetch_buf_size = buf_size; 186 169 187 170 if (posix_memalign((void **)&t->fetch[i].fetch_buf, pg_sz, ··· 212 185 { 213 186 int ret; 214 187 215 - ublk_assert(t->nr_commit_buf < 16); 188 + ublk_assert(t->nr_commit_buf < 2 * UBLK_MAX_QUEUES); 216 189 217 190 ret = alloc_batch_commit_buf(t); 218 191 if (ret) ··· 298 271 t->fetch[buf_idx].fetch_buf_off = 0; 299 272 } 300 273 301 - void ublk_batch_start_fetch(struct ublk_thread *t, 302 - struct ublk_queue *q) 274 + void ublk_batch_start_fetch(struct ublk_thread *t) 303 275 { 304 276 int i; 277 + int j = 0; 305 278 306 - for (i = 0; i < UBLKS_T_NR_FETCH_BUF; i++) 307 - ublk_batch_queue_fetch(t, q, i); 279 + for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) { 280 + if (t->q_map[i]) { 281 + struct ublk_queue *q = &t->dev->q[i]; 282 + 283 + /* submit two fetch commands for each queue */ 284 + ublk_batch_queue_fetch(t, q, j++); 285 + ublk_batch_queue_fetch(t, q, j++); 286 + } 287 + } 308 288 } 309 289 310 290 static unsigned short ublk_compl_batch_fetch(struct ublk_thread *t, ··· 351 317 return buf_idx; 352 318 } 353 319 354 - int ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q) 320 + static int __ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q) 355 321 { 356 322 unsigned short nr_elem = q->q_depth; 357 323 unsigned short buf_idx = ublk_alloc_commit_buf(t); ··· 386 352 t->commit_buf_elem_size, nr_elem, buf_idx); 387 353 ublk_setup_commit_sqe(t, sqe, buf_idx); 388 354 return 0; 355 + } 356 + 357 + int ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q) 358 + { 359 + int ret = 0; 360 + 361 + pthread_spin_lock(&q->lock); 362 + if (q->flags & UBLKS_Q_PREPARED) 363 + goto unlock; 364 + ret = __ublk_batch_queue_prep_io_cmds(t, q); 365 + if (!ret) 366 + q->flags |= UBLKS_Q_PREPARED; 367 + unlock: 368 + pthread_spin_unlock(&q->lock); 369 + 370 + return ret; 389 371 } 390 372 391 373 static void ublk_batch_compl_commit_cmd(struct ublk_thread *t, ··· 451 401 } 452 402 } 453 403 454 - void ublk_batch_commit_io_cmds(struct ublk_thread *t) 404 + static void __ublk_batch_commit_io_cmds(struct ublk_thread *t, 405 + struct batch_commit_buf *cb) 455 406 { 456 407 struct io_uring_sqe *sqe; 457 408 unsigned short buf_idx; 458 - unsigned short nr_elem = t->commit.done; 409 + unsigned short nr_elem = cb->done; 459 410 460 411 /* nothing to commit */ 461 412 if (!nr_elem) { 462 - ublk_free_commit_buf(t, t->commit.buf_idx); 413 + ublk_free_commit_buf(t, cb->buf_idx); 463 414 return; 464 415 } 465 416 466 417 ublk_io_alloc_sqes(t, &sqe, 1); 467 - buf_idx = t->commit.buf_idx; 468 - sqe->addr = (__u64)t->commit.elem; 418 + buf_idx = cb->buf_idx; 419 + sqe->addr = (__u64)cb->elem; 469 420 sqe->len = nr_elem * t->commit_buf_elem_size; 470 421 471 422 /* commit isn't per-queue command */ 472 - ublk_init_batch_cmd(t, t->commit.q_id, sqe, UBLK_U_IO_COMMIT_IO_CMDS, 423 + ublk_init_batch_cmd(t, cb->q_id, sqe, UBLK_U_IO_COMMIT_IO_CMDS, 473 424 t->commit_buf_elem_size, nr_elem, buf_idx); 474 425 ublk_setup_commit_sqe(t, sqe, buf_idx); 475 426 } 476 427 477 - static void ublk_batch_init_commit(struct ublk_thread *t, 478 - unsigned short buf_idx) 428 + void ublk_batch_commit_io_cmds(struct ublk_thread *t) 429 + { 430 + int i; 431 + 432 + for (i = 0; i < t->nr_queues; i++) { 433 + struct batch_commit_buf *cb = &t->commit[i]; 434 + 435 + if (cb->buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX) 436 + __ublk_batch_commit_io_cmds(t, cb); 437 + } 438 + 439 + } 440 + 441 + static void __ublk_batch_init_commit(struct ublk_thread *t, 442 + struct batch_commit_buf *cb, 443 + unsigned short buf_idx) 479 444 { 480 445 /* so far only support 1:1 queue/thread mapping */ 481 - t->commit.q_id = t->idx; 482 - t->commit.buf_idx = buf_idx; 483 - t->commit.elem = ublk_get_commit_buf(t, buf_idx); 484 - t->commit.done = 0; 485 - t->commit.count = t->commit_buf_size / 446 + cb->buf_idx = buf_idx; 447 + cb->elem = ublk_get_commit_buf(t, buf_idx); 448 + cb->done = 0; 449 + cb->count = t->commit_buf_size / 486 450 t->commit_buf_elem_size; 487 451 } 488 452 489 - void ublk_batch_prep_commit(struct ublk_thread *t) 453 + /* COMMIT_IO_CMDS is per-queue command, so use its own commit buffer */ 454 + static void ublk_batch_init_commit(struct ublk_thread *t, 455 + struct batch_commit_buf *cb) 490 456 { 491 457 unsigned short buf_idx = ublk_alloc_commit_buf(t); 492 458 493 459 ublk_assert(buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX); 494 - ublk_batch_init_commit(t, buf_idx); 460 + ublk_assert(!ublk_batch_commit_prepared(cb)); 461 + 462 + __ublk_batch_init_commit(t, cb, buf_idx); 463 + } 464 + 465 + void ublk_batch_prep_commit(struct ublk_thread *t) 466 + { 467 + int i; 468 + 469 + for (i = 0; i < t->nr_queues; i++) 470 + t->commit[i].buf_idx = UBLKS_T_COMMIT_BUF_INV_IDX; 495 471 } 496 472 497 473 void ublk_batch_complete_io(struct ublk_thread *t, struct ublk_queue *q, 498 474 unsigned tag, int res) 499 475 { 500 - struct batch_commit_buf *cb = &t->commit; 501 - struct ublk_batch_elem *elem = (struct ublk_batch_elem *)(cb->elem + 502 - cb->done * t->commit_buf_elem_size); 476 + unsigned q_t_idx = ublk_queue_idx_in_thread(t, q); 477 + struct batch_commit_buf *cb = &t->commit[q_t_idx]; 478 + struct ublk_batch_elem *elem; 503 479 struct ublk_io *io = &q->ios[tag]; 504 480 505 - ublk_assert(q->q_id == t->commit.q_id); 481 + if (!ublk_batch_commit_prepared(cb)) 482 + ublk_batch_init_commit(t, cb); 506 483 484 + ublk_assert(q->q_id == cb->q_id); 485 + 486 + elem = (struct ublk_batch_elem *)(cb->elem + cb->done * t->commit_buf_elem_size); 507 487 elem->tag = tag; 508 488 elem->buf_index = ublk_batch_io_buf_idx(t, q, tag); 509 489 elem->result = res; ··· 543 463 544 464 cb->done += 1; 545 465 ublk_assert(cb->done <= cb->count); 466 + } 467 + 468 + void ublk_batch_setup_map(unsigned char (*q_thread_map)[UBLK_MAX_QUEUES], 469 + int nthreads, int queues) 470 + { 471 + int i, j; 472 + 473 + /* 474 + * Setup round-robin queue-to-thread mapping for arbitrary N:M combinations. 475 + * 476 + * This algorithm distributes queues across threads (and threads across queues) 477 + * in a balanced round-robin fashion to ensure even load distribution. 478 + * 479 + * Examples: 480 + * - 2 threads, 4 queues: T0=[Q0,Q2], T1=[Q1,Q3] 481 + * - 4 threads, 2 queues: T0=[Q0], T1=[Q1], T2=[Q0], T3=[Q1] 482 + * - 3 threads, 3 queues: T0=[Q0], T1=[Q1], T2=[Q2] (1:1 mapping) 483 + * 484 + * Phase 1: Mark which queues each thread handles (boolean mapping) 485 + */ 486 + for (i = 0, j = 0; i < queues || j < nthreads; i++, j++) { 487 + q_thread_map[j % nthreads][i % queues] = 1; 488 + } 489 + 490 + /* 491 + * Phase 2: Convert boolean mapping to sequential indices within each thread. 492 + * 493 + * Transform from: q_thread_map[thread][queue] = 1 (handles queue) 494 + * To: q_thread_map[thread][queue] = N (queue index within thread) 495 + * 496 + * This allows each thread to know the local index of each queue it handles, 497 + * which is essential for buffer allocation and management. For example: 498 + * - Thread 0 handling queues [0,2] becomes: q_thread_map[0][0]=1, q_thread_map[0][2]=2 499 + * - Thread 1 handling queues [1,3] becomes: q_thread_map[1][1]=1, q_thread_map[1][3]=2 500 + */ 501 + for (j = 0; j < nthreads; j++) { 502 + unsigned char seq = 1; 503 + 504 + for (i = 0; i < queues; i++) { 505 + if (q_thread_map[j][i]) 506 + q_thread_map[j][i] = seq++; 507 + } 508 + } 509 + 510 + #if 0 511 + for (j = 0; j < nthreads; j++) { 512 + printf("thread %0d: ", j); 513 + for (i = 0; i < queues; i++) { 514 + if (q_thread_map[j][i]) 515 + printf("%03u ", i); 516 + } 517 + printf("\n"); 518 + } 519 + printf("\n"); 520 + for (j = 0; j < nthreads; j++) { 521 + for (i = 0; i < queues; i++) { 522 + printf("%03u ", q_thread_map[j][i]); 523 + } 524 + printf("\n"); 525 + } 526 + #endif 546 527 }
+38 -11
tools/testing/selftests/ublk/kublk.c
··· 455 455 int cmd_buf_size, io_buf_size, integrity_size; 456 456 unsigned long off; 457 457 458 + pthread_spin_init(&q->lock, PTHREAD_PROCESS_PRIVATE); 458 459 q->tgt_ops = dev->tgt.ops; 459 460 q->flags = 0; 460 461 q->q_depth = depth; ··· 522 521 523 522 /* FETCH_IO_CMDS is multishot, so increase cq depth for BATCH_IO */ 524 523 if (ublk_dev_batch_io(dev)) 525 - cq_depth += dev->dev_info.queue_depth; 524 + cq_depth += dev->dev_info.queue_depth * 2; 526 525 527 526 ret = ublk_setup_ring(&t->ring, ring_depth, cq_depth, 528 527 IORING_SETUP_COOP_TASKRUN | ··· 958 957 sem_t *ready; 959 958 cpu_set_t *affinity; 960 959 unsigned long long extra_flags; 960 + unsigned char (*q_thread_map)[UBLK_MAX_QUEUES]; 961 961 }; 962 962 963 963 static void ublk_thread_set_sched_affinity(const struct ublk_thread_info *info) ··· 972 970 { 973 971 int i; 974 972 975 - /* setup all queues in the 1st thread */ 976 973 for (i = 0; i < t->dev->dev_info.nr_hw_queues; i++) { 977 974 struct ublk_queue *q = &t->dev->q[i]; 978 975 int ret; 979 976 977 + /* 978 + * Only prepare io commands in the mapped thread context, 979 + * otherwise io command buffer index may not work as expected 980 + */ 981 + if (t->q_map[i] == 0) 982 + continue; 983 + 980 984 ret = ublk_batch_queue_prep_io_cmds(t, q); 981 - ublk_assert(ret == 0); 982 - ret = ublk_process_io(t); 983 985 ublk_assert(ret >= 0); 984 986 } 985 987 } ··· 996 990 }; 997 991 int dev_id = info->dev->dev_info.dev_id; 998 992 int ret; 993 + 994 + /* Copy per-thread queue mapping into thread-local variable */ 995 + if (info->q_thread_map) 996 + memcpy(t.q_map, info->q_thread_map[info->idx], sizeof(t.q_map)); 999 997 1000 998 ret = ublk_thread_init(&t, info->extra_flags); 1001 999 if (ret) { ··· 1016 1006 /* submit all io commands to ublk driver */ 1017 1007 ublk_submit_fetch_commands(&t); 1018 1008 } else { 1019 - struct ublk_queue *q = &t.dev->q[t.idx]; 1020 - 1021 - /* prepare all io commands in the 1st thread context */ 1022 - if (!t.idx) 1023 - ublk_batch_setup_queues(&t); 1024 - ublk_batch_start_fetch(&t, q); 1009 + ublk_batch_setup_queues(&t); 1010 + ublk_batch_start_fetch(&t); 1025 1011 } 1026 1012 1027 1013 do { ··· 1091 1085 struct ublk_thread_info *tinfo; 1092 1086 unsigned long long extra_flags = 0; 1093 1087 cpu_set_t *affinity_buf; 1088 + unsigned char (*q_thread_map)[UBLK_MAX_QUEUES] = NULL; 1094 1089 void *thread_ret; 1095 1090 sem_t ready; 1096 1091 int ret, i; ··· 1110 1103 ret = ublk_ctrl_get_affinity(dev, &affinity_buf); 1111 1104 if (ret) 1112 1105 return ret; 1106 + 1107 + if (ublk_dev_batch_io(dev)) { 1108 + q_thread_map = calloc(dev->nthreads, sizeof(*q_thread_map)); 1109 + if (!q_thread_map) { 1110 + ret = -ENOMEM; 1111 + goto fail; 1112 + } 1113 + ublk_batch_setup_map(q_thread_map, dev->nthreads, 1114 + dinfo->nr_hw_queues); 1115 + } 1113 1116 1114 1117 if (ctx->auto_zc_fallback) 1115 1118 extra_flags = UBLKS_Q_AUTO_BUF_REG_FALLBACK; ··· 1144 1127 tinfo[i].idx = i; 1145 1128 tinfo[i].ready = &ready; 1146 1129 tinfo[i].extra_flags = extra_flags; 1130 + tinfo[i].q_thread_map = q_thread_map; 1147 1131 1148 1132 /* 1149 1133 * If threads are not tied 1:1 to queues, setting thread ··· 1164 1146 for (i = 0; i < dev->nthreads; i++) 1165 1147 sem_wait(&ready); 1166 1148 free(affinity_buf); 1149 + free(q_thread_map); 1167 1150 1168 1151 /* everything is fine now, start us */ 1169 1152 if (ctx->recovery) ··· 1333 1314 goto fail; 1334 1315 } 1335 1316 1336 - if (nthreads != nr_queues && !ctx->per_io_tasks) { 1317 + if (nthreads != nr_queues && (!ctx->per_io_tasks && 1318 + !(ctx->flags & UBLK_F_BATCH_IO))) { 1337 1319 ublk_err("%s: threads %u must be same as queues %u if " 1338 1320 "not using per_io_tasks\n", 1339 1321 __func__, nthreads, nr_queues); ··· 1957 1937 ctx.csum_type != LBMD_PI_CSUM_NONE || 1958 1938 ctx.tag_size) { 1959 1939 ublk_err("integrity parameters require metadata_size\n"); 1940 + return -EINVAL; 1941 + } 1942 + 1943 + if ((ctx.flags & UBLK_F_AUTO_BUF_REG) && 1944 + (ctx.flags & UBLK_F_BATCH_IO) && 1945 + (ctx.nthreads > ctx.nr_hw_queues)) { 1946 + ublk_err("too many threads for F_AUTO_BUF_REG & F_BATCH_IO\n"); 1960 1947 return -EINVAL; 1961 1948 } 1962 1949
+32 -8
tools/testing/selftests/ublk/kublk.h
··· 173 173 const struct ublk_tgt_ops *tgt_ops; 174 174 struct ublksrv_io_desc *io_cmd_buf; 175 175 176 - /* borrow one bit of ublk uapi flags, which may never be used */ 176 + /* borrow three bit of ublk uapi flags, which may never be used */ 177 177 #define UBLKS_Q_AUTO_BUF_REG_FALLBACK (1ULL << 63) 178 178 #define UBLKS_Q_NO_UBLK_FIXED_FD (1ULL << 62) 179 + #define UBLKS_Q_PREPARED (1ULL << 61) 179 180 __u64 flags; 180 181 int ublk_fd; /* cached ublk char device fd */ 181 182 __u8 metadata_size; 182 183 struct ublk_io ios[UBLK_QUEUE_DEPTH]; 184 + 185 + /* used for prep io commands */ 186 + pthread_spinlock_t lock; 183 187 }; 184 188 185 189 /* align with `ublk_elem_header` */ ··· 210 206 }; 211 207 212 208 struct ublk_thread { 209 + /* Thread-local copy of queue-to-thread mapping for this thread */ 210 + unsigned char q_map[UBLK_MAX_QUEUES]; 211 + 213 212 struct ublk_dev *dev; 214 - unsigned idx; 213 + unsigned short idx; 214 + unsigned short nr_queues; 215 215 216 216 #define UBLKS_T_STOPPING (1U << 0) 217 217 #define UBLKS_T_IDLE (1U << 1) ··· 238 230 void *commit_buf; 239 231 #define UBLKS_T_COMMIT_BUF_INV_IDX ((unsigned short)-1) 240 232 struct allocator commit_buf_alloc; 241 - struct batch_commit_buf commit; 233 + struct batch_commit_buf *commit; 242 234 /* FETCH_IO_CMDS buffer */ 243 - #define UBLKS_T_NR_FETCH_BUF 2 244 - struct batch_fetch_buf fetch[UBLKS_T_NR_FETCH_BUF]; 235 + unsigned short nr_fetch_bufs; 236 + struct batch_fetch_buf *fetch; 245 237 246 238 struct io_uring ring; 247 239 }; ··· 520 512 return ublk_queue_use_zc(q) || ublk_queue_use_auto_zc(q); 521 513 } 522 514 515 + static inline int ublk_batch_commit_prepared(struct batch_commit_buf *cb) 516 + { 517 + return cb->buf_idx != UBLKS_T_COMMIT_BUF_INV_IDX; 518 + } 519 + 520 + static inline unsigned ublk_queue_idx_in_thread(const struct ublk_thread *t, 521 + const struct ublk_queue *q) 522 + { 523 + unsigned char idx; 524 + 525 + idx = t->q_map[q->q_id]; 526 + ublk_assert(idx != 0); 527 + return idx - 1; 528 + } 529 + 523 530 /* 524 531 * Each IO's buffer index has to be calculated by this helper for 525 532 * UBLKS_T_BATCH_IO ··· 543 520 const struct ublk_thread *t, const struct ublk_queue *q, 544 521 unsigned tag) 545 522 { 546 - return tag; 523 + return ublk_queue_idx_in_thread(t, q) * q->q_depth + tag; 547 524 } 548 525 549 526 /* Queue UBLK_U_IO_PREP_IO_CMDS for a specific queue with batch elements */ 550 527 int ublk_batch_queue_prep_io_cmds(struct ublk_thread *t, struct ublk_queue *q); 551 528 /* Start fetching I/O commands using multishot UBLK_U_IO_FETCH_IO_CMDS */ 552 - void ublk_batch_start_fetch(struct ublk_thread *t, 553 - struct ublk_queue *q); 529 + void ublk_batch_start_fetch(struct ublk_thread *t); 554 530 /* Handle completion of batch I/O commands (prep/commit) */ 555 531 void ublk_batch_compl_cmd(struct ublk_thread *t, 556 532 const struct io_uring_cqe *cqe); ··· 567 545 /* Add a completed I/O operation to the current batch commit buffer */ 568 546 void ublk_batch_complete_io(struct ublk_thread *t, struct ublk_queue *q, 569 547 unsigned tag, int res); 548 + void ublk_batch_setup_map(unsigned char (*q_thread_map)[UBLK_MAX_QUEUES], 549 + int nthreads, int queues); 570 550 571 551 static inline int ublk_complete_io(struct ublk_thread *t, struct ublk_queue *q, 572 552 unsigned tag, int res)
+30
tools/testing/selftests/ublk/test_batch_02.sh
··· 1 + #!/bin/bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + . "$(cd "$(dirname "$0")" && pwd)"/test_common.sh 5 + 6 + TID="batch_02" 7 + ERR_CODE=0 8 + 9 + if ! _have_feature "BATCH_IO"; then 10 + exit "$UBLK_SKIP_CODE" 11 + fi 12 + 13 + if ! _have_program fio; then 14 + exit "$UBLK_SKIP_CODE" 15 + fi 16 + 17 + _prep_test "generic" "test UBLK_F_BATCH_IO with 4_threads vs. 1_queues" 18 + 19 + _create_backfile 0 512M 20 + 21 + dev_id=$(_add_ublk_dev -t loop -q 1 --nthreads 4 -b "${UBLK_BACKFILES[0]}") 22 + _check_add_dev $TID $? 23 + 24 + # run fio over the ublk disk 25 + fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite \ 26 + --iodepth=32 --size=100M --numjobs=4 > /dev/null 2>&1 27 + ERR_CODE=$? 28 + 29 + _cleanup_test "generic" 30 + _show_result $TID $ERR_CODE
+30
tools/testing/selftests/ublk/test_batch_03.sh
··· 1 + #!/bin/bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + . "$(cd "$(dirname "$0")" && pwd)"/test_common.sh 5 + 6 + TID="batch_03" 7 + ERR_CODE=0 8 + 9 + if ! _have_feature "BATCH_IO"; then 10 + exit "$UBLK_SKIP_CODE" 11 + fi 12 + 13 + if ! _have_program fio; then 14 + exit "$UBLK_SKIP_CODE" 15 + fi 16 + 17 + _prep_test "generic" "test UBLK_F_BATCH_IO with 1_threads vs. 4_queues" 18 + 19 + _create_backfile 0 512M 20 + 21 + dev_id=$(_add_ublk_dev -t loop -q 4 --nthreads 1 -b "${UBLK_BACKFILES[0]}") 22 + _check_add_dev $TID $? 23 + 24 + # run fio over the ublk disk 25 + fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio --rw=readwrite \ 26 + --iodepth=32 --size=100M --numjobs=4 > /dev/null 2>&1 27 + ERR_CODE=$? 28 + 29 + _cleanup_test "generic" 30 + _show_result $TID $ERR_CODE