Linux设备中的并发控制

一、并发和竞态场景

(1)对称多处理器的多CPU

(2)单CPU内进程与抢占它的进程

(3)中断(硬中断,软中断,Tasklet,底半部)与进程之间

二、并发和竞态根因

1、编译乱序

高性能编译器具有乱序优化能力,以提升Cache命中率和CPU的Load/Store单元的工作效率。

2、执行乱序

高级处理器具有“乱序执行”策略,以提高Cache命中率。

三、并发和竞态解决方案

1、中断屏蔽

1.1 使用场景:只能解决单核中断相关并发问题。

1.2 常用操作:

local_irq_disable()  //屏蔽中断

local_irq_enable()  //开启中断

local_irq_save(flags)  //屏蔽中断并保存CPU的中断位信息(CPSR)

local_irq_restore(flags)  //开启中断并恢复CPU的中断位信息(CPSR)

1.3 使用模板:

local_irq_disable()  //屏蔽中断

...                                //临界区代码

local_irq_enable()  //开启中断

2、原子操作

2.1 使用场景:保证对一个整形数据的修改是排他的。

2.2 常用操作

(1)整形原子操作:

void atomic_set(atomic_t *v,int i); //设置原子变量的值为i

atomic_t v= ATOMIC_INIT(0); //定义原子变量v并初始化为0

atomic_read(atomic_t *v); //返回原子变量的值

void atomic_add(int i,atomic_t *v);  //原子变量加i

void atomic_sub(int i,atomic_t *v);  //原子变量减i

void atomic_inc(atomic_t *v); //原子变量加1

void atomic_sub(atomic_t *v); //原子变量减1

int atomic_inc_and_test(atomic_t *v);   //自增后测试是否为0,为0返回true,非0返回false

int atomic_dec_and_test(atomic_t *v);   //自减后测试是否为0,为0返回true,非0返回false

int atomic_sub_and_test(int i,atomic_t *v);  //减去i后测试是否为0,为0返回true,非0返回false

int atomic_add_return(int i,atomic_t *v); //增加i后返回新的值

int atomic_sub_return(int i,atomic_t *v); //减去i后返回新的值

int atomic_inc_return(int i,atomic_t *v); //自增1后返回新的值

int atomic_dec_return(int i,atomic_t *v); //自减1后返回新的值

(2)位原子操作:

void set_bit(nr,void *addr);  //设置addr地址的第nr位为1

void clear_bit(nr,void *addr);  //清空addr地址的第nr位为0

void change_bit(nr,void *addr);  //反转addr地址的第nr位,1->0,0->1

test_bit(nr,void *addr);   //返回addr地址的第nr位

int test_and_set_bit(nr,void *addr);  //返回地址addr的第nr位之后再设置其为1

int test_and_clear_bit(nr,void *addr); //返回地址addr的第nr位之后再清空其为0

int test_and_change_bit(nr,void *addr);  //返回地址addr的第nr位之后再对其进行翻转

2.3 使用模板:

static atomic_t xxx_available=ATOMIC_INIT(1);   //定义原子变量



static int xxx_open(stuct inode *inode,struct file *filp)

{

    ...

    if(!atomic_dec_and_test(&xxx_available))  //将原子变量减1后测试是否为0,为0返回true,否则返回false

    {  

        atomic_inc(&xxx_available);   //返回非0,表示后续代码有其他进程占用,恢复原子变量后退出

        return - EBUSY;

    }

    ...

    return 0;

}



static int xxx_release(struct inode *inode,struct file *filep)  //恢复原子变量的值

{

    atomic_inc(&xxx_available);

    return 0;

}

3、自旋锁

3.1 使用场景:使用自旋锁,可能会降低性能;可能导致死锁;锁定期间,不能调用引起进程调度的函数;若进程中使用了自旋锁,则中断服务程序中需要使用spine_lock();

3.2 分类

3.2.1 普通自旋锁(读和读互斥,读和写互斥,写和写互斥)

(1)常用操作

spinlock_t lock;  //定义自旋锁

spin_lock_init(lock);  //初始化自旋锁

spin_lock(lock);  //获得自旋锁

spin_trylock(lock);  //尝试获得自旋锁,如能立即获得锁,获得锁并返回true,否则返回false,返回false后不再原地打转

spin_unlock(lock);  //释放自旋锁

(2)使用模板

(2.1)普通自旋锁

spinlock_t lock;  //定义一个自旋锁

spin_lock_init(&lock);  //初始化一个自旋锁

spin_lock(&lock);  //获取自旋锁,进入代码临界区

