linux 内核同步介绍

一、造成并发执行的原因:

1.中断   2.软中断和tasklet   3.内核抢占     4.睡眠与用户空间的同步。5.两个或多个处理器可以同时执行代码。

二、预防死锁:

1、按顺序加锁。可以防止指明拥抱类型的死锁

2、防止发生饥饿,如果A不发生,B要一直等待下去吗?

3、不要重复请求同一个锁。

4、设计应简单。

三、在编写代码时,要考虑如下:

1、这个数据是不是全局的,除了当前线程外,其他线程能不能访问它?

2、进程在访问数据时可不可以被抢占?在调度的新程序会不会访问同一数据?

3、当前进程是不是会睡眠(阻塞)在某种资源上,如果是,它会让共享数据处于何种状态?

4、如果这个函数又在另一个处理器上被调度将会发生什么呢?

四、内核同步方法-原子操作

原子操作,它通过把读取和增加变量的行为包含在一个单步中执行,从而防止竞争的发生保证了操作结果总是一致的。

内核提供了两组原子操作接口:原子整数操作,原子位操作。

 五、内核同步方法-自旋锁

自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图获得一个被已经持有的自旋锁,那么该线程就会一直进行忙循环-旋转-等待锁重新可用。如果锁未被争用,请求锁的执行线程便能立刻得到它。

一个被争用的自旋锁使得请求的线程在等待锁重现可用时自旋(特别浪费处理器时间)在短期内进行轻量级加锁,使该线程不会进入到睡眠状态。(持有锁的时间最好小于两次上下文切换的开销)两次(将请求线程睡眠和唤醒)。linux中的自旋锁不可递归

使用场景:

  • tasklet之间的同步(当两个不同类型的tasklet共享同一数据时)需要正确使用锁机制。
  • 如果进程上下文和一个下半部分共享数据,在访问数据之前,你需要禁止下半部分的处理并得到锁的使用权。防止死锁
  • 如果中断上下文和一个下半部分共享数据,在访问数据之前,你需要禁止中断并得到锁的使用权。防止死锁

中断处理程序:(不能使用信号量,因为他们会导致睡眠)。在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在当前处理器上的中断请求),否则,中断处理程序会打断正在持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁,这样一来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行,就发生双重请求死锁。(内核提供禁止中断同时请求锁的接口)。

 六、内核同步方法-读写自旋锁

注意:大量的读者必定会使挂起的写者处于饥饿状态。

一个或多个读任务可以并发地持有读者锁;相反,用于写的锁最多只能被一个写任务持有,而且此时不能有并发的读操作。

七、内核同步方法-信号量

linux中的信号量是一种睡眠锁。发生争用时,等待的线程会投入睡眠,会进行上下文切换(耗费处理器时间)

信号量适合锁会被长时间持有的情况。

信号量同时允许任意数量的锁持有者,自旋锁只能是一个。

八、内核同步方法-读写信号量

和自旋锁差不多,不过发生争用时,等待的线程会投入睡眠

特殊点:读写信号量可以动态的将写锁转换为读锁。写者数量不限。

九、内核同步方法-互斥体

其行为和适用计数为1的信号量类似,但操作接口更简单,实现也更高效。

只允许任意1个数量的锁持有者。

低开销加锁 自旋锁
短期锁定 自旋锁
长期加锁 互斥体
中断上下文加锁 自旋锁
持有锁需要睡眠 互斥体

十、顺序锁

用于读写共享数据,实现这种锁主要依靠一个序列计数器。当有数据被写入时,会得到一个锁,并且序列值会增加。在读取数据之前和之后,序列号都被读取。读取的序列号值相同,说明在读操作进行的过程中没有被写操作打断过。

适用场景:

数据存在很多读者。虽然读者少,但希望写优先于读,而且不允许读者让写者饥饿。

猜你喜欢

转载自blog.csdn.net/qq_36183935/article/details/81287203