From: "Alex Bennée" <alex.bennee@linaro.org>
To: mttcg@listserver.greensocs.com, qemu-devel@nongnu.org,
fred.konrad@greensocs.com, a.rigo@virtualopensystems.com,
serge.fdrv@gmail.com, cota@braap.org, bobby.prani@gmail.com
Cc: mark.burton@greensocs.com, pbonzini@redhat.com,
jan.kiszka@siemens.com, rth@twiddle.net,
peter.maydell@linaro.org, claudio.fontana@huawei.com,
"Sergey Fedorov" <sergey.fedorov@linaro.org>,
"Alex Bennée" <alex.bennee@linaro.org>,
"Peter Crosthwaite" <crosthwaite.peter@gmail.com>,
"Riku Voipio" <riku.voipio@iki.fi>
Subject: [Qemu-devel] [PATCH v5 11/13] cpu-exec-common: Introduce async_safe_run_on_cpu()
Date: Tue, 2 Aug 2016 18:27:42 +0100 [thread overview]
Message-ID: <1470158864-17651-12-git-send-email-alex.bennee@linaro.org> (raw)
In-Reply-To: <1470158864-17651-1-git-send-email-alex.bennee@linaro.org>
From: Sergey Fedorov <serge.fdrv@gmail.com>
This patch is based on the ideas found in work of KONRAD Frederic [1],
Alex Bennée [2], and Alvise Rigo [3].
This mechanism allows to perform an operation safely in a quiescent
state. Quiescent state means: (1) no vCPU is running and (2) BQL in
system-mode or 'exclusive_lock' in user-mode emulation is held while
performing the operation. This functionality is required e.g. for
performing translation buffer flush safely in multi-threaded user-mode
emulation.
The existing CPU work queue is used to schedule such safe operations. A
new 'safe' flag is added into struct qemu_work_item to designate the
special requirements of the safe work. An operation in a quiescent sate
can be scheduled by using async_safe_run_on_cpu() function which is
actually the same as sync_run_on_cpu() except that it marks the queued
work item with the 'safe' flag set to true. Given this flag set
queue_work_on_cpu() atomically increments 'safe_work_pending' global
counter and kicks all the CPUs instead of just the target CPU as in case
of normal CPU work. This allows to force other CPUs to exit their
execution loops and wait in wait_safe_cpu_work() function for the safe
work to finish. When a CPU drains its work queue, if it encounters a
work item marked as safe, it first waits for other CPUs to exit their
execution loops, then called the work item function, and finally
decrements 'safe_work_pending' counter with signalling other CPUs to let
them continue execution as soon as all pending safe work items have been
processed. The 'tcg_pending_threads' protected by 'exclusive_lock' in
user-mode or by 'qemu_global_mutex' in system-mode emulation is used to
determine if there is any CPU run and wait for it to exit the execution
loop. The fairness of all the CPU work queues is ensured by draining all
the pending safe work items before any CPU can run.
[1] http://lists.nongnu.org/archive/html/qemu-devel/2015-08/msg01128.html
[2] http://lists.nongnu.org/archive/html/qemu-devel/2016-04/msg02531.html
[3] http://lists.nongnu.org/archive/html/qemu-devel/2016-05/msg04792.html
Signed-off-by: Sergey Fedorov <serge.fdrv@gmail.com>
Signed-off-by: Sergey Fedorov <sergey.fedorov@linaro.org>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
---
v5 (ajb)
- signal another thread if sleeping and tcg_pending_threads == 0
- add can_wait_for_safe() to skip in single thread SoftMMU mode
---
bsd-user/main.c | 3 ++-
cpu-exec-common.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++-
cpus.c | 20 +++++++++++++++
include/exec/exec-all.h | 14 +++++++++++
include/qom/cpu.h | 14 +++++++++++
linux-user/main.c | 13 +++++-----
6 files changed, 123 insertions(+), 8 deletions(-)
diff --git a/bsd-user/main.c b/bsd-user/main.c
index 24d33c9..6f6a03c 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -69,9 +69,10 @@ int cpu_get_pic_interrupt(CPUX86State *env)
void qemu_init_cpu_loop(void)
{
/* We need to do this becuase process_queued_cpu_work() calls
- * qemu_cond_broadcast() on it
+ * qemu_cond_broadcast() on them
*/
qemu_cond_init(&qemu_work_cond);
+ qemu_cond_init(&qemu_safe_work_cond);
}
QemuMutex *qemu_get_cpu_work_mutex(void)
diff --git a/cpu-exec-common.c b/cpu-exec-common.c
index a233f01..6d5da15 100644
--- a/cpu-exec-common.c
+++ b/cpu-exec-common.c
@@ -25,6 +25,7 @@
bool exit_request;
CPUState *tcg_current_cpu;
+int tcg_pending_threads;
/* exit the current TB, but without causing any exception to be raised */
void cpu_loop_exit_noexc(CPUState *cpu)
@@ -79,6 +80,35 @@ void cpu_loop_exit_restore(CPUState *cpu, uintptr_t pc)
}
QemuCond qemu_work_cond;
+QemuCond qemu_safe_work_cond;
+QemuCond qemu_exclusive_cond;
+
+static int safe_work_pending;
+
+#ifdef CONFIG_USER_ONLY
+#define can_wait_for_safe() (1)
+#else
+/*
+ * We never sleep in SoftMMU emulation because we would deadlock as
+ * all vCPUs are in the same thread. This will change for MTTCG
+ * however.
+ */
+#define can_wait_for_safe() (0)
+#endif
+
+void wait_safe_cpu_work(void)
+{
+ while (can_wait_for_safe() && atomic_mb_read(&safe_work_pending) > 0) {
+ /*
+ * If there is pending safe work and no pending threads we
+ * need to signal another thread to start its work.
+ */
+ if (tcg_pending_threads == 0) {
+ qemu_cond_signal(&qemu_exclusive_cond);
+ }
+ qemu_cond_wait(&qemu_safe_work_cond, qemu_get_cpu_work_mutex());
+ }
+}
static void queue_work_on_cpu(CPUState *cpu, struct qemu_work_item *wi)
{
@@ -91,9 +121,18 @@ static void queue_work_on_cpu(CPUState *cpu, struct qemu_work_item *wi)
cpu->queued_work_last = wi;
wi->next = NULL;
wi->done = false;
+ if (wi->safe) {
+ atomic_inc(&safe_work_pending);
+ }
qemu_mutex_unlock(&cpu->work_mutex);
- qemu_cpu_kick(cpu);
+ if (!wi->safe) {
+ qemu_cpu_kick(cpu);
+ } else {
+ CPU_FOREACH(cpu) {
+ qemu_cpu_kick(cpu);
+ }
+ }
}
void run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data)
@@ -108,6 +147,7 @@ void run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data)
wi.func = func;
wi.data = data;
wi.free = false;
+ wi.safe = false;
queue_work_on_cpu(cpu, &wi);
while (!atomic_mb_read(&wi.done)) {
@@ -131,6 +171,20 @@ void async_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data)
wi->func = func;
wi->data = data;
wi->free = true;
+ wi->safe = false;
+
+ queue_work_on_cpu(cpu, wi);
+}
+
+void async_safe_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data)
+{
+ struct qemu_work_item *wi;
+
+ wi = g_malloc0(sizeof(struct qemu_work_item));
+ wi->func = func;
+ wi->data = data;
+ wi->free = true;
+ wi->safe = true;
queue_work_on_cpu(cpu, wi);
}
@@ -150,9 +204,20 @@ void process_queued_cpu_work(CPUState *cpu)
if (!cpu->queued_work_first) {
cpu->queued_work_last = NULL;
}
+ if (wi->safe) {
+ while (tcg_pending_threads) {
+ qemu_cond_wait(&qemu_exclusive_cond,
+ qemu_get_cpu_work_mutex());
+ }
+ }
qemu_mutex_unlock(&cpu->work_mutex);
wi->func(cpu, wi->data);
qemu_mutex_lock(&cpu->work_mutex);
+ if (wi->safe) {
+ if (!atomic_dec_fetch(&safe_work_pending)) {
+ qemu_cond_broadcast(&qemu_safe_work_cond);
+ }
+ }
if (wi->free) {
g_free(wi);
} else {
diff --git a/cpus.c b/cpus.c
index 282d7e3..b712204 100644
--- a/cpus.c
+++ b/cpus.c
@@ -903,6 +903,8 @@ void qemu_init_cpu_loop(void)
qemu_cond_init(&qemu_cpu_cond);
qemu_cond_init(&qemu_pause_cond);
qemu_cond_init(&qemu_work_cond);
+ qemu_cond_init(&qemu_safe_work_cond);
+ qemu_cond_init(&qemu_exclusive_cond);
qemu_cond_init(&qemu_io_proceeded_cond);
qemu_mutex_init(&qemu_global_mutex);
@@ -926,6 +928,20 @@ static void qemu_tcg_destroy_vcpu(CPUState *cpu)
{
}
+/* called with qemu_global_mutex held */
+static inline void tcg_cpu_exec_start(CPUState *cpu)
+{
+ tcg_pending_threads++;
+}
+
+/* called with qemu_global_mutex held */
+static inline void tcg_cpu_exec_end(CPUState *cpu)
+{
+ if (--tcg_pending_threads) {
+ qemu_cond_broadcast(&qemu_exclusive_cond);
+ }
+}
+
static void qemu_wait_io_event_common(CPUState *cpu)
{
if (cpu->stop) {
@@ -950,6 +966,8 @@ static void qemu_tcg_wait_io_event(CPUState *cpu)
CPU_FOREACH(cpu) {
qemu_wait_io_event_common(cpu);
}
+
+ wait_safe_cpu_work();
}
static void qemu_kvm_wait_io_event(CPUState *cpu)
@@ -1485,7 +1503,9 @@ static void tcg_exec_all(void)
(cpu->singlestep_enabled & SSTEP_NOTIMER) == 0);
if (cpu_can_run(cpu)) {
+ tcg_cpu_exec_start(cpu);
r = tcg_cpu_exec(cpu);
+ tcg_cpu_exec_end(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
break;
diff --git a/include/exec/exec-all.h b/include/exec/exec-all.h
index e4dfd3c..ed5b9c8 100644
--- a/include/exec/exec-all.h
+++ b/include/exec/exec-all.h
@@ -407,12 +407,22 @@ extern int singlestep;
/* cpu-exec.c, accessed with atomic_mb_read/atomic_mb_set */
extern CPUState *tcg_current_cpu;
+extern int tcg_pending_threads;
extern bool exit_request;
/**
* qemu_work_cond - condition to wait for CPU work items completion
*/
extern QemuCond qemu_work_cond;
+/**
+ * qemu_safe_work_cond - condition to wait for safe CPU work items completion
+ */
+extern QemuCond qemu_safe_work_cond;
+/**
+ * qemu_exclusive_cond - condition to wait for all TCG threads to be out of
+ * guest code execution loop
+ */
+extern QemuCond qemu_exclusive_cond;
/**
* qemu_get_cpu_work_mutex() - get the mutex which protects CPU work execution
@@ -425,5 +435,9 @@ QemuMutex *qemu_get_cpu_work_mutex(void);
* @cpu: The CPU which work queue to process.
*/
void process_queued_cpu_work(CPUState *cpu);
+/**
+ * wait_safe_cpu_work() - wait until all safe CPU work items has processed
+ */
+void wait_safe_cpu_work(void);
#endif
diff --git a/include/qom/cpu.h b/include/qom/cpu.h
index bd76a27..bc24514 100644
--- a/include/qom/cpu.h
+++ b/include/qom/cpu.h
@@ -240,6 +240,7 @@ struct qemu_work_item {
void *data;
int done;
bool free;
+ bool safe;
};
/**
@@ -638,6 +639,19 @@ void run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data);
void async_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data);
/**
+ * async_safe_run_on_cpu:
+ * @cpu: The vCPU to run on.
+ * @func: The function to be executed.
+ * @data: Data to pass to the function.
+ *
+ * Schedules the function @func for execution on the vCPU @cpu asynchronously
+ * and in quiescent state. Quiescent state means: (1) all other vCPUs are
+ * halted and (2) #qemu_global_mutex (a.k.a. BQL) in system-mode or
+ * #exclusive_lock in user-mode emulation is held while @func is executing.
+ */
+void async_safe_run_on_cpu(CPUState *cpu, run_on_cpu_func func, void *data);
+
+/**
* qemu_get_cpu:
* @index: The CPUState@cpu_index value of the CPU to obtain.
*
diff --git a/linux-user/main.c b/linux-user/main.c
index 13ac77d..e7d7dd4 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -113,18 +113,17 @@ int cpu_get_pic_interrupt(CPUX86State *env)
which requires quite a lot of per host/target work. */
static QemuMutex cpu_list_mutex;
static QemuMutex exclusive_lock;
-static QemuCond exclusive_cond;
static QemuCond exclusive_resume;
static bool exclusive_pending;
-static int tcg_pending_threads;
void qemu_init_cpu_loop(void)
{
qemu_mutex_init(&cpu_list_mutex);
qemu_mutex_init(&exclusive_lock);
- qemu_cond_init(&exclusive_cond);
qemu_cond_init(&exclusive_resume);
qemu_cond_init(&qemu_work_cond);
+ qemu_cond_init(&qemu_safe_work_cond);
+ qemu_cond_init(&qemu_exclusive_cond);
}
/* Make sure everything is in a consistent state for calling fork(). */
@@ -151,9 +150,10 @@ void fork_end(int child)
exclusive_pending = false;
qemu_mutex_init(&exclusive_lock);
qemu_mutex_init(&cpu_list_mutex);
- qemu_cond_init(&exclusive_cond);
qemu_cond_init(&exclusive_resume);
qemu_cond_init(&qemu_work_cond);
+ qemu_cond_init(&qemu_safe_work_cond);
+ qemu_cond_init(&qemu_exclusive_cond);
qemu_mutex_init(&tcg_ctx.tb_ctx.tb_lock);
gdbserver_fork(thread_cpu);
} else {
@@ -193,7 +193,7 @@ static inline void start_exclusive(void)
}
}
while (tcg_pending_threads) {
- qemu_cond_wait(&exclusive_cond, &exclusive_lock);
+ qemu_cond_wait(&qemu_exclusive_cond, &exclusive_lock);
}
}
@@ -222,10 +222,11 @@ static inline void cpu_exec_end(CPUState *cpu)
cpu->running = false;
tcg_pending_threads--;
if (!tcg_pending_threads) {
- qemu_cond_signal(&exclusive_cond);
+ qemu_cond_broadcast(&qemu_exclusive_cond);
}
exclusive_idle();
process_queued_cpu_work(cpu);
+ wait_safe_cpu_work();
qemu_mutex_unlock(&exclusive_lock);
}
--
2.7.4
next prev parent reply other threads:[~2016-08-02 17:28 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-08-02 17:27 [Qemu-devel] [PATCH v5 00/13] cpu-exec: Safe work in quiescent state Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 01/13] atomic: introduce atomic_dec_fetch Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 02/13] cpus: pass CPUState to run_on_cpu helpers Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 03/13] cpus: Move common code out of {async_, }run_on_cpu() Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 04/13] cpus: Wrap mutex used to protect CPU work Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 05/13] cpus: Rename flush_queued_work() Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 06/13] linux-user: Use QemuMutex and QemuCond Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 07/13] linux-user: Rework exclusive operation mechanism Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 08/13] linux-user: Add qemu_cpu_is_self() and qemu_cpu_kick() Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 09/13] linux-user: Support CPU work queue Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 10/13] bsd-user: " Alex Bennée
2016-08-02 17:27 ` Alex Bennée [this message]
2016-08-02 19:22 ` [Qemu-devel] [PATCH v5 11/13] cpu-exec-common: Introduce async_safe_run_on_cpu() Emilio G. Cota
2016-08-03 21:02 ` Alex Bennée
2016-08-03 23:17 ` Emilio G. Cota
2016-08-04 6:44 ` Alex Bennée
2016-08-28 0:21 ` Paolo Bonzini
2016-08-29 17:26 ` Paolo Bonzini
2016-08-31 10:09 ` Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 12/13] tcg: Make tb_flush() thread safe Alex Bennée
2016-08-02 17:27 ` [Qemu-devel] [PATCH v5 13/13] cpu-exec: replace cpu->queued_work with GArray Alex Bennée
2016-08-02 17:36 ` Alex Bennée
2016-08-02 17:42 ` Alex Bennée
2016-08-02 18:53 ` Emilio G. Cota
2016-08-03 8:34 ` Alex Bennée
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1470158864-17651-12-git-send-email-alex.bennee@linaro.org \
--to=alex.bennee@linaro.org \
--cc=a.rigo@virtualopensystems.com \
--cc=bobby.prani@gmail.com \
--cc=claudio.fontana@huawei.com \
--cc=cota@braap.org \
--cc=crosthwaite.peter@gmail.com \
--cc=fred.konrad@greensocs.com \
--cc=jan.kiszka@siemens.com \
--cc=mark.burton@greensocs.com \
--cc=mttcg@listserver.greensocs.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=riku.voipio@iki.fi \
--cc=rth@twiddle.net \
--cc=serge.fdrv@gmail.com \
--cc=sergey.fedorov@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).