All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V2 0/2] ublk: add support for UBLK_IO_NEED_GET_DATA
@ 2022-07-26 11:44 ZiyangZhang
  2022-07-26 11:44 ` [PATCH V2 1/2] ublk_cmd.h: add one new ublk command: UBLK_IO_NEED_GET_DATA ZiyangZhang
  2022-07-26 11:44 ` [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
  0 siblings, 2 replies; 6+ messages in thread
From: ZiyangZhang @ 2022-07-26 11:44 UTC (permalink / raw)
  To: axboe, ming.lei; +Cc: ZiyangZhang, linux-block, xiaoguang.wang

1. Introduction:
UBLK_IO_NEED_GET_DATA is a new ublk IO command. It is designed for a user
application who wants to allocate IO buffer and set IO buffer address
only after it receives an IO request from ublksrv. This is a reasonable
scenario because these users may use a RPC framework as one IO backend
to handle IO requests passed from ublksrv. And a RPC framework may
allocate its own buffer(or memory pool).

This new feature (UBLK_F_NEED_GET_DATA) is optional for ublk users.
Related userspace code has been added in ublksrv[1] as one pull request.

We have add some test cases in ublksrv and all of them pass. The
performance result shows that this new feature does bring additional
latency because one IO is issued back to ublk_drv once again to copy data
from bio vectors to user-provided data buffer.

2. Background:
For now, ublk requires the user to set IO buffer address in advance(with
last UBLK_IO_COMMIT_AND_FETCH_REQ command)so the user has to
pre-allocate IO buffer.

For READ requests, this work flow looks good because the data copy
happens after user application gets a cqe and the kernel copies data.
So user application can allocate IO buffer, copy data to be read into
it, and issues a sqe with the newly allocated IO buffer.

However, for WRITE requests, this work flow looks weird because
the data copy happens in a task_work before the user application gets one
cqe. So it is inconvenient for users who allocates(or fetch from a
memory pool)buffer after it gets one request(and know the actual data
size). For these users, they have to memcpy from ublksrv's pre-allocated
buffer to their internal buffer(such as RPC buffer). We think this
additional memcpy could be a bottleneck and it is avoidable.

2. Design:
Consider add a new feature flag: UBLK_F_NEED_GET_DATA.

If user sets this new flag(through libublksrv) and pass it to kernel
driver, ublk kernel driver should returns a cqe with
UBLK_IO_RES_NEED_GET_DATA after a new blk-mq WRITE request comes.

A user application now can allocate data buffer for writing and pass its
address in UBLK_IO_NEED_GET_DATA command after ublk kernel driver returns
cqe with UBLK_IO_RES_NEED_GET_DATA.

After the kernel side gets the sqe (UBLK_IO_NEED_GET_DATA command), it
now copies(address pinned, indeed) data to be written from bio vectors
to newly returned IO data buffer.

Finally, the kernel side returns UBLK_IO_RES_OK and ublksrv handles the
IO request as usual.The new feature: UBLK_F_NEED_GET_DATA is enabled on
demand ublksrv still can pre-allocate data buffers with task_work.

3. Evaluation:
Related userspace code and tests have been added in ublksrv[1] as one
pull request. We evaluate performance based on this PR.

We have tested write latency with:
  (1)  No UBLK_F_NEED_GET_DATA(the old commit) as baseline
  (2)  UBLK_F_NEED_GET_DATA enabled/disabled
on demo_null and demo_event of newest ublksrv project.

Config of fio:bs=4k, iodepth=1, numjobs=1, rw=write/randwrite, direct=1,
ioengine=libaio.

Here is the comparison of lat(usec) in fio:

demo_null:
write:        28.74(baseline) -- 28.77(disable) -- 57.20(enable)
randwrite:    27.81(baseline) -- 28.51(disable) -- 54.81(enable)

demo_event:
write:        46.45(baseline) -- 43.31(disable) -- 75.50(enable)
randwrite:    45.39(baseline) -- 43.66(disable) -- 76.02(enable)

Looks like:
  (1) UBLK_F_NEED_GET_DATA does not introduce additional overhead when
      comparing baseline and disable.
  (2) enabling UBLK_F_NEED_GET_DATA adds about two times more latency
      than disabling it. And it is reasonable since the IO goes through
      the total ublk IO stack(ubd_drv <--> ublksrv) once again.
  (3) demo_null and demo_event are both null targets. And I think this
      overhead is not too heavy if real data handling backend is used.

Without UBLK_IO_NEED_GET_DATA, an additional memcpy(from pre-allocated
ublksrv buffer to user's buffer) is necessary for a WRITE request.
However, UBLK_IO_NEED_GET_DATA does bring addtional syscall
(io_uring_enter). To prove the value of UBLK_IO_NEED_GET_DATA, we test
the single IO latency (usec) of demo_null with:
  (1) UBLK_F_NEED_GET_DATA disabled; additional memcpy
  (2) UBLK_F_NEED_GET_DATA enabled

Config of fio:iodepth=1, numjobs=1, rw=randwrite, direct=1,
ioengine=libaio.

For block size, we choose 4k/64k/128k/256k/512k/1m. Note that with 1m block
size, the original IO request will be split into two blk-mq requests.

Here is the comparison of lat(usec) in fio:

                 2 memcpy, w/o NEED_GET_DATA     1 memcpy, w/ NEED_GET_DATA
4k-randwrite:               9.65                            10.06
64k-randwrite:              15.19                           13.38
128k-randwrite:             19.47                           17.77
256k-randwrite:             32.63                           25.33
512k-randwrite:             90.57                           46.08
1m-randwrite:               177.06                          117.26

We find that with bigger block size, cases with one memcpy w/ NEED_GET_DATA
result in lower latency than cases with two memcpy w/o NEED_GET_DATA.
Therefore, we think NEED_GET_DATA is suitable for bigger block size,
such as 512B or 1MB.

[1] https://github.com/ming1/ubdsrv

Since V1:

(1) Add tests to compare (1)2 memcpy, w/o NEED_GET_DATA and (2)1 memcpy,
    w/ NEED_GET_DATA to show value of UBLK_IO_NEED_GET_DATA.
(2) rebase on the newest version of ublk_drv

ZiyangZhang (2):
  ublk_cmd.h: add one new ublk command: UBLK_IO_NEED_GET_DATA
  ublk_drv: add support for UBLK_IO_NEED_GET_DATA

 drivers/block/ublk_drv.c      | 88 +++++++++++++++++++++++++++++++----
 include/uapi/linux/ublk_cmd.h | 18 +++++++
 2 files changed, 97 insertions(+), 9 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH V2 1/2] ublk_cmd.h: add one new ublk command: UBLK_IO_NEED_GET_DATA
  2022-07-26 11:44 [PATCH V2 0/2] ublk: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
@ 2022-07-26 11:44 ` ZiyangZhang
  2022-07-26 11:44 ` [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
  1 sibling, 0 replies; 6+ messages in thread
From: ZiyangZhang @ 2022-07-26 11:44 UTC (permalink / raw)
  To: axboe, ming.lei; +Cc: ZiyangZhang, linux-block, xiaoguang.wang

This patch add one new ublk command: UBLK_IO_NEED_GET_DATA. It is
prepared for the next patch which uses this command and adds a new
feature.

Signed-off-by: ZiyangZhang <ZiyangZhang@linux.alibaba.com>
---
 include/uapi/linux/ublk_cmd.h | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h
index ca33092354ab..c986b4971423 100644
--- a/include/uapi/linux/ublk_cmd.h
+++ b/include/uapi/linux/ublk_cmd.h
@@ -28,12 +28,21 @@
  *      this IO request, request's handling result is committed to ublk
  *      driver, meantime FETCH_REQ is piggyback, and FETCH_REQ has to be
  *      handled before completing io request.
+ *
+ * NEED_GET_DATA: only used for write requests to set io addr and copy data
+ *      When NEED_GET_DATA is set, ublksrv has to issue UBLK_IO_NEED_GET_DATA
+ *      command after ublk driver returns UBLK_IO_RES_NEED_GET_DATA.
+ *
+ *      It is only used if ublksrv set UBLK_F_NEED_GET_DATA flag
+ *      while starting a ublk device.
  */
 #define	UBLK_IO_FETCH_REQ		0x20
 #define	UBLK_IO_COMMIT_AND_FETCH_REQ	0x21
+#define UBLK_IO_NEED_GET_DATA	0x22
 
 /* only ABORT means that no re-fetch */
 #define UBLK_IO_RES_OK			0
+#define UBLK_IO_RES_NEED_GET_DATA	1
 #define UBLK_IO_RES_ABORT		(-ENODEV)
 
 #define UBLKSRV_CMD_BUF_OFFSET	0
@@ -54,6 +63,15 @@
  */
 #define UBLK_F_URING_CMD_COMP_IN_TASK	(1ULL << 1)
 
+/*
+ * User should issue io cmd again for write requests to
+ * set io buffer address and copy data from bio vectors
+ * to the userspace io buffer.
+ *
+ * In this mode, task_work is not used.
+ */
+#define UBLK_F_NEED_GET_DATA (1UL << 2)
+
 /* device state */
 #define UBLK_S_DEV_DEAD	0
 #define UBLK_S_DEV_LIVE	1
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA
  2022-07-26 11:44 [PATCH V2 0/2] ublk: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
  2022-07-26 11:44 ` [PATCH V2 1/2] ublk_cmd.h: add one new ublk command: UBLK_IO_NEED_GET_DATA ZiyangZhang
@ 2022-07-26 11:44 ` ZiyangZhang
  2022-07-27  2:05   ` Ming Lei
  1 sibling, 1 reply; 6+ messages in thread
From: ZiyangZhang @ 2022-07-26 11:44 UTC (permalink / raw)
  To: axboe, ming.lei; +Cc: ZiyangZhang, linux-block, xiaoguang.wang

UBLK_IO_NEED_GET_DATA is one ublk IO command. It is designed for a user
application who wants to allocate IO buffer and set IO buffer address
only after it receives an IO request from ublksrv. This is a reasonable
scenario because these users may use a RPC framework as one IO backend
to handle IO requests passed from ublksrv. And a RPC framework may
allocate its own buffer(or memory pool).

This new feature (UBLK_F_NEED_GET_DATA) is optional for ublk users.
Related userspace code has been added in ublksrv[1] as one pull request.

Test cases for this feature are added in ublksrv and all the tests pass.
The performance result shows that this new feature does bring additional
latency because one IO is issued back to ublk_drv once again to copy data
from bio vectors to user-provided data buffer. UBLK_IO_NEED_GET_DATA is
suitable for bigger block size such as 512B or 1MB.

[1] https://github.com/ming1/ubdsrv

Signed-off-by: ZiyangZhang <ZiyangZhang@linux.alibaba.com>
---
 drivers/block/ublk_drv.c | 88 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 79 insertions(+), 9 deletions(-)

diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 255b2de46a24..c2d2cd5ab25c 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -47,7 +47,9 @@
 #define UBLK_MINORS		(1U << MINORBITS)
 
 /* All UBLK_F_* have to be included into UBLK_F_ALL */
-#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_URING_CMD_COMP_IN_TASK)
+#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY \
+		| UBLK_F_URING_CMD_COMP_IN_TASK \
+		| UBLK_F_NEED_GET_DATA)
 
 struct ublk_rq_data {
 	struct callback_head work;
@@ -86,6 +88,15 @@ struct ublk_uring_cmd_pdu {
  */
 #define UBLK_IO_FLAG_ABORTED 0x04
 
+/*
+ * UBLK_IO_FLAG_NEED_GET_DATA is set because IO command requires
+ * get data buffer address from ublksrv.
+ *
+ * Then, bio data could be copied into this data buffer for a WRITE request
+ * after the IO command is issued again and UBLK_IO_FLAG_NEED_GET_DATA is unset.
+ */
+#define UBLK_IO_FLAG_NEED_GET_DATA 0x08
+
 struct ublk_io {
 	/* userspace buffer address from io cmd */
 	__u64	addr;
@@ -163,11 +174,19 @@ static struct miscdevice ublk_misc;
 static inline bool ublk_can_use_task_work(const struct ublk_queue *ubq)
 {
 	if (IS_BUILTIN(CONFIG_BLK_DEV_UBLK) &&
+			!(ubq->flags & UBLK_F_NEED_GET_DATA) &&
 			!(ubq->flags & UBLK_F_URING_CMD_COMP_IN_TASK))
 		return true;
 	return false;
 }
 
+static inline bool ublk_need_get_data(const struct ublk_queue *ubq)
+{
+	if (ubq->flags & UBLK_F_NEED_GET_DATA)
+		return true;
+	return false;
+}
+
 static struct ublk_device *ublk_get_device(struct ublk_device *ub)
 {
 	if (kobject_get_unless_zero(&ub->cdev_dev.kobj))
@@ -509,6 +528,21 @@ static void __ublk_fail_req(struct ublk_io *io, struct request *req)
 	}
 }
 
+static void ubq_complete_io_cmd(struct ublk_io *io, int res)
+{
+	/* mark this cmd owned by ublksrv */
+	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
+
+	/*
+	 * clear ACTIVE since we are done with this sqe/cmd slot
+	 * We can only accept io cmd in case of being not active.
+	 */
+	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
+
+	/* tell ublksrv one io request is coming */
+	io_uring_cmd_done(io->cmd, res, 0);
+}
+
 #define UBLK_REQUEUE_DELAY_MS	3
 
 static inline void __ublk_rq_task_work(struct request *req)
@@ -531,6 +565,20 @@ static inline void __ublk_rq_task_work(struct request *req)
 		return;
 	}
 
+	if (ublk_need_get_data(ubq) &&
+			(req_op(req) == REQ_OP_WRITE ||
+			req_op(req) == REQ_OP_FLUSH) &&
+			!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA)) {
+
+		io->flags |= UBLK_IO_FLAG_NEED_GET_DATA;
+
+		pr_devel("%s: ublk_need_get_data. op %d, qid %d tag %d io_flags %x\n",
+				__func__, io->cmd->cmd_op, ubq->q_id, req->tag, io->flags);
+
+		ubq_complete_io_cmd(io, UBLK_IO_RES_NEED_GET_DATA);
+		return;
+	}
+
 	mapped_bytes = ublk_map_io(ubq, req, io);
 
 	/* partially mapped, update io descriptor */
@@ -553,17 +601,13 @@ static inline void __ublk_rq_task_work(struct request *req)
 			mapped_bytes >> 9;
 	}
 
-	/* mark this cmd owned by ublksrv */
-	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
-
 	/*
-	 * clear ACTIVE since we are done with this sqe/cmd slot
-	 * We can only accept io cmd in case of being not active.
+	 * Anyway, we have handled UBLK_IO_NEED_GET_DATA for WRITE/FLUSH requests,
+	 * or we did nothing for other types requests.
 	 */
-	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
+	io->flags &= ~UBLK_IO_FLAG_NEED_GET_DATA;
 
-	/* tell ublksrv one io request is coming */
-	io_uring_cmd_done(io->cmd, UBLK_IO_RES_OK, 0);
+	ubq_complete_io_cmd(io, UBLK_IO_RES_OK);
 }
 
 static void ublk_rq_task_work_cb(struct io_uring_cmd *cmd)
@@ -846,6 +890,16 @@ static void ublk_mark_io_ready(struct ublk_device *ub, struct ublk_queue *ubq)
 	mutex_unlock(&ub->mutex);
 }
 
+static void ublk_handle_need_get_data(struct ublk_device *ub, int q_id,
+		int tag, struct io_uring_cmd *cmd)
+{
+	struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
+	struct request *req = blk_mq_tag_to_rq(ub->tag_set.tags[q_id], tag);
+
+	pdu->req = req;
+	io_uring_cmd_complete_in_task(cmd, ublk_rq_task_work_cb);
+}
+
 static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
 {
 	struct ublksrv_io_cmd *ub_cmd = (struct ublksrv_io_cmd *)cmd->cmd;
@@ -884,6 +938,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
 		goto out;
 	}
 
+	/*
+	 * ensure that the user issues UBLK_IO_NEED_GET_DATA
+	 * iff the driver have set the UBLK_IO_FLAG_NEED_GET_DATA.
+	 */
+	if ((!!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA))
+			^ (cmd_op == UBLK_IO_NEED_GET_DATA))
+		goto out;
+
 	switch (cmd_op) {
 	case UBLK_IO_FETCH_REQ:
 		/* UBLK_IO_FETCH_REQ is only allowed before queue is setup */
@@ -917,6 +979,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
 		io->cmd = cmd;
 		ublk_commit_completion(ub, ub_cmd);
 		break;
+	case UBLK_IO_NEED_GET_DATA:
+		if (!(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV))
+			goto out;
+		io->addr = ub_cmd->addr;
+		io->cmd = cmd;
+		io->flags |= UBLK_IO_FLAG_ACTIVE;
+		ublk_handle_need_get_data(ub, ub_cmd->q_id, ub_cmd->tag, cmd);
+		break;
 	default:
 		goto out;
 	}
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA
  2022-07-26 11:44 ` [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
@ 2022-07-27  2:05   ` Ming Lei
  2022-07-27  3:09     ` Ziyang Zhang
  0 siblings, 1 reply; 6+ messages in thread
From: Ming Lei @ 2022-07-27  2:05 UTC (permalink / raw)
  To: ZiyangZhang; +Cc: axboe, linux-block, xiaoguang.wang

On Tue, Jul 26, 2022 at 07:44:05PM +0800, ZiyangZhang wrote:
> UBLK_IO_NEED_GET_DATA is one ublk IO command. It is designed for a user
> application who wants to allocate IO buffer and set IO buffer address
> only after it receives an IO request from ublksrv. This is a reasonable
> scenario because these users may use a RPC framework as one IO backend
> to handle IO requests passed from ublksrv. And a RPC framework may
> allocate its own buffer(or memory pool).
> 
> This new feature (UBLK_F_NEED_GET_DATA) is optional for ublk users.
> Related userspace code has been added in ublksrv[1] as one pull request.
> 
> Test cases for this feature are added in ublksrv and all the tests pass.
> The performance result shows that this new feature does bring additional
> latency because one IO is issued back to ublk_drv once again to copy data
> from bio vectors to user-provided data buffer. UBLK_IO_NEED_GET_DATA is
> suitable for bigger block size such as 512B or 1MB.
> 
> [1] https://github.com/ming1/ubdsrv
> 
> Signed-off-by: ZiyangZhang <ZiyangZhang@linux.alibaba.com>
> ---
>  drivers/block/ublk_drv.c | 88 ++++++++++++++++++++++++++++++++++++----
>  1 file changed, 79 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
> index 255b2de46a24..c2d2cd5ab25c 100644
> --- a/drivers/block/ublk_drv.c
> +++ b/drivers/block/ublk_drv.c
> @@ -47,7 +47,9 @@
>  #define UBLK_MINORS		(1U << MINORBITS)
>  
>  /* All UBLK_F_* have to be included into UBLK_F_ALL */
> -#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_URING_CMD_COMP_IN_TASK)
> +#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY \
> +		| UBLK_F_URING_CMD_COMP_IN_TASK \
> +		| UBLK_F_NEED_GET_DATA)
>  
>  struct ublk_rq_data {
>  	struct callback_head work;
> @@ -86,6 +88,15 @@ struct ublk_uring_cmd_pdu {
>   */
>  #define UBLK_IO_FLAG_ABORTED 0x04
>  
> +/*
> + * UBLK_IO_FLAG_NEED_GET_DATA is set because IO command requires
> + * get data buffer address from ublksrv.
> + *
> + * Then, bio data could be copied into this data buffer for a WRITE request
> + * after the IO command is issued again and UBLK_IO_FLAG_NEED_GET_DATA is unset.
> + */
> +#define UBLK_IO_FLAG_NEED_GET_DATA 0x08
> +
>  struct ublk_io {
>  	/* userspace buffer address from io cmd */
>  	__u64	addr;
> @@ -163,11 +174,19 @@ static struct miscdevice ublk_misc;
>  static inline bool ublk_can_use_task_work(const struct ublk_queue *ubq)
>  {
>  	if (IS_BUILTIN(CONFIG_BLK_DEV_UBLK) &&
> +			!(ubq->flags & UBLK_F_NEED_GET_DATA) &&
>  			!(ubq->flags & UBLK_F_URING_CMD_COMP_IN_TASK))
>  		return true;
>  	return false;

NEED_GET_DATA isn't related with using task work.

That said if use task work return true, you use task work to handle
GET_DATA, otherwise use io_uring_cmd_complete_in_task() to handle
it, then either we use task work or use io_uring_cmd_complete_in_task,
not mix the two.

BTW, in my test, it is observed reliably that task work can get better iops.

>  }
>  
> +static inline bool ublk_need_get_data(const struct ublk_queue *ubq)
> +{
> +	if (ubq->flags & UBLK_F_NEED_GET_DATA)
> +		return true;
> +	return false;
> +}
> +
>  static struct ublk_device *ublk_get_device(struct ublk_device *ub)
>  {
>  	if (kobject_get_unless_zero(&ub->cdev_dev.kobj))
> @@ -509,6 +528,21 @@ static void __ublk_fail_req(struct ublk_io *io, struct request *req)
>  	}
>  }
>  
> +static void ubq_complete_io_cmd(struct ublk_io *io, int res)
> +{
> +	/* mark this cmd owned by ublksrv */
> +	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
> +
> +	/*
> +	 * clear ACTIVE since we are done with this sqe/cmd slot
> +	 * We can only accept io cmd in case of being not active.
> +	 */
> +	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
> +
> +	/* tell ublksrv one io request is coming */
> +	io_uring_cmd_done(io->cmd, res, 0);
> +}
> +
>  #define UBLK_REQUEUE_DELAY_MS	3
>  
>  static inline void __ublk_rq_task_work(struct request *req)
> @@ -531,6 +565,20 @@ static inline void __ublk_rq_task_work(struct request *req)
>  		return;
>  	}
>  
> +	if (ublk_need_get_data(ubq) &&
> +			(req_op(req) == REQ_OP_WRITE ||
> +			req_op(req) == REQ_OP_FLUSH) &&
> +			!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA)) {
> +
> +		io->flags |= UBLK_IO_FLAG_NEED_GET_DATA;
> +
> +		pr_devel("%s: ublk_need_get_data. op %d, qid %d tag %d io_flags %x\n",
> +				__func__, io->cmd->cmd_op, ubq->q_id, req->tag, io->flags);
> +
> +		ubq_complete_io_cmd(io, UBLK_IO_RES_NEED_GET_DATA);
> +		return;
> +	}
> +
>  	mapped_bytes = ublk_map_io(ubq, req, io);
>  
>  	/* partially mapped, update io descriptor */
> @@ -553,17 +601,13 @@ static inline void __ublk_rq_task_work(struct request *req)
>  			mapped_bytes >> 9;
>  	}
>  
> -	/* mark this cmd owned by ublksrv */
> -	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
> -
>  	/*
> -	 * clear ACTIVE since we are done with this sqe/cmd slot
> -	 * We can only accept io cmd in case of being not active.
> +	 * Anyway, we have handled UBLK_IO_NEED_GET_DATA for WRITE/FLUSH requests,
> +	 * or we did nothing for other types requests.
>  	 */
> -	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
> +	io->flags &= ~UBLK_IO_FLAG_NEED_GET_DATA;
>  
> -	/* tell ublksrv one io request is coming */
> -	io_uring_cmd_done(io->cmd, UBLK_IO_RES_OK, 0);
> +	ubq_complete_io_cmd(io, UBLK_IO_RES_OK);
>  }
>  
>  static void ublk_rq_task_work_cb(struct io_uring_cmd *cmd)
> @@ -846,6 +890,16 @@ static void ublk_mark_io_ready(struct ublk_device *ub, struct ublk_queue *ubq)
>  	mutex_unlock(&ub->mutex);
>  }
>  
> +static void ublk_handle_need_get_data(struct ublk_device *ub, int q_id,
> +		int tag, struct io_uring_cmd *cmd)
> +{
> +	struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
> +	struct request *req = blk_mq_tag_to_rq(ub->tag_set.tags[q_id], tag);
> +
> +	pdu->req = req;
> +	io_uring_cmd_complete_in_task(cmd, ublk_rq_task_work_cb);
> +}

