Patchwork [RFC,03/13] qemu-threads: add QemuEvent

login
register
mail settings
Submitter Paolo Bonzini
Date Aug. 15, 2011, 9:08 p.m.
Message ID <1313442520-12062-4-git-send-email-pbonzini@redhat.com>
Download mbox | patch
Permalink /patch/110104/
State New
Headers show

Comments

Paolo Bonzini - Aug. 15, 2011, 9:08 p.m.
This emulates Win32 manual-reset events using futexes or conditional
variables.  Typical ways to use them are with multi-producer,
single-consumer data structures, to test for a complex condition whose
elements come from different threads:

    for (;;) {
        qemu_event_reset(ev);
        ... test complex condition ...
        if (condition is true) {
            break;
        }
        qemu_event_wait(ev);
    }

Alternatively:

    ... compute condition ...
    if (condition) {
        do {
            qemu_event_wait(ev);
            qemu_event_reset(ev);
            ... compute condition ...
        } while(condition);
        qemu_event_set(ev);
    }

QemuEvent provides a very fast userspace path in the common case when
no other thread is waiting, or the event is not changing state.  It
is used to report RCU quiescent states to the thread calling
synchronize_rcu (the latter being the single consumer), and to report
call_rcu invocations to the thread that receives them.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 qemu-thread-posix.c |  124 +++++++++++++++++++++++++++++++++++++++++++++++++++
 qemu-thread-posix.h |    8 +++
 qemu-thread-win32.c |   26 +++++++++++
 qemu-thread-win32.h |    4 ++
 qemu-thread.h       |    8 +++
 5 files changed, 170 insertions(+), 0 deletions(-)
Blue Swirl - Aug. 17, 2011, 5:09 p.m.
On Mon, Aug 15, 2011 at 9:08 PM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> This emulates Win32 manual-reset events using futexes or conditional
> variables.  Typical ways to use them are with multi-producer,
> single-consumer data structures, to test for a complex condition whose
> elements come from different threads:
>
>    for (;;) {
>        qemu_event_reset(ev);
>        ... test complex condition ...
>        if (condition is true) {
>            break;
>        }
>        qemu_event_wait(ev);
>    }
>
> Alternatively:
>
>    ... compute condition ...
>    if (condition) {
>        do {
>            qemu_event_wait(ev);
>            qemu_event_reset(ev);
>            ... compute condition ...
>        } while(condition);
>        qemu_event_set(ev);
>    }
>
> QemuEvent provides a very fast userspace path in the common case when
> no other thread is waiting, or the event is not changing state.  It
> is used to report RCU quiescent states to the thread calling
> synchronize_rcu (the latter being the single consumer), and to report
> call_rcu invocations to the thread that receives them.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  qemu-thread-posix.c |  124 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  qemu-thread-posix.h |    8 +++
>  qemu-thread-win32.c |   26 +++++++++++
>  qemu-thread-win32.h |    4 ++
>  qemu-thread.h       |    8 +++
>  5 files changed, 170 insertions(+), 0 deletions(-)
>
> diff --git a/qemu-thread-posix.c b/qemu-thread-posix.c
> index 2bd02ef..50e7421 100644
> --- a/qemu-thread-posix.c
> +++ b/qemu-thread-posix.c
> @@ -17,7 +17,10 @@
>  #include <signal.h>
>  #include <stdint.h>
>  #include <string.h>
> +#include <limits.h>
> +#include <unistd.h>
>  #include "qemu-thread.h"
> +#include "qemu-barrier.h"
>
>  static void error_exit(int err, const char *msg)
>  {
> @@ -115,6 +118,127 @@ void qemu_cond_wait(QemuCond *cond, QemuMutex *mutex)
>         error_exit(err, __func__);
>  }
>
> +#ifdef __linux__
> +#include <sys/syscall.h>

futex manual page says
#include <linux/futex.h>
#include <sys/time.h>

Maybe the compatibility stuff below belongs to linux-headers.

