做者:刘洪涛,华清远见嵌入式学院高级讲师,ARM公司受权ATC讲师。html
关于自旋锁用法介绍的文章,已经有不少,但有些细节的地方点的还不够透。我这里就把我我的认为你们容易有疑问的地方拿出来讨论一下。linux
1、自旋锁(spinlock)简介 安全
自旋锁在同一时刻只能被最多一个内核任务持有,因此一个时刻只有一个线程容许存在于临界区中。这点能够应用在多处理机器、或运行在单处理器上的抢占式内核中须要的锁定服务。并发
2、信号量简介函数
这里也介绍下信号量的概念,由于它的用法和自旋锁有类似的地方。post
Linux中的信号量是一种睡眠锁。若是有一个任务试图得到一个已被持有的信号量时,信号量会将其推入等待队列,而后让其睡眠。这时处理器得到自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而即可以得到这个信号量。ui
3、自旋锁和信号量对比atom
在不少地方自旋锁和信号量能够选择任何一个使用,但也有一些地方只能选择某一种。下面对比一些二者的用法。spa
表1-1自旋锁和信号量对比线程
应用场合 |
信号量or自旋锁 |
低开销加锁(临界区执行时间较快) |
优先选择自旋锁 |
低开销加锁(临界区执行时间较长) |
优先选择信号量 |
临界区可能包含引发睡眠的代码 |
不能选自旋锁,能够选择信号量 |
临界区位于非进程上下文时,此时不能睡眠 |
优先选择自旋锁,即便选择信号量也只能用down_trylock非阻塞的方式 |
4、自旋锁与linux内核进程调度关系
咱们讨论下表1-1中的第3种状况(其它几种状况比较好理解),若是临界区可能包含引发睡眠的代码则不能使用自旋锁,不然可能引发死锁。
那么为何信号量保护的代码能够睡眠而自旋锁就不能呢?
先看下自旋锁的实现方法吧,自旋锁的基本形式以下:
spin_lock(&mr_lock);
//临界区
spin_unlock(&mr_lock);
跟踪一下spin_lock(&mr_lock)的实现
#define spin_lock(lock) _spin_lock(lock)
#define _spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
注意到“preempt_disable()”,这个调用 的功能是“关抢占”(在spin_unlock中会从新开启抢占功能)。从中能够看出,使用自旋锁保护的区域是工做在非抢占的状态;即便获取不到锁,在 “自旋”状态也是禁止抢占的。了解到这,我想我们应该可以理解为什么自旋锁保护的代码不能睡眠了。试想一下,若是在自旋锁保护的代码中间睡眠,此时发生进程 调度,则可能另一个进程会再次调用spinlock保护的这段代码。而咱们如今知道了即便在获取不到锁的“自旋”状态,也是禁止抢占的,而“自旋”又是 动态的,不会再睡眠了,也就是说在这个处理器上不会再有进程调度发生了,那么死锁天然就发生了。
我们能够总结下自旋锁的特色:
● 单处理器非抢占内核下:自旋锁会在编译时被忽略;
● 单处理器抢占内核下:自旋锁仅仅看成一个设置内核抢占的开关;
● 多处理器下:此时才能彻底发挥出自旋锁的做用,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占形成的竞争。
5、linux抢占发生的时间
最后在了解下linux抢占发生的时间,抢占分为用户抢占和内核抢占。
用户抢占在如下状况下产生:
● 从系统调用返回用户空间
● 从中断处理程序返回用户空间
内核抢占会发生在:
● 当从中断处理程序返回内核空间的时候,且当时内核具备可抢占性;
● 当内核代码再一次具备可抢占性的时候。(如:spin_unlock时)
● 若是内核中的任务显式的调用schedule()
● 若是内核中的任务阻塞。
基本的进程调度就是发生在时钟中断后,而且发现进程的时间 片已经使用完了,则发生进程抢占。一般咱们会利用中断处理程序返回内核空间的时候能够进行内核抢占这个特性来提升一些I/O操做的实时性,如:当I/O事 件发生的是时候,对应的中断处理程序被激活,当它发现有进程在等待这个I/O事件的时候,它会激活等待进程,而且设置当前正在执行进程的 need_resched标志,这样在中断处理程序返回的时候,调度程序被激活,原来在等待I/O事件的进程(极可能)得到执行权,从而保证了对I/O事 件的相对快速响应(毫秒级)。能够看出,在I/O事件发生的时候,I/O事件的处理进程会抢占当前进程,系统的响应速度与调度时间片的长度无关。
本章已介绍了不少符号给并发的管理. 最重要的这些在此总结:
-
#include <asm/semaphore.h> //定义信号量和其上操做的包含文件.
-
信号量的定义和初始化
(1)静态定义及初始化
-
DECLARE_MUTEX(name);
-
DECLARE_MUTEX_LOCKED(name);
-
(2)动态定义及初始化
struct semaphore sem
-
void init_MUTEX(struct semaphore *sem);
-
-
void init_MUTEX_LOCKED(struct semaphore *sem);
信号量的获取和释放
-
void down(struct semaphore *sem);
-
-
int down_interruptible(struct semaphore *sem);
-
-
int down_trylock(struct semaphore *sem);
-
-
void up(struct semaphore *sem);
-
down 使调用进程进入不可中断的睡眠; down_interruptible, 相反, 能够被信号打断,通常状况下都应该使用这个操做. down_trylock 不睡眠; 相反, 它马上返回若是旗标不可用. 得到信号量并完成相关操做后必须使用 up 来释放他.
-
-
struct rw_semaphore;
-
-
init_rwsem(struct rw_semaphore *sem);
-
旗标的读者/写者版本和初始化它的函数.
-
void down_read(struct rw_semaphore *sem);
-
-
int down_read_trylock(struct rw_semaphore *sem);
-
-
void up_read(struct rw_semaphore *sem);
-
得到和释放对读者/写者旗标的读存取的函数.
-
void down_write(struct rw_semaphore *sem);
-
-
int down_write_trylock(struct rw_semaphore *sem);
-
-
void up_write(struct rw_semaphore *sem);
-
-
void downgrade_write(struct rw_semaphore *sem);
-
管理对读者/写者旗标写存取的函数.
-
#include <linux/completion.h>
-
-
DECLARE_COMPLETION(name);
-
-
init_completion(struct completion *c);
-
-
INIT_COMPLETION(struct completion c);
-
描述 Linux completion 机制的包含文件, 已经初始化 completion 的正常方法. INIT_COMPLETION 应当只用来从新初始化一个以前已经使用过的 completion.
-
void wait_for_completion(struct completion *c);
-
等待一个 completion 事件发出.
-
void complete(struct completion *c);
-
-
void complete_all(struct completion *c);
-
发出一个 completion 事件. completion 唤醒, 最多, 一个等待着的线程, 而 complete_all 唤醒所有等待者.
-
void complete_and_exit(struct completion *c, long retval);
-
经过调用 complete 来发出一个 completion 事件, 而且为当前线程调用 exit.
自旋锁
-
#include <linux/spinlock.h>
-
-
spinlock_t lock = SPIN_LOCK_UNLOCKED;
-
-
spin_lock_init(spinlock_t *lock);
-
定义自旋锁接口的包含文件, 以及初始化锁的 2 个方法.
-
void spin_lock(spinlock_t *lock);
-
-
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
-
-
void spin_lock_irq(spinlock_t *lock);
-
-
void spin_lock_bh(spinlock_t *lock);
-
加锁一个自旋锁的各类方法, 而且, 可能地, 禁止中断.
-
int spin_trylock(spinlock_t *lock);
-
-
int spin_trylock_bh(spinlock_t *lock);
-
上面函数的非自旋版本; 在获取锁失败时返回 0, 不然非零.
-
void spin_unlock(spinlock_t *lock);
-
-
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
-
-
void spin_unlock_irq(spinlock_t *lock);
-
-
void spin_unlock_bh(spinlock_t *lock);
-
释放一个自旋锁的相应方法.
-
rwlock_t lock = RW_LOCK_UNLOCKED
-
-
rwlock_init(rwlock_t *lock);
-
初始化读者/写者锁的 2 个方法.
-
void read_lock(rwlock_t *lock);
-
-
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
-
-
void read_lock_irq(rwlock_t *lock);
-
-
void read_lock_bh(rwlock_t *lock);
-
得到一个读者/写者锁的读存取的函数.
-
void read_unlock(rwlock_t *lock);
-
-
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
-
-
void read_unlock_irq(rwlock_t *lock);
-
-
void read_unlock_bh(rwlock_t *lock);
-
释放一个读者/写者自旋锁的读存取.
-
void write_lock(rwlock_t *lock);
-
-
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
-
-
void write_lock_irq(rwlock_t *lock);
-
-
void write_lock_bh(rwlock_t *lock);
-
得到一个读者/写者锁的写存取的函数.
-
void write_unlock(rwlock_t *lock);
-
-
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
-
-
void write_unlock_irq(rwlock_t *lock);
-
-
void write_unlock_bh(rwlock_t *lock);
-
释放一个读者/写者自旋锁的写存取的函数.
-
#include <asm/atomic.h>
-
-
atomic_t v = ATOMIC_INIT(value);
-
-
void atomic_set(atomic_t *v, int i);
-
-
int atomic_read(atomic_t *v);
-
-
void atomic_add(int i, atomic_t *v);
-
-
void atomic_sub(int i, atomic_t *v);
-
-
void atomic_inc(atomic_t *v);
-
-
void atomic_dec(atomic_t *v);
-
-
int atomic_inc_and_test(atomic_t *v);
-
-
int atomic_dec_and_test(atomic_t *v);
-
-
int atomic_sub_and_test(int i, atomic_t *v);
-
-
int atomic_add_negative(int i, atomic_t *v);
-
-
int atomic_add_return(int i, atomic_t *v);
-
-
int atomic_sub_return(int i, atomic_t *v);
-
-
int atomic_inc_return(atomic_t *v);
-
-
int atomic_dec_return(atomic_t *v);
-
原子地存取整数变量. atomic_t 变量必须只经过这些函数存取.
-
#include <asm/bitops.h>
-
-
void set_bit(nr, void *addr);
-
-
void clear_bit(nr, void *addr);
-
-
void change_bit(nr, void *addr);
-
-
test_bit(nr, void *addr);
-
-
int test_and_set_bit(nr, void *addr);
-
-
int test_and_clear_bit(nr, void *addr);
-
-
int test_and_change_bit(nr, void *addr);
-
原子地存取位值; 它们可用作标志或者锁变量. 使用这些函数阻止任何与并发存取这个位相关的竞争状况.
-
#include <linux/seqlock.h>
-
-
seqlock_t lock = SEQLOCK_UNLOCKED;
-
-
seqlock_init(seqlock_t *lock);
-
定义 seqlock 的包含文件, 已经初始化它们的 2 个方法.
-
unsigned int read_seqbegin(seqlock_t *lock);
-
-
unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
-
-
int read_seqretry(seqlock_t *lock, unsigned int seq);
-
-
int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);
-
得到一个 seqlock-保护 的资源的读权限的函数.
-
void write_seqlock(seqlock_t *lock);
-
-
void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
-
-
void write_seqlock_irq(seqlock_t *lock);
-
-
void write_seqlock_bh(seqlock_t *lock);
-
获取一个 seqlock-保护的资源的写权限的函数.
-
void write_sequnlock(seqlock_t *lock);
-
-
void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
-
-
void write_sequnlock_irq(seqlock_t *lock);
-
-
void write_sequnlock_bh(seqlock_t *lock);
-
释放一个 seqlock-保护的资源的写权限的函数.
-
#include <linux/rcupdate.h>
-
须要使用读取-拷贝-更新(RCU)机制的包含文件.
-
void rcu_read_lock;
-
-
void rcu_read_unlock;
-
获取对由 RCU 保护的资源的原子读权限的宏定义.
-
void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
-
安排一个回调在全部处理器已经被调度以及一个 RCU-保护的资源可用被安全的释放以后运行.