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

一、自旋锁spinlock的实现

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

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

1.1、自旋锁的内核结构体

嵌入式Linux驱动开发(同步与互斥专题)(二)_第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函数定义如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第2张图片
对于spin_lock_irq(),在UP系统中就退化为local_irq_disable()和preempt_disable(),如下图所示:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第3张图片
对于spin_lock_bh(),在UP系统中就退化为禁止软件中断和preempt_disable(),如下图所示:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第4张图片
对于spin_lock_irqsave,它跟spin_lock_irq类似,只不过它是先保存中断状态再禁止中断,如下:

嵌入式Linux驱动开发(同步与互斥专题)(二)_第5张图片

1.3、spinlock在SMP系统中的实现

在ARMv6及以上的ARM架构中,支持SMP系统。它的spinlock结构体定义如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第6张图片
spin_lock函数调用关系如下,核心是arch_spin_lock:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第7张图片
arch_spin_lock代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第8张图片
假设第1个程序取到了号码,它访问了临界资源后,调用spin_unlock,代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第9张图片
我借用这篇文章的例子讲解,餐厅里只有一个座位,去吃饭的人都得先取号、等叫号。注意,有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中定义,如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第10张图片
初始化semaphore之后,就可以使用down函数或其他衍生版本来获取信号量,使用up函数释放信号量。

1.4.2、down函数的实现

如果semaphore中的count大于0,那么down函数就可以获得信号量;否则就休眠。在读取、修改count时,要使用spinlock来实现互斥。
休眠时,要把当前进程放在semaphore的wait_list链表中,别的进程释放信号量时去wait_list中把进程取出、唤醒。
代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第11张图片

1.4.3、 up函数的实现

如果有其他进程在等待信号量,则count值无需调整,直接取出第1个等待信号量的进程,把信号量给它,共把它唤醒。
如果没有其他进程在等待信号量,则调整count。
整个过程需要使用spinlock来保护,代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第12张图片

1.5、互斥量mutex的实现

1.5.1、mutex的内核结构体

mutex的定义及操作函数都在Linux内核文件include\linux\mutex.h中定义,如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第13张图片
我们使用mutex的目的一般是用来保护一小段代码,这段代码运行的时间很快。这意味着一个获得mutex的进程,可能很快就会释放掉mutex。

1.5.2、mutex_lock函数的实现

1.5.2.1、fastpath

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

嵌入式Linux驱动开发(同步与互斥专题)(二)_第14张图片
对于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中的代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第15张图片
大部分情况下,mutex当前值都是1,所以通过fastpath函数可以非常快速地获得mutex。

1.5.2.1、slowpath

如果mutex当前值是0或负数,则需要调用__mutex_lock_slowpath慢慢处理:可能会休眠等待。
嵌入式Linux驱动开发(同步与互斥专题)(二)_第16张图片
__mutex_lock_common函数也是在内核文件kernel/locking/mutex.c中实现的,下面分段讲解。

① 分析第一段代码:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第17张图片
② 分析第二段代码:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第18张图片
③ 分析第三段代码:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第19张图片
这个wait_list是FIFO(Firt In Firs Out),谁先排队,谁就可以先得到mutex。

④ 分析第四段代码:for循环,这是重点
嵌入式Linux驱动开发(同步与互斥专题)(二)_第20张图片
⑤ 分析第五段代码:收尾工作
嵌入式Linux驱动开发(同步与互斥专题)(二)_第21张图片

1.5.3、mutex_unlock函数的实现

mutex_unlock函数中也有fastpath、slowpath两条路径(快速、慢速):如果fastpath成功,就不必使用slowpath。
代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第22张图片

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中的代码如下:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第23张图片
大部分情况下,加1后mutex的值都是1,表示无人等待mutex,所以通过fastpath函数直接增加mutex的count值为1就可以了。
如果mutex的值加1后还是小于等于0,就表示有人在等待mutex,需要去wait_list把它取出唤醒,这需要用到slowpath的函数:__mutex_unlock_slowpath。

1.5.3.2 、slowpath

嵌入式Linux驱动开发(同步与互斥专题)(二)_第24张图片
__mutex_unlock_common_slowpath函数代码如下,主要工作就是从wait_list中取出并唤醒第1个进程:
嵌入式Linux驱动开发(同步与互斥专题)(二)_第25张图片

你可能感兴趣的:(Linux,驱动以及裸机,linux,驱动开发,运维)