Linux嵌入式驱动开发-并发与竞争

news/2025/2/23 15:30:18

文章目录

  • 原子操作
    • 原子整型操作 API 函数
    • Example
    • 原子位操作 API 函数
  • 自旋锁
    • Warning
    • 自旋锁 API 函数
    • Example:
  • 读写自旋锁
    • 读写锁API 函数
  • 顺序锁
    • 顺序锁API 函数
  • 信号量
    • 特点
    • 信号量 API 函数
    • Example
  • 互斥体
    • Warning
    • 互斥体 API 函数
    • Example

原子操作

Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量,此结构体定义在 include/linux/types.h 文件中。

原子整型操作 API 函数

函数描述
ATOMIC_INIT(int i)定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v)读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i)向 v 写入 i 值。
void atomic_add(int i, atomic_t *v)给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v)从 v 减去 i 值。
void atomic_inc(atomic_t *v)给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v)从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v)从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v)给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v)从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v)从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v)给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v)给 v 加 i,如果结果为负就返回真,否则返回假

如果使用 64 位的 SOC 的话,就要用到 64 位的原子变量,API 函数用法一样,只是将“atomic_”前缀换为“atomic64_”,将 int 换为 long long。如果使用的是 64 位的 SOC,那么就要使用 64 位的原子操作函数。

Example

atomic_t v = ATOMIC_INIT(0); /* 定义并初始化原子变零 v=0 */
atomic_set(&v, 10); /* 设置 v=10 */
atomic_read(&v); /* 读取 v 的值,肯定是 10 */
atomic_inc(&v); /* v 的值加 1,v=11 */

原子位操作 API 函数

原子位操作不像原子整形变量那样有个 atomic_t 的数据结构,原子位操作是直接对内存进行操作。

函数描述
void set_bit(int nr, void *p)将 p 地址的第 nr 位置 1。
void clear_bit(int nr, void *p)将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p)将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p)获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p)将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p)将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p)将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

自旋锁

等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长,自旋锁适用于短时期的轻量级加锁。

Warning

为避免中断抢走CPU使用权,获取锁之前需要关闭本地中断

  1. 因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式。

  2. 自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。

  3. 不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了!

  4. 在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。

自旋锁 API 函数

函数描述
DEFINE_SPINLOCK(spinlock_t lock)定义并初始化一个自旋变量。
int spin_lock_init(spinlock_t *lock)初始化自旋锁。
void spin_lock(spinlock_t *lock)获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock)释放指定的自旋锁。
int spin_trylock(spinlock_t *lock)尝试获取指定的自旋锁,如果没有获取到就返回 0。
int spin_is_locked(spinlock_t *lock)检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0。
void spin_lock_irq(spinlock_t *lock)禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock)激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。
void spin_lock_bh(spinlock_t *lock)关闭下半部,并获取自旋锁。
void spin_unlock_bh(spinlock_t *lock)打开下半部,并释放自旋锁。

一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock,

Example:

DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */

/* 线程 A */
void functionA()
{
    unsigned long flags;            /* 中断状态*/
    spin_lock_irqsave(&lock, flags) /* 获取锁*/
    /* 临界区 */
    spin_unlock_irqrestore(&lock, flags) /* 释放锁*/
}

/* 中断服务函数 */
void irq()
{
    spin_lock(&lock) /* 获取锁*/
    /* 临界区 */
    spin_unlock(&lock) /* 释放锁*/
}

读写自旋锁

读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个写操作,也就是只能一个线程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,可以进行并发的读操作。

读写锁API 函数

函数描述
DEFINE_RWLOCK(rwlock_t lock)定义并初始化读写锁。
void rwlock_init(rwlock_t *lock)初始化读写锁。
读锁读锁
void read_lock(rwlock_t *lock)获取读锁。
void read_unlock(rwlock_t *lock)释放读锁。
void read_lock_irq(rwlock_t *lock)禁止本地中断,并且获取读锁。
void read_unlock_irq(rwlock_t *lock)打开本地中断,并且释放读锁。
void read_lock_irqsave(rwlock_t *lock, unsigned long flags)保存中断状态,禁止本地中断,并获取读锁。
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放读锁。
void read_lock_bh(rwlock_t *lock)关闭下半部,并获取读锁。
void read_unlock_bh(rwlock_t *lock)打开下半部,并释放读锁。
写锁写锁
void write_lock(rwlock_t *lock)获取写锁。
void write_unlock(rwlock_t *lock)释放写锁。
void write_lock_irq(rwlock_t *lock)禁止本地中断,并且获取写锁。
void write_unlock_irq(rwlock_t *lock)打开本地中断,并且释放写锁。
void write_lock_irqsave(rwlock_t *lock, unsigned long flags)保存中断状态,禁止本地中断,并获取写锁。
void write_unlock_irqrestore(rwlock_t *lock, unsigned long lags)将中断状态恢复到以前的状态,并且激活本地中断,释放读锁。
void write_lock_bh(rwlock_t *lock)关闭下半部,并获取读锁。
void write_unlock_bh(rwlock_t *lock)打开下半部,并释放读锁。

顺序锁

顺序锁可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取,保证数据完整性。

顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃。

顺序锁API 函数

