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

一、内联汇编

1.1、语法

在这里插入图片描述
内联汇编实现加法
在这里插入图片描述

1.2、同步互斥失败的例子

在这里插入图片描述
进程A在读出valid时发现它是1,减1后为0,这时if不成立;但是修改后的值尚未写回内存;假设这时被程序B抢占,程序B读出valid仍为1,减1后为0,这时if不成立,最后成功返回;轮到A继续执行,它把0值写到valid变量,最后也成功返回。这样程序A、B都成功打开了驱动程序。

1.3、原子操作的原理与使用

所谓“原子操作”就是1.2的操作不会被打断。

原子变量类型如下,实际上就是一个结构体(内核文件include/linux/types.h):

typedef struct{int counter;
}atomic_t;

操作函数如下(下表中v都是atomic_t指针):
在这里插入图片描述

1.4、原子变量的内核实现

atomic_read,atomic_set这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。问题在于atomic_inc这类操作,要读出、修改、写回。
以atomic_inc为例,在atomic.h文件中,如下定义:

#define atomic_inc(v)		atomic_add(1, v)

atomic_add又是怎样实现的呢?用下面这个宏:

ATOMIC_OPS(add, +=, add)

把这个宏展开:

#define ATOMIC_OPS(op, c_op, asm_op)					\ATOMIC_OP(op, c_op, asm_op)					\ATOMIC_OP_RETURN(op, c_op, asm_op)				\ATOMIC_FETCH_OP(op, c_op, asm_op)

以ATOMIC_OP为例在UP系统中的实现

对于ARMv6以下的CPU系统,不支持SMP(单核CPU)。原子变量的操作简单粗暴:关中断。代码如下(arch\arm\include\asm\atomic.h):
在这里插入图片描述
对于ARMv6及以上的CPU(多核),有一些特殊的汇编指令来实现原子操作,不再需要关中断,代码如下(arch\arm\include\asm\atomic.h):
在这里插入图片描述
在ARMv6及以上的架构中,有ldrex、strex指令,ex表示exclude,意为独占地。这2条指令要配合使用,举例如下:
① 读出:ldrex r0, [r1]
读取r1所指内存的数据,存入r0;并且标记r1所指内存为“独占访问”。
如果有其他程序再次执行“ldrex r0, [r1]”,一样会成功,一样会标记r1所指内存为“独占访问”。
② 修改r0的值
③ 写入:strex r2, r0, [r1]:
如果r1的“独占访问”标记还存在,则把r0的新值写入r1所指内存,并且清除“独占访问”的标记,把r2设为0表示成功。
如果r1的“独占访问”标记不存在了,就不会更新内存,并且把r2设为1表示失败。

1.4.1、原子变量使用案例

static atomic_t valid = ATOMIC_INIT(1);static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)
{if (atomic_dec_and_test(&valid)){return 0;}atomic_inc(&valid);return -EBUSY;
}static int gpio_key_drv_close (struct inode *node, struct file *file)
{atomic_inc(&valid);return 0;
}

在这里插入图片描述

1.5、原子位介绍

加粗样式

1.5.1、原子位的内核实现

在ARMv6以下的架构里,不支持SMP系统,关中断。
在这里插入图片描述
在ARMv6及以上的架构中,不需要关中断,有ldrex、strex等指令。
在这里插入图片描述

二、Linux锁的介绍与使用

Linux内核提供了很多类型的锁,它们可以分为两类:
① 自旋锁(spinning lock);
② 睡眠锁(sleeping lock)。

2.1、自旋锁

在这里插入图片描述

2.2、睡眠锁

在这里插入图片描述

2.3、锁的内核函数

2.3.1、自旋锁

spinlock函数在内核文件include\linux\spinlock.h中声明,如下表:
在这里插入图片描述
自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:
在这里插入图片描述

2.3.2、信号量semaphore

semaphore函数在内核文件include\linux\semaphore.h中声明,如下表:
在这里插入图片描述在这里插入图片描述

2.3.3、互斥量mutex

mutex函数在内核文件include\linux\mutex.h中声明,如下表:
在这里插入图片描述在这里插入图片描述

2.3.4、自旋锁使用条件(自旋锁可以用在中断上下文,但是睡眠锁不可以用在中断上下文)

在这里插入图片描述举例简单介绍一下,上表中第一行“IRQ Handler A”和第一列“Softirq A”的交叉点是“spin_lock_irq()”,意思就是说如果“IRQ Handler A”和“Softirq A”要竞争临界资源,那么需要使用“spin_lock_irq()”函数。为什么不能用spin_lock而要用spin_lock_irq?也就是为什么要把中断给关掉?假设在Softirq A中获得了临界资源,这时发生了IRQ A中断,IRQ Handler A去尝试获得自旋锁,这就会导致死锁:所以需要关中断。

2.3.4.1、只在用户上下文加锁

假设只有程序A、程序B会抢占资源,这2个程序都是可以休眠的,所以可以使用信号量,代码如下:

static DEFINE_SPINLOCK(clock_lock); // 或 struct semaphore sem;  sema_init(&sem, 1);
if (down_interruptible(&sem))  // if (down_trylock(&sem))
{/* 获得了信号量 */
}/* 释放信号量 */
up(&sem); 

