- Linux内核提供的原子整形操作API
- 借助多核Cache一致性协议可以很方便实现原子操作
- 在x86架构下,处理器提供了在指令执行期间对总线加锁的手段。通过在需要原子操作的指令前附加lock指令前缀将总线锁定,保证了指令执行时不会受到其它处理器的影响。
-
ARM架构下的原子操作不使用总线锁定的方式,而是采用独占访问机制。ARM提供了一对独占内存加载和存储的指令,用于支持原子操作实现:
ldxr指令:内存独占加载指令。从内存中以独占的方式加载内存地址的值到通用寄存器里;
stxr指令:内存独占存储指令。以独占的方式把新的数据存储到内存中。 -
#inlcude "include/linux/types.h" typedef struct { int counter; } atomic_t; typedef struct { long long counter; } atomic64_t; 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
-
#include "include/linux/types.h" 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位原来的值
- 自旋锁
- ①、因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要
短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处
理方式,比如稍后要讲的信号量和互斥体。
②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能
导致死锁。
③、不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就
必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己
把自己锁死了!
④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还
是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。 -
#include "include/linux/types.h" 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; 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) //将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁 /*下半部/底半部(BH),*/ void spin_lock_bh(spinlock_t *lock) //关闭下半部,并获取自旋锁 void spin_unlock_bh(spinlock_t *lock) //打开下半部,并释放自旋锁
- 读写自旋锁
- 读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个写操作,也就是只能一个线
程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,
可以进行并发的读操作。 -
typedef struct { arch_rwlock_t raw_lock; } rwlock_t; 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) //打开下半部,并释放写锁
- 顺序锁
- 顺序锁在读写锁的基础上衍生而来的,使用读写锁的时候读操作和写操作不能同时进行。
使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行
并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,
最好重新进行读取,保证数据完整性。 - 顺序锁保护的资源不能是指针,因为如果在写操作的时
候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读
取野指针导致系统崩溃。 -
typedef struct { struct seqcount seqcount; spinlock_t lock; } seqlock_t; DEFINE_SEQLOCK(seqlock_t sl) //定义并初始化顺序锁 void seqlock_init(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_irqstore(seqlock_t *sl, unsigned log 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) //读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读
- 信号量
- ①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场
合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换
线程引起的开销要远大于信号量带来的那点优势。 -
struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list; }; 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) //获取信号量,和down类似,只是使用down进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。 void up(struct semaphore *sem) //释放信号量
- 互斥体
- ①、mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
②、和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。
③、因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并
且 mutex 不能递归上锁和解锁。 -
struct mutex { /*1: unlocked, 0: locked, negative: locked, possible waiters*/ atomic_t count; spinlock_t wait_lock; }; 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 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) //使用此函数获取信号量失败进入休眠以后可以被信号打断
- Completion (用于同步)
struct completion done; static int arm11mcu_spi_txrx(struct spi_device *spi, struct spi_transfer *t) { init_completion(&done); ... wait_for_completion(&done); } static irqreturn_t arm11mcu__spi_irq(int irq, void *dev) { ... complete(&hw->done); return IRQ_HANDLED; }
- 等待队列:代表一切睡眠进程的集合
- 阻塞与非阻塞使用(用等待队列)
DECLARE_WAITQUEUE(wait, current); //定义等待队列 add_wait_queue(&xxx_wait, &wait); //添加等待队列 ... __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态(浅度睡眠) ... shchedule(); //调度其他进程执行 if (signal_pending(current)) //如果是因为信号唤醒 { if (!ret) ret = -ERESTARTSYS; goto out; } out: remove_wait_queue(&xxx_wait, &wait); //将等待队列移出等待队列头 set_current_state(TASK_RUNNING); //设置进程状态为TASK_RUNNING