...

spin_unlock(&lock);  //解锁

(2.2)使用自旋锁使得设备只能被单进程打开

int xxx_count=0   //定义引用计数



static int xxx_open(stuct inode *inode,struct file *filp)

{

    ...

    spin_lock(&xxx_lock);  //加锁

    if(xxx_count)  //若引用计数不为0,即后续代码有其他进程占用

    {  

        spin_unlock(&xxx_lock);   //释放锁

        return - EBUSY;

    }

    xxx_count++;

    spin_unlock(&xxx_lock);

    ...

    return 0;

}



static int xxx_release(struct inode *inode,struct file *filep)  //恢复原子变量的值

{

    ...

    spinlock(&xxx_lock);

    xxx_count--;

    spin_unlock(&xxx_lock);

    return 0;

}

3.2.2 考虑中断介入的自旋锁(读和读互斥,读和写互斥,写和写互斥)

(1)常用操作

spin_lock_irq()=spin_lock()+local_irq_disable();   //获取自旋锁+屏蔽中断

spin_unlock_irq()=spin_unlock()+local_irq_enable();  //释放自旋锁+使能中断

spin_lock_irqsave()=spin_lock()+local_irq_save();  //获取自旋锁+屏蔽中断后存储中断位

spin_unlock_irqrestore()=spin_unlock()+local_irq_restore();  //释放自旋锁+使能中断后恢复中断位

spin_lock_bh()=spin_lock()+local_bh_disable();  //获取自旋锁+屏蔽底半部中断

spin_unlock_bh()=spin_unlock()+local_bh_enable();  //获取自旋锁+使能底半部中断

(2)使用模板

同3.2.1(2)略

3.2.3  读写自旋锁(读和读不互斥,读和写互斥,写和写互斥)

(1)常用操作

rwlock_t my_rwlock;   //定义读写锁

rwlock_init(&my_rwlock); //动态初始化

//读锁定

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);

int write_trylock(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);

(2)使用模板

rwlock_t lock;   //定义锁

rwlock_init(&lock);  //初始化锁

read_lock(&lock);  //获取读锁

…  //代码临界区

read_unlock(&lock); //释放读锁



write_lock_irqsave(&lock,flags); //获取写锁

…  //代码临界区

write_unlock(&lock); //释放写锁

3.2.4  顺序锁(读和读不互斥,读和写不互斥,写和写互斥)

说明:如果在读执行单元执行期间,写执行单元已经发生了写操作,那么,读执行单元必须重新获取数据,以确保得到的数据是完整的。

(1)常用操作

(1.1)写执行单元

//获得顺序锁

void write_seqlock(seqlock_t *sl);  

int write_tryseqlock(seqlock_t *sl);

write_seqlock_irqsave(lock,flags)=local_irq_save()+write_seqlock()

write_seqlock_irq(lock)=local_irq_disable()+write_seqlock()

write_seqlock_bh(lock)=local_bh_disable()+write_seqlock()

//释放顺序锁

void write_sequnlock(seqlock_t *s1);

write_sequnlock_irqrestore(lock,flags)=local_irq_restore()+write_sequnlock()

write_sequnlock_irq(lock)=local_irq_enable()+write_sequnlock()

write_sequnlock_bh(lock)=local_bh_enable()+write_sequnlock()

(1.2)读执行单元

//读开始

unsigned read_seqbegin(const seqlock_t *sl);

read_seqbegin_irqsave(lock,flags)=local_irq_save()+read_seqbegin();

//重读

int read_seqretry(const seqlock_t *sl,unsigned iv);

read_seqretry_irqresotre(lock,iv,flags)=read_seqretry()+local_irq_restore();

(2)使用模板

(2.1)写执行单元使用顺序锁

write_seqlock(&seqlock_a);  //获取写锁

...  //代码临界区

write_sequnlock(&seqlock_a);  //获取写锁

(2.2)读执行单元使用顺序锁

do{

    seqnum=read_seqbegin(&seqlock_a);

}while(read_seqretry(&seqlock_a,seqnum));

3.2.5 R-C-U(读-复制-更新,读和读不互斥,读和写不互斥且开销小,写和写互斥且开销大)

(1)常用操作

(1.1)使用rcu读

//读锁定

rcu_read_lock()

rcu_read_lock_bh()

//读解锁

rcu_read_unlock()

rcu_read_unlock_bh()

(1.2)使用rcu写(挂接回调)

//同步rcu,指导当前CPU上所有的读执行单元完成读临界区后,写执行单元才可以执行下一步操作

synchronize_rcu();  

