并发和竞态

来源:互联网 发布:免费的域名是什么样 编辑:程序博客网 时间: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();

原创粉丝点击