Kernel / Linux · 2023年8月9日

kernel中的并发控制

一.中断屏蔽

local_save_flags(flags);

 local_irq_disable()

……..critical section…………

local_irq_restore(flags);

二.原子操作

位和整型变量的原子操作都依赖于底层CPU的原子操作,因此这些操作都与CPU架构密切相关。、

整型原子操作:

1.设置原子变量的值:

Void atomic_set(aotmic_v *v,int i)  //设置原子变量的值为i

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

2.获取原子变量的值:

Atomic_read(aotmic_ t *v)  //返回原子变量的值。

3.原子变量加/减:

Void atomic_add(int I ,atomic_t *v) //原子量加i

Void atomic_sub(int I ,atomic_t *v) //原子量减i

4.原子变量自增/自减:

Void atomic_inc(atomic_v *v)

Void atomic_dec(atomic_t *v)

5.操作并测试

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)

6.操作并返回

Int atomic_add_return(int I ,atomic_v *v)

Int atomic_sub_return(int I ,atomic_v *v)

Int atomic_inc_return (int I, atomic_v *v)

Int atomic_dec_return(int I,atomic_v *v)

三.自旋锁

自旋锁不关心锁定的临界区究竟在进行什么操作,不管是读还是写,它都一视同仁,即便多个单元同时读取临界资源也会被锁住,自旋锁的衍生锁读写自旋锁可允许读的并发,读写自旋锁是一种比自旋锁烂度更小锁机制,它保留了自旋的概念,但在写操作方面,只能最多有一个写进程,在读操作方面,可以有多个读执行单元,读和写不能同时进行。

自旋锁

1.定义自旋锁

  spinlock_t lock;

2.初始化自旋锁

Spin_lock_init(lock)

3.获得自旋锁

Spin_lock(lock)

Spin_trylock(lock)

四.顺序锁

顺序锁是对读写锁的一种优化,若使用顺序锁,读执行单元不会被写执行单元所阻塞,写操作之间互斥,读操作与写操作之间不会被阻塞,如果读执行单元在读操作时,发生了写操作,那么读操作必须重新读,这样会导致读多次。

写执行单元涉及的顺序锁

1。获得顺序锁

Void write_seqlock(seqlock_t *sl)

Int write_tryseqlock(seqlock_t *sl)

Write_seqlock_irqsave(lock, flags)

Write_seqlock_irq(lock)

Write_seqlock_bh(lock)

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

2。释放顺序锁

Void write_sequnlock(seqlock_t *sl)

Write_sequnlock_irqstore(lock,flags)

Write_sequnlock_irq(lock)

Write_sequnlock_bh(lock)

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

Write_sequnlock_irq(lock) = write_sequnlock()+local_irq_enable()

Write_sequnlock_bh(lock) = write_sequnlock()+local_bh_enable()

读执行单元涉及的顺序锁

1.读开始

Unsigned read_seqbegin(const seqlock_t *sl)

Read_seqbegin_irqsave(lock,flags)

读执行单元对被顺序锁sl保护的共享资源访问前需要调用此函数 ,此函数返回顺序锁sl的当前顺序号

2.重读

Int read_seqretry(const seqlock_t *sl,unsigned iv)

Read_seqretry_irqrestore(lock,iv,flags)

读—复制—更新

RCU

不同于自旋锁,RUC的读端没有锁,几乎可以认为是直接读,而RCU的写执行单元在访问它的共享资源前先复制一个复本,然后对副本进行修改,最后使用一个回调机制,在适当的时机指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用数据的CPU都退出去共享数据读操作的时候,等适当时机的这一段时间称为宽限期。

RCU 操作

1.读锁定

Rcu_read_lock()

Rcu_read_lock_bh()

2.读解锁

Rcu_read_unlock()

Rcu_read_unlock_bh()

使用RCU进行读的模式下:

3.同步RCU

Synchronize_rcu()

4.挂接回调

Void call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu))

4.释放自旋锁

Spin_unlock(lock)

自旋锁主要针对SMP或者单CPU但内核可抢占的情况,对于单CPU和内核不抢占的系统,自旋锁退化为空操作。尽管自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候,还可能受到中断和底半部BH的影响。

关中断  :local_irq_disable()

开中断:     local_irq_enable()

关底半部 : local_bh_diable()

开底半部: local_bh_enable()

关中断并保存状态字:local_irq_save()

开中断并恢复状态字:local_irq_restore()

在多核中,如果进程和中断访问同一片临界资源 ,我们一般需要在进程上下文中调用spin_lock_irqsave()/spin_unlock_irqrestore(), 在中断上下文调用spin_lock()/spin_unlock()

注意:

 1.自旋锁实际上是忙等锁,当锁不可用时,cpu一直执行 测试并设置,直到得到,CPU在等锁时,不做任何有用的工作,因此只在占用锁很短情况下,才可以

