并发与竞争
原子操作
原子操作只能对整形变量或者位进行保护
原子整形操作
// include/linux/types.h
// 如果使用 32位的 SOC
typedef struct {
int counter;
} atomic_t;
// 如果使用 64位的 SOC
typedef struct {
long long counter;
} atomic64_t;
// 下面所有介绍都基于32位系统说明
// 使用示例
atomic_t a; /*定义a */
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_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,如果结果为负就返回真,否则返回假 |
原子位操作
基本API
| 函数 | 功能 |
|---|---|
| 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位原来的值。 |
原子变量实验
参见代码【07.atomic】工程,该工程在【05.led_pinctrl_gpio】工程上只修改入口参数和open函数
入口函数新增原子变量的初始化
static int __init led_init(void)
{
/*
* 初始化原子变量 初始值为1
*/
atomic_set(&g_led_dev.lock, 1);
/*
* 3:注册自己的 platform_driver 到平台的总线驱动链表里面
* 注册的过程中会根据匹配规则 到【总线平台设备链表】里面找,
* 如果匹配成功会调用驱动里面的platform_driver下的probe函数
*/
printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__);
return platform_driver_register(&led_driver);
}
open函数新增原子变量来防止重复open的问题
static int led_drv_open (struct inode *node, struct file *file)
{
/*
* 如果dec后 通过判断原子变量的值来检查LED有没有被别的应用使用
* 1:lock初始化为1
* 2:第一次open后,lock=0 atomic_dec_and_test(&g_led_dev.lock) 为真,执行下面的hw_led_init
* 3:如果第二次open的时候,atomic_dec_and_test(&g_led_dev.lock)为假(lock=-1), 故而执行atomic_inc(&g_led_dev.lock),把lock=0;且直接返回,提示用户Dev busy
* 4:如果再有应用open的话,和3条一样
*/
if (!atomic_dec_and_test(&g_led_dev.lock)) {
atomic_inc(&g_led_dev.lock); /* 小于0的话就加1,使其原子变量等于0 */
printk("%s %s line %d:Dev busy locking\n", __FILE__, __FUNCTION__, __LINE__);
return -EBUSY; /* LED被使用,返回忙 */
}
hw_led_init(g_led_dev.nd);
file->private_data = &g_led_dev;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
自旋锁
概念:对于自旋锁而言,如果自旋锁正在被线程 A持有,线程 B想要获取自旋锁,那么线程 B就会处于忙循环-旋转-等待状态,线程B不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等待线程A释放锁。
缺点:等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁 的持有时间不能太长。自旋锁适用于短时期的轻量级加锁,如果遇到需要长时间持有锁的场景那就需要换其他的方法了。
注意☀️:自旋锁加锁后。不要调用引起系统睡眠和阻塞的API的函数,否则会可能导致锁死现象 Linux关于自旋锁的结构体定义如下
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
自旋锁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) | 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁 |
使用场景:
一般在线程中使用 spin_lock_irqsave/ spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock 应用举例
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) /* 释放锁 */
}
下半部 (BH)也会竞争共享资源,有些资料也会将下半部叫做底半部。如果要在下半部里面使用自旋锁, API函数:
| 函数 | 功能 |
|---|---|
| void spin_lock_bh(spinlock_t *lock) | 关闭下半部,并获取自旋锁。 |
| void spin_unlock_bh(spinlock_t *lock) | 打开下半部,并释放自旋锁 |
自旋锁实验
参见代码【08.spinlock】工程,该工程在【07.atomic】工程上只修改入口函数、open和close函数
入口函数
static int __init led_init(void) { /* * 初始化自旋锁 */ spin_lock_init(&g_led_dev.lock); g_led_dev.dev_stats = 0; /* * 3:注册自己的 platform_driver 到平台的总线驱动链表里面 * 注册的过程中会根据匹配规则 到【总线平台设备链表】里面找, * 如果匹配成功会调用驱动里面的platform_driver下的probe函数 */ printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__); return platform_driver_register(&led_driver); }
open函数
static int led_drv_open (struct inode *node, struct file *file) { unsigned long flags; spin_lock_irqsave(&g_led_dev.lock, flags); /* 上锁 */ if (g_led_dev.dev_stats) { /* 如果设备被使用了 */ printk("%s %s line %d:dev is busy\n", __FILE__, __FUNCTION__, __LINE__); spin_unlock_irqrestore(&g_led_dev.lock, flags);/* 解锁 */ return -EBUSY; } hw_led_init(g_led_dev.nd); file->private_data = &g_led_dev; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; }
close函数
static int led_drv_close (struct inode *node, struct file *file) { unsigned long flags; struct led_char_dev *dev = file->private_data; /* 关闭驱动文件的时候将dev_stats减1 */ spin_lock_irqsave(&dev->lock, flags); /* 上锁 */ if (dev->dev_stats) { dev->dev_stats--; } spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */ printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); hw_led_reinit(); return 0; }
读写自旋锁
在自旋锁的基础上衍生而来 特点:允许读并发
typedef struct {
arch_rwlock_t raw_lock;
} rwlock_t;
读写锁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 flags) | 将中断状态恢复到以前的状态,并且激活本地中断,释放读锁 |
| void write_lock_bh(rwlock_t *lock) | 关闭下半部,并获取读锁 |
| void write_unlock_bh(rwlock_t *lock) | 打开下半部,并释放读锁。 |
顺序锁
在读写锁的基础上衍生而来允许读写并发,但是在读操作的时候同时进行了写操作,最好重新读取,保证读取的数据正确 最好重新进行读取,保证数据完整性。顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃。 Linux 关于顺序锁的定义
typedef struct {
struct seqcount seqcount;
spinlock_t lock;
} seqlock_t;
顺序锁定义并初始化
| 函数 | 功能 |
|---|---|
| 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) | 读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读 |
自旋锁使用注意事项
因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要 短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处 理方式,比如稍后要讲的信号量和互斥体。
自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API函数,否则的话可能 导致 死锁。
不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就 必须“自旋”, 等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己 把自己锁死了!
在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还 是多核的 SOC,都将其当做多核 SOC来编写驱动程序。
信号量
相比于自旋锁,信号量可以使线程进入休眠状态,但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态 以后会切换线程,切换线程就会有开销。总结一下信号量的特点:
因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合;
因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠;
如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
数型信号量,计数型信号量不能用于互斥访问,因为它允许多个线程同时访问共享资源。线程申请一次信号量就会自减一次知道为0;如果要互斥的访问共享资源那么信号量的值就不能大于1,此时的信号量就是一个二值信号量。 Linux内核使用 semaphore结构体表示信号量,结构体内容如下所示:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
信号量的API函数
| 函数 | 功能 |
|---|---|
| DEFINE_SEAMPHORE(name) | 定义一个信号量,并且设置信号量的值为 |
| 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) | 释放信号量 |
应用举例
struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */
信号量实验
参见代码【09.semaphone】工程,入口函数、open和close函数
入口函数
static int __init led_init(void) { /* * 初始化信号量 */ sema_init(&g_led_dev.sem, 1); /* * 3:注册自己的 platform_driver 到平台的总线驱动链表里面 * 注册的过程中会根据匹配规则 到【总线平台设备链表】里面找, * 如果匹配成功会调用驱动里面的platform_driver下的probe函数 */ printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__); return platform_driver_register(&led_driver); }
open函数
static int led_drv_open (struct inode *node, struct file *file) { /* 获取信号量 */ if (down_interruptible(&g_led_dev.sem)) { /* 获取信号量,进入休眠状态的进程可以被信号打断 */ printk("%s %s line %d:sem get err ,dev busy\n", __FILE__, __FUNCTION__, __LINE__); return -ERESTARTSYS; } #if 0 down(&gpioled.sem); /* 不能被信号打断 */ #endif hw_led_init(g_led_dev.nd); file->private_data = &g_led_dev; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; }
close函数
static int led_drv_close (struct inode *node, struct file *file) { struct led_char_dev *dev = file->private_data; up(&dev->sem); /* 释放信号量,信号量值加1 */ printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); hw_led_reinit(); return 0; }
互斥体
将信号量的值设置为1就可以使用信号量进行互斥访问了,虽然可以通过信号量实现互斥,但是 Linux提供了一个比信号量更专业的机制来进行 互斥,它就是互斥体 mutex。互斥访问表示一次只有一个线程可以访问共享资源,不能递归申 请互斥体。 在我们编写 Linux驱动的时候遇到需要互斥访问的地方建议使用 mutex LINUX关于互斥体的定义如下
struct mutex {
atomic_t count; /* 1: unlocked, 0: locked, negative: locked, possible waiters */
spinlock_t wait_lock;
};
在使用 mutex之前要先定义一个 mutex变量。在使用 mutex的时候要注 意如下几点:
mutex可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
和信号量一样,mutex保护的临界区可以调用引起阻塞的 API函数。
因为一次只有一个线程可以持有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) | 使用此函数获取信号量失败进入休眠以后可以被信号打断。 |
应用举例
struct mutex lock; /* 定义一个互斥体 */
mutex_init(&lock); /* 初始化互斥体 */
mutex_lock(&lock); /* 上锁 */
/* 临界区 */
mutex_unlock(&lock); /* 解锁 */
互斥体实验
参见工程【10.mutex】
入口函数
static int __init led_init(void) { /* * 初始化互斥变量 */ mutex_init(&g_led_dev.lock); /* * 3:注册自己的 platform_driver 到平台的总线驱动链表里面 * 注册的过程中会根据匹配规则 到【总线平台设备链表】里面找, * 如果匹配成功会调用驱动里面的platform_driver下的probe函数 */ printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__); return platform_driver_register(&led_driver); }
open函数
static int led_drv_open (struct inode *node, struct file *file) { /* 获取互斥体,可以被信号打断 */ if (mutex_lock_interruptible(&g_led_dev.lock)) { printk("%s %s line %d:mutex get err dev busy\n", __FILE__, __FUNCTION__, __LINE__); return -ERESTARTSYS; } #if 0 mutex_lock(&g_led_dev.lock); /* 不能被信号打断 */ #endif hw_led_init(g_led_dev.nd); file->private_data = &g_led_dev; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; }
close函数
static int led_drv_close (struct inode *node, struct file *file) { struct led_char_dev *dev = file->private_data; /* 释放互斥锁 */ mutex_unlock(&dev->lock); printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); hw_led_reinit(); return 0; }