美文网首页Linux驱动
L14. 并发与竞态(自旋锁)

L14. 并发与竞态(自旋锁)

作者: 拂去尘世尘 | 来源:发表于2020-08-06 14:43 被阅读0次

    简介

    自旋锁: 它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。 ----百度百科

    由以上介绍大概了解,自旋锁就是内核为防止临界区被多个进程同时访问出错的一种机制。其作用时保证临界区在任一时刻有且仅有一个进程访问。

    应用场景

    举个例子:
    app1: 向驱动设备(/dev/driver_case)写入1024个字节的'a'字符,然后读取,25s后再次读取;
    app2: 向驱动设备(/dev/driver_case)写入1024个字节的'b'字符,然后读取,25s后再次读取;
    驱动设备:注册驱动/dev/driver_case。将app写入的数据存入到内核空间,并在app读取时打印出来。

    问题:两个app都会占用驱动25s,如果先执行app1占用驱动,在app1没有结束占用之前,再执行app_2占用了驱动,会出现什么后果?
    现象:

    第一次访问.png 第二次访问.png
    分析:第一次读取时,发现app1写入的是‘a’,app2写入的是‘b’。但是第二次读取时,发现app1写入的值被app2覆盖掉了,这就会出现问题了。

    解决方法:驱动引入自旋锁,在驱动被一个进程占用没有释放之前,不允许其他进程占用。

    实例分析

    1.声明自旋锁

    #include <linux/uaccess.h>
    ……
    struct driver_case_dev {
    ……
    #ifdef SPINLOCK_SUPPORT 
        spinlock_t lock;
    #endif
    };
        
    struct driver_case_dev driver_case;
    

    2.初始化自旋锁

    ……
    spin_lock_init(&driver_case.lock);
    ……
    

    3.入口设计

    static int driver_case_open(struct inode *inode, struct file *filp)
    {
    #ifdef SPINLOCK_SUPPORT  
        unsigned long flags;
      
        spin_lock_irqsave(&driver_case.lock, flags);    
        if (driver_case.dev_status) {                   
            spin_unlock_irqrestore(&driver_case.lock, flags);
            return -EBUSY;
        }
        driver_case.dev_status++;   
        spin_unlock_irqrestore(&driver_case.lock, flags);
    #endif
    ……
        
        return 0;
    }
    

    4.出口设计

    static int driver_case_release(struct inode *inode, struct file *filp)
    {
    #ifdef SPINLOCK_SUPPORT
        unsigned long flags;
        
        spin_lock_irqsave(&driver_case.lock, flags);      
        if (driver_case.dev_status) {
            driver_case.dev_status--;
        }
        spin_unlock_irqrestore(&driver_case.lock, flags); 
    #endif
    ……
        return 0;
    }
    

    注意:需要注意的是,这里自旋锁保护的临界区是driver_case.dev_status增减操作。驱动通过判断driver_case.dev_status的值来确定当前驱动是否被占用。
    这里的例子效果不是很明显,理想的是在open入口直接给上一个自旋锁,然后在release出口函数释放。但是自旋锁的缺点是在整个持锁过程中,会导致访问的进程阻塞,且本例期间会屏蔽中断。由于app占用时间25s,会导致期间中断不会响应,且进程长时间阻塞。因此没有按照理想情况设计,driver_case.dev_status增减操作执行很快,不会导致以上问题出现,因此选用此种方案。

    测试

    测试图.png

    由上可知,在执行第一个app时,是正常运行的。此时再执行第二个app时,会发现无法访问驱动。从而避免驱动在被占用期间,再次被其他进程访问的问题了。

    自旋锁常用API

    spinlock函数在内核文件include\linux\spinlock.h中声明,如下表:

    函数 作用
    spin_lock_init(_lock) 初始化自旋锁为unlock状态
    void spin_lock(spinlock_t *lock) 获取自旋锁(加锁),返回后肯定获得了锁
    int spin_trylock(spinlock_t *lock) 尝试获得自旋锁,成功获得锁则返回1,否则返回0
    void spin_unlock(spinlock_t *lock) 释放自旋锁,或称解锁
    int spin_is_locked(spinlock_t *lock) 返回自旋锁的状态,已加锁返回1,否则返回0

    自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:

    后缀 功能
    _bh() 加锁时禁止下半部(软中断),解锁时使能下半部(软中断)
    _irq() 加锁时禁止中断,解锁时使能中断
    _irqsave/restore() 加锁时禁止中断并记录状态,解锁时恢复中断为所记录的状态(enable /disable)

    参考: 《驱动大全之同步与互斥》 韦东山著
    聊天窗口发送 “自旋锁” 获取笔者本篇代码实例,如有技术交流需要,请“开源519”公众号。

    开源519.jpg

    相关文章

      网友评论

        本文标题:L14. 并发与竞态(自旋锁)

        本文链接:https://www.haomeiwen.com/subject/mpporktx.html