• 嵌入式Linux驱动开发(同步与互斥专题)(二)


    一、自旋锁spinlock的实现

    自旋锁,顾名思义:自己在原地打转,等待资源可用,一旦可用就上锁霸占它。
    ① 原地打转的是CPU x,以后CPU y会解锁:这涉及多个CPU,适用于SMP系统;
    ② 对于单CPU系统,自旋锁的“自旋”功能就去掉了:只剩下禁止抢占、禁止中断

    要理解spinlock,要通过2个情景来分析:
    ① 一开始,怎么争抢资源?不能2个程序都抢到。
    这挺好解决,使用原子变量就可以实现。
    ② 某个程序已经获得资源,怎么防止别人来同时使用这个资源。
    这是使用spinlock时要注意的地方,对应会有不同的衍生函数(_bh/_irq/_irqsave/_restore)。

    1.1、自旋锁的内核结构体

    在这里插入图片描述
    上述__raw_tickets结构体中有owner、next两个成员,这是在SMP系统中实现spinlock的关键。

    1.2、spinlock在单CUP系统中的实现

    对于单CPU系统,没有“其他CPU”;如果内核不支持preempt,当前在内核态执行的线程也不可能被其他线程抢占,也就“没有其他进程/线程”。所以,对于不支持preempt的单CPU系统,spin_lock是空函数,不需要做其他事情。

    如果单CPU系统的内核支持preempt,即当前线程正在执行内核态函数时,它是有可能被别的线程抢占的。这时spin_lock的实现就是调用“preempt_disable()”:你想抢我,我干脆禁止你运行。
    在单CUP系统中,spin_lock函数定义如下:
    在这里插入图片描述
    对于spin_lock_irq(),在UP系统中就退化为local_irq_disable()和preempt_disable(),如下图所示:
    在这里插入图片描述
    对于spin_lock_bh(),在UP系统中就退化为禁止软件中断和preempt_disable(),如下图所示:
    在这里插入图片描述
    对于spin_lock_irqsave,它跟spin_lock_irq类似,只不过它是先保存中断状态再禁止中断,如下:

    在这里插入图片描述

    1.3、spinlock在SMP系统中的实现

    在ARMv6及以上的ARM架构中,支持SMP系统。它的spinlock结构体定义如下:
    在这里插入图片描述
    spin_lock函数调用关系如下,核心是arch_spin_lock:
    在这里插入图片描述
    arch_spin_lock代码如下:
    在这里插入图片描述
    假设第1个程序取到了号码,它访问了临界资源后,调用spin_unlock,代码如下:
    在这里插入图片描述
    我借用这篇文章的例子讲解,餐厅里只有一个座位,去吃饭的人都得先取号、等叫号。注意,有2个动作:顾客从取号机取号,电子叫号牌叫号。
    ① 一开始取号机待取号码为0
    ② 顾客A从取号机得到号码0,电子叫号牌显示0,顾客A上座;
    取号机显示下一个待取号码为1。
    ③ 顾客B从取号机得到号码1,电子叫号牌还显示为0,顾客B等待;
    取号机显示下一个待取号码为2。
    ④ 顾客C从取号机得到号码2,电子叫号牌还显示为0,顾客C等待;
    取号机显示下一个待取号码为3。
    ⑤ 顾客A吃完离座,电子叫号牌显示为1,顾客B的号码等于1,他上座;
    ⑥ 顾客B吃完离座,电子叫号牌显示为2,顾客C的号码等于2,他上座;
    在这个例子中有2个号码:取号机显示的“下一个号码”,顾客取号后它会自动加1;电子叫号牌显示“当前号码”,顾客离座后它会自动加1。某个客户手上拿到的号码等于电子叫号牌的号码时,该客户上座。
    在这个过程中,即使顾客B、C同时到店,只要保证他们从取号机上得到的号码不同,他们就不会打架。
    所以,关键点在于:取号机的号码发放,必须互斥,保证客户的号码互不相同。而电子叫号牌上号码的变动不需要保护,只有顾客离开后它才会变化,没人争抢它。

    1.4、信号量semaphore的实现

    1.4.1、semaphore的内核结构体

    信号量的定义及操作函数都在Linux内核文件include\linux\semaphore.h中定义,如下:
    在这里插入图片描述
    初始化semaphore之后,就可以使用down函数或其他衍生版本来获取信号量,使用up函数释放信号量。

    1.4.2、down函数的实现

    如果semaphore中的count大于0,那么down函数就可以获得信号量;否则就休眠。在读取、修改count时,要使用spinlock来实现互斥。
    休眠时,要把当前进程放在semaphore的wait_list链表中,别的进程释放信号量时去wait_list中把进程取出、唤醒。
    代码如下:
    在这里插入图片描述

    1.4.3、 up函数的实现

    如果有其他进程在等待信号量,则count值无需调整,直接取出第1个等待信号量的进程,把信号量给它,共把它唤醒。
    如果没有其他进程在等待信号量,则调整count。
    整个过程需要使用spinlock来保护,代码如下:
    在这里插入图片描述

    1.5、互斥量mutex的实现

    1.5.1、mutex的内核结构体

    mutex的定义及操作函数都在Linux内核文件include\linux\mutex.h中定义,如下:
    在这里插入图片描述
    我们使用mutex的目的一般是用来保护一小段代码,这段代码运行的时间很快。这意味着一个获得mutex的进程,可能很快就会释放掉mutex。

    1.5.2、mutex_lock函数的实现

    1.5.2.1、fastpath

    首先要知道mutex的操作函数中有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。

    在这里插入图片描述
    对于ARMv6以下的架构,使用include/asm-generic/mutex-xchg.h中的__mutex_fastpath_lock函数;对于ARMv6及以上的架构,使用include/asm-generic/mutex-dec.h中的__mutex_fastpath_lock函数。这2个文件中的__mutex_fastpath_lock函数是类似的,mutex-dec.h中的代码如下:
    在这里插入图片描述
    大部分情况下,mutex当前值都是1,所以通过fastpath函数可以非常快速地获得mutex。

    1.5.2.1、slowpath

    如果mutex当前值是0或负数,则需要调用__mutex_lock_slowpath慢慢处理:可能会休眠等待。
    在这里插入图片描述
    __mutex_lock_common函数也是在内核文件kernel/locking/mutex.c中实现的,下面分段讲解。

    ① 分析第一段代码:
    在这里插入图片描述
    ② 分析第二段代码:
    在这里插入图片描述
    ③ 分析第三段代码:
    在这里插入图片描述
    这个wait_list是FIFO(Firt In Firs Out),谁先排队,谁就可以先得到mutex。

    ④ 分析第四段代码:for循环,这是重点
    在这里插入图片描述
    ⑤ 分析第五段代码:收尾工作
    在这里插入图片描述

    1.5.3、mutex_unlock函数的实现

    mutex_unlock函数中也有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。
    代码如下:
    在这里插入图片描述

    1.5.3.1、fastpath

    对于ARMv6以下的架构,使用include/asm-generic/mutex-xchg.h中的__mutex_fastpath_lock函数;对于ARMv6及以上的架构,使用include/asm-generic/mutex-dec.h中的__mutex_fastpath_lock函数。这2个文件中的__mutex_fastpath_lock函数是类似的,mutex-dec.h中的代码如下:
    在这里插入图片描述
    大部分情况下,加1后mutex的值都是1,表示无人等待mutex,所以通过fastpath函数直接增加mutex的count值为1就可以了。
    如果mutex的值加1后还是小于等于0,就表示有人在等待mutex,需要去wait_list把它取出唤醒,这需要用到slowpath的函数:__mutex_unlock_slowpath。

    1.5.3.2 、slowpath

    在这里插入图片描述
    __mutex_unlock_common_slowpath函数代码如下,主要工作就是从wait_list中取出并唤醒第1个进程:
    在这里插入图片描述

  • 相关阅读:
    es示例。。。
    Linux环境下安装jdk8
    第68步 时间序列建模实战:ARIMA建模(Matlab)
    unity 使用模拟器进行Profiler性能调试
    外包干了3个月,技术退步明显。。。。。
    Google Earth Engine(GEE) 03-矢量数据类型
    LM339模块电路故障查询
    中国各省市相关图标
    操作系统学习笔记3 | 操作系统简史
    系统性学习vue-vue3
  • 原文地址:https://blog.csdn.net/afddasfa/article/details/132760929