Linux是一个多任务系统,肯定存在多个应用程序访问同一个驱动的情况,若这个驱动中存在全局变量或者其他共享资源,此时便会出现访问冲突的问题。
下面以多任务操作 LED 为例,介绍三种互斥访问共享资源的方法。
目录
一、原子操作变量
1、API 介绍
原子操作变量是一种特殊变量,具备原子性,在操作过程中不会被中断或者其他线程干扰,通常用于多线程环境,用于管理共享数据的并发访问。常见的原子操作变量如下:
原子整型 | 用于对整型变量进行原子操作 |
原子布尔型 | 用于对布尔型变量进行原子操作 |
原子引用 | 用于对对象引用进行原子操作 |
原子数组 | 用于对数组进行原子操作 |
在linux内核中,原子变量相关类型声明在 linux/atomic.h 文件中。对原子变量的操作需要借助linux内核提供的 API。下面以介绍整型原子变量为例,整型原子变量使用 atomic_t 表示。
/** 修改原子变量的值
* @param v 要操作的原子变量
* @param i 修改值
*/
void atomic_set(atomic_t *v, int i);
/** 获取原子变量的值
* @param v 要操作的原子变量
* @return 返回整型原子变量的值
*/
int atomic_read(atomic_t *v);
/** 原子变量 v 加上一个值 i
*/
void atomic_add(int i, atomic_t *v);
/** 原子变量 v 自增 1
*/
void atomic_inc(atomic_t *v);
/** 原子变量 v 减去一个值 i
*/
void atomic_sub(int i, atomic_t *v);
/** 原子变量 v 自减 1
*/
void atomic_dec(atomic_t *v);
2、原子变量控制LED
如果使用原子变量来控制LED,基本步骤如下:
- 在驱动入口函数,初始化原子变量 devNum 为 1(表示可用设备为1)
- 在open操作函数中,如果 devNum = 1,那么 devNum 自减;如果 devNum <= 0,说明设备正在被占用,返回 -1
- 在release操作函数中,devNum 自增 1(需注意自增后的值不能超过1)
static atomic_t devNum;
/* 驱动入口函数 */
atomic_set(&devNum, 1);
/* open操作函数 */
if (atomic_read(&devNum) <= 0)
{
printk("open dev failed, device is ocuppied!");
return -1;
}
else
atomic_dec(&devNum);
/* release操作函数 */
if (atomic_read(&devNum) == 0)
{
atomic_inc(&devNum);
}
二、信号量
信号量相当于一个计数器,统计的是目前临界资源的剩余数量,申请一个信号量时,计数器自减;释放一个信号量时,计数器自增。
1、API 介绍
信号量相关API声明在 linux/semaphore.h 文件中,Linux内核提供了数据类型 struct semaphore 来表示一个信号量。因为信号量相当于一个计数器,所以在使用之前需要先初始化信号量:
/** 信号量初始化
* @param sem 要初始化的信号量
* @param val 信号量初值
*/
void sema_init(struct semaphore *sem, int val);
申请信号量:
/** 申请信号量(申请失败会进入休眠)
* @param sem 要操作的信号量
* @return 成功返回 0,失败返回其他
* 1、EINTR/ERESTARTSYS: 等待过程被中断
* 2、EACCES: 权限不足
* 3、EAGAIN: 资源暂时不可用
*/
// 进入休眠后,无法被信号打断(即便按下 Ctrl + C 也无法关闭进程)
void down(struct semaphore *sem);
// 进入休眠后,可以被信号打断 (只有在被信号打断的时候才会返回,否则会一直阻塞)
int down_interruptible(struct semaphore *sem);
释放信号量:
/** 释放信号量
* @param sem 要操作的信号量
*/
void up(struct semaphore *sem);
2、信号量控制LED
如果使用信号量来控制LED,基本步骤如下:
- 在驱动入口函数,初始化信号量 dev_sem 为 1(表示可用设备为1)
- 在open操作函数中,申请信号量
- 在release操作函数中,释放信号量
static struct semaphore dev_sem;
/* 驱动入口函数 */
sema_init(&dev_sem, 1);
/* open操作函数 */
if (down_interruptible(&dev_sem))
{
printk("open dev failed, device is ocuppied!\n");
return -1;
}
/* release操作函数 */
up(&dev_sem);
三、互斥锁
互斥锁相当于一块令牌,拿到令牌的人才能访问临界资源,没有拿到令牌的只能挂起等待,只有当令牌处于释放状态时才能被各方争抢。
1、API 介绍
互斥锁相关的API声明在 linux/mutex.h 文件中,Linux内核提供了数据类型 struct mutex 来表示一个互斥锁。和信号量一样,在使用互斥锁之前需要先初始化互斥锁:
/** 互斥锁初始化
* @param lock 要初始化的互斥锁
*/
void mutex_init(mutex *lock);
加锁:
/** 加锁(如果没有申请到互斥锁便会进入休眠)
* @param lock 要操作的互斥锁
* @return 成功返回 0,失败返回其他
*/
// 休眠无法被信号打断
void mutex_lock(struct mutex *lock);
// 休眠可以被信号打断 (只有在被信号打断的时候才会返回,否则会一直阻塞)
int mutex_lock_interruptible(struct mutex *lock);
解锁:
/** 解锁
* @param lock 要操作的互斥锁
*/
void mutex_unlock(struct mutex *lock);
2、互斥锁控制LED
如果使用信号量来控制LED,基本步骤如下:
- 在驱动入口函数,初始化互斥锁 dev_mtx
- 在open操作函数中,申请信号量
- 在release操作函数中,释放信号量
static struct mutex dev_mtx;
/* 驱动入口函数 */
mutex_init(&dev_mtx);
/* open操作函数 */
if (mutex_lock_interruptible(&dev_mtx))
{
printk("open dev failed, device is ocuppied!\n");
return -1;
}
/* release操作函数 */
mutex_unlock(&dev_mtx);