//挂接回调

void call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu));  //将函数挂接在rcu回调函数链上,然后立即返回。挂接的回调函数会在一个宽限期结束后执行。

rcu_assign_pointer(p,v); //给rcu保护的指针赋值

rcu_deference(p); //获取rcu保护的指针,之后读端只能在read_lock()/read_unlock()中的代码临界区引用该指针

rcu_access_pointer(p); //读端获取指针p的值

(1.3)rcu保护的链表操作

static inline void list_add_rcu(struct list_head *new,struct list_head *head); //将new插入rcu保护的链表head的开头

static inline void list_tail_rcu(struct list_head *new,struct list_head *head); //将new插入rcu保护的链表head的末尾

static inline void list_del_rcu(struct list_head *entry); //删除rcu保护的指定的链表中的元素entry

static inline void list_replace_rcu(struct list_head *old,struct list_head *new); //使用新的链表元素new替换旧的链表元素old

list_for_each_entry(pos,head); //遍历rcu保护的链表head

(2)使用模板

(2.1 )使用rcu进行读

rcu_read_lock()  //获取读锁

...  //代码临界区

rcu_read_unlock()  //释放读锁

(2.2) 使用rcu进行写(挂接回调)

//写端,视作发布gp

struct foo{

    int a;

    int b;

    int c;

};



struct foo *gp=NULL;



p=kmalloc(size(*p),GFP_KERNEL);

p->a=1;

p->b=2;

p->c=3;

rcu_assign_pointer(gp,p);



//读端,视作订阅gp

rcu_read_lock();

p=rcu_deference(p);

if(p!=NULL)

{

    do_something_with(p->a,p->b,p->c);

}

rcu_read_unlock();

(2.3)RCU保护的链表操作

//写端,视作发布gp

struct foo{

    struct list_head_list;

    int a;

    int b;

    int c;

};

LIST_HEAD(head);



struct foo *gp=NULL;



p=kmalloc(size(*p),GFP_KERNEL);

p->a=1;

p->b=2;

p->c=3;

list_add_rcu(&p->list,&head);





//读端,视作订阅gp

rcu_read_lock();

list_for_each_entry_rcu(p,head,list){

    do_something_with(p->a,p->b,p->c);

}

rcu_read_unlock();

4、信号量

4.1 使用场景

(1)如果信号量S的值大于0,该进程继续进行

(2)如果信号量S的值等于0,将该进程置位等待状态,排入信号量的等待队列,知道V操作唤醒之

4.2 常用操作

struct semaphore sem; //定义一个信号量sem

void sema_init(struct semaphore *sem,int val); //初始化信号量

void down(struct semaphore * sem); //获取信号量,会导致睡眠,不能用在中断中

int down_interruptible(struct semaphore * sem); //获取信号量,导致进程睡眠,睡眠会被信号打断,信号也会导致该函数返回非0

int down_trylock(struct semaphore *sem); //尝试获得信号量,如果能够获得,则获得信号量并返回0,否则返回非0,不会导致睡眠,可以在中断中使用

void up(struct semaphore * sem); //释放信号量,唤醒等待者

4.3 使用模板

类似自旋锁,略。

5、互斥体

5.1 使用场景:用于多个进程之间对资源的互斥。适用于进程切换开销较大的场景。不适用中断/软中断场景(因为互斥体只存在于进程上下文)。

5.2 常用操作

void mutex_lock(struct mutex *lock); //获取互斥体,会导致睡眠,不能用在中断中

int mutex_lock_interruptible(struct mutex *lock); //获取互斥体,导致进程睡眠,睡眠会被信号打断,信号也会导致该函数返回非0

int mutex_trylock(struct mutex *lock); //尝试获取互斥体,获取不到mutex互斥体的时候不会引起睡眠

void mutex_unlock(struct mutex *look);  //释放互斥体

5.3 使用模板:

struct mutex my_mutex;    //定义互斥体

mutex_init(&my_mutex);    //初始化互斥体

mutex_lock(&my_mutex);    //获取互斥体

...                       //代码临界区

mutex_unlock(&my_mutex);  //释放互斥体

6、完成量

6.1 使用场景:用于一个执行单元等待另一个执行单元执行完某事

6.2 常用操作

stuct completion my_completion;    //定义完成量

init_completion(&my_completion);   //初始化完成量

reinit_completion(&my_completion); //重新初始化完成量的值为0

void wait_for_completion(struct completion *c);  //等待完成量

void complete(struct completion *c);   //唤醒完成量

void complete_all(struct completion *c); //唤醒完成量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值