分享

spinlock理解-郭耀今

 黄浦江中的一条鱼 2010-01-22
  1. 原创 关于内核中spinlock的一些个人理解收藏
  2. 新一篇: Windows程序中的字符编码 | 旧一篇: 主流显卡Linux系统驱动安装--intel、ATi篇
  3. 由 于2.6内核可以抢占,应该在驱动程序中使用 preempt_disable() 和 preempt_enable(),从而保护代码段不被抢占(禁 止 IRQ 同时也就隐式地禁止了抢占)。preempt_disable和preempt_enable 调用。spin_lock_irq的功能和上 面的spin_lock提供的功能差不多,只不过它还多做了一步,就是把中断也关上,主要用于当前保护的数据在可能的中断程序中也要用到的情况。 spin_lock_irqsave和spin_lock_irq的功能一样,只不过调用这个函数以后可以把当前的中断状态记下了,以备以后恢复。
  4. 在 多CPU的环境下情况就比较复杂了,因为同时可能有几个程序在运行(是真正的同时),所以必须要定义一个变量当作锁的功能,linux是这样规定的,当这 个变量为1时,那么其保护的变量可以被访问,当其值为0时,那么其保护的临界数据不可以被访问,其中,要改变变量锁的值也很有学问,就是不能让几个CPU 同时去改,负责就会出现不同步的情况。如spin_lock在多cpu的时候就被?/
  5. 在这里,我主要把自己对内核中spinlock的一些理解写出来,并不是要告诉大家什么(因为我对我所说的也不能确定),而是希望大家对我的这些理解对的地方给我肯定,错误的地方给我指出。
  6. 和 spinlock 相关的文件主要有两个,一个是include/linux/spinlock.h,主要是提供关于和硬件无关的spinlock的几个 对外主函数,一个是 include/asm-XXX/spinlock.h,用来提供和硬件相关的功能函数。另外,在2.6的内核中,又多了一个文 件, include/linux/preempt.h,为新增加的抢占式多任务功能提供一些服务。
  7. spinlock的作用:spinlock系列函数主要用于保护临界数据(非常重要的数据)不被同时访问(给临界数据加锁),用以达到多任务的同步。如果一个数据当前不可访问,那么就一直等,直到可以访问为止。
  8. spinlock 函 数的使用前提:首先,spinklock函数只能使用在内核中,或者说只能使用在内核状态下,在2.6以前的内核是不可抢占的,也就是说,当运行于内核状 态下时,是不容许切换到其他进程的。而在2.6以后的内核中,编译内核的时候多了一个选项,可以配置内核是否可以被抢占,这也就是为什么在2.6的内核中 多了一个preempt.h的原因。
  9. spinlock主要包含以下几个函数:
  10. spin_lock
  11. spin_unlock
  12. spin_lock_irqsave
  13. spin_lock_irq
  14. spin_unlock_irqrestore
  15. spin_unlock_irq
  16. 另 外还有其他很多,如关于读者写者的一套函数,关于bottom half一套函数(关于bottom half的代码我还没有读到),还有还提供了一套用 bit实现加锁的函数,由于大概意思都相同,所以我这里就不说了(只想简单说说,没想到东西还挺多,我的手都快冻僵了,江南的冬天真的受不了:)
  17. spinlock函数根据机器的配置分为两套,单CPU和多CPU,先来看看单CPU的情况。
  18. 在单CPU的情况下,spin_lock和spin_unlock函数都被定义成空操作(do { } while(0)), 这是因为我们上面说的,内核不可以被抢占的原因。所以,在单CPU的情况下,只要你能够保证你要保护的临界数据不会在中断中用到的话,那么你的数据已经是 受保护的了,不需要做任何操作。在2.6内核中,这两个函数就不再这么简单了,因为内核也有可能被其他程序中断,所以要保护数据,还要让调度程序暂时不调 度此段程序,也就是说,暂时禁止抢占式任务调度功能,所以在上面两个函数中分别多了一个
  19. 需要澄清的是,互斥手段的选择,不是根据临界区的大小,而是根据临界区的性质,以及
  20. 有哪些部分的代码,即哪些内核执行路径来争夺。
  21. 从严格意义上说,semaphore和spinlock_XXX属于不同层次的互斥手段,前者的
  22. 实现有赖于后者,这有点象HTTP和TCP的关系,都是协议,但层次是不同的。
  23. 先说semaphore,它是进程级的,用于多个进程之间对资源的互斥,虽然也是在
  24. 内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果
  25. 竞争不上,会有context switch,进程可以去sleep,但CPU不会停,会接着运行
  26. 其他的执行路径。从概念上说,这和单CPU或多CPU没有直接的关系,只是在
  27. semaphore本身的实现上,为了保证semaphore结构存取的原子性,在多CPU中需要
  28. spinlock来互斥。
  29. 在内核中,更多的是要保持内核各个执行路径之间的数据访问互斥,这是最基本的
  30. 互斥问题,即保持数据修改的原子性。semaphore的实现,也要依赖这个。在单CPU
  31. 中,主要是中断和bottom_half的问题,因此,开关中断就可以了。在多CPU中,
  32. 又加上了其他CPU的干扰,因此需要spinlock来帮助。这两个部分结合起来,
  33. 就形成了spinlock_XXX。它的特点是,一旦CPU进入了spinlock_XXX,它就不会
  34. 干别的,而是一直空转,直到锁定成功为止。因此,这就决定了被
  35. spinlock_XXX锁住的临界区不能停,更不能context switch,要存取完数据后赶快
  36. 出来,以便其他的在空转的执行路径能够获得spinlock。这也是spinlock的原则
  37. 所在。如果当前执行路径一定要进行context switch,那就要在schedule()之前
  38. 释放spinlock,否则,容易死锁。因为在中断和bh中,没有context,无法进行
  39. context switch,只能空转等待spinlock,你context switch走了,谁知道猴年
  40. 马月才能回来。
  41. 因为spinlock的原意和目的就是保证数据修改的原子性,因此也没有理由在spinlock
  42. 锁住的临界区中停留。
  43. spinlock_XXX有很多形式,有
  44.   spin_lock()/spin_unlock(),
  45.   spin_lock_irq()/spin_unlock_irq(),
  46.   spin_lock_irqsave/spin_unlock_irqrestore()
  47.   spin_lock_bh()/spin_unlock_bh()
  48.   local_irq_disable/local_irq_enable
  49.   local_bh_disable/local_bh_enable
  50. 那么,在什么情况下具体用哪个呢?这要看是在什么内核执行路径中,以及要与哪些内核
  51. 执行路径相互斥。我们知道,内核中的执行路径主要有:
  52.  1  用户进程的内核态,此时有进程context,主要是代表进程在执行系统调用
  53.     等。
  54.  2  中断或者异常或者自陷等,从概念上说,此时没有进程context,不能进行
  55.     context switch
  56.  3  bottom_half,从概念上说,此时也没有进程context。
  57.  4  同时,相同的执行路径还可能在其他的CPU上运行。
  58. 这样,考虑这四个方面的因素,通过判断我们要互斥的数据会被这四个因素中
  59. 的哪几个来存取,就可以决定具体使用哪种形式的spinlock。如果只要和其他CPU
  60. 互斥,就要用spin_lock/spin_unlock,如果要和irq及其他CPU互斥,就要用
  61. spin_lock_irq/spin_unlock_irq,如果既要和irq及其他CPU互斥,又要保存
  62. EFLAG的状态,就要用spin_lock_irqsave/spin_unlock_irqrestore,如果
  63. 要和bh及其他CPU互斥,就要用spin_lock_bh/spin_unlock_bh,如果不需要和
  64. 其他CPU互斥,只要和irq互斥,则用local_irq_disable/local_irq_enable,
  65. 如果不需要和其他CPU互斥,只要和bh互斥,则用local_bh_disable/local_bh_enable,
  66. 等等。值得指出的是,对同一个数据的互斥,在不同的内核执行路径中,
  67. 所用的形式有可能不同(见下面的例子)。
  68. 举一个例子。在中断部分中有一个irq_desc_t类型的结构数组变量irq_desc[],
  69. 该数组每个成员对应一个irq的描述结构,里面有该irq的响应函数等。
  70. 在irq_desc_t结构中有一个spinlock,用来保证存取(修改)的互斥。
  71. 对于具体一个irq成员,irq_desc[irq],对其存取的内核执行路径有两个,一是
  72. 在设置该irq的响应函数时(setup_irq),这通常发生在module的初始化阶段,或
  73. 系统的初始化阶段;二是在中断响应函数中(do_IRQ)。代码如下:
  74. int setup_irq(unsigned int irq, struct irqaction * new)
  75. {
  76.         int shared = 0;
  77.         unsigned long flags;
  78.         struct irqaction *old, **p;
  79.         irq_desc_t *desc = irq_desc + irq;
  80.         /*
  81.          * Some drivers like serial.c use request_irq() heavily,
  82.          * so we have to be careful not to interfere with a
  83.          * running system.
  84.          */
  85.         if (new->flags & SA_SAMPLE_RANDOM) {
  86.                 /*
  87.                  * This function might sleep, we want to call it first,
  88.                  * outside of the atomic block.
  89.                  * Yes, this might clear the entropy pool if the wrong
  90.                  * driver is attempted to be loaded, without actually
  91.                  * installing a new handler, but is this really a problem,
  92.                  * only the sysadmin is able to do this.
  93.                  */
  94.                 rand_initialize_irq(irq);
  95.         }
  96.         /*
  97.          * The following block of code has to be executed atomically
  98.          */
  99. [1]     spin_lock_irqsave(&desc->lock,flags);
  100.         p = &desc->action;
  101.         if ((old = *p) != NULL) {
  102.                 /* Can't share interrupts unless both agree to */
  103.                 if (!(old->flags & new->flags & SA_SHIRQ)) {
  104. [2]                     spin_unlock_irqrestore(&desc->lock,flags);
  105.                         return -EBUSY;
  106.                 }
  107.                 /* add new interrupt at end of irq queue */
  108.                 do {
  109.                         p = &old->next;
  110.                         old = *p;
  111.                 } while (old);
  112.                 shared = 1;
  113.         }
  114.         *p = new;
  115.         if (!shared) {
  116.                 desc->depth = 0;
  117.                 desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);
  118.                 desc->handler->startup(irq);
  119.         }
  120. [3]     spin_unlock_irqrestore(&desc->lock,flags);
  121.         register_irq_proc(irq);
  122.         return 0;
  123. }
  124. asmlinkage unsigned int do_IRQ(struct pt_regs regs)
  125. {        
  126.         /* 
  127.          * We ack quickly, we don't want the irq controller
  128.          * thinking we're snobs just because some other CPU has
  129.          * disabled global interrupts (we have already done the
  130.          * INT_ACK cycles, it's too late to try to pretend to the
  131.          * controller that we aren't taking the interrupt).
  132.          *
  133.          * 0 return value means that this irq is already being
  134.          * handled by some other CPU. (or is disabled)
  135.          */
  136.         int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code  */
  137.         int cpu = smp_processor_id();
  138.         irq_desc_t *desc = irq_desc + irq;
  139.         struct irqaction * action;
  140.         unsigned int status;
  141.         kstat.irqs[cpu][irq]++;
  142. [4]     spin_lock(&desc->lock);
  143.         desc->handler->ack(irq);
  144.         /*
  145.            REPLAY is when Linux resends an IRQ that was dropped earlier
  146.            WAITING is used by probe to mark irqs that are being tested
  147.            */
  148.         status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
  149.         status |= IRQ_PENDING; /* we _want_ to handle it */
  150.         /*
  151.          * If the IRQ is disabled for whatever reason, we cannot
  152.          * use the action we have.
  153.          */
  154.         action = NULL;
  155.         if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {
  156.                 action = desc->action;
  157.                 status &= ~IRQ_PENDING; /* we commit to handling */
  158.                 status |= IRQ_INPROGRESS; /* we are handling it */
  159.         }
  160.         desc->status = status;
  161.         /*
  162.          * If there is no IRQ handler or it was disabled, exit early.
  163.            Since we set PENDING, if another processor is handling
  164.            a different instance of this same irq, the other processor
  165.            will take care of it.
  166.          */
  167.         if (!action)
  168.                 goto out;
  169.         /*
  170.          * Edge triggered interrupts need to remember
  171.          * pending events.
  172.          * This applies to any hw interrupts that allow a second
  173.          * instance of the same irq to arrive while we are in do_IRQ
  174.          * or in the handler. But the code here only handles the _second_
  175.          * instance of the irq, not the third or fourth. So it is mostly
  176.          * useful for irq hardware that does not mask cleanly in an
  177.          * SMP environment.
  178.          */
  179.         for (;;) {
  180. [5]             spin_unlock(&desc->lock);
  181.                 handle_IRQ_event(irq, ®s, action);
  182. [6]             spin_lock(&desc->lock);
  183.                 
  184.                 if (!(desc->status & IRQ_PENDING))
  185.                         break;
  186.                 desc->status &= ~IRQ_PENDING;
  187.         }
  188.         desc->status &= ~IRQ_INPROGRESS;
  189. out:
  190.         /*
  191.          * The ->end() handler has to deal with interrupts which got
  192.          * disabled while the handler was running.
  193.          */
  194.         desc->handler->end(irq);
  195. [7]     spin_unlock(&desc->lock);
  196.         if (softirq_pending(cpu))
  197.                 do_softirq();
  198.         return 1;
  199. }
  200. 在setup_irq()中,因为其他CPU可能同时在运行setup_irq(),或者在运行setup_irq()时,
  201. 本地irq中断来了,要执行do_IRQ()以修改desc->status。为了同时防止来自其他CPU和
  202. 本地irq中断的干扰,如[1][2][3]处所示,使用了spin_lock_irqsave/spin_unlock_irqrestore()
  203. 而在do_IRQ()中,因为do_IRQ()本身是在中断中,而且此时还没有开中断,本CPU中没有
  204. 什么可以中断其运行,其他CPU则有可能在运行setup_irq(),或者也在中断中,但这二者
  205. 对本地do_IRQ()的影响没有区别,都是来自其他CPU的干扰,因此只需要用spin_lock/spin_unlock,
  206. 如[4][5][6][7]处所示。值得注意的是[5]处,先释放该spinlock,再调用具体的响应函数。
  207. 再举个例子:
  208. static void tasklet_hi_action(struct softirq_action *a)
  209. {
  210.         int cpu = smp_processor_id();
  211.         struct tasklet_struct *list;
  212. [8]     local_irq_disable();
  213.         list = tasklet_hi_vec[cpu].list;
  214.         tasklet_hi_vec[cpu].list = NULL;
  215. [9]     local_irq_enable();
  216.         while (list) {
  217.                 struct tasklet_struct *t = list;
  218.                 list = list->next;
  219.                 if (tasklet_trylock(t)) {
  220.                         if (!atomic_read(&t->count)) {
  221.                                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
  222.                                         BUG();
  223.                                 t->func(t->data);
  224.                                 tasklet_unlock(t);
  225.                                 continue;
  226.                         }
  227.                         tasklet_unlock(t);
  228.                 }
  229. [10]            local_irq_disable();
  230.                 t->next = tasklet_hi_vec[cpu].list;
  231.                 tasklet_hi_vec[cpu].list = t;
  232.                 __cpu_raise_softirq(cpu, HI_SOFTIRQ);
  233. [11]            local_irq_enable();
  234.         }
  235. }
  236. 这里,对tasklet_hi_vec[cpu]的修改,不存在CPU之间的竞争,因为每个CPU有各自独立的数据,
  237. 所以只要防止irq的干扰,用local_irq_disable/local_irq_enable即可,如[8][9][10][11]处
  238. 所示。  
 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多