Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
提到锁”,可能想到的更多的是限制。现实中,生活中锁也 存在于身边的方方面面。正所谓没有规矩不成方圆, 没有身边的这些锁,这些限制,社会将会变得无序、混乱。为了规范这些无序和混乱,就得根据实际情况制定规则制度甚至法律来进行束缚和限制。正如本篇笔记要讲解的内容一样,Linux内核采用一定的方式方法(函数)制定了这些所谓的规则,才能使程序变得更流畅。
本篇笔记主要学习Linux处理并发与竞争的机制。主要内容包括原子操作、自旋锁、信号量和互斥体。
一、并发与竞争
1.简介
Linux是多任务操作系统,存在多个任务同时访问同一内存区域,造成这些任务会相互覆盖这段内存中的数据,从而造成内存数据的混乱。多个线程同时操作临界区就会发生竞争(竞争)并发导致。
2.保护什么
保护共享资源(数据)。
二、原子操作
1.简介
原子操作就是指不能再进一步分割的操作,用于变量和位操作。
2.原子整形操作API函数
函数 | 描述 |
ATOMIC_INIT(init i) | 定义原子变量的时候对其进行初始化。 |
int atomic_read(atomic_t *v) | 读取v的值,并返回。 |
void atomic_set(atomic_t *v,int i) | 向v写入i值。 |
void atomic_add(atomic_t *v) | 给v加上i值。 |
void atomic_sub(atomic_t *v) | 给v减去i值。 |
void atomic_inc(atomic_t *v) | 自增 |
void atomic_dec(atomic_t *v) | 自减 |
void atomic_inc_return(atomic_t *v) | 自增并返回v值 |
void atomic_dec_return(atomic_t *v) | 自减并返回v值 |
int atomic_sub_and_test(int i,atomic_t *v) | 从v减i,如果结果为0就返回真,否则返回为假 |
int atomic_dec_and_test(int i,atomic_t *v) | 从v减1,如果结果为0就返回真,否则返回为假 |
int atomic_add_and_test(int i,atomic_t *v) | 从v加i,如果结果为0就返回真,否则返回为假 |
int atomic_inc_and_test(int i,atomic_t *v) | 从v加1,如果结果为0就返回真,否则返回为假 |
3.原子位操作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(int nr,void *p) | 将p地址的nr位置1,并返回nr位原来的值 |
int test_and_clear(int nr,void *p) | 将p地址的nr位清零,并返回nr位原来的值 |
int test_and_change(int nr,void *p) | 将p地址的第nr位翻转,并返回nr位原来的值 |
三、自旋锁
1.简介
当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放拥有的锁,那么其他的线程就不能获取此锁。
自旋---原地打转
缺点:时间短。
使用结构体spinlock_t表示自旋锁。
2.自旋锁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 |
自旋锁适用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能引起睡眠和阻塞的API函数,否则会导致死锁的发生。
自旋锁会自动禁止抢占。
中断里面可以使用自旋锁,但在中断里面使用自旋锁的时候,在获取之前一定要先禁止本地中断。相应的API函数如下:
函数 | 描述 |
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags) | 保存中断状态,禁止本地中断,并获取自旋锁。 |
void spin_unlock_irqrestore(spinlock_t *lock,unsigned long flags) | 将中断状态恢复打以前状态,并且激活本地中断,释放自旋锁。 |
下半部也会竞争共享资源,下半部使用自旋锁的API函数有:
函数 | 描述 |
void spin_lock_bh(spinlock_t *lock) | 关闭下半部,并获取自旋锁 |
void spin_unlock_bh(spinlock_t lock) | 打开下半部,并释放自旋锁 |
3.其他类型的锁
实际应用中用的不多,多的是在Linux内核中使用。
1)、读写自旋锁
2)、顺序锁
4.使用注意事项
1)、持有时间不能太长
2)、自旋锁保护的临界区内部能调用任何可能导致线程休眠的API函数,否则可能会导致死锁。
3)、不能递归申请自旋锁
4)、多核SOC编写程序
四、信号量
1.简介
信号量常用于对共享资源的访问。
信号量可以使线程进入休眠状态。
信号量的开销比自旋锁要大。
信号量特点:
1)、适用于占用资源比较长的场所。
2)、不能用于中断中。
通过信号量控制访问资源的线程数。
不能用于互斥访问。
2.信号量API函数
使用semaphore结构体表示信号量。相关的API函数如下:
函数 | 描述 |
DEFINE_SEMAPHORE(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) | 获取信号量,进入休眠以后是可以被信号打断的。 |
void up(struct semaphore *sem) | 释放信号量 |
五、互斥体
1.简介
一次只有一个线程访问共享资源,不能递归申请。需要互斥访问的时候建议使用mutex.
注意以下几点:
1)、不能在中断中使用。
2)、保护的临界区可以调用引起阻塞的API函数。
3)、必须由mutex的持有者释放mutex。mutex不能递归上锁和解锁。
2.互斥体API函数
相关的API函数有:
函数 | 描述 |
DEFINE_MUXTEX(name) | 定义并初始化一个mutex变量 |
void mutex_init(mutex *lock) | 初始化mutex |
void mutex_lock(struct mutex *lock) | 获取mutex 上锁 获取不到就休眠 |
void mutex_unlock(struct mutex *lock) | 释放mutex 解锁 |
iint mutex_trylock(struct mutex *lock) | 尝试获取mutex 成功返回0 失败返回0 |
int mutex_is_locked(struct mutex *lock) | 判断mutex是否被获取 获取返回1 否则返回0 |
int mutex_lock_interruptible(struct mutex *lock) | 使用此函数获取信号量失败进入休眠以后可以被信号打断 |
六、总结
本篇笔记主要学习了相关的概念及API函数,并没有相关的案例进行说明。案例将在下一篇笔记中给出。本篇笔记主要内容包括原子操作、自旋锁、信号量和互斥体。