Here you can choose to use task work or io_uring_cmd_complete_in_task() by
checking ublk_can_use_task_work(), just like what ublk_queue_rq() does.

> +
>  static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
>  {
>  	struct ublksrv_io_cmd *ub_cmd = (struct ublksrv_io_cmd *)cmd->cmd;
> @@ -884,6 +938,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
>  		goto out;
>  	}
>  
> +	/*
> +	 * ensure that the user issues UBLK_IO_NEED_GET_DATA
> +	 * iff the driver have set the UBLK_IO_FLAG_NEED_GET_DATA.
> +	 */
> +	if ((!!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA))
> +			^ (cmd_op == UBLK_IO_NEED_GET_DATA))
> +		goto out;
> +
>  	switch (cmd_op) {
>  	case UBLK_IO_FETCH_REQ:
>  		/* UBLK_IO_FETCH_REQ is only allowed before queue is setup */
> @@ -917,6 +979,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
>  		io->cmd = cmd;
>  		ublk_commit_completion(ub, ub_cmd);
>  		break;
> +	case UBLK_IO_NEED_GET_DATA:
> +		if (!(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV))
> +			goto out;
> +		io->addr = ub_cmd->addr;
> +		io->cmd = cmd;
> +		io->flags |= UBLK_IO_FLAG_ACTIVE;
> +		ublk_handle_need_get_data(ub, ub_cmd->q_id, ub_cmd->tag, cmd);

You still need to clear UBLK_IO_FLAG_OWNED_BY_SRV here.

In future, UBLK_IO_FLAG_OWNED_BY_SRV can be removed actually.


Thanks,
Ming


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA
  2022-07-27  2:05   ` Ming Lei
@ 2022-07-27  3:09     ` Ziyang Zhang
  2022-07-27 15:48       ` Ming Lei
  0 siblings, 1 reply; 6+ messages in thread
From: Ziyang Zhang @ 2022-07-27  3:09 UTC (permalink / raw)
  To: Ming Lei; +Cc: axboe, linux-block, xiaoguang.wang

On 2022/7/27 10:05, Ming Lei wrote:
> On Tue, Jul 26, 2022 at 07:44:05PM +0800, ZiyangZhang wrote:
>> UBLK_IO_NEED_GET_DATA is one ublk IO command. It is designed for a user
>> application who wants to allocate IO buffer and set IO buffer address
>> only after it receives an IO request from ublksrv. This is a reasonable
>> scenario because these users may use a RPC framework as one IO backend
>> to handle IO requests passed from ublksrv. And a RPC framework may
>> allocate its own buffer(or memory pool).
>>
>> This new feature (UBLK_F_NEED_GET_DATA) is optional for ublk users.
>> Related userspace code has been added in ublksrv[1] as one pull request.
>>
>> Test cases for this feature are added in ublksrv and all the tests pass.
>> The performance result shows that this new feature does bring additional
>> latency because one IO is issued back to ublk_drv once again to copy data
>> from bio vectors to user-provided data buffer. UBLK_IO_NEED_GET_DATA is
>> suitable for bigger block size such as 512B or 1MB.
>>
>> [1] https://github.com/ming1/ubdsrv
>>
>> Signed-off-by: ZiyangZhang <ZiyangZhang@linux.alibaba.com>
>> ---
>>  drivers/block/ublk_drv.c | 88 ++++++++++++++++++++++++++++++++++++----
>>  1 file changed, 79 insertions(+), 9 deletions(-)
>>
>> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
>> index 255b2de46a24..c2d2cd5ab25c 100644
>> --- a/drivers/block/ublk_drv.c
>> +++ b/drivers/block/ublk_drv.c
>> @@ -47,7 +47,9 @@
>>  #define UBLK_MINORS		(1U << MINORBITS)
>>  
>>  /* All UBLK_F_* have to be included into UBLK_F_ALL */
>> -#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_URING_CMD_COMP_IN_TASK)
>> +#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY \
>> +		| UBLK_F_URING_CMD_COMP_IN_TASK \
>> +		| UBLK_F_NEED_GET_DATA)
>>  
>>  struct ublk_rq_data {
>>  	struct callback_head work;
>> @@ -86,6 +88,15 @@ struct ublk_uring_cmd_pdu {
>>   */
>>  #define UBLK_IO_FLAG_ABORTED 0x04
>>  
>> +/*
>> + * UBLK_IO_FLAG_NEED_GET_DATA is set because IO command requires
>> + * get data buffer address from ublksrv.
>> + *
>> + * Then, bio data could be copied into this data buffer for a WRITE request
>> + * after the IO command is issued again and UBLK_IO_FLAG_NEED_GET_DATA is unset.
>> + */
>> +#define UBLK_IO_FLAG_NEED_GET_DATA 0x08
>> +
>>  struct ublk_io {
>>  	/* userspace buffer address from io cmd */
>>  	__u64	addr;
>> @@ -163,11 +174,19 @@ static struct miscdevice ublk_misc;
>>  static inline bool ublk_can_use_task_work(const struct ublk_queue *ubq)
>>  {
>>  	if (IS_BUILTIN(CONFIG_BLK_DEV_UBLK) &&
>> +			!(ubq->flags & UBLK_F_NEED_GET_DATA) &&
>>  			!(ubq->flags & UBLK_F_URING_CMD_COMP_IN_TASK))
>>  		return true;
>>  	return false;
> 
> NEED_GET_DATA isn't related with using task work.
> 
> That said if use task work return true, you use task work to handle
> GET_DATA, otherwise use io_uring_cmd_complete_in_task() to handle
> it, then either we use task work or use io_uring_cmd_complete_in_task,
> not mix the two.
> 
> BTW, in my test, it is observed reliably that task work can get better iops.

Ok, I will check whether UBLK_F_NEED_GET_DATA works with task_work_add().

I find that checking flags of ublk_io must stay in current ubq process.
Otherwise there may be potential data race while aborting IOs.
So whatever I use NEED_GET_DATA or not, task work is necessary. 

Using task_work_add() or io_uring_cmd_complete_in_task() depends on:
 (1) the module is built-in or not
 (2) the user requires the feature: UBLK_F_URING_CMD_COMP_IN_TASK

In the future, in our case, we may choose
UBLK_F_URING_CMD_COMP_IN_TASK | UBLK_F_NEED_GET_DATA as an option.

> 
>>  }
>>  
>> +static inline bool ublk_need_get_data(const struct ublk_queue *ubq)
>> +{
>> +	if (ubq->flags & UBLK_F_NEED_GET_DATA)
>> +		return true;
>> +	return false;
>> +}
>> +
>>  static struct ublk_device *ublk_get_device(struct ublk_device *ub)
>>  {
>>  	if (kobject_get_unless_zero(&ub->cdev_dev.kobj))
>> @@ -509,6 +528,21 @@ static void __ublk_fail_req(struct ublk_io *io, struct request *req)
>>  	}
>>  }
>>  
>> +static void ubq_complete_io_cmd(struct ublk_io *io, int res)
>> +{
>> +	/* mark this cmd owned by ublksrv */
>> +	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
>> +
>> +	/*
>> +	 * clear ACTIVE since we are done with this sqe/cmd slot
>> +	 * We can only accept io cmd in case of being not active.
>> +	 */
>> +	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
>> +
>> +	/* tell ublksrv one io request is coming */
>> +	io_uring_cmd_done(io->cmd, res, 0);
>> +}
>> +
>>  #define UBLK_REQUEUE_DELAY_MS	3
>>  
>>  static inline void __ublk_rq_task_work(struct request *req)
>> @@ -531,6 +565,20 @@ static inline void __ublk_rq_task_work(struct request *req)
>>  		return;
>>  	}
>>  
>> +	if (ublk_need_get_data(ubq) &&
>> +			(req_op(req) == REQ_OP_WRITE ||
>> +			req_op(req) == REQ_OP_FLUSH) &&
>> +			!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA)) {
>> +
>> +		io->flags |= UBLK_IO_FLAG_NEED_GET_DATA;
>> +
>> +		pr_devel("%s: ublk_need_get_data. op %d, qid %d tag %d io_flags %x\n",
>> +				__func__, io->cmd->cmd_op, ubq->q_id, req->tag, io->flags);
>> +
>> +		ubq_complete_io_cmd(io, UBLK_IO_RES_NEED_GET_DATA);
>> +		return;
>> +	}
>> +
>>  	mapped_bytes = ublk_map_io(ubq, req, io);
>>  
>>  	/* partially mapped, update io descriptor */
>> @@ -553,17 +601,13 @@ static inline void __ublk_rq_task_work(struct request *req)
>>  			mapped_bytes >> 9;
>>  	}
>>  
>> -	/* mark this cmd owned by ublksrv */
>> -	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
>> -
>>  	/*
>> -	 * clear ACTIVE since we are done with this sqe/cmd slot
>> -	 * We can only accept io cmd in case of being not active.
>> +	 * Anyway, we have handled UBLK_IO_NEED_GET_DATA for WRITE/FLUSH requests,
>> +	 * or we did nothing for other types requests.
>>  	 */
>> -	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
>> +	io->flags &= ~UBLK_IO_FLAG_NEED_GET_DATA;
>>  
>> -	/* tell ublksrv one io request is coming */
>> -	io_uring_cmd_done(io->cmd, UBLK_IO_RES_OK, 0);
>> +	ubq_complete_io_cmd(io, UBLK_IO_RES_OK);
>>  }
>>  
>>  static void ublk_rq_task_work_cb(struct io_uring_cmd *cmd)
>> @@ -846,6 +890,16 @@ static void ublk_mark_io_ready(struct ublk_device *ub, struct ublk_queue *ubq)
>>  	mutex_unlock(&ub->mutex);
>>  }
>>  
>> +static void ublk_handle_need_get_data(struct ublk_device *ub, int q_id,
>> +		int tag, struct io_uring_cmd *cmd)
>> +{
>> +	struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
>> +	struct request *req = blk_mq_tag_to_rq(ub->tag_set.tags[q_id], tag);
>> +
>> +	pdu->req = req;
>> +	io_uring_cmd_complete_in_task(cmd, ublk_rq_task_work_cb);
>> +}
> 
> Here you can choose to use task work or io_uring_cmd_complete_in_task() by
> checking ublk_can_use_task_work(), just like what ublk_queue_rq() does.

OK, I will add a check on ublk_can_use_task_work().

> 
>> +
>>  static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
>>  {
>>  	struct ublksrv_io_cmd *ub_cmd = (struct ublksrv_io_cmd *)cmd->cmd;
>> @@ -884,6 +938,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
>>  		goto out;
>>  	}
>>  
>> +	/*
>> +	 * ensure that the user issues UBLK_IO_NEED_GET_DATA
>> +	 * iff the driver have set the UBLK_IO_FLAG_NEED_GET_DATA.
>> +	 */
>> +	if ((!!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA))
>> +			^ (cmd_op == UBLK_IO_NEED_GET_DATA))
>> +		goto out;
>> +
>>  	switch (cmd_op) {
>>  	case UBLK_IO_FETCH_REQ:
>>  		/* UBLK_IO_FETCH_REQ is only allowed before queue is setup */
>> @@ -917,6 +979,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
>>  		io->cmd = cmd;
>>  		ublk_commit_completion(ub, ub_cmd);
>>  		break;
>> +	case UBLK_IO_NEED_GET_DATA:
>> +		if (!(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV))
>> +			goto out;
>> +		io->addr = ub_cmd->addr;
>> +		io->cmd = cmd;
>> +		io->flags |= UBLK_IO_FLAG_ACTIVE;
>> +		ublk_handle_need_get_data(ub, ub_cmd->q_id, ub_cmd->tag, cmd);
> 
> You still need to clear UBLK_IO_FLAG_OWNED_BY_SRV here.

If ublk_drv gets one UBLK_IO_NEED_GET_DATA command, it should immediately call
io_uring_cmd_complete_in_task() and handling NEED_GET_DATA logic in the task work.

Since UBLK_IO_FLAG_OWNED_BY_SRV means the "slot" is owned by nbd driver or ublk driver,
I don't think UBLK_IO_FLAG_OWNED_BY_SRV should be cleared.

BTW, UBLK_IO_FLAG_OWNED_BY_SRV is set in the task work finally.
> 
> In future, UBLK_IO_FLAG_OWNED_BY_SRV can be removed actually.

Agree. UBLK_IO_FLAG_OWNED_BY_SRV should be integrated with UBLK_IO_FLAG_ACTIVE.

Now I am trying to implement crash recovery mechanism and I concern about memory order
while operating these IO flags. IMO, too many IO flags looks dangerous
and I made mistakes on flags while developing UBLK_NEED_GET_DATA. :(

Thanks,
Zhang

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA
  2022-07-27  3:09     ` Ziyang Zhang
@ 2022-07-27 15:48       ` Ming Lei
  0 siblings, 0 replies; 6+ messages in thread
From: Ming Lei @ 2022-07-27 15:48 UTC (permalink / raw)
  To: Ziyang Zhang; +Cc: axboe, linux-block, xiaoguang.wang

On Wed, Jul 27, 2022 at 11:09:13AM +0800, Ziyang Zhang wrote:
> On 2022/7/27 10:05, Ming Lei wrote:
> > On Tue, Jul 26, 2022 at 07:44:05PM +0800, ZiyangZhang wrote:
> >> UBLK_IO_NEED_GET_DATA is one ublk IO command. It is designed for a user
> >> application who wants to allocate IO buffer and set IO buffer address
> >> only after it receives an IO request from ublksrv. This is a reasonable
> >> scenario because these users may use a RPC framework as one IO backend
> >> to handle IO requests passed from ublksrv. And a RPC framework may
> >> allocate its own buffer(or memory pool).
> >>
> >> This new feature (UBLK_F_NEED_GET_DATA) is optional for ublk users.
> >> Related userspace code has been added in ublksrv[1] as one pull request.
> >>
> >> Test cases for this feature are added in ublksrv and all the tests pass.
> >> The performance result shows that this new feature does bring additional
> >> latency because one IO is issued back to ublk_drv once again to copy data
> >> from bio vectors to user-provided data buffer. UBLK_IO_NEED_GET_DATA is
> >> suitable for bigger block size such as 512B or 1MB.
> >>
> >> [1] https://github.com/ming1/ubdsrv
> >>
> >> Signed-off-by: ZiyangZhang <ZiyangZhang@linux.alibaba.com>
> >> ---
> >>  drivers/block/ublk_drv.c | 88 ++++++++++++++++++++++++++++++++++++----
> >>  1 file changed, 79 insertions(+), 9 deletions(-)
> >>
> >> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
> >> index 255b2de46a24..c2d2cd5ab25c 100644
> >> --- a/drivers/block/ublk_drv.c
> >> +++ b/drivers/block/ublk_drv.c
> >> @@ -47,7 +47,9 @@
> >>  #define UBLK_MINORS		(1U << MINORBITS)
> >>  
> >>  /* All UBLK_F_* have to be included into UBLK_F_ALL */
> >> -#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_URING_CMD_COMP_IN_TASK)
> >> +#define UBLK_F_ALL (UBLK_F_SUPPORT_ZERO_COPY \
> >> +		| UBLK_F_URING_CMD_COMP_IN_TASK \
> >> +		| UBLK_F_NEED_GET_DATA)
> >>  
> >>  struct ublk_rq_data {
> >>  	struct callback_head work;
> >> @@ -86,6 +88,15 @@ struct ublk_uring_cmd_pdu {
> >>   */
> >>  #define UBLK_IO_FLAG_ABORTED 0x04
> >>  
> >> +/*
> >> + * UBLK_IO_FLAG_NEED_GET_DATA is set because IO command requires
> >> + * get data buffer address from ublksrv.
> >> + *
> >> + * Then, bio data could be copied into this data buffer for a WRITE request
> >> + * after the IO command is issued again and UBLK_IO_FLAG_NEED_GET_DATA is unset.
> >> + */
> >> +#define UBLK_IO_FLAG_NEED_GET_DATA 0x08
> >> +
> >>  struct ublk_io {
> >>  	/* userspace buffer address from io cmd */
> >>  	__u64	addr;
> >> @@ -163,11 +174,19 @@ static struct miscdevice ublk_misc;
> >>  static inline bool ublk_can_use_task_work(const struct ublk_queue *ubq)
> >>  {
> >>  	if (IS_BUILTIN(CONFIG_BLK_DEV_UBLK) &&
> >> +			!(ubq->flags & UBLK_F_NEED_GET_DATA) &&
> >>  			!(ubq->flags & UBLK_F_URING_CMD_COMP_IN_TASK))
> >>  		return true;
> >>  	return false;
> > 
> > NEED_GET_DATA isn't related with using task work.
> > 
> > That said if use task work return true, you use task work to handle
> > GET_DATA, otherwise use io_uring_cmd_complete_in_task() to handle
> > it, then either we use task work or use io_uring_cmd_complete_in_task,
> > not mix the two.
> > 
> > BTW, in my test, it is observed reliably that task work can get better iops.
> 
> Ok, I will check whether UBLK_F_NEED_GET_DATA works with task_work_add().
> 
> I find that checking flags of ublk_io must stay in current ubq process.
> Otherwise there may be potential data race while aborting IOs.
> So whatever I use NEED_GET_DATA or not, task work is necessary. 

Yeah.

> 
> Using task_work_add() or io_uring_cmd_complete_in_task() depends on:
>  (1) the module is built-in or not
>  (2) the user requires the feature: UBLK_F_URING_CMD_COMP_IN_TASK
> 
> In the future, in our case, we may choose
> UBLK_F_URING_CMD_COMP_IN_TASK | UBLK_F_NEED_GET_DATA as an option.

That is fine, what I meant is that UBLK_F_NEED_GET_DATA doesn't have
to require UBLK_F_URING_CMD_COMP_IN_TASK logically.

> 
> > 
> >>  }
> >>  
> >> +static inline bool ublk_need_get_data(const struct ublk_queue *ubq)
> >> +{
> >> +	if (ubq->flags & UBLK_F_NEED_GET_DATA)
> >> +		return true;
> >> +	return false;
> >> +}
> >> +
> >>  static struct ublk_device *ublk_get_device(struct ublk_device *ub)
> >>  {
> >>  	if (kobject_get_unless_zero(&ub->cdev_dev.kobj))
> >> @@ -509,6 +528,21 @@ static void __ublk_fail_req(struct ublk_io *io, struct request *req)
> >>  	}
> >>  }
> >>  
> >> +static void ubq_complete_io_cmd(struct ublk_io *io, int res)
> >> +{
> >> +	/* mark this cmd owned by ublksrv */
> >> +	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
> >> +
> >> +	/*
> >> +	 * clear ACTIVE since we are done with this sqe/cmd slot
> >> +	 * We can only accept io cmd in case of being not active.
> >> +	 */
> >> +	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
> >> +
> >> +	/* tell ublksrv one io request is coming */
> >> +	io_uring_cmd_done(io->cmd, res, 0);
> >> +}
> >> +
> >>  #define UBLK_REQUEUE_DELAY_MS	3
> >>  
> >>  static inline void __ublk_rq_task_work(struct request *req)
> >> @@ -531,6 +565,20 @@ static inline void __ublk_rq_task_work(struct request *req)
> >>  		return;
> >>  	}
> >>  
> >> +	if (ublk_need_get_data(ubq) &&
> >> +			(req_op(req) == REQ_OP_WRITE ||
> >> +			req_op(req) == REQ_OP_FLUSH) &&
> >> +			!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA)) {
> >> +
> >> +		io->flags |= UBLK_IO_FLAG_NEED_GET_DATA;
> >> +
> >> +		pr_devel("%s: ublk_need_get_data. op %d, qid %d tag %d io_flags %x\n",
> >> +				__func__, io->cmd->cmd_op, ubq->q_id, req->tag, io->flags);
> >> +
> >> +		ubq_complete_io_cmd(io, UBLK_IO_RES_NEED_GET_DATA);
> >> +		return;
> >> +	}
> >> +
> >>  	mapped_bytes = ublk_map_io(ubq, req, io);
> >>  
> >>  	/* partially mapped, update io descriptor */
> >> @@ -553,17 +601,13 @@ static inline void __ublk_rq_task_work(struct request *req)
> >>  			mapped_bytes >> 9;
> >>  	}
> >>  
> >> -	/* mark this cmd owned by ublksrv */
> >> -	io->flags |= UBLK_IO_FLAG_OWNED_BY_SRV;
> >> -
> >>  	/*
> >> -	 * clear ACTIVE since we are done with this sqe/cmd slot
> >> -	 * We can only accept io cmd in case of being not active.
> >> +	 * Anyway, we have handled UBLK_IO_NEED_GET_DATA for WRITE/FLUSH requests,
> >> +	 * or we did nothing for other types requests.
> >>  	 */
> >> -	io->flags &= ~UBLK_IO_FLAG_ACTIVE;
> >> +	io->flags &= ~UBLK_IO_FLAG_NEED_GET_DATA;
> >>  
> >> -	/* tell ublksrv one io request is coming */
> >> -	io_uring_cmd_done(io->cmd, UBLK_IO_RES_OK, 0);
> >> +	ubq_complete_io_cmd(io, UBLK_IO_RES_OK);
> >>  }
> >>  
> >>  static void ublk_rq_task_work_cb(struct io_uring_cmd *cmd)
> >> @@ -846,6 +890,16 @@ static void ublk_mark_io_ready(struct ublk_device *ub, struct ublk_queue *ubq)
> >>  	mutex_unlock(&ub->mutex);
> >>  }
> >>  
> >> +static void ublk_handle_need_get_data(struct ublk_device *ub, int q_id,
> >> +		int tag, struct io_uring_cmd *cmd)
> >> +{
> >> +	struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
> >> +	struct request *req = blk_mq_tag_to_rq(ub->tag_set.tags[q_id], tag);
> >> +
> >> +	pdu->req = req;
> >> +	io_uring_cmd_complete_in_task(cmd, ublk_rq_task_work_cb);
> >> +}
> > 
> > Here you can choose to use task work or io_uring_cmd_complete_in_task() by
> > checking ublk_can_use_task_work(), just like what ublk_queue_rq() does.
> 
> OK, I will add a check on ublk_can_use_task_work().
> 
> > 
> >> +
> >>  static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
> >>  {
> >>  	struct ublksrv_io_cmd *ub_cmd = (struct ublksrv_io_cmd *)cmd->cmd;
> >> @@ -884,6 +938,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
> >>  		goto out;
> >>  	}
> >>  
> >> +	/*
> >> +	 * ensure that the user issues UBLK_IO_NEED_GET_DATA
> >> +	 * iff the driver have set the UBLK_IO_FLAG_NEED_GET_DATA.
> >> +	 */
> >> +	if ((!!(io->flags & UBLK_IO_FLAG_NEED_GET_DATA))
> >> +			^ (cmd_op == UBLK_IO_NEED_GET_DATA))
> >> +		goto out;
> >> +
> >>  	switch (cmd_op) {
> >>  	case UBLK_IO_FETCH_REQ:
> >>  		/* UBLK_IO_FETCH_REQ is only allowed before queue is setup */
> >> @@ -917,6 +979,14 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
> >>  		io->cmd = cmd;
> >>  		ublk_commit_completion(ub, ub_cmd);
> >>  		break;
> >> +	case UBLK_IO_NEED_GET_DATA:
> >> +		if (!(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV))
> >> +			goto out;
> >> +		io->addr = ub_cmd->addr;
> >> +		io->cmd = cmd;
> >> +		io->flags |= UBLK_IO_FLAG_ACTIVE;
> >> +		ublk_handle_need_get_data(ub, ub_cmd->q_id, ub_cmd->tag, cmd);
> > 
> > You still need to clear UBLK_IO_FLAG_OWNED_BY_SRV here.
> 
> If ublk_drv gets one UBLK_IO_NEED_GET_DATA command, it should immediately call
> io_uring_cmd_complete_in_task() and handling NEED_GET_DATA logic in the task work.
> 
> Since UBLK_IO_FLAG_OWNED_BY_SRV means the "slot" is owned by nbd driver or ublk driver,
> I don't think UBLK_IO_FLAG_OWNED_BY_SRV should be cleared.

OK, fine.

> 
> BTW, UBLK_IO_FLAG_OWNED_BY_SRV is set in the task work finally.
> > 
> > In future, UBLK_IO_FLAG_OWNED_BY_SRV can be removed actually.
> 
> Agree. UBLK_IO_FLAG_OWNED_BY_SRV should be integrated with UBLK_IO_FLAG_ACTIVE.
> 
> Now I am trying to implement crash recovery mechanism and I concern about memory order

IO flags should be only touched in ubq daemon context, so no need to
worry about memory order.

> while operating these IO flags. IMO, too many IO flags looks dangerous
> and I made mistakes on flags while developing UBLK_NEED_GET_DATA. :(

There are just 3 commands if GET_DATA is added, and each io command's state is
very limited, so there shouldn't be too many io flags.



Thanks,
Ming


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2022-07-27 15:48 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-26 11:44 [PATCH V2 0/2] ublk: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
2022-07-26 11:44 ` [PATCH V2 1/2] ublk_cmd.h: add one new ublk command: UBLK_IO_NEED_GET_DATA ZiyangZhang
2022-07-26 11:44 ` [PATCH V2 2/2] ublk_drv: add support for UBLK_IO_NEED_GET_DATA ZiyangZhang
2022-07-27  2:05   ` Ming Lei
2022-07-27  3:09     ` Ziyang Zhang
2022-07-27 15:48       ` Ming Lei

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.