嵌入式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个进程:

相关推荐
火车头-1109 小时前
【docker 部署nacos1.4.7】
运维·docker·容器
深藏bIue9 小时前
linux服务器mysql目录下的binlog文件删除
linux·服务器·mysql
虾..10 小时前
Linux 进程状态
linux·运维·服务器
测试者家园10 小时前
DevOps 到底改变了测试什么?
运维·自动化测试·软件测试·devops·持续测试·智能化测试·软件测试和开发
扛枪的书生11 小时前
Linux 通用软件包 AppImage 打包详解
linux
只想安静的写会代码11 小时前
网卡信息查询、配置、常见故障排查
linux·服务器·windows
jiayong2311 小时前
多子系统架构下的Nginx部署策略与最佳实践
运维·nginx·系统架构
皮糖小王子11 小时前
Docker打开本地镜像
运维·docker·容器
偶像你挑的噻13 小时前
9-Linux驱动开发-设备树=>设备树插件实现 RGB 灯驱动
linux·驱动开发·stm32·嵌入式硬件
叫致寒吧13 小时前
Nginx基于域名的虚拟主机实操案例
运维·服务器·nginx