rcu: Allow calling rcu_(un)register_thread() during synchronize_rcu()
PATCH v2 1/1 rcu: Allow calling rcu_(un)register_thread() during synchronize_rcu()
If rcu_(un)register_thread() is called together with synchronize_rcu(), it will wait for the synchronize_rcu() to finish. But when synchronize_rcu() waits for some events, we can modify the list registry.
We also use the lock rcu_gp_lock to assume that synchronize_rcu() isn't executed in more than one thread at the same time. Add a new mutex lock rcu_sync_lock to assume it and rename rcu_gp_lock to rcu_registry_lock. Release rcu_registry_lock when synchronize_rcu() waits for some events.
--- util/rcu.c | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-)
util/rcu.c +35 -13
@@ -47,7 +47,8 @@unsigned long rcu_gp_ctr =RCU_GP_LOCKED ;QemuEvent rcu_gp_event; -static QemuMutex rcu_gp_lock; +static QemuMutex rcu_registry_lock; +static QemuMutex rcu_sync_lock;/* * Check whether a quiescent state was crossed between the beginning of
@@ -66,7 +67,7 @@ static inline int rcu_gp_ongoing(unsigned long *ctr)*/ __threadstruct rcu_reader_data rcu_reader; -/* Protected by rcu_gp_lock. */ +/* Protected by rcu_registry_lock. */ typedef QLIST_HEAD(, rcu_reader_data) ThreadList;static ThreadList registry =QLIST_HEAD_INITIALIZER (registry);
@@ -114,10 +115,26 @@ static void wait_for_readers(void)break ; } -/* Wait for one thread to report a quiescent state and -* try again. +/* Wait for one thread to report a quiescent state and try again. +* Release rcu_registry_lock, so rcu_(un)register_thread() doesn't +* wait too much time. +* +* rcu_register_thread() may add nodes to ®istry; it will not +* wake up synchronize_rcu, but that is okay because at least another +* thread must exit its RCU read-side critical section before +* synchronize_rcu is done. The next iteration of the loop will +* move the new thread's rcu_reader from ®istry to &qsreaders, +* because rcu_gp_ongoing() will return false. +* +* rcu_unregister_thread() may remove nodes from &qsreaders instead +* of ®istry if it runs during qemu_event_wait. That's okay; +* the node then will not be added back to ®istry by QLIST_SWAP +* below. The invariant is that the node is part of one list when +* rcu_registry_lock is released. */ +qemu_mutex_unlock (&rcu_registry_lock);qemu_event_wait (&rcu_gp_event); +qemu_mutex_lock (&rcu_registry_lock); }/* put back the reader list in the registry */
@@ -126,7 +143,8 @@ static void wait_for_readers(void)void synchronize_rcu (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_sync_lock); +qemu_mutex_lock (&rcu_registry_lock);if (!QLIST_EMPTY (®istry)) {/* In either case, the atomic_mb_set below blocks stores that free
@@ -149,7 +167,8 @@ void synchronize_rcu(void)wait_for_readers (); } -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); +qemu_mutex_unlock (&rcu_sync_lock); }
@@ -273,23 +292,24 @@ void call_rcu1(struct rcu_head *node, void (*func)(struct rcu_head *node))void rcu_register_thread (void ) {assert (rcu_reader.ctr ==0 ); -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_registry_lock);QLIST_INSERT_HEAD (®istry, &rcu_reader, node); -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); }void rcu_unregister_thread (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_registry_lock);QLIST_REMOVE (&rcu_reader, node); -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); }static void rcu_init_complete (void ) {QemuThread thread; -qemu_mutex_init (&rcu_gp_lock); +qemu_mutex_init (&rcu_registry_lock); +qemu_mutex_init (&rcu_sync_lock);qemu_event_init (&rcu_gp_event, true);qemu_event_init (&rcu_call_ready_event, false);
@@ -306,12 +326,14 @@ static void rcu_init_complete(void)#ifdef CONFIG_POSIX static void rcu_init_lock (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_sync_lock); +qemu_mutex_lock (&rcu_registry_lock); }static void rcu_init_unlock (void ) { -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); +qemu_mutex_unlock (&rcu_sync_lock); }#endif
Signature · 1 line
2.4.3
Quoted 123 lines · Wen Congyang
If rcu_(un)register_thread() is called together with
synchronize_rcu(), it will wait for thesynchronize_rcu()to finish. But whensynchronize_rcu()waits for some events, we can modify the list registry.
We also use the lockrcu_gp_lockto assume thatsynchronize_rcu()isn't executed in more than one thread at the same time. Add a new mutex lockrcu_sync_lockto assume it and renamercu_gp_locktorcu_registry_lock. Releasercu_registry_lockwhensynchronize_rcu()waits for some events.Signed-off-by Wen Congyang <[email protected]>--- util/rcu.c | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-)util/rcu.c +33
-12@@ -47,7 +47,8 @@unsigned long rcu_gp_ctr =RCU_GP_LOCKED ;QemuEvent rcu_gp_event; -static QemuMutex rcu_gp_lock; +static QemuMutex rcu_registry_lock; +static QemuMutex rcu_sync_lock;/* * Check whether a quiescent state was crossed between the beginning of @@ -66,7 +67,7 @@ static inline int rcu_gp_ongoing(unsigned long *ctr)*/ __threadstruct rcu_reader_data rcu_reader; -/* Protected by rcu_gp_lock. */ +/* Protected by rcu_registry_lock. */ typedef QLIST_HEAD(, rcu_reader_data) ThreadList;static ThreadList registry =QLIST_HEAD_INITIALIZER (registry);@@ -114,10 +115,26 @@ static void wait_for_readers(void)break ; } -/* Wait for one thread to report a quiescent state and -* try again. +/* Wait for one thread to report a quiescent state and try again. +* Release rcu_registry_lock, so rcu_(un)register_thread() doesn't +* wait too much time. +* +* rcu_register_thread() may add nodes to ®istry; it will not +* wake up synchronize_rcu, but that is okay because at least another +* thread must exit its RCU read-side critical section before +* synchronize_rcu is done. The next iteration of the loop will +* move the new thread's rcu_reader from ®istry to &qsreaders, +* because rcu_gp_ongoing() will return false. +* +* rcu_unregister_thread() may remove nodes from &qsreaders instead +* of ®istry if it runs during qemu_event_wait. That's okay; +* the node then will not be added back to ®istry by QLIST_SWAP +* below. The invariant is that the node is part of one list when +* rcu_registry_lock is released. */ +qemu_mutex_unlock (&rcu_registry_lock);qemu_event_wait (&rcu_gp_event); +qemu_mutex_lock (&rcu_registry_lock); }/* put back the reader list in the registry */ @@ -126,7 +143,8 @@ static void wait_for_readers(void)void synchronize_rcu (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_sync_lock); +qemu_mutex_lock (&rcu_registry_lock);if (!QLIST_EMPTY (®istry)) {/* In either case, the atomic_mb_set below blocks stores that free @@ -149,7 +167,8 @@ void synchronize_rcu(void)wait_for_readers (); } -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); +qemu_mutex_unlock (&rcu_sync_lock); }@@ -273,23 +292,24 @@ void call_rcu1(struct rcu_head *node, void (*func)(struct rcu_head *node))void rcu_register_thread (void ) {assert (rcu_reader.ctr ==0 ); -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_registry_lock);QLIST_INSERT_HEAD (®istry, &rcu_reader, node); -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); }void rcu_unregister_thread (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_registry_lock);QLIST_REMOVE (&rcu_reader, node); -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); }static void rcu_init_complete (void ) {QemuThread thread; -qemu_mutex_init (&rcu_gp_lock); +qemu_mutex_init (&rcu_registry_lock); +qemu_mutex_init (&rcu_sync_lock);qemu_event_init (&rcu_gp_event, true);qemu_event_init (&rcu_call_ready_event, false);@@ -306,12 +326,14 @@ static void rcu_init_complete(void)#ifdef CONFIG_POSIX static void rcu_init_lock (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_sync_lock); +qemu_mutex_lock (&rcu_registry_lock);
util/rcu.c static void rcu_init_complete(void) · line 339
Quoted 10 lines
util/rcu.c +2
-1}static void rcu_init_unlock (void ) { -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); +qemu_mutex_unlock (&rcu_sync_lock); }#endif
Ok, thanks.
Paolo
Quoted 127 lines · Paolo Bonzini
Quoted 124 lines · Wen Congyang
If rcu_(un)register_thread() is called together with
synchronize_rcu(), it will wait for thesynchronize_rcu()to finish. But whensynchronize_rcu()waits for some events, we can modify the list registry.
We also use the lockrcu_gp_lockto assume thatsynchronize_rcu()isn't executed in more than one thread at the same time. Add a new mutex lockrcu_sync_lockto assume it and renamercu_gp_locktorcu_registry_lock. Releasercu_registry_lockwhensynchronize_rcu()waits for some events.Signed-off-by Wen Congyang <[email protected]>--- util/rcu.c | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-)util/rcu.c +33
-12@@ -47,7 +47,8 @@unsigned long rcu_gp_ctr =RCU_GP_LOCKED ;QemuEvent rcu_gp_event; -static QemuMutex rcu_gp_lock; +static QemuMutex rcu_registry_lock; +static QemuMutex rcu_sync_lock;/* * Check whether a quiescent state was crossed between the beginning of @@ -66,7 +67,7 @@ static inline int rcu_gp_ongoing(unsigned long *ctr)*/ __threadstruct rcu_reader_data rcu_reader; -/* Protected by rcu_gp_lock. */ +/* Protected by rcu_registry_lock. */ typedef QLIST_HEAD(, rcu_reader_data) ThreadList;static ThreadList registry =QLIST_HEAD_INITIALIZER (registry);@@ -114,10 +115,26 @@ static void wait_for_readers(void)break ; } -/* Wait for one thread to report a quiescent state and -* try again. +/* Wait for one thread to report a quiescent state and try again. +* Release rcu_registry_lock, so rcu_(un)register_thread() doesn't +* wait too much time. +* +* rcu_register_thread() may add nodes to ®istry; it will not +* wake up synchronize_rcu, but that is okay because at least another +* thread must exit its RCU read-side critical section before +* synchronize_rcu is done. The next iteration of the loop will +* move the new thread's rcu_reader from ®istry to &qsreaders, +* because rcu_gp_ongoing() will return false. +* +* rcu_unregister_thread() may remove nodes from &qsreaders instead +* of ®istry if it runs during qemu_event_wait. That's okay; +* the node then will not be added back to ®istry by QLIST_SWAP +* below. The invariant is that the node is part of one list when +* rcu_registry_lock is released. */ +qemu_mutex_unlock (&rcu_registry_lock);qemu_event_wait (&rcu_gp_event); +qemu_mutex_lock (&rcu_registry_lock); }/* put back the reader list in the registry */ @@ -126,7 +143,8 @@ static void wait_for_readers(void)void synchronize_rcu (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_sync_lock); +qemu_mutex_lock (&rcu_registry_lock);if (!QLIST_EMPTY (®istry)) {/* In either case, the atomic_mb_set below blocks stores that free @@ -149,7 +167,8 @@ void synchronize_rcu(void)wait_for_readers (); } -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); +qemu_mutex_unlock (&rcu_sync_lock); }@@ -273,23 +292,24 @@ void call_rcu1(struct rcu_head *node, void (*func)(struct rcu_head *node))void rcu_register_thread (void ) {assert (rcu_reader.ctr ==0 ); -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_registry_lock);QLIST_INSERT_HEAD (®istry, &rcu_reader, node); -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); }void rcu_unregister_thread (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_registry_lock);QLIST_REMOVE (&rcu_reader, node); -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); }static void rcu_init_complete (void ) {QemuThread thread; -qemu_mutex_init (&rcu_gp_lock); +qemu_mutex_init (&rcu_registry_lock); +qemu_mutex_init (&rcu_sync_lock);qemu_event_init (&rcu_gp_event, true);qemu_event_init (&rcu_call_ready_event, false);@@ -306,12 +326,14 @@ static void rcu_init_complete(void)#ifdef CONFIG_POSIX static void rcu_init_lock (void ) { -qemu_mutex_lock (&rcu_gp_lock); +qemu_mutex_lock (&rcu_sync_lock); +qemu_mutex_lock (&rcu_registry_lock); }
Quoted 11 lines
util/rcu.c static void rcu_init_complete(void) · line 339
Quoted 9 lines
util/rcu.c +2
-1static void rcu_init_unlock (void ) { -qemu_mutex_unlock (&rcu_gp_lock); +qemu_mutex_unlock (&rcu_registry_lock); +qemu_mutex_unlock (&rcu_sync_lock); }#endif Ok, thanks.
I have a question about rcu: while do we call wait_for_readers() twice for 32-bit host?
Thanks
Wen Congyang
Quoted 4 lines
Paolo
.
Quoted 2 lines · Wen Congyang
I have a question about rcu: while do we call
wait_for_readers()twice for 32-bit host?
Because there is a very small but non-zero probability of the counter going up by exactly 2^31 periods (periods are stored in bits 1-31 so you lose one bit) while the thread is sleeping. This detail of the implementation comes from URCU.
Paolo
Quoted 10 lines · Paolo Bonzini
Quoted 2 lines · Wen Congyang
I have a question about rcu: while do we call
wait_for_readers()twice for 32-bit host?Because there is a very small but non-zero probability of the counter going up by exactly 2^31 periods (periods are stored in bits 1-31 so you lose one bit) while the thread is sleeping. This detail of the implementation comes from URCU.
Yes, so you use rcu_gp_ctr ^ RCU_GP_CTR to instead of rcu_gp_ctr + RCU_GP_CTR. The initial value is 1, so rcu_gp_ctr is: 1, 3, 1, 3, ...
The rcu_gp_ctr will never be 0. I think calling wait_for_readers() once is enough.
Do I miss something?
Thanks
Wen Congyang
Quoted 3 lines
Paolo
Quoted 12 lines · Wen Congyang
Quoted 10 lines · Paolo Bonzini
Quoted 2 lines · Wen Congyang
I have a question about rcu: while do we call
wait_for_readers()twice for 32-bit host?Because there is a very small but non-zero probability of the counter going up by exactly 2^31 periods (periods are stored in bits 1-31 so you lose one bit) while the thread is sleeping. This detail of the implementation comes from URCU.
Quoted 6 lines
Yes, so you use
rcu_gp_ctr^RCU_GP_CTRto instead ofrcu_gp_ctr+RCU_GP_CTR. The initial value is 1, sorcu_gp_ctris: 1, 3, 1, 3, ...
Thercu_gp_ctrwill never be 0. I think callingwait_for_readers()once is enough.Do I miss something?
If you call it just once, you have the same problem as before. In fact, it's worse because instead of having an overflow every 2^31 periods, you have one every 2 periods. Instead, by checking that rcu_reader went through 1 _and_ 3 (or that it was at least once 0, i.e. the thread was quiescent), you are sure that the thread went through _at least one_ grace period.
Paolo
At 2015/7/28 19:46, Paolo Bonzini Wrote:
Quoted 20 lines · Wen Congyang
Quoted 17 lines · Paolo Bonzini
Quoted 10 lines · Wen Congyang
Quoted 2 lines
I have a question about rcu: while do we call
wait_for_readers()twice for 32-bit host?Because there is a very small but non-zero probability of the counter going up by exactly 2^31 periods (periods are stored in bits 1-31 so you lose one bit) while the thread is sleeping. This detail of the implementation comes from URCU.
Yes, so you use
rcu_gp_ctr^RCU_GP_CTRto instead ofrcu_gp_ctr+RCU_GP_CTR. The initial value is 1, sorcu_gp_ctris: 1, 3, 1, 3, ...
Thercu_gp_ctrwill never be 0. I think callingwait_for_readers()once is enough.
Quoted 8 lines
Quoted 1 line
Do I miss something?
If you call it just once, you have the same problem as before. In fact, it's worse because instead of having an overflow every 2^31 periods, you have one every 2 periods. Instead, by checking that
rcu_readerwent through 1 _and_ 3 (or that it was at least once 0, i.e. the thread was quiescent), you are sure that the thread went through_atleastone_grace period.
The overflow is acceptable. We only compare if rcu_reader.ctr is equal than rcu_gp_ctr. If not, we should wait that thread to call rcu_read_unlock(). We don't care which is bigger. If no threads calls sync_rcu(), all threads rcu_read.ctr is 0 or rcu_gp_ctr. If one thread calls sync_rcu(), all threads rcu_read.ctr is 0, old_rcu_gp_ctr, or new_rcu_gp_ctr. We only wait the thread that's rcu_read.ctr is old_rcu_gp_ctr.
Thanks
Wen Congyang
Quoted 4 lines
Paolo
Quoted 9 lines · Wen Congyang
Quoted 8 lines
Quoted 1 line
If you call it just once, you have the same problem as before. In fact, it's worse because instead of having an overflow every 2^31 periods, you have one every 2 periods. Instead, by checking that
rcu_readerwent through 1 _and_ 3 (or that it was at least once 0, i.e. the thread was quiescent), you are sure that the thread went through_atleastone_grace period.
Quoted 6 lines
The overflow is acceptable. We only compare if rcu_reader.ctr is equal than
rcu_gp_ctr. If not, we should wait that thread to callrcu_read_unlock(). We don't care which is bigger. If no threads callssync_rcu(), all threads rcu_read.ctr is 0 orrcu_gp_ctr. If one thread callssync_rcu(), all threads rcu_read.ctr is 0,old_rcu_gp_ctr, ornew_rcu_gp_ctr. We only wait the thread that's rcu_read.ctr isold_rcu_gp_ctr.
Suppose you have
reader writer
---------------------------------------------------------------
rcu_read_lock()
ctr = atomic_read(&rcu_gp_ctr);
ctr = 1
synchronize_rcu()
rcu_gp_ctr = 3
atomic_xchg(&p_rcu_reader->ctr, ctr);
rcu_reader.ctr = 1We're in the critical section.
synchronize_rcu()
rcu_gp_ctr = 1rcu_gp_ongoing(&p_rcu_reader->ctr) returns false, so synchronize_rcu() exits. But we're still in the critical section.
p = atomic_rcu_read(&foo);
g_free(p);Boom. :) For more information see
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.220.8810
Paolo
Quoted 36 lines · Paolo Bonzini
Quoted 9 lines · Wen Congyang
Quoted 8 lines
Quoted 1 line
If you call it just once, you have the same problem as before. In fact, it's worse because instead of having an overflow every 2^31 periods, you have one every 2 periods. Instead, by checking that
rcu_readerwent through 1 _and_ 3 (or that it was at least once 0, i.e. the thread was quiescent), you are sure that the thread went through_atleastone_grace period.Quoted 6 lines
The overflow is acceptable. We only compare if rcu_reader.ctr is equal than
rcu_gp_ctr. If not, we should wait that thread to callrcu_read_unlock(). We don't care which is bigger. If no threads callssync_rcu(), all threads rcu_read.ctr is 0 orrcu_gp_ctr. If one thread callssync_rcu(), all threads rcu_read.ctr is 0,old_rcu_gp_ctr, ornew_rcu_gp_ctr. We only wait the thread that's rcu_read.ctr isold_rcu_gp_ctr.Suppose you have
reader writer
---------------------------------------------------------------rcu_read_lock() ctr = atomic_read(&rcu_gp_ctr); ctr = 1 synchronize_rcu() rcu_gp_ctr = 3 atomic_xchg(&p_rcu_reader->ctr, ctr); rcu_reader.ctr = 1We're in the critical section.
synchronize_rcu() rcu_gp_ctr = 1
Quoted 8 lines
rcu_gp_ongoing(&p_rcu_reader->ctr)returns false, sosynchronize_rcu()exits. But we're still in the critical section.p = atomic_rcu_read(&foo); g_free(p);Boom. :) For more information see
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.220.8810
Thanks for your explanation. I understand it now. If we use rcu_gp_ctr + RCU_GP_CTR for 32-bit host, the problem only exists when we call synchronize_rcu() 2^31 times while another thread calls rcu_read_lock().
Thanks
Wen Congyang
Quoted 3 lines
Paolo