> +#ifndef FUTEX_WAIT
> +#define FUTEX_WAIT              0
> +#endif
> +#ifndef FUTEX_WAKE
> +#define FUTEX_WAKE              1
> +#endif
> +
> +#define futex(...)              syscall(__NR_futex, __VA_ARGS__)
> +
> +static inline void futex_wake(QemuEvent *ev, int n)
> +{
> +    futex(ev, FUTEX_WAKE, n, NULL, NULL, 0);
> +}
> +
> +static inline void futex_wait(QemuEvent *ev, unsigned val)
> +{
> +    futex(ev, FUTEX_WAIT, (int) val, NULL, NULL, 0);
> +}
> +#else
> +static inline void futex_wake(QemuEvent *ev, int n)
> +{
> +  if (n == 1)

Missing braces, please use checkpatch.pl.

> +    pthread_cond_signal(&ev->cond);
> +  else
> +    pthread_cond_broadcast(&ev->cond);
> +}
> +
> +static inline void futex_wait(QemuEvent *ev, unsigned val)
> +{
> +  pthread_mutex_lock(&ev->lock);
> +  if (ev->value == val)
> +    pthread_cond_wait(&ev->cond, &ev->lock);
> +  pthread_mutex_unlock(&ev->lock);
> +}
> +#endif
> +
> +/* Bit 0 is 1 if there are no waiters.  Bit 1 is 1 if the event is set.
> + * The combination "event_set && event_has_waiters" is impossible.  */
> +#define EV_FREE_BIT    1
> +#define EV_SET_BIT     2
> +
> +#define EV_BUSY                0
> +#define EV_FREE                1
> +#define EV_SET         3
> +
> +void qemu_event_init(QemuEvent *ev, bool init)
> +{
> +#ifndef __linux__
> +    pthread_mutex_init(&ev->lock, NULL);
> +    pthread_cond_init(&ev->cond, NULL);
> +#endif
> +
> +    ev->value = (init ? EV_SET : EV_FREE);
> +}
> +
> +void qemu_event_destroy(QemuEvent *ev)
> +{
> +#ifndef __linux__
> +    pthread_mutex_destroy(&ev->lock);
> +    pthread_cond_destroy(&ev->cond);
> +#endif
> +}
> +
> +void qemu_event_set(QemuEvent *ev)
> +{
> +    unsigned value;
> +
> +    smp_mb();
> +    value = ev->value;
> +    if (value == EV_SET) {
> +        /* Exit on a pre-existing/concurrent set.  */
> +        smp_mb();
> +    } else {
> +        if (__sync_fetch_and_or(&ev->value, EV_SET) == EV_BUSY) {
> +            /* There were waiters, wake them up.  */
> +            futex_wake(ev, INT_MAX);
> +        }
> +    }
> +}
> +
> +void qemu_event_reset(QemuEvent *ev)
> +{
> +    unsigned value;
> +
> +    smp_mb();
> +    value = ev->value;
> +    if (value != EV_SET) {
> +        /* Exit on a pre-existing reset.  */
> +        smp_mb();
> +    } else {
> +        /* If there was a concurrent reset (or even reset+wait),
> +         * do nothing.  Otherwise change EV_SET->EV_FREE.  */
> +        __sync_fetch_and_and(&ev->value, ~EV_SET_BIT);
> +    }
> +}
> +
> +void qemu_event_wait(QemuEvent *ev)
> +{
> +    unsigned value, old;
> +
> +    smp_mb();
> +    value = ev->value;
> +    if (value == EV_SET) {
> +        smp_mb();
> +    } else {
> +        if (value == EV_FREE) {
> +            /* Leave the event reset and tell qemu_event_set that there
> +             * are waiters.  No need to retry, because there cannot be
> +             * a concurent busy->free transition.  After the CAS, the
> +             * event will be either set or busy.  */
> +            old = __sync_val_compare_and_swap(&ev->value, EV_FREE, EV_BUSY);
> +            if (old == EV_SET) {
> +                return;
> +            }
> +        }
> +        futex_wait(ev, EV_BUSY);
> +    }
> +}
> +
>  void qemu_thread_create(QemuThread *thread,
>                        void *(*start_routine)(void*),
>                        void *arg)
> diff --git a/qemu-thread-posix.h b/qemu-thread-posix.h
> index ee4618e..2f5b63d 100644
> --- a/qemu-thread-posix.h
> +++ b/qemu-thread-posix.h
> @@ -10,6 +10,14 @@ struct QemuCond {
>     pthread_cond_t cond;
>  };
>
> +struct QemuEvent {
> +#ifndef __linux__
> +    pthread_mutex_t lock;
> +    pthread_cond_t cond;
> +#endif
> +    unsigned value;
> +};
> +
>  struct QemuThread {
>     pthread_t thread;
>  };
> diff --git a/qemu-thread-win32.c b/qemu-thread-win32.c
> index 2d2d5ab..9bdbb48 100644
> --- a/qemu-thread-win32.c
> +++ b/qemu-thread-win32.c
> @@ -192,6 +192,32 @@ void qemu_cond_wait(QemuCond *cond, QemuMutex *mutex)
>     qemu_mutex_lock(mutex);
>  }
>
> +void qemu_event_init(QemuEvent *ev, bool init)
> +{
> +    /* Manual reset.  */
> +    ev->event = CreateEvent(NULL, TRUE, init, NULL);
> +}
> +
> +void qemu_event_destroy(QemuEvent *mutex)
> +{
> +    CloseHandle(ev->event);
> +}
> +
> +void qemu_event_set(QemuEvent *ev)
> +{
> +    SetEvent(ev->event);
> +}
> +
> +void qemu_event_reset(QemuEvent *ev)
> +{
> +    ResetEvent(ev->event);
> +}
> +
> +void qemu_event_wait(QemuEvent *ev)
> +{
> +    WaitForSingleObject(ev->event, INFINITE);
> +}
> +
>  struct QemuThreadData {
>     QemuThread *thread;
>     void *(*start_routine)(void *);
> diff --git a/qemu-thread-win32.h b/qemu-thread-win32.h
> index 878f86a..ddd6d0f 100644
> --- a/qemu-thread-win32.h
> +++ b/qemu-thread-win32.h
> @@ -13,6 +13,10 @@ struct QemuCond {
>     HANDLE continue_event;
>  };
>
> +struct QemuEvent {
> +    HANDLE event;
> +};
> +
>  struct QemuThread {
>     HANDLE thread;
>     void *ret;
> diff --git a/qemu-thread.h b/qemu-thread.h
> index 0a73d50..8353e3d 100644
> --- a/qemu-thread.h
> +++ b/qemu-thread.h
> @@ -2,9 +2,11 @@
>  #define __QEMU_THREAD_H 1
>
>  #include <inttypes.h>
> +#include <stdbool.h>
>
>  typedef struct QemuMutex QemuMutex;
>  typedef struct QemuCond QemuCond;
> +typedef struct QemuEvent QemuEvent;
>  typedef struct QemuThread QemuThread;
>
>  #ifdef _WIN32
> @@ -31,6 +33,12 @@ void qemu_cond_signal(QemuCond *cond);
>  void qemu_cond_broadcast(QemuCond *cond);
>  void qemu_cond_wait(QemuCond *cond, QemuMutex *mutex);
>
> +void qemu_event_init(QemuEvent *ev, bool init);
> +void qemu_event_set(QemuEvent *ev);
> +void qemu_event_reset(QemuEvent *ev);
> +void qemu_event_wait(QemuEvent *ev);
> +void qemu_event_destroy(QemuEvent *ev);
> +
>  void qemu_thread_create(QemuThread *thread,
>                        void *(*start_routine)(void*),
>                        void *arg);
> --
> 1.7.6
>
>
>
>

Patch

diff --git a/qemu-thread-posix.c b/qemu-thread-posix.c
index 2bd02ef..50e7421 100644
--- a/qemu-thread-posix.c
+++ b/qemu-thread-posix.c
@@ -17,7 +17,10 @@ 
 #include <signal.h>
 #include <stdint.h>
 #include <string.h>
+#include <limits.h>
+#include <unistd.h>
 #include "qemu-thread.h"
+#include "qemu-barrier.h"
 
 static void error_exit(int err, const char *msg)
 {
@@ -115,6 +118,127 @@  void qemu_cond_wait(QemuCond *cond, QemuMutex *mutex)
         error_exit(err, __func__);
 }
 
+#ifdef __linux__
+#include <sys/syscall.h>
+#ifndef FUTEX_WAIT
+#define FUTEX_WAIT              0
+#endif
+#ifndef FUTEX_WAKE
+#define FUTEX_WAKE              1
+#endif
+
+#define futex(...)              syscall(__NR_futex, __VA_ARGS__)
+
+static inline void futex_wake(QemuEvent *ev, int n)
+{
+    futex(ev, FUTEX_WAKE, n, NULL, NULL, 0);
+}
+
+static inline void futex_wait(QemuEvent *ev, unsigned val)
+{
+    futex(ev, FUTEX_WAIT, (int) val, NULL, NULL, 0);
+}
+#else
+static inline void futex_wake(QemuEvent *ev, int n)
+{
+  if (n == 1)
+    pthread_cond_signal(&ev->cond);
+  else
+    pthread_cond_broadcast(&ev->cond);
+}
+
+static inline void futex_wait(QemuEvent *ev, unsigned val)
+{
+  pthread_mutex_lock(&ev->lock);
+  if (ev->value == val)
+    pthread_cond_wait(&ev->cond, &ev->lock);
+  pthread_mutex_unlock(&ev->lock);
+}
+#endif
+
+/* Bit 0 is 1 if there are no waiters.  Bit 1 is 1 if the event is set.
+ * The combination "event_set && event_has_waiters" is impossible.  */
+#define EV_FREE_BIT	1
+#define EV_SET_BIT	2
+
+#define EV_BUSY		0
+#define EV_FREE		1
+#define EV_SET		3
+
+void qemu_event_init(QemuEvent *ev, bool init)
+{
+#ifndef __linux__
+    pthread_mutex_init(&ev->lock, NULL);
+    pthread_cond_init(&ev->cond, NULL);
+#endif
+
+    ev->value = (init ? EV_SET : EV_FREE);
+}
+
+void qemu_event_destroy(QemuEvent *ev)
+{
+#ifndef __linux__
+    pthread_mutex_destroy(&ev->lock);
+    pthread_cond_destroy(&ev->cond);
+#endif
+}
+
+void qemu_event_set(QemuEvent *ev)
+{
+    unsigned value;
+
+    smp_mb();
+    value = ev->value;
+    if (value == EV_SET) {
+        /* Exit on a pre-existing/concurrent set.  */
+        smp_mb();
+    } else {
+        if (__sync_fetch_and_or(&ev->value, EV_SET) == EV_BUSY) {
+            /* There were waiters, wake them up.  */
+            futex_wake(ev, INT_MAX);
+        }
+    }
+}
+
+void qemu_event_reset(QemuEvent *ev)
+{
+    unsigned value;
+
+    smp_mb();
+    value = ev->value;
+    if (value != EV_SET) {
+        /* Exit on a pre-existing reset.  */
+        smp_mb();
+    } else {
+        /* If there was a concurrent reset (or even reset+wait),
+         * do nothing.  Otherwise change EV_SET->EV_FREE.  */
+        __sync_fetch_and_and(&ev->value, ~EV_SET_BIT);
+    }
+}
+
+void qemu_event_wait(QemuEvent *ev)
+{
+    unsigned value, old;
+
+    smp_mb();
+    value = ev->value;
+    if (value == EV_SET) {
+        smp_mb();
+    } else {
+        if (value == EV_FREE) {
+            /* Leave the event reset and tell qemu_event_set that there
+             * are waiters.  No need to retry, because there cannot be
+             * a concurent busy->free transition.  After the CAS, the
+             * event will be either set or busy.  */
+            old = __sync_val_compare_and_swap(&ev->value, EV_FREE, EV_BUSY);
+            if (old == EV_SET) {
+                return;
+            }
+        }
+        futex_wait(ev, EV_BUSY);
+    }
+}
+
 void qemu_thread_create(QemuThread *thread,
                        void *(*start_routine)(void*),
                        void *arg)
diff --git a/qemu-thread-posix.h b/qemu-thread-posix.h
index ee4618e..2f5b63d 100644
--- a/qemu-thread-posix.h
+++ b/qemu-thread-posix.h
@@ -10,6 +10,14 @@  struct QemuCond {
     pthread_cond_t cond;
 };
 
+struct QemuEvent {
+#ifndef __linux__
+    pthread_mutex_t lock;
+    pthread_cond_t cond;
+#endif
+    unsigned value;
+};
+
 struct QemuThread {
     pthread_t thread;
 };
diff --git a/qemu-thread-win32.c b/qemu-thread-win32.c
index 2d2d5ab..9bdbb48 100644
--- a/qemu-thread-win32.c
+++ b/qemu-thread-win32.c
@@ -192,6 +192,32 @@  void qemu_cond_wait(QemuCond *cond, QemuMutex *mutex)
     qemu_mutex_lock(mutex);
 }
 
+void qemu_event_init(QemuEvent *ev, bool init)
+{
+    /* Manual reset.  */
+    ev->event = CreateEvent(NULL, TRUE, init, NULL);
+}
+
+void qemu_event_destroy(QemuEvent *mutex)
+{
+    CloseHandle(ev->event);
+}
+
+void qemu_event_set(QemuEvent *ev)
+{
+    SetEvent(ev->event);
+}
+
+void qemu_event_reset(QemuEvent *ev)
+{
+    ResetEvent(ev->event);
+}
+
+void qemu_event_wait(QemuEvent *ev)
+{
+    WaitForSingleObject(ev->event, INFINITE);
+}
+
 struct QemuThreadData {
     QemuThread *thread;
     void *(*start_routine)(void *);
diff --git a/qemu-thread-win32.h b/qemu-thread-win32.h
index 878f86a..ddd6d0f 100644
--- a/qemu-thread-win32.h
+++ b/qemu-thread-win32.h
@@ -13,6 +13,10 @@  struct QemuCond {
     HANDLE continue_event;
 };
 
+struct QemuEvent {
+    HANDLE event;
+};
+
 struct QemuThread {
     HANDLE thread;
     void *ret;
diff --git a/qemu-thread.h b/qemu-thread.h
index 0a73d50..8353e3d 100644
--- a/qemu-thread.h
+++ b/qemu-thread.h
@@ -2,9 +2,11 @@ 
 #define __QEMU_THREAD_H 1
 
 #include <inttypes.h>
+#include <stdbool.h>
 
 typedef struct QemuMutex QemuMutex;
 typedef struct QemuCond QemuCond;
+typedef struct QemuEvent QemuEvent;
 typedef struct QemuThread QemuThread;
 
 #ifdef _WIN32
@@ -31,6 +33,12 @@  void qemu_cond_signal(QemuCond *cond);
 void qemu_cond_broadcast(QemuCond *cond);
 void qemu_cond_wait(QemuCond *cond, QemuMutex *mutex);
 
+void qemu_event_init(QemuEvent *ev, bool init);
+void qemu_event_set(QemuEvent *ev);
+void qemu_event_reset(QemuEvent *ev);
+void qemu_event_wait(QemuEvent *ev);
+void qemu_event_destroy(QemuEvent *ev);
+
 void qemu_thread_create(QemuThread *thread,
                        void *(*start_routine)(void*),
                        void *arg);