📄 kernel-locking.tmpl
字号:
to justify the extra complexity. </para> <para> You'll need to use <function>spin_lock()</function> and <function>spin_unlock()</function> for shared data. </para> </sect2> <sect2 id="lock-softirqs-different"> <title>Different Softirqs</title> <para> You'll need to use <function>spin_lock()</function> and <function>spin_unlock()</function> for shared data, whether it be a timer, tasklet, different softirq or the same or another softirq: any of them could be running on a different CPU. </para> </sect2> </sect1> </chapter> <chapter id="hardirq-context"> <title>Hard IRQ Context</title> <para> Hardware interrupts usually communicate with a tasklet or softirq. Frequently this involves putting work in a queue, which the softirq will take out. </para> <sect1 id="hardirq-softirq"> <title>Locking Between Hard IRQ and Softirqs/Tasklets</title> <para> If a hardware irq handler shares data with a softirq, you have two concerns. Firstly, the softirq processing can be interrupted by a hardware interrupt, and secondly, the critical region could be entered by a hardware interrupt on another CPU. This is where <function>spin_lock_irq()</function> is used. It is defined to disable interrupts on that cpu, then grab the lock. <function>spin_unlock_irq()</function> does the reverse. </para> <para> The irq handler does not to use <function>spin_lock_irq()</function>, because the softirq cannot run while the irq handler is running: it can use <function>spin_lock()</function>, which is slightly faster. The only exception would be if a different hardware irq handler uses the same lock: <function>spin_lock_irq()</function> will stop that from interrupting us. </para> <para> This works perfectly for UP as well: the spin lock vanishes, and this macro simply becomes <function>local_irq_disable()</function> (<filename class="headerfile">include/asm/smp.h</filename>), which protects you from the softirq/tasklet/BH being run. </para> <para> <function>spin_lock_irqsave()</function> (<filename>include/linux/spinlock.h</filename>) is a variant which saves whether interrupts were on or off in a flags word, which is passed to <function>spin_unlock_irqrestore()</function>. This means that the same code can be used inside an hard irq handler (where interrupts are already off) and in softirqs (where the irq disabling is required). </para> <para> Note that softirqs (and hence tasklets and timers) are run on return from hardware interrupts, so <function>spin_lock_irq()</function> also stops these. In that sense, <function>spin_lock_irqsave()</function> is the most general and powerful locking function. </para> </sect1> <sect1 id="hardirq-hardirq"> <title>Locking Between Two Hard IRQ Handlers</title> <para> It is rare to have to share data between two IRQ handlers, but if you do, <function>spin_lock_irqsave()</function> should be used: it is architecture-specific whether all interrupts are disabled inside irq handlers themselves. </para> </sect1> </chapter> <chapter id="cheatsheet"> <title>Cheat Sheet For Locking</title> <para> Pete Zaitcev gives the following summary: </para> <itemizedlist> <listitem> <para> If you are in a process context (any syscall) and want to lock other process out, use a semaphore. You can take a semaphore and sleep (<function>copy_from_user*(</function> or <function>kmalloc(x,GFP_KERNEL)</function>). </para> </listitem> <listitem> <para> Otherwise (== data can be touched in an interrupt), use <function>spin_lock_irqsave()</function> and <function>spin_unlock_irqrestore()</function>. </para> </listitem> <listitem> <para> Avoid holding spinlock for more than 5 lines of code and across any function call (except accessors like <function>readb</function>). </para> </listitem> </itemizedlist> <sect1 id="minimum-lock-reqirements"> <title>Table of Minimum Requirements</title> <para> The following table lists the <emphasis>minimum</emphasis> locking requirements between various contexts. In some cases, the same context can only be running on one CPU at a time, so no locking is required for that context (eg. a particular thread can only run on one CPU at a time, but if it needs shares data with another thread, locking is required). </para> <para> Remember the advice above: you can always use <function>spin_lock_irqsave()</function>, which is a superset of all other spinlock primitives. </para> <table><title>Table of Locking Requirements</title><tgroup cols="11"><tbody><row><entry></entry><entry>IRQ Handler A</entry><entry>IRQ Handler B</entry><entry>Softirq A</entry><entry>Softirq B</entry><entry>Tasklet A</entry><entry>Tasklet B</entry><entry>Timer A</entry><entry>Timer B</entry><entry>User Context A</entry><entry>User Context B</entry></row><row><entry>IRQ Handler A</entry><entry>None</entry></row><row><entry>IRQ Handler B</entry><entry>SLIS</entry><entry>None</entry></row><row><entry>Softirq A</entry><entry>SLI</entry><entry>SLI</entry><entry>SL</entry></row><row><entry>Softirq B</entry><entry>SLI</entry><entry>SLI</entry><entry>SL</entry><entry>SL</entry></row><row><entry>Tasklet A</entry><entry>SLI</entry><entry>SLI</entry><entry>SL</entry><entry>SL</entry><entry>None</entry></row><row><entry>Tasklet B</entry><entry>SLI</entry><entry>SLI</entry><entry>SL</entry><entry>SL</entry><entry>SL</entry><entry>None</entry></row><row><entry>Timer A</entry><entry>SLI</entry><entry>SLI</entry><entry>SL</entry><entry>SL</entry><entry>SL</entry><entry>SL</entry><entry>None</entry></row><row><entry>Timer B</entry><entry>SLI</entry><entry>SLI</entry><entry>SL</entry><entry>SL</entry><entry>SL</entry><entry>SL</entry><entry>SL</entry><entry>None</entry></row><row><entry>User Context A</entry><entry>SLI</entry><entry>SLI</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>None</entry></row><row><entry>User Context B</entry><entry>SLI</entry><entry>SLI</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>SLBH</entry><entry>DI</entry><entry>None</entry></row></tbody></tgroup></table> <table><title>Legend for Locking Requirements Table</title><tgroup cols="2"><tbody><row><entry>SLIS</entry><entry>spin_lock_irqsave</entry></row><row><entry>SLI</entry><entry>spin_lock_irq</entry></row><row><entry>SL</entry><entry>spin_lock</entry></row><row><entry>SLBH</entry><entry>spin_lock_bh</entry></row><row><entry>DI</entry><entry>down_interruptible</entry></row></tbody></tgroup></table></sect1></chapter> <chapter id="Examples"> <title>Common Examples</title> <para>Let's step through a simple example: a cache of number to namemappings. The cache keeps a count of how often each of the objects isused, and when it gets full, throws out the least used one. </para> <sect1 id="examples-usercontext"> <title>All In User Context</title> <para>For our first example, we assume that all operations are in usercontext (ie. from system calls), so we can sleep. This means we canuse a semaphore to protect the cache and all the objects withinit. Here's the code: </para> <programlisting>#include <linux/list.h>#include <linux/slab.h>#include <linux/string.h>#include <asm/semaphore.h>#include <asm/errno.h>struct object{ struct list_head list; int id; char name[32]; int popularity;};/* Protects the cache, cache_num, and the objects within it */static DECLARE_MUTEX(cache_lock);static LIST_HEAD(cache);static unsigned int cache_num = 0;#define MAX_CACHE_SIZE 10/* Must be holding cache_lock */static struct object *__cache_find(int id){ struct object *i; list_for_each_entry(i, &cache, list) if (i->id == id) { i->popularity++; return i; } return NULL;}/* Must be holding cache_lock */static void __cache_delete(struct object *obj){ BUG_ON(!obj); list_del(&obj->list); kfree(obj); cache_num--;}/* Must be holding cache_lock */static void __cache_add(struct object *obj){ list_add(&obj->list, &cache); if (++cache_num > MAX_CACHE_SIZE) { struct object *i, *outcast = NULL; list_for_each_entry(i, &cache, list) { if (!outcast || i->popularity < outcast->popularity) outcast = i; } __cache_delete(outcast); }}int cache_add(int id, const char *name){ struct object *obj; if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL) return -ENOMEM; strlcpy(obj->name, name, sizeof(obj->name)); obj->id = id; obj->popularity = 0; down(&cache_lock); __cache_add(obj); up(&cache_lock); return 0;}void cache_delete(int id){ down(&cache_lock); __cache_delete(__cache_find(id)); up(&cache_lock);}int cache_find(int id, char *name){ struct object *obj; int ret = -ENOENT; down(&cache_lock); obj = __cache_find(id); if (obj) { ret = 0; strcpy(name, obj->name); } up(&cache_lock); return ret;}</programlisting> <para>Note that we always make sure we have the cache_lock when we add,delete, or look up the cache: both the cache infrastructure itself andthe contents of the objects are protected by the lock. In this caseit's easy, since we copy the data for the user, and never let themaccess the objects directly. </para> <para>There is a slight (and common) optimization here: in<function>cache_add</function> we set up the fields of the objectbefore grabbing the lock. This is safe, as no-one else can access ituntil we put it in cache.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -