当前位置:Linux教程 - Linux - 内核空间SMP编程

内核空间SMP编程



        

    作者:Dave Jones 编译:杨继张

      多处理机系统正在变得越来越普通。尽管大多数用户空间代码仍将完美地运
    行,而且些情况下不需要增加额外的代码就能利用SMP特性的优势,但是内核
    空间代码必须编写成备“SMP意识”且是“SMP安全的”。以下几段文字解释
    如何去做。

    问题

      当有多个CPU时,同样的代码可能同时在两个或多个CPU上执行。这在如
    下所示用于初始化某个图像设备的例程中可能会出问题。
    void init_hardware(void)
    {
    outb(0x1, hardware_base + 0x30);
    outb(0x2, hardware_base + 0x30);
    outb(0x3, hardware_base + 0x30);
    outb(0x4, hardware_base + 0x30);
    }
    假设该硬件依赖于寄存器0x30按顺序依次被设为0、1、2、3来初始化,那么要
    是有另一个CPU来参乎的话,事情就会搞糟。想象有两个CPU的情形,它们都
    在执行这个例程,不过2号CPU进入得稍慢点:
    CPU 1 CPU 2

    0x30 = 1
    0x30 = 2 0x30 = 1
    0x30 = 3 0x30 = 2
    0x30 = 4 0x30 = 3
    0x30 = 4
    这会发生什么情况呢?从我们设想的硬件设备看来,它在寄存器0x30上收到的
    字节按顺序为:1、2、1、3、2、4、3、4。
    啊!原本好好的事第二个CPU一来就搞得一团糟了也。所幸的是,我们有防止
    这类事情发生的办法。

    自旋锁小历史

      2.0.x版本的Linux内核通过给整个内核引入一个全局变量来防止多于一个
    CPU会造成的问题。这意味着任何时刻只有一个CPU能够执行来自内核空间的
    代码。这样尽管能工作,但是当系统开始以多于2个的CPU出现时,扩展性能就
    不怎么好。
      2.1.x版本的内核系列加入了粒度更细的SMP支持。这意味着不再依赖于以
    前作为全局变量出现的“大锁”,而是每个没有SMP意识的例程现在都需要各
    自的自旋锁。文件asm/spinlock.h中定义了若干类型的自旋锁。有了局部化的
    自旋锁后,不止一个CPU同时执行内核空间代码就变得可能了。

    简单的自旋锁

      理解自旋锁的最简单方法是把它作为一个变量看待,该变量把一个例程或者
    标记为“我当前在另一个CPU上运行,请稍等一会”,或者标记为“我当前不
    在运行”。如果1号CPU首先进入该例程,它就获取该自旋锁。当2号CPU试图

    进入同一个例程时,该自旋锁告诉它自己已为1号CPU所持有,需等到1号CPU
    释放自己后才能进入。
    spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;
    unsigned long flags;

    spin_lock (&my_spinlock);
    ...
    critical section
    ...
    spin_unlock (&my_spinlock);

    中断

      设想我们的硬件的驱动程序还有一个中断处理程序。该处理程序需要修改某
    些由我们的驱动程序定义的全局变量。这会造成混乱。我们如何解决呢?
      保护某个数据结构,使它免遭中断之修改的最初方法是全局地禁止中断。在
    已知只有自己的中断才会修改自己的驱动程序变量时,这么做效率很低。所幸的
    是,我们现在有更好的办法了。我们只是在使用共享变量期间禁止中断,此后重
    新使能。
    实现这种办法的函数有三个:
    disable_irq()
    enable_irq()
    disable_irq_nosync()
      这三个函数都取一个中断号作为参数。注意,禁止一个中断的时间太长会导
    致难以追踪程序缺陷,丢失数据,甚至更坏。
      disable_irq函数的非同步版本允许所指定的IRQ处理程序继续运行,前提
    是它已经在运行,普通的disable_irq则所指定的IRQ处理程序不在如何CPU上
    运行。
      如果需要在中断处理程序中修改自旋锁,那就不能使用普通的spin_lock()
    和spin_unlock(),而应该保存中断状态。这可通过给这两个函数添加
    _irqsave后缀很容易地做到:
    spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;
    unsigned long flags;
    spin_lock_irqsave(&my_spinlock, flags);
    ...
    critical section
    ...
    spin_unlock_irqrestore (&my_spinlock, flags);

    发布人:netbull 来自:LinuxAid