创建锁定服务

有些程序员可能希望实现他们自己的高级锁定服务,而不是使用线程库中提供的标准锁定服务(互斥对象)。

例如,数据库产品可能已经使用内部定义的服务集合;修改这些锁定服务使其适应新的系统会比修改所有使用这些服务的内部模块容易。

因此,AIX®提供了原子锁定服务基元,可用于构建更高级别的锁定服务。 要创建多处理器安全的服务 (如标准互斥服务) ,程序员必须使用本节中描述的原子锁定服务,而不是原子操作服务,例如 compare_and_swap 子例程。

多处理器安全锁定服务

锁定服务用来串行化对可能被并发使用的资源的访问。 例如,锁定服务能够用来在已链接列表中进行插入,这需要多次更新指针。 如果一个进程的更新序列被第二个试图访问同一个列表的进程中断,那么可能出现错误。 不应该被中断的操作序列称为临界区

锁定服务使用锁定词表明锁定状态:0(零)为空闲可用,1(一)则为忙。 因此,要获取锁,服务将执行以下操作:

test the lock word
if the lock is free
        set the lock word to busy
        return SUCCESS
... 
因为该操作(读、测试、设置)序列本身为临界区,所以需要进行特殊处理。 在单处理器系统上,在临界区禁用中断能够防止上下文切换造成的中断。 但是,在多处理器系统上,硬件必须提供测试与设置 (test-and-set) 原语,通常还带有特殊的机器指令。 另外,还使用特殊的处理器相关同步指令(称为导入和导出防护)暂时阻塞其他的读或者写操作。 它们阻止多个处理器的并发存取以及新式处理器所执行的读和写重排序,其定义如下:
导入防护
导入防护是特殊的机器指令,在完成所有先前发出的指令之前其会延迟。 当与锁一起使用时,这会阻止推测性地执行指令,直到获得锁。
导出防护
导出防护保证在锁被释放之前,正在受保护的数据对于所有其他处理器可见。

要掩盖该复杂性,并从这些机器相关指令提供独立性,定义了以下子例程:

检查锁定
原子地对单字变量进行有条件更新,对多处理器系统发出导入防护compare_and_swap 子例程是类似的,但是它并不发出导入防护,因此它不可用于实现锁。
_clear_lock
原子地写单字变量,对多处理器系统发出导出防护

内核编程

有关内核编程的完整详细信息,请参阅 内核扩展和设备支持编程概念。 本节突出显示多处理器系统所需的主要区别。

当访问某些临界资源时,常常需要序列化。 能够使用锁定服务在进程环境中序列化线程访问,但是它们并不防止在中断环境中进行访问。 新代码或移植代码应使用 disable_lockunlock_enable 内核服务,这些服务除了使用中断控制外,还使用简单锁定。 而不是 i_disable 内核服务。 这些内核服务也可用于单处理器系统,在这些系统上它们只使用不带锁定的中断服务。 有关详细信息,请参阅 内核扩展和设备支持编程概念中的 锁定内核服务

缺省情况下,设备驱动程序以称为 funneled 的方式在逻辑单处理器环境中运行。 多数写得好的单处理器系统驱动程序在此方式下不用修改就能工作,但是要从多处理受益则必须仔细检查并修改。 最后,计时器的内核服务现在有返回值,因为在多处理器环境中它们并不总是成功。 因此,新的或者移植的代码必须检查这些返回值。 有关详细信息,请参阅 内核扩展和设备支持编程概念中的 使用多处理器-安全计时器服务

锁定服务的示例

多处理器安全锁定子例程能用来创建独立于线程库的定制高级例程。 以下示例显示了子例程的部分实现,类似于线程库中的 pthread_mutex_lockpthread_mutex_unlock 子例程:
#include <sys/atomic_op.h>       /* for locking primitives */
#define SUCCESS          0
#define FAILURE          -1
#define LOCK_FREE        0
#define LOCK_TAKEN       1

typdef struct {
        atomic_p         lock;   /* lock word */
        tid_t            owner;  /* identifies the lock owner */
        ...              /* implementation dependent fields */
} my_mutex_t;

...

int my_mutex_lock(my_mutex_t *mutex)
{
tid_t   self;   /* caller's identifier */
  
        /*
        Perform various checks:
          is mutex a valid pointer?
          has the mutex been initialized?
        */
        ...

        /* test that the caller does not have the mutex */
        self = thread_self();
        if (mutex->owner == self)
                return FAILURE;

        /*
        Perform a test-and-set primitive in a loop.
        In this implementation, yield the processor if failure.
        Other solutions include: spin (continuously check);
                 or yield after a fixed number of checks.
        */
        while (_check_lock(mutex->lock, LOCK_FREE, LOCK_TAKEN))
                yield();

        mutex->owner = self;
        return SUCCESS;
} /* end of my_mutex_lock */

int my_mutex_unlock(my_mutex_t *mutex)
{
        /*
        Perform various checks:
          is mutex a valid pointer?
          has the mutex been initialized?
        */
        ...

        /* test that the caller owns the mutex */
        if (mutex->owner != thread_self())
                return FAILURE;

        _clear_lock(mutex->lock, LOCK_FREE);
        return SUCCESS;
} /* end of my_mutex_unlock */