Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
  Previous   Contents   Next 
   
 
Chapter 3

Multithreading

This chapter describes the locking primitives and thread synchronization mechanisms of the Solaris multithreaded kernel. Device drivers should be designed to take advantage of multithreading. This chapter provides information on the following subjects:

Locking Primitives

In traditional UNIX systems, every section of kernel code runs until it explicitly gives up the processor by calling sleep(1) or is interrupted by hardware. This is not true in the Solaris operating environment. 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 (or mutex), 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 types of data storage classes are:

  • Automatic (stack) data - Every thread has a private stack, so drivers never need to lock automatic variables.

  • Global and static data - Global and 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 might share kernel heap data, such as data allocated by kmem_alloc(9F). If this data is shared, the driver needs to protect it at times.

Mutual-Exclusion Locks

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.

Table 3-1 Mutex Routines

Name

Description

mutex_init(9F)

Initializes a mutex

mutex_destroy(9F)

Releases any associated storage

mutex_enter(9F)

Acquires a mutex

mutex_tryenter(9F)

Acquires a mutex if available; but does not block

mutex_exit(9F)

Releases a mutex

mutex_owned(9F)

Tests to determine if the mutex is held by the current thread. To be used in ASSERT(9F) only

Setting Up Mutexes

Device drivers usually allocate a mutex for each driver data structure. The mutex is typically a field in the structure and is of type kmutex_t. mutex_init(9F) is called to prepare the mutex for use. This is usually done at attach(9E) time for per-device mutexes and _init(9E) time for global driver mutexes.

For example,

struct xxstate *xsp;
...
mutex_init(&xsp->mu, NULL, MUTEX_DRIVER, NULL);
...

For a more complete example of mutex initialization, see Chapter 5, Driver Autoconfiguration.

The driver must destroy the mutex with mutex_destroy(9F) before being unloaded. This is usually done at detach(9E) time for per-device mutexes and _fini(9E) time for global driver mutexes.

Using Mutexes

Every section of the driver code that needs to read or write the shared data structure must do the following:

  • Acquire the mutex

  • Access the data

  • Release the mutex

The scope of a mutex--the data it protects--is entirely up to the programmer. A mutex protects some particular data structure because the programmer chooses to do so and uses it accordingly. A mutex protects a data structure only if every code path that accesses the data structure does so while holding the mutex.

Readers/Writer Locks

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 it for writing.

Most device drivers do not use readers/writer locks. These locks are slower than mutexes and provide a performance gain only when protecting data that is not frequently written but is commonly read by many concurrent threads. 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.

Table 3-2 Readers/Writer Locks

Name

Description

rw_init(9F)

Initializes a readers/writer lock

rw_destroy(9F)

Destroys a readers/writer lock

rw_enter(9F)

Acquires a readers/writer lock

rw_tryenter

Attempts to acquire a reader/writer lock without waiting

rw_tryupgrade(9F)

Attempts to upgrade readers/writer lock holding from reader to writer

rw_downgrade(9F)

Downgrades a readers/writer lock holding from writer to reader

rw_exit(9F)

Releases a readers/writer lock

rw_read_locked(9F)

Determines whether readers/writer lock is held for read or write

Semaphores

Counting semaphores are available as an alternative primitive for managing threads within device drivers. See the semaphore(9F) man page for more information.

Table 3-3 Semaphores

Name

Description

sema_init(9F)

Initialize a semaphore

sema_destroy(9F)

Destroys a semaphore

sema_p(9F)

Decrement semaphore and possibly block

sema_tryp(9F)

Attempt to decrement semaphore, but do not block

sema_p_sig(9F)

Decrement semaphore, but do not block if signal is pending

sema_v(9F)

Increment semaphore and possibly unblock waiter

Thread Synchronization

In addition to protecting shared data, drivers often need to synchronize execution among multiple threads.

Condition Variables in Thread Synchronization

Condition variables are a standard form of thread synchronization. They are designed to be used with mutexes. The associated mutex is used to ensure that a condition can be checked atomically, and that the thread can block on the associated condition variable without missing either a change to the condition or a signal that the condition has changed.

Table 3-4 lists the condvar(9F) interfaces.

Table 3-4 Condition Variable Routines

Name

Description

cv_init(9F)

Initializes a condition variable

cv_destroy(9F)

Destroys a condition variable

cv_wait(9F)

Waits for condition

cv_timedwait(9F)

Waits for condition or timeout

cv_wait_sig

Waits for condition or return zero on receipt of a signal

cv_timedwait_sig(9F)

Waits for condition or timeout or signal

cv_signal(9F)

Signals one thread waiting on the condition variable

cv_broadcast(9F)

Signals all threads waiting on the condition variable

 
 
 
  Previous   Contents   Next