2.自旋锁可能导致系统死锁,递归锁

3。在自旋锁锁定期间,不能调用可能引起进程调度的函数,如果在获得自旋锁后再阻塞,如copy_from_user(),copy_to_user(),kmalloc() ,msleep()等

4。在单核情况下,要认为自己的多枋的,因为跨平台,在单CPU下,中断与进程可能访问同一临界区,进程中使用spin_lock_irqsave()是安全的,在中断里其实不调用spin_lock()也没有问题,在为spin_lock_irqsave()可以保证这个CPU的中断服务程序可能 执行,但是cpu如果是多核的,spin_lock_irqsave()不能屏蔽另外一个核的中断,所以另一个核就有可能引起并发。


读写自旋锁
 

1.定义和初始化读写自旋锁

 rwlock_t my_rw_lock

Rwlock_init(&my_rwlock)

2.读锁定

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)

3.读解锁

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)

4.写锁定

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_tyrlock(rwlock_t *lock)

5.写解锁

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)

五.信号量

信号量,是典型的用于同步与互斥的手段,信号量的值可以是0,1或者n,信号量与操作系统中的经典概念PV操作对应:

P:1. 将信号量S值减 1即S=S-1

   2.如果S>=0,则该进程继续执行,否则等待

V:1.将信号量S值加1,即S=S+1

      2.如果S>0,唤醒队列中等待信号量的进程。

1.定义信号量:

Struct semaphore sem

2.初始化信号量:

Void sema_init(struct semaphore *sem, int val)

3.获得信号量:

Void down(struct seaphore *sem)

用于获取信号量,它会导致睡眠,因此不能用在中断上下文

Void down_interruptible(struct semaphore *sem)

进入睡眠的信号,可以被打断

Int down_trylock(struct semaphore *sem)可以用在中断上下文

尝试获得信号

4.释放信号量

Void up(struct semaphore *sem)

六.互斥体

1、当锁不能被获取到时,通过开销来衡量

使用互斥的开销:进程上下文的切换时间。如果临界区很大,应该选用互斥锁。

使用自旋锁的开销:忙等待自旋锁,直到锁释放。如果执行临界区时间小,应该选自旋锁。

2、由是否引起阻塞。进程上下文切换容易进入睡眠状态,如果使用自旋锁就会发生死锁。

3、保护共享资源需要在中断或者软终端情况下使用,最好选自旋锁,不会引起睡眠,(当然还可以使用mutex_trylock()互斥体避免阻塞)。

1.初始化

Struct mutex my_mutex;

Mutex_init(&my_mutex)

2.获取互斥体

Void mutex_lock(struct mutex *lock)

Int mutex_lock_interruptible(struct mutex *lock)

Int mutex_trylock(struct mutext *lock)

3.释放互斥体

Void muext_unlock(struct mutex *lock)

七.完成量

1、当锁不能被获取到时,通过开销来衡量

使用互斥的开销:进程上下文的切换时间。如果临界区很大,应该选用互斥锁。

使用自旋锁的开销:忙等待自旋锁,直到锁释放。如果执行临界区时间小,应该选自旋锁。

2、由是否引起阻塞。进程上下文切换容易进入睡眠状态,如果使用自旋锁就会发生死锁。

3、保护共享资源需要在中断或者软终端情况下使用,最好选自旋锁,不会引起睡眠,(当然还可以使用mutex_trylock()互斥体避免阻塞)。

1.初始化

Struct mutex my_mutex;

Mutex_init(&my_mutex)

2.获取互斥体

Void mutex_lock(struct mutex *lock)

Int mutex_lock_interruptible(struct mutex *lock)

Int mutex_trylock(struct mutext *lock)

3.释放互斥体

Void muext_unlock(struct mutex *lock)

八.阻塞与非阻塞

阻塞即需要等待结果返回,当没有结果返回时,进程将进入休眠

非阻塞则不需要等待结果返回,立即返回

常用等待队列来实现阻塞进程的唤醒。

1。定义等待队列头部

——————————————–typedef struct __wait_queue_head wait_queue_head_t;

Wait_queue_head_t my_queue

Wait_queue_head_t 是__wait_queue_head结构体的一个typedef

2。初始化等待队列头部

Init_waitqueue_head(&my_queue)

DECLARE_WAIT_QUEUE_HEAD(name)

3。定义等待队列元素

DECLARE_WAITQUEUE

4.添加移除队列

Void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

Void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

5.等待事件

Wait_event(queue,condition)

Wait_event_interruptible(queue,condition)

Wait_event_timeout(queue,condition,timeout)

Wait_event_interruptible_timeout(queue,condition,timeout)

6。唤醒队列

Void wake_up(wait_queue_head_t *queue)

7。在等待队列上睡眠

Void wake_up_interruptible(wait_queue_head_t *queue)

打赏作者