From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] [PATCH 10/30] rcu: add call_rcu
Date: Fri, 28 Jun 2013 20:26:29 +0200 [thread overview]
Message-ID: <1372444009-11544-11-git-send-email-pbonzini@redhat.com> (raw)
In-Reply-To: <1372444009-11544-1-git-send-email-pbonzini@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
docs/rcu.txt | 108 +++++++++++++++++++++++++++++++++++++++++++++--
include/qemu/rcu.h | 22 ++++++++++
util/rcu.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 246 insertions(+), 4 deletions(-)
diff --git a/docs/rcu.txt b/docs/rcu.txt
index 118a28a..ca1c099 100644
--- a/docs/rcu.txt
+++ b/docs/rcu.txt
@@ -82,7 +82,50 @@ The core RCU API is small:
Note that it would be valid for another update to come while
synchronize_rcu is running. Because of this, it is better that
the updater releases any locks it may hold before calling
- synchronize_rcu.
+ synchronize_rcu. If this is not possible (for example, because
+ the updater is protected by the BQL), you can use call_rcu.
+
+ void call_rcu1(struct rcu_head * head,
+ void (*func)(struct rcu_head *head));
+
+ This function invokes func(head) after all pre-existing RCU
+ read-side critical sections on all threads have completed. This
+ marks the end of the removal phase, with func taking care
+ asynchronously of the reclamation phase.
+
+ The foo struct needs to have an rcu_head structure added,
+ perhaps as follows:
+
+ struct foo {
+ struct rcu_head rcu;
+ int a;
+ char b;
+ long c;
+ };
+
+ so that the reclaimer function can fetch the struct foo address
+ and free it:
+
+ call_rcu1(foo_reclaim, &foo.rcu);
+
+ void foo_reclaim(struct rcu_head *rp)
+ {
+ struct foo *fp = container_of(rp, struct foo, rcu);
+ g_free(fp);
+ }
+
+ For the common case where the rcu_head member is the first of the
+ struct, you can use the following macro.
+
+ void call_rcu(T *p,
+ void (*func)(T *p),
+ field-name);
+
+ call_rcu1 is typically used through this macro, in the common case
+ where the "struct rcu_head" is the first field in the struct. In
+ the above case, one could have written simply:
+
+ call_rcu(foo_reclaim, g_free, rcu);
typeof(*p) rcu_dereference(p);
typeof(p) rcu_assign_pointer(p, typeof(p) v);
@@ -176,6 +219,11 @@ DIFFERENCES WITH LINUX
- rcu_dereference takes a _pointer_ to the variable being accessed.
Wrong usage will be detected by the compiler.
+- call_rcu is a macro that has an extra argument (the name of the first
+ field in the struct, which must be a struct rcu_head), and expects the
+ type of the callback's argument to be the type of the first argument.
+ call_rcu1 is the same as Linux's call_rcu.
+
- Quiescent points must be marked explicitly in the thread.
@@ -231,7 +279,47 @@ The write side looks simply like this (with appropriate locking):
synchronize_rcu();
free(old);
-Note that the same idiom would be possible with reader/writer
+If the processing cannot be done purely within the critical section, it
+is possible to combine this idiom with a "real" reference count:
+
+ rcu_read_lock();
+ p = rcu_dereference(&foo);
+ foo_ref(p);
+ rcu_read_unlock();
+ /* do something with p. */
+ foo_unref(p);
+
+The write side can be like this:
+
+ qemu_mutex_lock(&foo_mutex);
+ old = foo;
+ rcu_assign_pointer(foo, new);
+ qemu_mutex_unlock(&foo_mutex);
+ synchronize_rcu();
+ foo_unref(old);
+
+or with call_rcu:
+
+ qemu_mutex_lock(&foo_mutex);
+ old = foo;
+ rcu_assign_pointer(foo, new);
+ qemu_mutex_unlock(&foo_mutex);
+ call_rcu(foo_unref, old, rcu);
+
+In both cases, the write side only performs removal. Reclamation
+happens when the last reference to a "foo" object is dropped.
+Using synchronize_rcu() is undesirably expensive, because the
+last reference may be dropped on the read side. Hence you can
+use call_rcu() instead:
+
+ foo_unref(struct foo *p) {
+ if (atomic_dec(&p->refcount) == 0) {
+ call_rcu(foo_destroy, p, rcu);
+ }
+ }
+
+
+Note that the same idioms would be possible with reader/writer
locks:
read_lock(&foo_rwlock); write_mutex_lock(&foo_rwlock);
@@ -241,13 +329,25 @@ locks:
write_mutex_unlock(&foo_rwlock);
free(p);
+ ------------------------------------------------------------------
+
+ read_lock(&foo_rwlock); write_mutex_lock(&foo_rwlock);
+ p = foo; old = foo;
+ foo_ref(p); foo = new;
+ read_unlock(&foo_rwlock); write_mutex_unlock(&foo_rwlock);
+ /* do something with p. */ foo_unref(old);
+ foo_unref(p);
+
+foo_unref could use a mechanism such as bottom halves to move deallocation
+out of hot paths.
+
RCU resizable arrays
--------------------
Resizable arrays can be used with RCU. The expensive RCU synchronization
-only needs to take place when the array is resized. The two items to
-take care of are:
+(or call_rcu) only needs to take place when the array is resized.
+The two items to take care of are:
- ensuring that the old version of the array is available between removal
and reclamation;
diff --git a/include/qemu/rcu.h b/include/qemu/rcu.h
index 96841e3..1ed70bb 100644
--- a/include/qemu/rcu.h
+++ b/include/qemu/rcu.h
@@ -134,6 +134,28 @@ extern void synchronize_rcu(void);
extern void rcu_register_thread(void);
extern void rcu_unregister_thread(void);
+struct rcu_head;
+typedef void RCUCBFunc(struct rcu_head *head);
+
+struct rcu_head {
+ struct rcu_head *next;
+ RCUCBFunc *func;
+};
+
+extern void call_rcu1(struct rcu_head *head, RCUCBFunc *func);
+
+/* The operands of the minus operator must have the same type,
+ * which must be the one that we specify in the cast.
+ */
+#define call_rcu(head, func, field) \
+ call_rcu1(({ \
+ char __attribute__((unused)) \
+ offset_must_be_zero[-offsetof(typeof(*(head)), field)], \
+ func_type_invalid = (func) - (void (*)(typeof(head)))(func); \
+ &(head)->field; \
+ }), \
+ (RCUCBFunc *)(func))
+
#ifdef __cplusplus
}
#endif
diff --git a/util/rcu.c b/util/rcu.c
index e1498db..f1c5736 100644
--- a/util/rcu.c
+++ b/util/rcu.c
@@ -26,6 +26,7 @@
* IBM's contributions to this file may be relicensed under LGPLv2 or later.
*/
+#include "qemu-common.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
@@ -33,6 +34,7 @@
#include <errno.h>
#include "qemu/rcu.h"
#include "qemu/atomic.h"
+#include "qemu/thread.h"
/*
* Global grace period counter. Bit 0 is one if the thread is online.
@@ -166,6 +168,119 @@ void synchronize_rcu(void)
}
}
+
+#define RCU_CALL_MIN_SIZE 30
+
+/* Multi-producer, single-consumer queue based on urcu/static/wfqueue.h
+ * from liburcu. Note that head is only used by the consumer.
+ */
+static struct rcu_head dummy;
+static struct rcu_head *head = &dummy, **tail = &dummy.next;
+static int rcu_call_count;
+static QemuEvent rcu_call_ready_event;
+
+static void enqueue(struct rcu_head *node)
+{
+ struct rcu_head **old_tail;
+
+ node->next = NULL;
+ old_tail = atomic_xchg(&tail, &node->next);
+ atomic_mb_set(old_tail, node);
+}
+
+static struct rcu_head *try_dequeue(void)
+{
+ struct rcu_head *node, *next;
+
+retry:
+ /* Test for an empty list, which we do not expect. Note that for
+ * the consumer head and tail are always consistent. The head
+ * is consistent because only the consumer reads/writes it.
+ * The tail, because it is the first step in the enqueuing.
+ * It is only the next pointers that might be inconsistent.
+ */
+ if (head == &dummy && atomic_mb_read(&tail) == &dummy.next) {
+ abort();
+ }
+
+ /* If the head node has NULL in its next pointer, the value is
+ * wrong and we need to wait until its enqueuer finishes the update.
+ */
+ node = head;
+ next = atomic_mb_read(&head->next);
+ if (!next) {
+ return NULL;
+ }
+
+ /* Since we are the sole consumer, and we excluded the empty case
+ * above, the queue will always have at least two nodes: the
+ * dummy node, and the one being removed. So we do not need to update
+ * the tail pointer.
+ */
+ head = next;
+
+ /* If we dequeued the dummy node, add it back at the end and retry. */
+ if (node == &dummy) {
+ enqueue(node);
+ goto retry;
+ }
+
+ return node;
+}
+
+static void *call_rcu_thread(void *opaque)
+{
+ struct rcu_head *node;
+
+ /* This thread is just a writer. */
+ rcu_thread_offline();
+
+ for (;;) {
+ int tries = 0;
+ int n = atomic_read(&rcu_call_count);
+
+ /* Heuristically wait for a decent number of callbacks to pile up.
+ * Fetch rcu_call_count now, we only must process elements that were
+ * added before synchronize_rcu() starts.
+ */
+ while (n < RCU_CALL_MIN_SIZE && ++tries <= 5) {
+ g_usleep(100000);
+ qemu_event_reset(&rcu_call_ready_event);
+ n = atomic_read(&rcu_call_count);
+ if (n < RCU_CALL_MIN_SIZE) {
+ qemu_event_wait(&rcu_call_ready_event);
+ n = atomic_read(&rcu_call_count);
+ }
+ }
+
+ atomic_sub(&rcu_call_count, n);
+ synchronize_rcu();
+ while (n > 0) {
+ node = try_dequeue();
+ while (!node) {
+ qemu_event_reset(&rcu_call_ready_event);
+ node = try_dequeue();
+ if (!node) {
+ qemu_event_wait(&rcu_call_ready_event);
+ node = try_dequeue();
+ }
+ }
+
+ n--;
+ node->func(node);
+ }
+ }
+ abort();
+}
+
+void call_rcu1(struct rcu_head *node, void (*func)(struct rcu_head *node))
+{
+ node->func = func;
+ enqueue(node);
+ atomic_inc(&rcu_call_count);
+ qemu_event_set(&rcu_call_ready_event);
+}
+
void rcu_register_thread(void)
{
if (!tls_get_rcu_reader()) {
@@ -189,7 +304,12 @@ void rcu_unregister_thread(void)
static void __attribute__((__constructor__)) rcu_init(void)
{
+ QemuThread thread;
+
qemu_mutex_init(&rcu_gp_lock);
qemu_event_init(&rcu_gp_event, true);
+
+ qemu_event_init(&rcu_call_ready_event, false);
+ qemu_thread_create(&thread, call_rcu_thread, NULL, QEMU_THREAD_DETACHED);
rcu_register_thread();
}
--
1.8.1.4
next prev parent reply other threads:[~2013-06-28 18:27 UTC|newest]
Thread overview: 68+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-06-28 18:26 [Qemu-devel] [PATCH 00/30] Memory API changes for 1.6: RCU-protected address space dispatch Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 01/30] memory: access FlatView from a local variable Paolo Bonzini
2013-06-28 20:01 ` Anthony Liguori
2013-06-28 18:26 ` [Qemu-devel] [PATCH 02/30] memory: use a new FlatView pointer on every topology update Paolo Bonzini
2013-06-28 20:02 ` Anthony Liguori
2013-06-28 18:26 ` [Qemu-devel] [PATCH 03/30] memory: add reference counting to FlatView Paolo Bonzini
2013-06-28 20:07 ` Anthony Liguori
2013-06-28 18:26 ` [Qemu-devel] [PATCH 04/30] add a header file for atomic operations Paolo Bonzini
2013-06-28 20:41 ` Anthony Liguori
2013-07-01 10:21 ` Paolo Bonzini
2013-07-01 13:00 ` Anthony Liguori
2013-07-01 13:04 ` Paolo Bonzini
2013-07-01 13:20 ` Anthony Liguori
2013-07-04 5:24 ` liu ping fan
2013-07-01 11:08 ` Peter Maydell
2013-07-03 2:24 ` liu ping fan
2013-07-03 5:59 ` Paolo Bonzini
2013-07-03 7:07 ` liu ping fan
2013-06-28 18:26 ` [Qemu-devel] [PATCH 05/30] exec: do not use qemu/tls.h Paolo Bonzini
2013-06-28 20:43 ` Anthony Liguori
2013-06-28 23:53 ` Ed Maste
2013-07-01 10:16 ` Paolo Bonzini
2013-06-29 10:55 ` Peter Maydell
2013-07-01 10:45 ` Paolo Bonzini
2013-07-01 11:05 ` Peter Maydell
2013-07-01 16:21 ` Paolo Bonzini
2013-07-01 16:26 ` Peter Maydell
2013-07-01 20:52 ` Paolo Bonzini
2013-07-01 21:34 ` Peter Maydell
2013-07-02 13:40 ` Andreas Färber
2013-07-02 14:06 ` Alexander Graf
2013-06-28 18:26 ` [Qemu-devel] [PATCH 06/30] qemu-thread: add TLS wrappers Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 07/30] qemu-thread: add QemuEvent Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 08/30] rcu: add rcu library Paolo Bonzini
2013-07-01 9:47 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 09/30] qemu-thread: register threads with RCU Paolo Bonzini
2013-06-28 18:26 ` Paolo Bonzini [this message]
2013-06-28 18:26 ` [Qemu-devel] [PATCH 11/30] rcu: add rcutorture Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 12/30] rcu: allow nested calls to rcu_thread_offline/rcu_thread_online Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 13/30] qemu-thread: report RCU quiescent states Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 14/30] event loop: " Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 15/30] cpus: " Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 16/30] block: " Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 17/30] migration: " Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 18/30] memory: protect current_map by RCU Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 19/30] memory: avoid ref/unref in memory_region_find Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 20/30] exec: change well-known physical sections to macros Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 21/30] exec: separate current memory map from the one being built Paolo Bonzini
2013-07-02 14:41 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 22/30] memory: move MemoryListener declaration earlier Paolo Bonzini
2013-07-02 14:41 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 23/30] exec: move listener from AddressSpaceDispatch to AddressSpace Paolo Bonzini
2013-07-02 14:41 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 24/30] exec: separate current radix tree from the one being built Paolo Bonzini
2013-07-02 14:41 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 25/30] exec: put memory map in AddressSpaceDispatch Paolo Bonzini
2013-07-02 14:42 ` Jan Kiszka
2013-07-02 15:08 ` Paolo Bonzini
2013-07-02 15:48 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 26/30] exec: remove cur_map Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 27/30] exec: change some APIs to take AddressSpaceDispatch Paolo Bonzini
2013-07-02 14:47 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 28/30] exec: change iotlb " Paolo Bonzini
2013-07-02 10:00 ` Jan Kiszka
2013-06-28 18:26 ` [Qemu-devel] [PATCH 29/30] exec: add a reference to the region returned by address_space_translate Paolo Bonzini
2013-06-28 18:26 ` [Qemu-devel] [PATCH 30/30] exec: put address space dispatch under RCU critical section Paolo Bonzini
2013-06-28 19:38 ` Jan Kiszka
2013-07-01 11:48 ` Paolo Bonzini
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=1372444009-11544-11-git-send-email-pbonzini@redhat.com \
--to=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.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).