
一、同步与互斥的失败例子
- 同步:等待
- 互斥:先到先用,不能同时用
失败例子1:
此时有2个程序,程序A先执行到11行之前,程序B抢占了CPU资源,此时valid还为1,则执行11行改为0,该程序A执行时,也返回了0,则2个程序同时执行了驱动。
01 staticint valid = 1;
02
03 staticssize_t gpio_key_drv_open (struct inode *node, struct file *file)
04 {
05 if (!valid)
06 {
07 return -EBUSY;
08 }
09 else
10 {
11 valid = 0;
12 }
13
14 return 0; //成功
15 }
16
17 staticint gpio_key_drv_close (struct inode *node, struct file *file)
18 {
19 valid = 1;
20 return 0;
21 }
22
失败例子2:
进程A在读出valid时发现它是1,减1后为0,这时if不成立;但是修改后的值尚未写回内存;
假设这时被程序B抢占,程序B读出valid仍为1,减1后为0,这时if不成立,最后成功返回;
轮到A继续执行,它把0值写到valid变量,最后也成功返回。这样程序A、B都成功打开了驱动程序
01 staticint valid = 1;
02
03 staticssize_t gpio_key_drv_open (struct inode *node, struct file *file)
04 {
05 if (--valid)
06 {
07 valid++;
08 return -EBUSY;
09 }
10 return 0;
11 }
12
13 staticint gpio_key_drv_close (struct inode *node, struct file *file)
14 {
15 valid = 1;
16 return 0;
17 }
18
失败例子3:
第06行直接关中断,这样别的线程、中断都不能来打扰本线程了,在它读取、修改valid变量的过程中无人打扰。
对于单CPU核的系统上述代码是没问题的;但是对于SMP系统,你只能关闭当前CPU核的中断,别的CPU核还可以运行程序,它们也可以来执行这个函数,同样导致问题,假设CPU0上进程A、CPU1上进程B同时运行到上图中读出valid的地方,它们同时发现valid都是1,减减后都等于0,在第07行判断条件都不成立,所以在第14行都可以返回0,都可以成功打开驱动。
01 staticint valid = 1;
02
03 staticssize_t gpio_key_drv_open (struct inode *node, struct file *file)
04 {
05 unsigned long flags;
06 raw_local_irq_save(flags); // 关中断
07 if (--valid)
08 {
09 valid++;
10 raw_local_irq_restore(flags); // 恢复之前的状态
11 return -EBUSY;
12 }
13 raw_local_irq_restore(flags); // 恢复之前的状态
14 return 0;
15 }
16
17 staticint gpio_key_drv_close (struct inode *node, struct file *file)
18 {
19 valid = 1;
20 return 0;
21 }
原子操作
介绍:
所谓“原子操作”就是这个操作不会被打断。Linux有2种原子操作:原子变量、原子位。
原子变量的操作函数在Linux内核文件: arch\arm\include\asm\atomic.h 中。
原子操作函数:
函数名 | 作用 |
atomic_read(v) | 读出原子变量的值,即v->counter |
atomic_set(v,i) | 设置原子变量的值,即v->counter = i |
atomic_inc(v) | v->counter++ |
atomic_dec(v) | v->counter– |
atomic_add(i,v) | v->counter += i |
atomic_sub(i,v) | v->counter -= i |
atomic_inc_and_test(v) | 先加1,再判断新值是否等于0;等于0的话,返回值为1 |
atomic_dec_and_test(v) | 先减1,再判断新值是否等于0;等于0的话,返回值为1 |
原子操作使用案例:
// 定义一个原子变量valid,用于同步访问,初始化为1,表示设备可用
static atomic_t valid = ATOMIC_INIT(1);
/**
* 设备打开函数
* @param node 设备在文件系统中的inode结构
* @param file 设备对应的file结构
* @return 如果设备可用,返回0,否则返回-EBUSY错误码
*
* 该函数通过原子操作来判断设备是否已被打开,如果可用,则允许打开,否则返回错误。
*/
static ssize_t gpio_key_drv_open(struct inode *node, struct file *file)
{
// 如果设备可用,则递减valid值并检查是否为0,如果是,则设备可以被打开
if (atomic_dec_and_test(&valid))
{
return 0;
}
// 如果设备已被打开,则增加valid值,表示设备被占用,并返回错误
atomic_inc(&valid);
return -EBUSY;
}
static int gpio_key_drv_close(struct inode *node, struct file *file)
{
// 设备关闭时,增加valid值,表示设备变为可用
atomic_inc(&valid);
return 0;
}
原子位操作函数:
能操作原子变量,再去操作其中的某一位;
原子位的操作函数在Linux内核文件arch\arm\include\asm\bitops.h中,下表中p是一个unsigned long指针。
函数名 | 作用 |
set_bit(nr,p) | 设置(*p)的bit nr为1 |
clear_bit(nr,p) | 清除(*p)的bit nr为0 |
change_bit(nr,p) | 改变(*p)的bit nr,从1变为0,或是从0变为1 |
test_and_set_bit(nr,p) | 设置(*p)的bit nr为1,返回该位的老值 |
test_and_clear_bit(nr,p) | 清除(*p)的bit nr为0,返回该位的老值 |
test_and_change_bit(nr,p) | 改变(*p)的bit nr,从1变为0,或是从0变为1;返回该位的老值 |
Linux锁
锁的类型:
① 自旋锁(spinning lock);
② 睡眠锁(sleeping lock)。
自旋锁:
简单地说就是无法获得锁时,不会休眠,会一直循环等待。有这些自旋锁:
自旋锁 | 描述 |
raw_spinlock_t | 原始自旋锁 |
bit spinlocks | 位自旋锁(似乎没什么意义) |
spinlock函数在内核文件include\linux\spinlock.h中声明,如下表:
函数名 | 作用 |
spin_lock_init(_lock) | 初始化自旋锁为unlock状态 |
void spin_lock(spinlock_t *lock) | 获取自旋锁(加锁),返回后肯定获得了锁 |
int spin_trylock(spinlock_t *lock) | 尝试获得自旋锁,成功获得锁则返回1,否则返回0 |
void spin_unlock(spinlock_t *lock) | 释放自旋锁,或称解锁 |
int spin_is_locked(spinlock_t *lock) | 返回自旋锁的状态,已加锁返回1,否则返回0 |
自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:
后缀 | 描述 |
_bh() | 加锁时禁止下半部(软中断),解锁时使能下半部(软中断) |
_irq() | 加锁时禁止中断,解锁时使能中断 |
_irqsave/restore() | 加锁时禁止并中断并记录状态,解锁时恢复中断为所记录的状态 |
睡眠锁:
简单地说就是无法获得锁时,当前线程就会休眠。有这些休眠锁:
休眠锁 | 描述 |
mutex | mutual exclusion,彼此排斥,即互斥锁(后面讲解) |
rt_mutex | |
semaphore | 信号量、旗语(后面讲解) |
rw_semaphore | 读写信号量,读写互斥,但是可以多人同时读 |
ww_mutex | |
percpu_rw_semaphore | 对rw_semaphore的改进,性能更优 |
信号量 semaphore:
程序A在等待数据──想获得锁,程序B产生数据后释放锁,这会唤醒A来读取数据。semaphore的锁定与释放,并不限定为同一个进程。
semaphore函数在内核文件include\linux\semaphore.h中声明,如下表:
函数名 | 作用 |
DEFINE_SEMAPHORE(name) | 定义一个struct semaphore name结构体,count值设置为1 |
void sema_init(struct semaphore *sem, int val) | 初始化semaphore |
void down(struct semaphore *sem) | 获得信号量,如果暂时无法获得就会休眠返回之后就表示肯定获得了信号量在休眠过程中无法被唤醒,即使有信号发给这个进程也不处理 |
int down_interruptible(struct semaphore *sem) | 获得信号量,如果暂时无法获得就会休眠,休眠过程有可能收到信号而被唤醒,要判断返回值:0:获得了信号量-EINTR:被信号打断 |
int down_killable(struct semaphore *sem) | 跟down_interruptible类似,down_interruptible可以被任意信号唤醒,但down_killable只能被“fatal signal”唤醒,返回值:0:获得了信号量-EINTR:被信号打断 |
int down_trylock(struct semaphore *sem) | 尝试获得信号量,不会休眠,返回值:0:获得了信号量1:没能获得信号量 |
int down_timeout(struct semaphore *sem, long jiffies) | 获得信号量,如果不成功,休眠一段时间返回值:0:获得了信号量-ETIME:这段时间内没能获取信号量,超时返回down_timeout休眠过程中,它不会被信号唤醒 |
void up(struct semaphore *sem) | 释放信号量,唤醒其他等待信号量的进程 |
互斥量 mutex:
个mutex只能在进程上下文中使用:谁给mutex加锁,就只能由谁来解锁。
mutex函数在内核文件include\linux\mutex.h中声明,如下表:
函数名 | 作用 |
mutex_init(mutex) | 初始化一个struct mutex指针 |
DEFINE_MUTEX(mutexname) | 初始化struct mutex mutexname |
int mutex_is_locked(struct mutex *lock) | 判断mutex的状态1:被锁了(locked)0:没有被锁 |
void mutex_lock(struct mutex *lock) | 获得mutex,如果暂时无法获得,休眠返回之时必定是已经获得了mutex |
int mutex_lock_interruptible(struct mutex *lock) | 获得mutex,如果暂时无法获得,休眠;休眠过程中可以被信号唤醒,返回值:0:成功获得了mutex-EINTR:被信号唤醒了 |
int mutex_lock_killable(struct mutex *lock) | 跟mutex_lock_interruptible类似,mutex_lock_interruptible可以被任意信号唤醒,但mutex_lock_killable只能被“fatal signal”唤醒,返回值:0:获得了mutex-EINTR:被信号打断 |
int mutex_trylock(struct mutex *lock) | 尝试获取mutex,如果无法获得,不会休眠,返回值:1:获得了mutex,0:没有获得注意,这个返回值含义跟一般的mutex函数相反, |
void mutex_unlock(struct mutex *lock) | 释放mutex,会唤醒其他等待同一个mutex的线程 |
int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock) | 让原子变量的值减1,如果减1后等于0,则获取mutex,返回值:1:原子变量等于0并且获得了mutex0:原子变量减1后并不等于0,没有获得mutex |
semaphore和mutex区别:
semaphore | mutex | |
几把锁 | 任意,可设置 | 1 |
谁能解锁 | 别的程序、中断等都可以 | 谁加锁,就得由谁解锁 |
多次解锁 | 可以 | 不可以,因为只有1把锁 |
循环加锁 | 可以 | 不可以,因为只有1把锁 |
任务在持有锁的期间可否退出 | 可以 | 不建议,容易导致死锁 |
硬件中断、软件中断上下文中使用 | 可以 | 不可以 |
评论