Linux 同步方法
如果您了解了一些基本理论并且明白了需要解决的问题,接下来将学习 Linux
支持并发和互斥锁的各种方法。在以前,互斥锁是通过禁用中断来提供的,但是这种形式的锁定效率比较低(现在在内核中仍然存在这种用法)。这种方法也不能进行扩展,而且不能保证其他处理器上的互斥锁。
在以下关于锁定机制的讨论中,我们首先看一下原子运算符,它可以保护简单变量(计数器和位掩码(bitmask))。然后介绍简单的自旋锁和读/写锁,它们构成了一个
SMP 架构的忙等待锁(busy-wait lock)覆盖。最后,我们讨论构建在原子 API 上的内核互斥锁。
(1)原子操作
Linux 中最简单的同步方法就是原子操作。原子意味着临界段被包含在 API 函数中。不需要额外的锁定,因为 API
函数已经包含了锁定。由于 C 不能实现原子操作,因此 Linux
依靠底层架构来提供这项功能。各种底层架构存在很大差异,因此原子函数的实现方法也各不相同。一些方法完全通过汇编语言来实现,而另一些方法依靠 c 语言并且使用 local_irq_save 和
local_irq_restore 禁用中断。
当需要保护的数据非常简单时,例如一个计数器,原子运算符是种理想的方法。尽管原理简单,原子 API
提供了许多针对不同情形的运算符。下面是一个使用此 API 的示例。
要声明一个原子变量(atomic variable),首先声明一个 atomic_t 类型的变量。这个结构包含了单个 int
元素。接下来,需确保您的原子变量使用 ATOMIC_INIT 符号常量进行了初始化。 在清单 1 的情形中,原子计数器被设置为
0。也可以使用 atomic_set function 在运行时对原子变量进行初始化。
清单 1.
创建和初始化原子变量
atomic_t my_counter ATOMIC_INIT(0);
... or ...
atomic_set( &my_counter, 0 );
原子 API 支持一个涵盖许多用例的富函数集。可以使用 atomic_read 读取原子变量中的内容,也可以使用
atomic_add 为一个变量添加指定值。最常用的操作是使用 atomic_inc
使变量递增。也可用减号运算符,它的作用与相加和递增操作相反。清单 2. 演示了这些函数。
清单 2. 简单的算术原子函数
val = atomic_read( &my_counter );
atomic_add( 1, &my_counter );
atomic_inc( &my_counter );
atomic_sub( 1, &my_counter );
atomic_dec( &my_counter );
该 API 也支持许多其他常用用例,包括 operate-and-test
例程。这些例程允许对原子变量进行操纵和测试(作为一个原子操作来执行)。一个叫做 atomic_add_negative
的特殊函数被添加到原子变量中,然后当结果值为负数时返回真(true)。这被内核中一些依赖于架构的信号量函数使用。
许多函数都不返回变量的值,但两个函数除外。它们会返回结果值( atomic_add_return 和
atomic_sub_return),如清单 3所示。
清单 3. Operate-and-test 原子函数
if (atomic_sub_and_test( 1, &my_counter ))
{
// my_counter is zero
}
if (atomic_dec_and_test( &my_counter )) {
// my_counter is zero
}
if (atomic_inc_and_test( &my_counter )) {
// my_counter is zero
}
if (atomic_add_negative( 1, &my_counter )) {
// my_counter is less than zero
}
val = atomic_add_return( 1, &my_counter ));
val = atomic_sub_return( 1, &my_counter
));
如果您的架构支持 64 位长类型(BITS_PER_LONG 是 64 的),那么可以使用 long_t atomic
操作。可以在 linux/include/asm-generic/atomic.h 中查看可用的长操作(long
operation)。
原子 API
还支持位掩码(bitmask)操作。跟前面提到的算术操作不一样,它只包含设置和清除操作。许多驱动程序使用这些原子操作,特别是
SCSI。位掩码原子操作的使用跟算术操作存在细微的差别,因为其中只有两个可用的操作(设置掩码和清除掩码)。使用这些操作前,需要提供一个值和将要进行操作的位掩码,如清单
4 所示。
清单 4. 位掩码原子函数
unsigned long my_bitmask;
atomic_clear_mask( 0, &my_bitmask );
atomic_set_mask( (1<<24),
&my_bitmask );