对于down_interruptible函数,如果信号量暂时无法获得,此函数会令程序进入休眠;别的程序调用up()函数释放信号量时会唤醒它。
在down_interruptible函数休眠过程中,如果进程收到了信号,则会从down_interruptible中返回;对应的有另一个函数down,在它休眠过程中会忽略任何信号。
也可以使用mutex,代码如下:

static DEFINE_MUTEX(mutex);  //或 static struct mutex mutex; mutex_init(&mutex);
mutex_lock(&mutex);
/* 临界区 */
mutex_unlock(&mutex);

注意:一般来说在同一个函数里调用mutex_lock或mutex_unlock,不会长期持有它。这只是惯例,如果你使用mutex来实现驱动程序只能由一个进程打开,在drv_open中调用mutex_lock,在drv_close中调用mutex_unlock,这也完全没问题。

2.3.4.2、在用户上下文与Softirqs、Tasklet、Timer之间加锁

static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock_bh(&lock);  //禁止软中断
/* 临界区 */
spin_unlock_bh(&lock);

2.3.4.3、在Softirq之间加锁

在Softirq之间(含timer、tasklet、相同的Softirq、不同的Softirq),都可以使用spin_lock()、spin_unlock()来访问临界区。

2.3.4.4、硬中断上下文

示例代码如下:

static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock(&lock);
/* 临界区 */
spin_unlock(&lock);

示例代码如下:

unsigned long flags;
static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock_irqsave(&lock, flags);
/* 临界区 */
spin_unlock_irqrestore(&lock, flags);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/106624.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Newman+Jenkins实现接口自动化测试

一、是什么Newman Newman就是纽曼手机这个经典牌子,哈哈,开玩笑啦。。。别当真,简单地说Newman就是命令行版的Postman,查看官网地址。 Newman可以使用Postman导出的collection文件直接在命令行运行,把Postman界面化运…

tkinter树形图组件

文章目录 初步回调函数绑定滚动条 初步 Treeview是ttk中的树形表组件,功能十分强大,非常适用于系统路径的表达。为了知道属性图到底是什么,下面先做个最简单的树形图 其代码如下 import tkinter as tk from tkinter import ttkdct {"…

【Proteus仿真】【STM32单片机】安全驾驶检测系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 系统运行后,LCD1602显示传感器检测的酒精浓度和CO2值,以及阈值;若按下K3键进入阈值设置模式,默认以酒精阈值开始位置闪烁;再次按下K3键则进入CO…

无涯教程-JavaScript - NPV函数

描述 NPV函数通过使用折现率以及一系列未来付款(负值)和收入(正值)来计算投资的净现值。 语法 NPV (rate,value1,[value2],...)争论 Argument描述Required/OptionalRateThe rate of discount over the length of one period.RequiredValue11 to 254 arguments representing…

UDP的可靠性传输

UDP系列文章目录 第一章 UDP的可靠性传输-理论篇(一) 第二章 UDP的可靠性传输-理论篇(二) 文章目录 UDP系列文章目录前言1.TCP 和UDP格式对比2.UDP分片原理3.UDP 传输层应该注意问题4.MTU5.UDP 分片机制设计重点 一、ARQ协议什么…

2023年中国电影行业研究报告

第一章 行业概况 1.1 定义 电影行业是一门涉及电影制作、发行、放映和推广的综合艺术和商业活动。它结合了戏剧、音乐、舞蹈、绘画等多种艺术形式,通过视觉和听觉的方式向观众展示故事和情感。 电影不仅仅是一门艺术,更是一项复杂的商业运作。它涵盖了…

docker 生成镜像的几个问题

docker 生成镜像的几个问题 根据jdk8.tar.gz 打包Jdk8 镜像失败运行镜像报错差不多是网络ip错误,在网上说重启docker即可解决运行mysql5.7.25 镜像失败向daemon.json文件添加内容导致docker重启失败docker run 命令常用参数根据jdk8.tar.gz 打包Jdk8 镜像失败 首选做准备工作…

Go基础16-defer的运作机制及常见用法

defer的运作离不开函数,这至少有两层含义: ● 在Go中,只有在函数和方法内部才能使用defer; ● defer关键字后面只能接函数或方法,这些函数被称为deferred函数。defer将它们注册到其所在goroutine用于存放deferred函数…

时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化

时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化 目录 时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 RIME-VMD【23年新算法】霜冰优化算法优化VMD变分模态分…

面向对象技术

面向对象技术 考情分析面向对象基本概念面向对象分析面向对象的设计原则面向对象测试统一建模语言事务关系图 考情分析 设计模式在新版教材被删除了 考察偏向面向对象的基本概念和UML建模 但是设计模型在案例和论文题目中出现 面向对象基本概念 c b a 面向对象分析 记忆面向对…

分布式、锁、延时任务

1. redission redission 原理 Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案) 2.zk 2.1 指令 ls / / 下有哪些子节点 get /zookeeper 查看某个子节点内容 create /aa “test” delete /aa set /aa “test01” 2.2 创建节点 模式 默认创建永久 create -e …

Linux命令200例:write用于向特定用户或特定终端发送信息

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌。CSDN专家博主,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师&#xff0…