函数描述
DEFINE_SEQLOCK(seqlock_t sl)定义并初始化顺序锁。
void seqlock_ini seqlock_t *sl)初始化顺序锁。
顺序锁写操作顺序锁写操作
void write_seqlock(seqlock_t *sl)获取写顺序锁。
void write_sequnlock(seqlock_t *sl)释放写顺序锁。
void write_seqlock_irq(seqlock_t *sl)禁止本地中断,并且获取写顺序锁
void write_sequnlock_irq(seqlock_t *sl)打开本地中断,并且释放写顺序锁。
void write_seqlock_irqsave(seqlock_t *sl, unsigned long flags)保存中断状态,禁止本地中断,并获取写顺序锁。
void write_sequnlock_irqrestore(seqlock_t *sl, unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放写顺序锁。
void write_seqlock_bh(seqlock_t *sl)关闭下半部,并获取写读锁。
void write_sequnlock_bh(seqlock_t *sl)打开下半部,并释放写读锁。
顺序锁读操作顺序锁读操作
unsigned read_seqbegin(const seqlock_t *sl)读单元访问共享资源的时候调用此函数,此函数会返回顺序锁的顺序号。
unsigned read_seqretry(const seqlock_t *sl, unsigned start)读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读

信号量

相比于自旋锁,信号量可以使线程进入休眠状态,使用信号量会提高处理器的使用效率,毕竟不用一直在那里“自旋”等待,但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程就会有开销。

特点

  1. 信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。

  2. 信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。

  3. 如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

信号量 API 函数

函数描述
DEFINE_SEAMPHORE(name)定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val)初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem);尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem)释放信号量

Example

struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem);   /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */

互斥体

Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex。互斥访问表示一次只有一个线程可以访问共享资源,不能递归申请互斥体。

Warning

  1. mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁

  2. 和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。

  3. 因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。

互斥体 API 函数

函数描述
DEFINE_MUTEX(name)定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock)初始化 mutex。
void mutex_lock(struct mutex *lock)获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex *lock)释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock)尝试获取 mutex,如果成功就返回 1,如果失败就返回 0。
int mutex_is_locked(struct mutex *lock)判断 mutex 是否被获取,如果是的话就返回1,否则返回 0。
int mutex_lock_interruptible(struct mutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。

Example

struct mutex lock; /* 定义一个互斥体 */
mutex_init(&lock); /* 初始化互斥体 */
mutex_lock(&lock); /* 上锁 */
    /* 临界区 */
mutex_unlock(&lock); /* 解锁 */

http://www.niftyadmin.cn/n/4943129.html

相关文章

clickhouse-监控配置

一、概述 监控是运维的一大利器,要想运维好clickhouse,首先就要对其进行监控,clickhouse有几种监控数据的方式,一种是系统本身监控,一种是通过exporter来监控,下面分别描述一下 二、系统自带监控 我下面会对监控做一…

【ARM】基于platform总线驱动模板

❤️作者主页:凉开水白菜 ❤️作者简介:共同学习,互相监督,热于分享,多加讨论,一起进步! ❤️点赞 👍 收藏 ⭐再看,养成习惯 订阅的粉丝可通过PC端文末加我微信,可对文章的内容进行一对一答疑! Platform总线驱动模板 驱动程序❤基于设备树的platform总线模型代码创建…

【k8s】基于Prometheus监控Kubernetes集群安装部署

目录 基于Prometheus监控Kubernetes集群安装部署 一、环境准备 二、部署kubernetes集群 三、部署Prometheus监控平台 四、部署Grafana服务 五、grafana web操作 基于Prometheus监控Kubernetes集群安装部署 一、环境准备 IP地址 主机名 组件 192.168.100.131 k8s-ma…

【尚硅谷】第03章:随堂复习与企业真题(流程控制语句)

来源:尚硅谷Java零基础全套视频教程(宋红康2023版,java入门自学必备) 基本都是宋老师发的资料里面的内容,只不过补充几个资料里没直接给出答案的问题的答案。 不想安装markdown笔记的app所以干脆在这里发一遍。 第03章:随堂复习…

关于 LLM 和图数据库、知识图谱的那些事

本文整理自 NebulaGraph 布道师 wey 在「夜谈 LLM」主题分享上的演讲,主要包括以下内容: 背景 LLMRAGGraph 知识抽取Text2CypherGraph RAG未来规划 技术背景 LLM 是什么 这里简单、快速地介绍下大语言模型:从 GPT-2 开始,到后…

谈谈网络协议的定义、组成和重要性

个人主页:insist--个人主页​​​​​​ 本文专栏:网络基础——带你走进网络世界 本专栏会持续更新网络基础知识,希望大家多多支持,让我们一起探索这个神奇而广阔的网络世界。 目录 一、网络协议的定义 二、网络协议的组成 1、…

服务器卡顿了该如何处理

服务器卡顿了该如何处理 当Windows系统的服务器出现卡顿问题时,以下是一些常见的故障排除步骤: 1.检查网络连接:确保服务器的网络连接正常。检查网络设备、交换机、防火墙等设备,确保它们正常运行。尝试通过其他计算机访问服务器…

【虫洞攻击检测】使用多层神经网络的移动自组织网络中的虫洞攻击检测研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…