In traditional UNIX systems, every section of kernel code terminates either through
an explicit call to sleep(1) to give up the processor or through
a hardware interrupt. The Solaris OS operates differently. A kernel thread can be
preempted at any time to run another thread. Because all kernel threads
share kernel address space and often need to read and modify the
same data, the kernel provides a number of locking primitives to prevent
threads from corrupting shared data. These mechanisms include mutual exclusion locks, which are
also known as mutexes, readers/writer locks, and semaphores.
Storage Classes of Driver Data
The storage class of data is a guide to whether the driver
might need to take explicit steps to control access to the data.
The three data storage classes are:
Automatic (stack) data. Every thread has a private stack, so drivers never need to lock automatic variables.
Global static data. Global static data can be shared by any number of threads in the driver. The driver might need to lock this type of data at times.
Kernel heap data. Any number of threads in the driver can share kernel heap data, such as data allocated by kmem_alloc(9F). The driver needs to protect shared data at all times.
A mutual-exclusion lock, or mutex, is usually associated with a set of
data and regulates access to that data. Mutexes provide a way to
allow only one thread at a time access to that data. The mutex
Releases any associated storage.
Acquires a mutex.
Releases a mutex.
Initializes a mutex.
Tests to determine whether the mutex is held by the current thread. To be used in ASSERT(9F) only.
Acquires a mutex if available, but does not block.
Setting Up Mutexes
Device drivers usually allocate a mutex for each driver data structure. The mutex
is typically a field in the structure of type kmutex_t. mutex_init(9F) is
called to prepare the mutex for use. This call is usually made at
attach(9E) time for per-device mutexes and _init(9E) time for global driver mutexes.
struct xxstate *xsp;
/* ... */
mutex_init(&xsp->mu, NULL, MUTEX_DRIVER, NULL);
/* ... */
For a more complete example of mutex initialization, see Chapter 6, Driver Autoconfiguration.
The driver must destroy the mutex with mutex_destroy(9F) before being unloaded. Destroying
the mutex is usually done at detach(9E) time for per-device mutexes and
_fini(9E) time for global driver mutexes.
Every section of the driver code that needs to read or write
the shared data structure must do the following tasks:
Acquire the mutex
Access the data
Release the mutex
The scope of a mutex, that is, the data the mutex protects,
is entirely up to the programmer. A mutex protects a data structure
only if every code path that accesses the data structure does so
while holding the mutex.
A readers/writer lock regulates access to a set of data. The readers/writer lock
is so called because many threads can hold the lock simultaneously for reading,
but only one thread can hold the lock for writing.
Most device drivers do not use readers/writer locks. These locks are slower
than mutexes. The locks provide a performance gain only when they protect
commonly read data that is not frequently written. In this case, contention
for a mutex could become a bottleneck, so using a readers/writer lock might
be more efficient. The readers/writer functions are summarized in the following table.
See the rwlock(9F) man page for detailed information. The readers/writer lock functions are:
Destroys a readers/writer lock
Downgrades a readers/writer lock holder from writer to reader
Acquires a readers/writer lock
Releases a readers/writer lock
Initializes a readers/writer lock
Determines whether a readers/writer lock is held for read or write
Attempts to acquire a readers/writer lock without waiting
Attempts to upgrade readers/writer lock holder from reader to writer
Counting semaphores are available as an alternative primitive for managing threads within
device drivers. See the semaphore(9F) man page for more information. The semaphore functions
Destroys a semaphore.
Initialize a semaphore.
Decrement semaphore and possibly block.
Decrement semaphore but do not block if signal is pending. See Threads Unable to Receive Signals.
Attempt to decrement semaphore, but do not block.
Increment semaphore and possibly unblock waiter.