标签:const 前言 signed 运行 64bit str inux 原理 没有
内核驱动的并发&竟态很容易理解,其解决方法也不能,看看例程就可以了。
对于API,看看内核源码和内核文档即可。
原文链接:https://www.cnblogs.com/lizhuming/p/14907262.html
本章内容为驱动基石之一。
驱动只提供功能,不提供策略。
并发:
并发产生原因(大概):
竞态:
需要解决竞态是因为要保护数据。
确保每个时刻都只有一个执行单元访问共享资源。
竞态解决方法有:
参考文档:
都知道,在 C 的世界里,a = 10;
这样一个简单的赋值,到了汇编的世界就不止一条语句啦。若此时多线程往变量 a 的地址赋值,就可能会产生数据错误。
原子操作就是不可分割操作。
注意:原子操作只能对 整型变量 和 位操作 具有保护功能。
原子操作:
由于函数容易理解,所以就不像以前的笔记一样详细列出。
整型原子的操作需要个 atomic_t 结构体。
bit原子的操作只需要一个地址即可,是直接对内存操作。
atomic_t 32bit 整型原子变量结构体:
//atomic_t类型结构体
typedef struct
{
int counter;
}atomic_t;
atomic64_t 64bit 整型原子变量结构体:
//atomic64_t 类型结构体
typedef struct
{
long long counter;
}atomic64_t;
整型原子 API 汇总:
API | 描述 |
---|---|
ATOMIC_INIT(int i) | 定义原子变量时候的初始值 |
void atomic_set(atomic_t *v, int i) | 向 v 写入 i |
void atomic_read(atomic_t *v) | 读取 v 的值 |
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_add_return(int i, atomic_t *v) | v 加 i ,返回 v 的结果 |
int atomic_sub_return(int i, atomic_t *v) | v 减 i ,返回 v 的结果 |
int atomic_inc_return(int i, atomic_t *v) | v 加 1 ,返回 v 的结果 |
int atomic_dec_return(int i, atomic_t *v) | v 减 1 ,返回 v 的结果 |
int atomic_sub_and_test(int i, atomic_t *v) | v 减 i 后是否为 0 |
int atomic_inc_and_test(atomic_t *v) | v 加 1 后是否为 0 |
int atomic_dec_and_test(atomic_t *v) | v 减 1 后是否为 0 |
int atomic_add_negative(int i, atomic_t *v) | v 加 i 后是否为 负数 |
更多 API(如atomic_dec_unless_positive()、atomic_inc_unless_negative()) 请参考内核源码和推荐的文档。
bit原子的操作不需要 atomic_t 结构体,它是直接对 内存 操作的。
bit 原子 API 汇总:
API | 描述 |
---|---|
void set_bit(int nr, void *p) | 对地址 p 的第 nr 位置 1 |
void clear_bit(int nr, void *p) | 对地址 p 的第 nr 位置 0 |
void change_bit(int nr, void *p) | 对地址 p 的第 nr 位翻转 |
int test_bit(int nr, void *p) | 返回地址 p 的第 nr 位的值 |
void test_and_set_bit(int nr, void *p) | 对地址 p 的第 nr 位置 1,并返回原来的 nr 位值 |
void test_and_clear_bit(int nr, void *p) | 对地址 p 的第 nr 位置 0,并返回原来的 nr 位值 |
void test_and_change_bit(int nr, void *p) | 对地址 p 的第 nr 位翻转,并返回原来的 nr 位值 |
原子操作只能对整型变量或者bit进行保护。而自旋锁能对一个单元进行保护,是给代码段添加一把锁。
自旋锁是实现互斥访问的常用手段。
获取自旋锁后再运行代码才能被保护起来。
自旋锁特点:
自旋锁缺点:
自旋锁操作:
自旋锁使用注意事项:
spinlock_t 结构体:
typedef struct
{
struct lock_impl internal_lock;
}spinlock_t;
自旋锁 API 汇总:
API | 描述 |
---|---|
DEFINE_SPINLOCK(spinlock_t lock) | 定义、初始化一个自选变量 |
void spin_lock_init(spinlock_t *lock) | 初始化一个自旋锁 |
void spin_lock(spinlock_t *lock) | 加锁,即是获取一个自旋锁 |
int spin_trylock(spinlock_t *lock) | 尝试获取自旋锁,不等待,成功返回 true,失败返回 false |
void spin_unlock(spinlock_t *lock) | 释放自旋锁 |
int spin_is_locked(spinlock_t *lock) | 检查指定自旋锁是否已经被获取。若没有,则返回非0;否则返回 0 |
void spin_lock_irq(spinlock_t *lock) | 获取自旋锁并关中断(防止中断打断) |
void spin_unlock_irq(spinlock_t *lock) | 释放自旋锁并开中断 |
spin_lock_irqsave(lock, flags) | 获取自旋锁,并保存中断状态到flags。锁返回时,之前开的中断,之后也是开的;之前关,之后也是关 |
spin_unlock_irqrestore(lock, flags) | 释放自旋锁,并恢复中断状态,即是把 flags 值赋值给中断状态寄存器。 |
普通的自旋锁是一刀切的,不管访问者对临界区的操作是读还是写。
但是实际上,很多共享资源都允许多个执行单元同时读,这是不影响数据的。
所以,读写自旋锁 允许 读并发,但是不允许 写并发,且不允许读写同时出现。
即有允许以下情景:
读写自旋锁 结构体:
typedef struct
{
arch_rwlock_t raw_lock;
}rwlock_t;
读写自旋锁 API:
API | 描述 |
---|---|
DEFINE_RWLOCK(rwlock_t lock) | 定义、初始化一个自选变量 |
void rwlock_init(rwlock_t *lock) | 初始化一个自旋锁 |
API | 描述 |
---|---|
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) | 打开下半部,释放读自旋锁 |
顺序锁 是 读写锁 的一个优化。
读写锁 不允许读和写同时出现。有以下前景:
顺序锁 允许读和写同时出现,但是只能出现一个写。有以下前景:
顺序自旋锁 结构体:
typedef struct
{
struct seqcount seqcount;
spinlock_t lock;
}seqlock_t;
顺序自旋锁 API:
API | 描述 |
---|---|
DEFINE_SEQLOCK(seqlock_t sl) | 定义、初始化一个自选变量 |
void seqlock_init(seqlock_t *sl) | 初始化一个自旋锁 |
API | 描述 |
---|---|
unsigned read_seqbegin(const seqlock_t *sl) | 加锁,并返回获取到的顺序锁的顺序号 |
unsigned read_seqretry(const seqlock_t *sl) | 读结束后调用该函数。用于检查在读的过程中是否有对资源进行写操作,若有,则返回1,建议重新读去数据。 |
API | 描述 |
---|---|
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) | 打开下半部,释放读自旋锁 |
学过 RTOS 的都知道信号量了。可以看做一个全局计数器。
信号量常用于同步和互斥。
信号量的获取失败后,线程可引入休眠,当信号量可用时,系统会通知其退出休眠。
信号量操作:
信号量使用注意事项:
semaphore 结构体:
struct semaphore
{
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
API | 描述 |
---|---|
DEFINE_SEMAPHORE(name) | 定义一个信号量,并置为 1 |
void sema_init(struct semaphore *sem, int val) | 初始化信号量,并置为 val |
void down(struct semaphore *sem) | 获取信号量。因为信号量会导致休眠,且不能被信号打断,因此不能在中断中使用该函数 |
int down_trylock(struct semaphore *sem) | 尝试获取信号量,不休眠。成功返回 0,失败返回 非0 |
void down_interruptible(struct semaphore *sem) | 获取信号量。就算导致休眠后,也能被信号打断,因此该函数可以在中断中使用 |
void up(struct semaphore *sem) | 释放信号量 |
互斥体 的占用其实和 信号量量值为 1 的效果是一样的。
但是互斥体的执行效率更高,毕竟,专业的API做专业的事嘛。
互斥体执行操作:
互斥体使用注意事项:
API | 描述 |
---|---|
DEFINE_MUTEX(name) | 定义并初始化一个 mutex 变量 |
void mutex_init(mutex *lock) | 初始化 mutex |
void mutex_lock(struct mutex *lock) | 加锁,获取 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 |
void mutex_lock_interruptible(struct mutex *lock) | 加锁,获取 mutex。获取失败进入休眠后,依然能被信号打断。支持在中断中使用。 |
完成量(completion)。
完成量用于一个执行单元等待另一个执行单元。
完成量操作:
完成量结构体:
struct completion {
unsigned int done;
wait_queue_head_t wait;
};
API | 描述 |
---|---|
void complete(struct completion *x) | 唤醒一个等待完成量 x 的线程 |
void complete_all(struct completion *x) | 唤醒所有等待完成量 x 的线程 |
void wait_for_completion(struct completion *x) | 等待一个完成量 x |
unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout) | 限时等待一个完成量 x |
void init_completion(struct completion *c) | 初始化一个完成量 |
void reinit_completion(struct completion *c) | 重新初始化一个完成量 |
标签:const 前言 signed 运行 64bit str inux 原理 没有
原文地址:https://www.cnblogs.com/lizhuming/p/14907262.html