linux 驱动之自旋锁笔记

Table of Contents

 

一、自旋锁介绍

二、使用自旋锁注意事项

三、自旋锁编程步骤

1.定义自旋锁

2.初始化自旋锁

3.获得自旋锁

4.释放自旋锁

5、自旋锁使用模板

四、衍生自旋锁

五、参考代码


一、自旋锁介绍

自旋锁(Spin Lock)是一种典型的对临界资源进行互斥访问的手段,名称来源于它的工作方式。为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(Test-And-Set)某个内存变量。由于它是原子操作,所以在在该操作完成之前其他执行单元不可能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋

理解自旋锁最简单的方法是把自旋锁作为一个变量看待,该变量把一个临界区标记为“我当前在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用”。如果A执行单元首先进入例程,它将持有自旋锁;当B执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到A执行单元释放后才能进入。

二、使用自旋锁注意事项

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

2)自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁。

3)在自旋锁锁定期间不能调用可能引起进程调度的函数。如果进程获得自旋锁之后再阻塞,如调用copy_from_user()、copy_to_user()、kmalloc()和msleep()等函数,则可能导致内核的崩溃。

4)在单核情况下编程时,也应该认为自己的CPU是多核的,驱动特别强调跨平台的概念。比如,在单CPU的情况下,若中断和进程可能访问同一临界区,进程里调用spin_lock_irqsave()是安全的,在中断里其实不调用spin_lock()也没有问题,因为spin_lock_irqsave()可以保证这个CPU的中断服务程序不可能执行。但是,若CPU变成多核,spin_lock_irqsave()不能屏蔽另外一个核的中断,所以另外一个核就可能造成并发问题。因此,无论如何,在中断服务程序里也应该调用spin_lock()。

三、自旋锁编程步骤

1.定义自旋锁

spinlock_t lock;

2.初始化自旋锁

spin_lock_init(lock)

该宏用于动态初始化自旋锁lock。

3.获得自旋锁

spin_lock(lock)

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

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

4.释放自旋锁

spin_unlock(lock)

该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。

5、自旋锁使用模板

spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; /* 获取自旋锁,保护临界区 */
. . ./* 临界区 */

spin_unlock (&lock) ; /* 解锁 */

四、衍生自旋锁

尽管使用自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候,还可能受到中断和底半部(BH)的影响。为了防止这种影响,需要用到自旋锁的衍生。spin_lock()/spin_unlock()是自旋锁机制的基础,它们和关中断local_irq_disable()/开中断local_irq_enable()、关底半部local_bh_disable()/开底半部local_bh_enable()、关中断并保存状态标志local_irq_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()

 

五、参考代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>

static int major;
static struct cdev btn_cdev;
static struct class *cls;

static int open_cnt = 1; //共享资源
static spinlock_t btn_lock; //分配自旋锁

static int btn_open(struct inode *inode,
                        struct file *file)
{
    unsigned long flags;

    //1.获取锁
    spin_lock_irqsave(&btn_lock, flags);
    
    //2.执行临界区,A进程在执行这个if语句时,B进程或者别的中断就不会    来进行干扰,因为有衍生自旋锁来保护
    if(--open_cnt != 0) {
        printk("button can be open only once!\n");
        open_cnt++;
        spin_unlock_irqrestore(&btn_lock, flags);
        return -EBUSY;
    }   
    //3.释放锁
    spin_unlock_irqrestore(&btn_lock, flags);
    
    printk("open buttton successfully!\n");
    return 0;
}

static int btn_close(struct inode *inode,
                        struct file *file)
{
    unsigned long flags;

    spin_lock_irqsave(&btn_lock, flags); //获取锁
    open_cnt++; //执行临界区
    spin_unlock_irqrestore(&btn_lock, flags); //释放锁
    return 0;
}

static struct file_operations btn_fops = {
    .owner = THIS_MODULE,
    .open = btn_open,
    .release = btn_close
};

static int btn_init(void)
{
    dev_t dev;

    //1.申请设备号
    alloc_chrdev_region(&dev, 0, 1, "buttons");
    major = MAJOR(dev);

    //2.初始化注册cdev
    cdev_init(&btn_cdev, &btn_fops);
    cdev_add(&btn_cdev, dev, 1);

    //3.自动创建设备节点:/dev/mybuttons
    cls = class_create(THIS_MODULE, "buttons");
    device_create(cls, NULL, dev, NULL, "mybuttons"); 

    //4.初始化自旋锁
    spin_lock_init(&btn_lock);
    return 0;
}

static void btn_exit(void)
{
    dev_t dev = MKDEV(major, 0);
    
    //1.删除设备节点
    device_destroy(cls, dev);
    class_destroy(cls);

    //2.卸载cdev
    cdev_del(&btn_cdev);

    //3.释放设备号
    unregister_chrdev_region(dev, 1);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

参考文章:https://blog.csdn.net/xiezhi123456/article/details/80383040

发布了137 篇原创文章 · 获赞 106 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/shenlong1356/article/details/103328600