陕西油炸小吃有哪些:自旋锁的使用

来源:百度文库 编辑:偶看新闻 时间:2024/04/30 14:21:56

自旋锁 (spin lock) 是一种对临界资源进行互斥访问的典型手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某 CPU 上运行的代码需要先执行一个原子操作,该操作测试并设置( test and set ) 某个内存变量。由于是原子操作,所以在该操作完成之前其它执行单元不可能访问这个内存变量。

如果测试结果标名锁已经空闲,则程序获得这个自旋锁并继续执行;如果测试结果表明锁仍然被占用,那么程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋“,通俗地说,就是在”原地打转“。当自旋锁的持有者通过重置该变量并释放这个自旋锁后,某个等待的"测试并设置“操作向其调用者报告锁已经释放。

理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为“我当前正在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用“。

如果 A 执行单元首先进入例程,它将持有自旋锁;当 B 执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等待 A 执行单元释放后才能进入。

一般说来,在由自旋锁保护的每个临界区里内核抢占不起作用。但对于单 CPU 系统,把自己给锁起来没什么意义,自旋锁原语只是禁用或者启用内核抢占;需要注意的是,内核抢占在忙等待状态仍然有效,因此一个进程等待一个自旋锁释放也可被一个更高优先级的进程所取代。

Linux 系统中与自旋锁想关的操作主要有 4 种:

1、定义自旋锁
spinlock_t spin;

spinlock_t 的定义在include/linux/spinlock_types.h 中有:

引用

typedef struct {
       raw_spinlock_t raw_lock;

#ifdef CONFIG_GENERIC_LOCKBREAK
       unsigned int break_lock;
#endif

#ifdef CONFIG_DEBUG_SPINLOCK
        unsigned int magic, owner_cpu;
        void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map dep_map;
#endif
} spinlock_t;


其中,raw_spinlock_t 的定义在 include/linux/spinlock_types_up.h 中:

引用

typedef struct {
        volatile unsigned int slock;
} raw_spinlock_t;


上面,slock 变量为 1 表示非锁状态,如果为 0 或是任一负数则表示锁状态。

break_lock 表示进程是否在忙等待自旋锁。

2、初始化自旋锁
spin_lock_init (lock);

3、获得自旋锁
spin_lock (lock);
该宏用于获得自旋锁 lock ,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放。

spin_trylock (lock);
该宏尝试获得自旋锁 lock,如果能立即获得,它将获得锁并返回真,否则立即返回假,实际上不再“在原地打转” 。

4、释放自旋锁
spin_unlock (lock);
该宏释放自旋锁 lock ,它与 spin_trylock 或 spin_lock 配对使用。

自旋锁一般如下使用:

引用

spinlock_t lock;
spin_lock_init (&lock);

spin_lock (&lock);      /*获得自旋锁,保护临界区*/
...//临界区
spin_unlock (&lock);     /*解锁*/



自旋锁主要针对 SMP 或单 CPU 但内核可抢占的情况,对于单 CPU 且内核不支持抢占的系统,自旋锁退化为空操作。
在但 CPU 和内核可抢占的系统中,自旋作持有期间,内核的抢占将被禁止。由于内核可抢占的单 CPU 系统的行为实际类似于 SMP 系统,因此,在这样的单 CPU 系统中使用自旋锁仍十分必要。

尽管用了自旋锁可以保证临界区不受别的 CPU 和本 CPU 内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候仍然可能受到中断和底半部 (BH) 的影响。为了防止这种影响,就需要用到自旋锁的衍生。 spin_lock() / spin_unlock() 是自旋锁机制的基础,它们和关中断 lock_iqr_disable() / 开中断 lock_irq_enable() 、关底半部 lock_bh_disable() / 开底半部 local_bh_enable() 、关中断保存状态字 local_iqr_save() / 开中断并恢复状态 local_irq_restore() 结合,就形成了整套自旋锁机制,关系如下:

引用

spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_lock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()



使用自旋锁需要谨慎,特别要注意以下几个问题:

·                          自旋锁实际上是忙等锁,当锁不可用时,CPU 一直循环执行 "测试并设置" 直到可以取得该锁,CPU 在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。

·                          自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的 CPU 想第 2 次获得这个锁,则该 CPU 死锁。此外,如果进程获得自旋锁后再阻塞,也有可能导致死锁的发生。copy_from_user()、copy_to_user() 和 kmalloc() 等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数。

使用自旋锁使设备只能被一个进程打开

引用

int xxx_count = 0;   /*定义文件打开次数*/

static int xxx_open (struct inode *inode, struct file *filp)
{
     ...
     spin_lock (&xxx_lock);  /*获取自旋锁*/
    
     if (xxx_count){     /*获得自旋锁,但已有别的进程打开设备*/
          spin_unlock (&xxx_lock);    /*设备忙,释放自旋锁返回*/
          reurn (-EBUSY);
     }

     xxx_count++;        /*没有别的进程使用设备,增加使用计数*/
     spin_unlock (&xxx_lock);    /*已经成功打开设备,释放自旋锁*/
     ...
     return (0);        /*打开成功*/
}

static int xxx_release (struct inode *inode, struct file *filp)
{
    ...
    spin_lock (&xxx_lock);    /*获得自旋锁*/
    xxx_count--;       /*减少使用计数*/
    spin_unlock (&xxx_lock);

    return (0);
}


上面,使用自旋锁修改了全局变量 xxx_count 。程序段保证了一个设备仅能被一个进程打开。