并发和竞态
来源:互联网 发布:免费的域名是什么样 编辑:程序博客网 时间:2024/05/17 09:11
scull的缺陷
在scull内存管理代码中。scull必须判断所请求的内存是否已经分配好。
if( !dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if(!dptr->data[s_pos]){
goto out;
}
}
如果有AB两个进程,假设都同时到达if 判断语句。如果A先复制,则它的赋值会被进程B覆盖。scull会完全忘记由A分配的内存。所以A分配的内存将丢失,从而永远不会返回到系统中。
竞态会导致对共享数据的非控制访问。发生错误访问模式时,会产生非预期的结果。
并发及其管理
只要可能,就应该避免资源共享。避免使用全局变量。
信号量:
Linux 信号量的实现
要使用信号量,内核代码包括<asm/semaphore.h>。相关的类型是struct semaphore。
声明和初始化信号量:
void sema_init(struct semaphore *sem, int val); val是初值。
声明和初始化互斥体:
DECLARE_MUTEX(name); 信号量变量,初始化为1
DECLARE_MUTEX_LOCKED(name); 信号量变量,初始化为0
在运行的被初始化(在动态分配互斥体的情况下):
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
P函数被称为down或者这个名字的变种。用于减少信号量的值:
void down(struct semaphore *sem);
减少信号量的值,并在必要时一直等待。
int down_interruptible(struct semaphore *sem);
完成down相同的动作,但是操作可以中断。如果操作被中断,返回非零值,而调用者不会拥有该信号量。对down_interruptible的正确使用需要始终检查返回值,并作出相应。
int down_trylock(struct samaphore *sem);
永远不会休眠;如果信号量在调用的时候不可获得,返回一个非零值。
void up(struct semaphore *sem);
释放信号量
读取者/写入者信号量
rwsem(read/writer semaphore,读取者/写入者信号量)。在驱动程序中使用较少。
#inlude<linux/rwsem.h>。数据类型:struct rw_semaphore;
初始化:
void 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 tw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
用信号量来同步两个任务:
struct semaphore sem;
init_MUTEX_LOCKED(&sem);
start_external_task(&sem);
down(&sem);
completion接口
包含<linux/completion.h>
创建: DECLARE_COMPLETION(my_completion);
动态的创建: struct completion my_completion;
init_completion(&my_completion);
等待completion:
void wait_for_completion(struct completion *c);
执行一个非中断的等待,将产生一个不可杀的进程。
触发:
void complete(struct completion *c); /*唤醒一个线程*/
void complete_all(struct completion *c); /*唤醒所有线程*/
重新初始化:
INIT_COMPLETION(struct completion c);
等待退出函数:
void compete_and_exit(struct completion *c, long retval);
自旋锁:
自旋锁可在不能休眠的代码中使用,比如中断处理例程。
一个自旋锁是一个互斥设备,两个值:“锁定”和“解锁”。
如果锁可用,则“锁定”位被设置,而代码继续进入临界区;如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,直到锁可用为止。
包含:<linux/spinlock.h> 类型:spinlock_t.
初始化:
静态:spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
运行中: void spin_lock_init(spinlock_t *lock);
获得锁:
void spin_lock(spinlock_t *lock);
自旋锁不可中断,一旦调用了spin_lock,在获得锁之前将一直处于自旋状态。
释放锁:
void spin_unlock(spinlock_t *lock);
自旋锁的规则:
核心:任何拥有自旋锁的代码必须是原子的。它不能休眠,它不能因为任何原因放弃处理器。服务中断以外。在拥有自旋锁时禁止中断。自旋锁必须在可能的最短时间内拥有。
完整的自旋函数:
获得:
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
在获得自旋锁之前禁止中断,以前的中断状态保存在flags中。
void spin_lock_irq(spinlock_t *lock);
在释放自旋锁时应该启用中断。
void spin_lock_bh(spinlock_t *lock);
在获得锁之前禁止软中断,硬中断保持打开。
释放:
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
传递给spin_unlock_irqrestore的flags参数必须是传递给spin_lock_irqsave的同一个变量。必须在同一个函数中调用这两个函数。
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
阻塞和非阻塞:
阻塞:将进程切换成休眠状态,直到可以使用为止。
非阻塞:不切换进程,直接返回。
非阻塞的自旋锁:
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
在成功的时候返回非0值,否则返回0。对禁止中断的情况,没有对应的try版本。
读取者/写入者自旋锁:
<linux/spinlock.h> rwlock_t类型。
初始化:
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /*static way*/
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /*dynamic way*/
函数:
读取者:
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);
锁陷阱P123:
不明确的规则
锁的顺序规则
细粒度锁和粗粒度锁的对比
免锁算法:
设立缓冲区,内核有一个通用的缓冲区实现<linux/kfifo.h>
原子变量:
所谓的原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就是说,它的最小的执行单位,不能有比它更小的执行单元,因此这里的原子实际是使用了物理学里物质微粒的概念。
完整的锁机制对于一个简单的整数来讲显得有些浪费。提供了原子的整数类型。
atomic_t 定义在<asm/atomic.h>,在atomic_t变量中不能记录大于24位的整数。
初始化:将原子变量v的值设置为整数值i。
void atomic_set(atomic_t *v, int i);
atomic_t v = ATOMIC_INIT(0);
int atomic_read(atomic_t *v);
返回当前的V值。
void atomic_add(int i, atomic_t *v);
将i累加到V指向的原子变量。
void atomic_sub(int i, atomic_t *v);
从*v中减去i.
void atomic_inc(atomic *v);
void atomic_dec(atomic *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);
执行特定的操作(加1,减1,减i),如果在操作结束后,原子值为0,则返回true;否则,返回faulse.
int atomic_add_negative(int i, atomic_t *v);
将整数变量i累加到v。返回值在结果为负时为true,否则为false。
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_int_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
会将新的值返回给调用者。
只有原子变量数目是原子的,atomic_t变量才能工作。需要多个atomic_t变量的操作,仍然需要某种类型的锁。
atomic_sub(amount, &first_atomic);
atomic_add(amount, &second_atomic);
位操作:
以原子形式来操作单个的位时。提供了一组原子修改和测试单个位的函数。
<asm/bitops.h> nr参数用来描述要操作的位(通常int unsigned long)。
void set_bit(nr, void *addr);
设置addr指向的数据项的第nr位。
void clear_bit(nr, void *addr);
清除addr指向的数据项的第nr位,其原语和set_bit相反。
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);
在做相应操作的同时,返回这个位的先前值。
seqlock
当要保护的资源很小、很简单、会频繁被访问而且写入访问很少发生且必须快速时。
seqlock允许读取者对资源的自由访问,但需要读取者检查是否和写入者发生冲突,当冲突发生时,就要重试对资源的访问。
<linux/seqlock.h>
初始化:
seqlock_t lock1 = SEQLOCK_UNLOCKED;
seqlock_t lock2;
seqlock_init(&lock2);
读取访问者使用:
unsigned int read_seqbegin(seqlock_t *lock);
int read_seqretry(seqlock_t *lock, unsigned int seq);
读取访问通过获得一个(unsigned int)顺序值而进入临界区。在退出时,该顺序值会和当前值比较;如果不相等,则必须重试读取访问。
unsigned int seq;
do{
seq = read_seqbegin(&the_lock);
/*do job*/
}while read_seqretry(&the_lock, seq);
在中断处理例程中使用seqlock:
unsigned int read_seqbegin_irqsave(seqlock_t *lock,
unsigned long flags);
int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq,
unsigned long flags);
写入者:
void write_seqlock(seqlock_t *lock);
void write_sequnlock(seqlock_t *lock);
void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
void write_swqlock_irq(seqlock_t *lock);
void write_seqlock_bh(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);
读取-复制-更新
read-copy-update,RCU
<linux/rcupdate.h>
读取端:
代码使用受RCU保护的数据结构时,必须将引用数据结构的代码包括在rcu_read_lock和rcu_read_unlock调用之间。
struct my_stuff *stuff;
rcu_read_lock();
stuff = find_the_stuff(args…);
do_something_with(stuff);
rcu_read_unlock();
- 并发和竞态
- 并发和竞态
- 并发和竞态
- 并发和竞态
- 并发和竞态
- 并发和竞态
- 并发和竞态
- 竞态和并发
- 并发和竞态
- 并发和竞态
- 并发和竞态控制
- 并发和竞态(理论篇)
- 并发和竞态(实践篇)
- 并发和竞态(理论篇)
- 并发和竞态(实践篇)
- Linux中的并发和竞态
- (LDD) 第五章、并发和竞态
- 第五章--并发和竞态
- MDK V4.72/4.7 ULink2调试中的bug
- PreTranslateMessage作用和使用方法
- The Double life of Alf Bloggs
- 【贪心】tyvj P1260 最优分解方案
- D-Link routers found to contain backdoors
- 并发和竞态
- Unity3D使用SVN进行版本控制(unity3d3.5 or later)
- CAD的一些基本操作(快捷键)
- 终止正在运行的屏幕保护程序
- 一个fork的面试题
- vim基本操作
- 一个SQL Server转SQLite数据库的小工具(SQL Server To SQLite DB Converter)
- 配置管理(可参考用于hadoop集群部署)
- Oracle的体系结构与容易混淆的概念