mirror of
				https://gitlab.com/qemu-project/qemu.git
				synced 2025-10-30 07:57:14 +08:00 
			
		
		
		
	qemu-thread: Use futex for QemuEvent on Windows
Use the futex-based implementation of QemuEvent on Windows to remove code duplication and remove the overhead of event object construction and destruction. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Link: https://lore.kernel.org/r/20250526-event-v4-6-5b784cc8e1de@daynix.com Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
		 Akihiko Odaki
					Akihiko Odaki
				
			
				
					committed by
					
						 Paolo Bonzini
						Paolo Bonzini
					
				
			
			
				
	
			
			
			 Paolo Bonzini
						Paolo Bonzini
					
				
			
						parent
						
							d1895f4c17
						
					
				
				
					commit
					69e10db83e
				
			| @ -32,15 +32,6 @@ struct QemuSemaphore { | ||||
|     unsigned int count; | ||||
| }; | ||||
|  | ||||
| struct QemuEvent { | ||||
| #ifndef CONFIG_LINUX | ||||
|     pthread_mutex_t lock; | ||||
|     pthread_cond_t cond; | ||||
| #endif | ||||
|     unsigned value; | ||||
|     bool initialized; | ||||
| }; | ||||
|  | ||||
| struct QemuThread { | ||||
|     pthread_t thread; | ||||
| }; | ||||
|  | ||||
| @ -28,12 +28,6 @@ struct QemuSemaphore { | ||||
|     bool initialized; | ||||
| }; | ||||
|  | ||||
| struct QemuEvent { | ||||
|     int value; | ||||
|     HANDLE event; | ||||
|     bool initialized; | ||||
| }; | ||||
|  | ||||
| typedef struct QemuThreadData QemuThreadData; | ||||
| struct QemuThread { | ||||
|     QemuThreadData *data; | ||||
|  | ||||
| @ -3,13 +3,22 @@ | ||||
|  | ||||
| #include "qemu/processor.h" | ||||
| #include "qemu/atomic.h" | ||||
| #include "qemu/futex.h" | ||||
|  | ||||
| typedef struct QemuCond QemuCond; | ||||
| typedef struct QemuSemaphore QemuSemaphore; | ||||
| typedef struct QemuEvent QemuEvent; | ||||
| typedef struct QemuLockCnt QemuLockCnt; | ||||
| typedef struct QemuThread QemuThread; | ||||
|  | ||||
| typedef struct QemuEvent { | ||||
| #ifndef HAVE_FUTEX | ||||
|     pthread_mutex_t lock; | ||||
|     pthread_cond_t cond; | ||||
| #endif | ||||
|     unsigned value; | ||||
|     bool initialized; | ||||
| } QemuEvent; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include "qemu/thread-win32.h" | ||||
| #else | ||||
|  | ||||
							
								
								
									
										171
									
								
								util/event.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								util/event.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
|  | ||||
| #include "qemu/osdep.h" | ||||
| #include "qemu/thread.h" | ||||
|  | ||||
| /* | ||||
|  * Valid transitions: | ||||
|  * - FREE -> SET (qemu_event_set) | ||||
|  * - BUSY -> SET (qemu_event_set) | ||||
|  * - SET -> FREE (qemu_event_reset) | ||||
|  * - FREE -> BUSY (qemu_event_wait) | ||||
|  * | ||||
|  * With futex, the waking and blocking operations follow | ||||
|  * BUSY -> SET and FREE -> BUSY, respectively. | ||||
|  * | ||||
|  * Without futex, BUSY -> SET and FREE -> BUSY never happen. Instead, the waking | ||||
|  * operation follows FREE -> SET and the blocking operation will happen in | ||||
|  * qemu_event_wait() if the event is not SET. | ||||
|  * | ||||
|  * SET->BUSY does not happen (it can be observed from the outside but | ||||
|  * it really is SET->FREE->BUSY). | ||||
|  * | ||||
|  * busy->free provably cannot happen; to enforce it, the set->free transition | ||||
|  * is done with an OR, which becomes a no-op if the event has concurrently | ||||
|  * transitioned to free or busy. | ||||
|  */ | ||||
|  | ||||
| #define EV_SET         0 | ||||
| #define EV_FREE        1 | ||||
| #define EV_BUSY       -1 | ||||
|  | ||||
| void qemu_event_init(QemuEvent *ev, bool init) | ||||
| { | ||||
| #ifndef HAVE_FUTEX | ||||
|     pthread_mutex_init(&ev->lock, NULL); | ||||
|     pthread_cond_init(&ev->cond, NULL); | ||||
| #endif | ||||
|  | ||||
|     ev->value = (init ? EV_SET : EV_FREE); | ||||
|     ev->initialized = true; | ||||
| } | ||||
|  | ||||
| void qemu_event_destroy(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|     ev->initialized = false; | ||||
| #ifndef HAVE_FUTEX | ||||
|     pthread_mutex_destroy(&ev->lock); | ||||
|     pthread_cond_destroy(&ev->cond); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void qemu_event_set(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
| #ifdef HAVE_FUTEX | ||||
|     /* | ||||
|      * Pairs with both qemu_event_reset() and qemu_event_wait(). | ||||
|      * | ||||
|      * qemu_event_set has release semantics, but because it *loads* | ||||
|      * ev->value we need a full memory barrier here. | ||||
|      */ | ||||
|     smp_mb(); | ||||
|     if (qatomic_read(&ev->value) != EV_SET) { | ||||
|         int old = qatomic_xchg(&ev->value, EV_SET); | ||||
|  | ||||
|         /* Pairs with memory barrier in kernel futex_wait system call.  */ | ||||
|         smp_mb__after_rmw(); | ||||
|         if (old == EV_BUSY) { | ||||
|             /* There were waiters, wake them up.  */ | ||||
|             qemu_futex_wake_all(ev); | ||||
|         } | ||||
|     } | ||||
| #else | ||||
|     pthread_mutex_lock(&ev->lock); | ||||
|     /* Pairs with qemu_event_reset()'s load acquire.  */ | ||||
|     qatomic_store_release(&ev->value, EV_SET); | ||||
|     pthread_cond_broadcast(&ev->cond); | ||||
|     pthread_mutex_unlock(&ev->lock); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void qemu_event_reset(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
| #ifdef HAVE_FUTEX | ||||
|     /* | ||||
|      * If there was a concurrent reset (or even reset+wait), | ||||
|      * do nothing.  Otherwise change EV_SET->EV_FREE. | ||||
|      */ | ||||
|     qatomic_or(&ev->value, EV_FREE); | ||||
|  | ||||
|     /* | ||||
|      * Order reset before checking the condition in the caller. | ||||
|      * Pairs with the first memory barrier in qemu_event_set(). | ||||
|      */ | ||||
|     smp_mb__after_rmw(); | ||||
| #else | ||||
|     /* | ||||
|      * If futexes are not available, there are no EV_FREE->EV_BUSY | ||||
|      * transitions because wakeups are done entirely through the | ||||
|      * condition variable.  Since qatomic_set() only writes EV_FREE, | ||||
|      * the load seems useless but in reality, the acquire synchronizes | ||||
|      * with qemu_event_set()'s store release: if qemu_event_reset() | ||||
|      * sees EV_SET here, then the caller will certainly see a | ||||
|      * successful condition and skip qemu_event_wait(): | ||||
|      * | ||||
|      * done = 1;                 if (done == 0) | ||||
|      * qemu_event_set() {          qemu_event_reset() { | ||||
|      *   lock(); | ||||
|      *   ev->value = EV_SET ----->     load ev->value | ||||
|      *                                 ev->value = old value | EV_FREE | ||||
|      *   cond_broadcast() | ||||
|      *   unlock();                 } | ||||
|      * }                           if (done == 0) | ||||
|      *                               // qemu_event_wait() not called | ||||
|      */ | ||||
|     qatomic_set(&ev->value, qatomic_load_acquire(&ev->value) | EV_FREE); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void qemu_event_wait(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
| #ifdef HAVE_FUTEX | ||||
|     while (true) { | ||||
|         /* | ||||
|          * qemu_event_wait must synchronize with qemu_event_set even if it does | ||||
|          * not go down the slow path, so this load-acquire is needed that | ||||
|          * synchronizes with the first memory barrier in qemu_event_set(). | ||||
|          */ | ||||
|         unsigned value = qatomic_load_acquire(&ev->value); | ||||
|         if (value == EV_SET) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         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 concurrent | ||||
|              * busy->free transition.  After the CAS, the event will be either | ||||
|              * set or busy. | ||||
|              * | ||||
|              * This cmpxchg doesn't have particular ordering requirements if it | ||||
|              * succeeds (moving the store earlier can only cause | ||||
|              * qemu_event_set() to issue _more_ wakeups), the failing case needs | ||||
|              * acquire semantics like the load above. | ||||
|              */ | ||||
|             if (qatomic_cmpxchg(&ev->value, EV_FREE, EV_BUSY) == EV_SET) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * This is the final check for a concurrent set, so it does need | ||||
|          * a smp_mb() pairing with the second barrier of qemu_event_set(). | ||||
|          * The barrier is inside the FUTEX_WAIT system call. | ||||
|          */ | ||||
|         qemu_futex_wait(ev, EV_BUSY); | ||||
|     } | ||||
| #else | ||||
|     pthread_mutex_lock(&ev->lock); | ||||
|     while (qatomic_read(&ev->value) != EV_SET) { | ||||
|         pthread_cond_wait(&ev->cond, &ev->lock); | ||||
|     } | ||||
|     pthread_mutex_unlock(&ev->lock); | ||||
| #endif | ||||
| } | ||||
| @ -35,6 +35,7 @@ if glib_has_gslice | ||||
| endif | ||||
| util_ss.add(files('defer-call.c')) | ||||
| util_ss.add(files('envlist.c', 'path.c', 'module.c')) | ||||
| util_ss.add(files('event.c')) | ||||
| util_ss.add(files('host-utils.c')) | ||||
| util_ss.add(files('bitmap.c', 'bitops.c')) | ||||
| util_ss.add(files('fifo8.c')) | ||||
|  | ||||
| @ -317,176 +317,6 @@ void qemu_sem_wait(QemuSemaphore *sem) | ||||
|     qemu_mutex_unlock(&sem->mutex); | ||||
| } | ||||
|  | ||||
| #ifdef CONFIG_LINUX | ||||
| #include "qemu/futex.h" | ||||
| #endif | ||||
|  | ||||
| /* Valid transitions: | ||||
|  * - FREE -> SET (qemu_event_set) | ||||
|  * - BUSY -> SET (qemu_event_set) | ||||
|  * - SET -> FREE (qemu_event_reset) | ||||
|  * - FREE -> BUSY (qemu_event_wait) | ||||
|  * | ||||
|  * With futex, the waking and blocking operations follow | ||||
|  * BUSY -> SET and FREE -> BUSY, respectively. | ||||
|  * | ||||
|  * Without futex, BUSY -> SET and FREE -> BUSY never happen. Instead, the waking | ||||
|  * operation follows FREE -> SET and the blocking operation will happen in | ||||
|  * qemu_event_wait() if the event is not SET. | ||||
|  * | ||||
|  * SET->BUSY does not happen (it can be observed from the outside but | ||||
|  * it really is SET->FREE->BUSY). | ||||
|  * | ||||
|  * busy->free provably cannot happen; to enforce it, the set->free transition | ||||
|  * is done with an OR, which becomes a no-op if the event has concurrently | ||||
|  * transitioned to free or busy. | ||||
|  */ | ||||
|  | ||||
| #define EV_SET         0 | ||||
| #define EV_FREE        1 | ||||
| #define EV_BUSY       -1 | ||||
|  | ||||
| void qemu_event_init(QemuEvent *ev, bool init) | ||||
| { | ||||
| #ifndef CONFIG_LINUX | ||||
|     pthread_mutex_init(&ev->lock, NULL); | ||||
|     pthread_cond_init(&ev->cond, NULL); | ||||
| #endif | ||||
|  | ||||
|     ev->value = (init ? EV_SET : EV_FREE); | ||||
|     ev->initialized = true; | ||||
| } | ||||
|  | ||||
| void qemu_event_destroy(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|     ev->initialized = false; | ||||
| #ifndef CONFIG_LINUX | ||||
|     pthread_mutex_destroy(&ev->lock); | ||||
|     pthread_cond_destroy(&ev->cond); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void qemu_event_set(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
| #ifdef CONFIG_LINUX | ||||
|     /* | ||||
|      * Pairs with both qemu_event_reset() and qemu_event_wait(). | ||||
|      * | ||||
|      * qemu_event_set has release semantics, but because it *loads* | ||||
|      * ev->value we need a full memory barrier here. | ||||
|      */ | ||||
|     smp_mb(); | ||||
|     if (qatomic_read(&ev->value) != EV_SET) { | ||||
|         int old = qatomic_xchg(&ev->value, EV_SET); | ||||
|  | ||||
|         /* Pairs with memory barrier in kernel futex_wait system call.  */ | ||||
|         smp_mb__after_rmw(); | ||||
|         if (old == EV_BUSY) { | ||||
|             /* There were waiters, wake them up.  */ | ||||
|             qemu_futex_wake_all(ev); | ||||
|         } | ||||
|     } | ||||
| #else | ||||
|     pthread_mutex_lock(&ev->lock); | ||||
|     /* Pairs with qemu_event_reset()'s load acquire.  */ | ||||
|     qatomic_store_release(&ev->value, EV_SET); | ||||
|     pthread_cond_broadcast(&ev->cond); | ||||
|     pthread_mutex_unlock(&ev->lock); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void qemu_event_reset(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
| #ifdef CONFIG_LINUX | ||||
|     /* | ||||
|      * If there was a concurrent reset (or even reset+wait), | ||||
|      * do nothing.  Otherwise change EV_SET->EV_FREE. | ||||
|      */ | ||||
|     qatomic_or(&ev->value, EV_FREE); | ||||
|  | ||||
|     /* | ||||
|      * Order reset before checking the condition in the caller. | ||||
|      * Pairs with the first memory barrier in qemu_event_set(). | ||||
|      */ | ||||
|     smp_mb__after_rmw(); | ||||
| #else | ||||
|     /* | ||||
|      * If futexes are not available, there are no EV_FREE->EV_BUSY | ||||
|      * transitions because wakeups are done entirely through the | ||||
|      * condition variable.  Since qatomic_set() only writes EV_FREE, | ||||
|      * the load seems useless but in reality, the acquire synchronizes | ||||
|      * with qemu_event_set()'s store release: if qemu_event_reset() | ||||
|      * sees EV_SET here, then the caller will certainly see a | ||||
|      * successful condition and skip qemu_event_wait(): | ||||
|      * | ||||
|      * done = 1;                 if (done == 0) | ||||
|      * qemu_event_set() {          qemu_event_reset() { | ||||
|      *   lock(); | ||||
|      *   ev->value = EV_SET ----->     load ev->value | ||||
|      *                                 ev->value = old value | EV_FREE | ||||
|      *   cond_broadcast() | ||||
|      *   unlock();                 } | ||||
|      * }                           if (done == 0) | ||||
|      *                               // qemu_event_wait() not called | ||||
|      */ | ||||
|     qatomic_set(&ev->value, qatomic_load_acquire(&ev->value) | EV_FREE); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void qemu_event_wait(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
| #ifdef CONFIG_LINUX | ||||
|     while (true) { | ||||
|         /* | ||||
|          * qemu_event_wait must synchronize with qemu_event_set even if it does | ||||
|          * not go down the slow path, so this load-acquire is needed that | ||||
|          * synchronizes with the first memory barrier in qemu_event_set(). | ||||
|          */ | ||||
|         unsigned value = qatomic_load_acquire(&ev->value); | ||||
|         if (value == EV_SET) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         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 concurrent | ||||
|              * busy->free transition.  After the CAS, the event will be either | ||||
|              * set or busy. | ||||
|              * | ||||
|              * This cmpxchg doesn't have particular ordering requirements if it | ||||
|              * succeeds (moving the store earlier can only cause qemu_event_set() | ||||
|              * to issue _more_ wakeups), the failing case needs acquire semantics | ||||
|              * like the load above. | ||||
|              */ | ||||
|             if (qatomic_cmpxchg(&ev->value, EV_FREE, EV_BUSY) == EV_SET) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * This is the final check for a concurrent set, so it does need | ||||
|          * a smp_mb() pairing with the second barrier of qemu_event_set(). | ||||
|          * The barrier is inside the FUTEX_WAIT system call. | ||||
|          */ | ||||
|         qemu_futex_wait(ev, EV_BUSY); | ||||
|     } | ||||
| #else | ||||
|     pthread_mutex_lock(&ev->lock); | ||||
|     while (qatomic_read(&ev->value) != EV_SET) { | ||||
|         pthread_cond_wait(&ev->cond, &ev->lock); | ||||
|     } | ||||
|     pthread_mutex_unlock(&ev->lock); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| static __thread NotifierList thread_exit; | ||||
|  | ||||
| /* | ||||
|  | ||||
| @ -231,135 +231,6 @@ void qemu_sem_wait(QemuSemaphore *sem) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Wrap a Win32 manual-reset event with a fast userspace path.  The idea | ||||
|  * is to reset the Win32 event lazily, as part of a test-reset-test-wait | ||||
|  * sequence.  Such a sequence is, indeed, how QemuEvents are used by | ||||
|  * RCU and other subsystems! | ||||
|  * | ||||
|  * Valid transitions: | ||||
|  * - free->set, when setting the event | ||||
|  * - busy->set, when setting the event, followed by SetEvent | ||||
|  * - set->free, when resetting the event | ||||
|  * - free->busy, when waiting | ||||
|  * | ||||
|  * set->busy does not happen (it can be observed from the outside but | ||||
|  * it really is set->free->busy). | ||||
|  * | ||||
|  * busy->free provably cannot happen; to enforce it, the set->free transition | ||||
|  * is done with an OR, which becomes a no-op if the event has concurrently | ||||
|  * transitioned to free or busy (and is faster than cmpxchg). | ||||
|  */ | ||||
|  | ||||
| #define EV_SET         0 | ||||
| #define EV_FREE        1 | ||||
| #define EV_BUSY       -1 | ||||
|  | ||||
| void qemu_event_init(QemuEvent *ev, bool init) | ||||
| { | ||||
|     /* Manual reset.  */ | ||||
|     ev->event = CreateEvent(NULL, TRUE, TRUE, NULL); | ||||
|     ev->value = (init ? EV_SET : EV_FREE); | ||||
|     ev->initialized = true; | ||||
| } | ||||
|  | ||||
| void qemu_event_destroy(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|     ev->initialized = false; | ||||
|     CloseHandle(ev->event); | ||||
| } | ||||
|  | ||||
| void qemu_event_set(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
|     /* | ||||
|      * Pairs with both qemu_event_reset() and qemu_event_wait(). | ||||
|      * | ||||
|      * qemu_event_set has release semantics, but because it *loads* | ||||
|      * ev->value we need a full memory barrier here. | ||||
|      */ | ||||
|     smp_mb(); | ||||
|     if (qatomic_read(&ev->value) != EV_SET) { | ||||
|         int old = qatomic_xchg(&ev->value, EV_SET); | ||||
|  | ||||
|         /* Pairs with memory barrier after ResetEvent.  */ | ||||
|         smp_mb__after_rmw(); | ||||
|         if (old == EV_BUSY) { | ||||
|             /* There were waiters, wake them up.  */ | ||||
|             SetEvent(ev->event); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void qemu_event_reset(QemuEvent *ev) | ||||
| { | ||||
|     assert(ev->initialized); | ||||
|  | ||||
|     /* | ||||
|      * If there was a concurrent reset (or even reset+wait), | ||||
|      * do nothing.  Otherwise change EV_SET->EV_FREE. | ||||
|      */ | ||||
|     qatomic_or(&ev->value, EV_FREE); | ||||
|  | ||||
|     /* | ||||
|      * Order reset before checking the condition in the caller. | ||||
|      * Pairs with the first memory barrier in qemu_event_set(). | ||||
|      */ | ||||
|     smp_mb__after_rmw(); | ||||
| } | ||||
|  | ||||
| void qemu_event_wait(QemuEvent *ev) | ||||
| { | ||||
|     unsigned value; | ||||
|  | ||||
|     assert(ev->initialized); | ||||
|  | ||||
|     /* | ||||
|      * qemu_event_wait must synchronize with qemu_event_set even if it does | ||||
|      * not go down the slow path, so this load-acquire is needed that | ||||
|      * synchronizes with the first memory barrier in qemu_event_set(). | ||||
|      * | ||||
|      * If we do go down the slow path, there is no requirement at all: we | ||||
|      * might miss a qemu_event_set() here but ultimately the memory barrier in | ||||
|      * qemu_futex_wait() will ensure the check is done correctly. | ||||
|      */ | ||||
|     value = qatomic_load_acquire(&ev->value); | ||||
|     if (value != EV_SET) { | ||||
|         if (value == EV_FREE) { | ||||
|             /* | ||||
|              * Here the underlying kernel event is reset, but qemu_event_set is | ||||
|              * not yet going to call SetEvent.  However, there will be another | ||||
|              * check for EV_SET below when setting EV_BUSY.  At that point it | ||||
|              * is safe to call WaitForSingleObject. | ||||
|              */ | ||||
|             ResetEvent(ev->event); | ||||
|  | ||||
|             /* | ||||
|              * It is not clear whether ResetEvent provides this barrier; kernel | ||||
|              * APIs (KeResetEvent/KeClearEvent) do not.  Better safe than sorry! | ||||
|              */ | ||||
|             smp_mb(); | ||||
|  | ||||
|             /* | ||||
|              * Leave the event reset and tell qemu_event_set that there are | ||||
|              * waiters.  No need to retry, because there cannot be a concurrent | ||||
|              * busy->free transition.  After the CAS, the event will be either | ||||
|              * set or busy. | ||||
|              */ | ||||
|             if (qatomic_cmpxchg(&ev->value, EV_FREE, EV_BUSY) == EV_SET) { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * ev->value is now EV_BUSY.  Since we didn't observe EV_SET, | ||||
|          * qemu_event_set() must observe EV_BUSY and call SetEvent(). | ||||
|          */ | ||||
|         WaitForSingleObject(ev->event, INFINITE); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct QemuThreadData { | ||||
|     /* Passed to win32_start_routine.  */ | ||||
|     void             *(*start_routine)(void *); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user