linux驱动 同步机制

1、原子整形操作

1.1、头文件路径

#include <include/linux/types.h>
#include <include/linux/spinlock.h>

1.2、32位原子整形操作

1.1.1、atomic_t结构体

使用atomic_t结构体来完成32位整形数据的原子操作。

typedef struct {
    int counter;
} atomic_t;

1.1.2、ATOMIC_INIT宏

向原子变量赋初值。

#define ATOMIC_INIT(i)	{ (i) }

1.1.3、原子整形操作API

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_add_negative(int i, atomic_t *v)给 v 加 i,如果结果为负就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v)给 v 加 1,如果结果为 0 就返回真,否则返回假

1.2、64 位原子整形操作

1.2.1、atomic64_t结构体

使用atomic_t结构体来完成64位整形数据的原子操作。

typedef struct {
	long counter;
} atomic64_t;

1.2.2、ATOMIC64_INIT宏

向原子变量赋初值。

#define ATOMIC64_INIT(i)	{ (i) }

1.2.3、原子整形操作API
与32位原子整形操作API函数的用法一样,只是将"atomic_"前缀换为"atomic64_"

2、原子位操作

Linux 内核提供了一系列的原子位操作 API 函数。

注:原子位操作不像原子整形变量那样有个 atomic_t 的数据结构,原子位操作是直接对内存进行操作。

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 位原来的值。

3、自旋锁

Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的。

3.1、头文件路径

#include <include/linux/spinlock_types.h>

3.2、spinlock结构体

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;

3.3、DEFINE_SPINLOCK宏

向自旋锁赋初值。

#define DEFINE_SPINLOCK(x)	spinlock_t x = __SPIN_LOCK_UNLOCKED(x)

3.4、自旋锁操作API

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)
将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。
void spin_lock_bh(spinlock_t *lock)关闭下半部,并获取自旋锁。
void spin_unlock_bh(spinlock_t *lock)打开下半部,并释放自旋锁。

3.5、死锁

3.5.1、调用会引起睡眠和阻塞的API 函数

自旋锁会自动禁止抢占。线程 A 先运行获取锁,如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且此时内核抢占还被禁止了!线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,此时便发生死锁!

3.5.2、在中断中使用自旋锁

中断里面可以使用自旋锁。线程 A 先运行获取锁,在临界区中断发生了,中断抢走了 CPU 使用权。中断服务函数也要获取锁,但是这个锁被线程 A 占有着,中断就会一直自旋,等待锁有效。但是在中断服务函数执行完之前,线程 A 是不可能执行的,此时便发生死锁!

3.5.3、递归申请正在持有的自旋锁

通过递归的方式申请正在持有的锁,那么就必须“自旋”,等待锁被释放,然而目前锁正处于“自旋”状态,根本没法释放锁。此时便发生死锁!

3.6、总结

1) 自旋锁一定要短,否则的话会降低系统性能。

2)在自旋锁保护的临界区内不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。

3)在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断。否则可能导致锁死现象的发生。

4) 不能递归申请自旋锁

5)一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用spin_lock/spin_unlock!!!

4、读写自旋锁

1)只要没有线程持有某个给定的读写锁用于写,那么任意数量的线程可以持有该读写锁用于读。

2)仅当没有线程持有某个给定的读写锁用于读或写时,才能分配该读写锁用于写

4.1、文件路径

#include <include/linux/rwlock_types.h>

4.2、rwlock_t结构体

typedef struct {
	arch_rwlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
	unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} rwlock_t;

4.3、读写自旋锁操作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)打开下半部,并释放读锁。

5、顺序锁

顺序锁在读写锁的基础上衍生而来的。使用读写锁的时候读操作和写操作不能同时进行。使用顺序锁可以允许同时读写(虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取,保证数据完整性),但是不允许同时进行并发的写操作。

注:顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生。
 

5.1、头文件路径

#include <include/linux/seqlock.h>

5.2、seqlock_t结构体

typedef struct {
	struct seqcount seqcount;
	spinlock_t lock;
} seqlock_t;

5.3、顺序锁操作API

初始化
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)读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读

6、信号量

6.1、头文件路径

#include <include/linux/semaphore.h>

6.2、semaphore结构体

struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};

6.3、信号量操作API

DEFINE_SEAMPHORE(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)释放信号量

6.4、总结

1)信号量可以使等待资源线程进入休眠状态
2)信号量不能用于中断。因为信号量会引起休眠,中断不能休眠。
3)信号量不适合持有共享资源的时间短的情景。因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

7、读写信号量

7.1、头文件路径

#include <linux/rwsem.h>

7.2、rw_semaphore结构体

struct rw_semaphore

7.3、读写信号量操作API

DECLARE_RWSEM(name)声明名为name的读写信号量,并初始化它。
void init_rwsem(struct rw_semaphore *sem);对读写信号量sem进行初始化。
void down_read(struct rw_semaphore *sem);读者用来获取sem,若没获得时,则调用者睡眠等待。
int down_read_trylock(struct rw_semaphore *sem);读者尝试获取sem,如果获得返回1,如果没有获得返回0。可在中断上下文使用。
void up_read(struct rw_semaphore *sem);读者释放sem。
void down_write(struct rw_semaphore *sem);写者用来获取sem,若没获得时,则调用者睡眠等待。
int down_write_trylock(struct rw_semaphore *sem);写者尝试获取sem,如果获得返回1,如果没有获得返回0。可在中断上下文使用
void up_write(struct rw_semaphore *sem);写者释放sem。
void downgrade_write(struct rw_semaphore *sem);把写者降级为读者。

8、互斥体

8.1、头文件路径

#include <include/linux/mutex.h>

8.2、mutex结构体

struct mutex {
	/* 1: unlocked, 0: locked, negative: locked, possible waiters */
	atomic_t		count;
	spinlock_t		wait_lock;
	struct list_head	wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
	struct task_struct	*owner;
#endif
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};

8.3、互斥量操作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)使用此函数获取信号量失败进入休眠以后可
以被信号打断。

8.4、总结

1)互斥量可以导致休眠,因此不能在中断中使用互斥量。
2)必须由互斥量的持有者释放互斥量。
3)互斥量不能递归上锁和解锁。

9、Completions 机制

9.1、头文件路径

#include <linux/completion.h>

 9.2、

#define COMPLETION_INITIALIZER(work) \
	{ 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }
 
#define DECLARE_COMPLETION(work) \
	struct completion work = COMPLETION_INITIALIZER(work)
static inline void init_completion(struct completion *x)
{
	x->done = 0;
	init_waitqueue_head(&x->wait);
}

9.3、wait_for_completion()函数

void wait_for_completion(struct completion *c);

注:这个函数进行一个不可打断的等待. 如果代码调用wait_for_completion 并且没有人完成这个任务, 结果会是一个不可杀死的进程。

9.4、complete()函数

void complete(struct completion *c);

9.5、complete_all()函数

void complete_all(struct completion *c);


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值