The following information is provided to assist you in understanding the locking kernel services:
The following lock allocation services allocate and free internal operating system memory for simple and complex locks, or check if the caller owns a lock:
lock_alloc | Allocates system memory for a simple or complex lock. |
lock_free | Frees the system memory of a simple or complex lock. |
lock_mine | Checks whether a simple or complex lock is owned by the caller. |
Simple locks are exclusive-write, non-recursive locks which protect thread-thread or thread-interrupt critical sections. Simple locks are preemptable, meaning that a kernel thread can be preempted by another, higher priority kernel thread while it holds a simple lock. The simple lock kernel services are:
simple_lock_init | Initializes a simple lock. |
simple_lock, simple_lock_try | Locks a simple lock. |
simple_unlock | Unlocks a simple lock. |
On a multiprocessor system, simple locks which protect thread-interrupt critical sections must be used in conjunction with interrupt control in order to serialize execution both within the executing processor and between different processors. On a uniprocessor system interrupt control is sufficient; there is no need to use locks. The following kernel services provide appropriate locking calls for the system on which they are executed:
disable_lock | Raises the interrupt priority, and locks a simple lock if necessary. |
unlock_enable | Unlocks a simple lock if necessary, and restores the interrupt priority. |
Using the disable_lock and unlock_enable kernel services to protect thread-interrupt critical sections (instead of calling the underlying interrupt control and locking kernel services directly) ensures that multiprocessor-safe code does not make unnecessary locking calls on uniprocessor systems.
Simple locks are spin locks; a kernel thread which attempts to acquire a simple lock may spin (busy-wait: repeatedly execute instructions which do nothing) if the lock is not free. The table shows the behavior of kernel threads and interrupt handlers which attempt to acquire a busy simple lock.
Result of Attempting to Acquire a Busy Simple Lock | ||
Caller | Owner is Running | Owner is Sleeping |
Thread (with interrupts enabled) | Caller spins initially; it sleeps if the maximum spin threshold is crossed. | Caller sleeps immediately. |
Interrupt handler or thread (with interrupts disabled) | Caller spins until lock is freed. | Caller spins until lock is freed (must not happen). |
Note: On uniprocessor systems, the maximum spin threshold is set to one, meaning that a kernel thread will never spin waiting for a lock.
A simple lock that protects a thread-interrupt critical section must never be held across a sleep, otherwise the interrupt could spin for the duration of the sleep, as shown in the table. This means that such a routine must not call any external services which may result in a sleep. In general, using any kernel service which is callable from process level may result in a sleep, as can accessing unpinned data. These restrictions do not apply to simple locks that protect thread-thread critical sections.
The lock word of a simple lock must be located in pinned memory if simple locking services are called with interrupts disabled.
Complex locks are read-write locks which protect thread-thread critical sections. Complex locks are preemptable, meaning that a kernel thread can be preempted by another, higher priority kernel thread while it holds a complex lock. The complex lock kernel services are:
lock_init | Initializes a complex lock. |
lock_islocked | Tests whether a complex lock is locked. |
lock_done | Unlocks a complex lock. |
lock_read, lock_try_read | Locks a complex lock in shared-read mode. |
lock_read_to_write, lock_try_read_to_write | Upgrades a complex lock from shared-read mode to exclusive-write mode. |
lock_write, lock_try_write | Locks a complex lock in exclusive-write mode. |
lock_write_to_read | Downgrades a complex lock from exclusive-write mode to shared-read mode. |
lock_set_recursive | Prepares a complex lock for recursive use. |
lock_clear_recursive | Prevents a complex lock from being acquired recursively. |
By default, complex locks are not recursive (they cannot be nested). A complex lock can become recursive through the lock_set_recursive kernel service. A recursive complex lock is not freed until lock_done is called once for each time that the lock was locked.
Complex locks are spin locks; a kernel thread which attempts to acquire a complex lock may spin (busy-wait: repeatedly execute instructions which do nothing) if the lock is not free. The table shows the behavior of kernel threads which attempt to acquire a busy complex lock:
Result of Attempting to Acquire a Busy Complex Lock | ||
Current Lock Mode | Owner is Running | Owner is Sleeping |
Exclusive-write | Caller spins initially, but sleeps if the maximum spin threshold is crossed, or if the owner later sleeps. | Caller sleeps immediately. |
Shared-read being acquired for exclusive-write | Caller spins initially; it sleeps if the maximum spin threshold is crossed. | |
Shared-read being acquired for shared-read | Lock granted immediately |
Notes:
Note: Lockl locks (previously called conventional locks) are only provided to ensure compatibility with existing code. New code should use simple or complex locks.
Lockl locks are exclusive-access and recursive locks. The lockl lock kernel services are:
lockl | Locks a conventional lock. |
unlockl | Unlocks a conventional lock. |
A thread which tries to acquire a busy lockl lock sleeps immediately.
The lock word of a lockl lock must be located in pinned memory if the lockl service is called with interrupts disabled.
Atomic lock operations are services that read or write single word variables; on multiprocessor systems, they also protect against concurrent access using import and export fences (or synchronization instructions). They can be used to implement higher level locking services and are mainly intended to support the building of user mode lock services when POSIX 1003.1c mutexes are not appropriate. Thus, the following atomic lock operations are provided as user subroutines:
_check_lock | Conditionally updates a single word variable atomically, issuing an import fence for multiprocessor systems. The kernel service compare_and_swap is similar, but does not issue an import fence, and therefore is inappropriate for updates of lock words on multiprocessor systems. |
_clear_lock | Atomically writes a single word variable, issuing an export fence for multiprocessor systems. |
_safe_fetch | Atomically reads and returns a single word variable protected by an export fence. The read is safe for multiprocessor systems. |
Single word variables accessed by atomic lock operations must be aligned on a full word boundary, and must be located in pinned memory if called with interrupts disabled.
Atomic operations are sequences of instructions which guarantee atomic accesses and updates of shared single word variables. This means that atomic operations cannot protect accesses to complex data structures in the way that locks can, but they provide a very efficient way of serializing access to a single word.
The atomic operation kernel services are:
fetch_and_add | Increments a single word variable atomically. |
fetch_and_and, fetch_and_or | Manipulates bits in a single word variable atomically. |
compare_and_swap | Conditionally updates or returns a single word variable atomically. |
Single word variables accessed by atomic operations must be aligned on a full word boundary, and must be located in pinned memory if atomic operation kernel services are called with interrupts disabled.