📄 whatisrcu.txt
字号:
There are no fewer than three RCU mechanisms in the Linux kernel; thediagram above shows the first one, which is by far the most commonly used.The rcu_dereference() and rcu_assign_pointer() primitives are used forall three mechanisms, but different defer and protect primitives areused as follows: Defer Protecta. synchronize_rcu() rcu_read_lock() / rcu_read_unlock() call_rcu()b. call_rcu_bh() rcu_read_lock_bh() / rcu_read_unlock_bh()c. synchronize_sched() preempt_disable() / preempt_enable() local_irq_save() / local_irq_restore() hardirq enter / hardirq exit NMI enter / NMI exitThese three mechanisms are used as follows:a. RCU applied to normal data structures.b. RCU applied to networking data structures that may be subjected to remote denial-of-service attacks.c. RCU applied to scheduler and interrupt/NMI-handler tasks.Again, most uses will be of (a). The (b) and (c) cases are importantfor specialized uses, but are relatively uncommon.3. WHAT ARE SOME EXAMPLE USES OF CORE RCU API?This section shows a simple use of the core RCU API to protect aglobal pointer to a dynamically allocated structure. More-typicaluses of RCU may be found in listRCU.txt, arrayRCU.txt, and NMI-RCU.txt. struct foo { int a; char b; long c; }; DEFINE_SPINLOCK(foo_mutex); struct foo *gbl_foo; /* * Create a new struct foo that is the same as the one currently * pointed to by gbl_foo, except that field "a" is replaced * with "new_a". Points gbl_foo to the new structure, and * frees up the old structure after a grace period. * * Uses rcu_assign_pointer() to ensure that concurrent readers * see the initialized version of the new structure. * * Uses synchronize_rcu() to ensure that any readers that might * have references to the old structure complete before freeing * the old structure. */ void foo_update_a(int new_a) { struct foo *new_fp; struct foo *old_fp; new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL); spin_lock(&foo_mutex); old_fp = gbl_foo; *new_fp = *old_fp; new_fp->a = new_a; rcu_assign_pointer(gbl_foo, new_fp); spin_unlock(&foo_mutex); synchronize_rcu(); kfree(old_fp); } /* * Return the value of field "a" of the current gbl_foo * structure. Use rcu_read_lock() and rcu_read_unlock() * to ensure that the structure does not get deleted out * from under us, and use rcu_dereference() to ensure that * we see the initialized version of the structure (important * for DEC Alpha and for people reading the code). */ int foo_get_a(void) { int retval; rcu_read_lock(); retval = rcu_dereference(gbl_foo)->a; rcu_read_unlock(); return retval; }So, to sum up:o Use rcu_read_lock() and rcu_read_unlock() to guard RCU read-side critical sections.o Within an RCU read-side critical section, use rcu_dereference() to dereference RCU-protected pointers.o Use some solid scheme (such as locks or semaphores) to keep concurrent updates from interfering with each other.o Use rcu_assign_pointer() to update an RCU-protected pointer. This primitive protects concurrent readers from the updater, -not- concurrent updates from each other! You therefore still need to use locking (or something similar) to keep concurrent rcu_assign_pointer() primitives from interfering with each other.o Use synchronize_rcu() -after- removing a data element from an RCU-protected data structure, but -before- reclaiming/freeing the data element, in order to wait for the completion of all RCU read-side critical sections that might be referencing that data item.See checklist.txt for additional rules to follow when using RCU.And again, more-typical uses of RCU may be found in listRCU.txt,arrayRCU.txt, and NMI-RCU.txt.4. WHAT IF MY UPDATING THREAD CANNOT BLOCK?In the example above, foo_update_a() blocks until a grace period elapses.This is quite simple, but in some cases one cannot afford to wait solong -- there might be other high-priority work to be done.In such cases, one uses call_rcu() rather than synchronize_rcu().The call_rcu() API is as follows: void call_rcu(struct rcu_head * head, void (*func)(struct rcu_head *head));This function invokes func(head) after a grace period has elapsed.This invocation might happen from either softirq or process context,so the function is not permitted to block. The foo struct needs tohave an rcu_head structure added, perhaps as follows: struct foo { int a; char b; long c; struct rcu_head rcu; };The foo_update_a() function might then be written as follows: /* * Create a new struct foo that is the same as the one currently * pointed to by gbl_foo, except that field "a" is replaced * with "new_a". Points gbl_foo to the new structure, and * frees up the old structure after a grace period. * * Uses rcu_assign_pointer() to ensure that concurrent readers * see the initialized version of the new structure. * * Uses call_rcu() to ensure that any readers that might have * references to the old structure complete before freeing the * old structure. */ void foo_update_a(int new_a) { struct foo *new_fp; struct foo *old_fp; new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL); spin_lock(&foo_mutex); old_fp = gbl_foo; *new_fp = *old_fp; new_fp->a = new_a; rcu_assign_pointer(gbl_foo, new_fp); spin_unlock(&foo_mutex); call_rcu(&old_fp->rcu, foo_reclaim); }The foo_reclaim() function might appear as follows: void foo_reclaim(struct rcu_head *rp) { struct foo *fp = container_of(rp, struct foo, rcu); kfree(fp); }The container_of() primitive is a macro that, given a pointer into astruct, the type of the struct, and the pointed-to field within thestruct, returns a pointer to the beginning of the struct.The use of call_rcu() permits the caller of foo_update_a() toimmediately regain control, without needing to worry further about theold version of the newly updated element. It also clearly shows theRCU distinction between updater, namely foo_update_a(), and reclaimer,namely foo_reclaim().The summary of advice is the same as for the previous section, exceptthat we are now using call_rcu() rather than synchronize_rcu():o Use call_rcu() -after- removing a data element from an RCU-protected data structure in order to register a callback function that will be invoked after the completion of all RCU read-side critical sections that might be referencing that data item.Again, see checklist.txt for additional rules governing the use of RCU.5. WHAT ARE SOME SIMPLE IMPLEMENTATIONS OF RCU?One of the nice things about RCU is that it has extremely simple "toy"implementations that are a good first step towards understanding theproduction-quality implementations in the Linux kernel. This sectionpresents two such "toy" implementations of RCU, one that is implementedin terms of familiar locking primitives, and another that more closelyresembles "classic" RCU. Both are way too simple for real-world use,lacking both functionality and performance. However, they are usefulin getting a feel for how RCU works. See kernel/rcupdate.c for aproduction-quality implementation, and see: http://www.rdrop.com/users/paulmck/RCUfor papers describing the Linux kernel RCU implementation. The OLS'01and OLS'02 papers are a good introduction, and the dissertation providesmore details on the current implementation as of early 2004.5A. "TOY" IMPLEMENTATION #1: LOCKINGThis section presents a "toy" RCU implementation that is based onfamiliar locking primitives. Its overhead makes it a non-starter forreal-life use, as does its lack of scalability. It is also unsuitablefor realtime use, since it allows scheduling latency to "bleed" fromone read-side critical section to another.However, it is probably the easiest implementation to relate to, so isa good starting point.It is extremely simple: static DEFINE_RWLOCK(rcu_gp_mutex); void rcu_read_lock(void) { read_lock(&rcu_gp_mutex); } void rcu_read_unlock(void) { read_unlock(&rcu_gp_mutex); } void synchronize_rcu(void) { write_lock(&rcu_gp_mutex); write_unlock(&rcu_gp_mutex); }[You can ignore rcu_assign_pointer() and rcu_dereference() withoutmissing much. But here they are anyway. And whatever you do, don'tforget about them when submitting patches making use of RCU!] #define rcu_assign_pointer(p, v) ({ \ smp_wmb(); \ (p) = (v); \ }) #define rcu_dereference(p) ({ \ typeof(p) _________p1 = p; \ smp_read_barrier_depends(); \ (_________p1); \ })The rcu_read_lock() and rcu_read_unlock() primitive read-acquireand release a global reader-writer lock. The synchronize_rcu()primitive write-acquires this same lock, then immediately releasesit. This means that once synchronize_rcu() exits, all RCU read-sidecritical sections that were in progress before synchronize_rcu() wascalled are guaranteed to have completed -- there is no way thatsynchronize_rcu() would have been able to write-acquire the lockotherwise.It is possible to nest rcu_read_lock(), since reader-writer locks maybe recursively acquired. Note also that rcu_read_lock() is immunefrom deadlock (an important property of RCU). The reason for this isthat the only thing that can block rcu_read_lock() is a synchronize_rcu().But synchronize_rcu() does not acquire any locks while holding rcu_gp_mutex,so there can be no deadlock cycle.Quick Quiz #1: Why is this argument naive? How could a deadlock occur when using this algorithm in a real-world Linux kernel? How could this deadlock be avoided?5B. "TOY" EXAMPLE #2: CLASSIC RCUThis section presents a "toy" RCU implementation that is based on"classic RCU". It is also short on performance (but only for updates) andon features such as hotplug CPU and the ability to run in CONFIG_PREEMPTkernels. The definitions of rcu_dereference() and rcu_assign_pointer()are the same as those shown in the preceding section, so they are omitted. void rcu_read_lock(void) { } void rcu_read_unlock(void) { } void synchronize_rcu(void)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -