This chapter discusses topics associated with kernel threads with an emphasis on when and why you would use them in device drivers. Before writing device drivers that make use of kernel threads, you should understand:
The following sections briefly describe each of these topics.
A thread is a single, sequential flow of control within a program. Within a single thread, there is a single point of execution. Application programs use threads to improve their performance (throughput, computational speed, responsiveness or some combination). To start, terminate, delete, and perform other operations on threads, the application programmer calls the interfaces (routines) that the DECthreads product provides.
The term kernel thread distinguishes between the threads that application programs use. A kernel thread is a single sequential flow of control within a device driver or other systems-based program. The device driver or other systems-based program makes use of the kernel interfaces (instead of a threads library package such as DECthreads) to start, terminate, delete, and perform other kernel threads-related operations.
Kernel threads execute within (and share) a single address space. Therefore, kernel threads read and write the same memory locations.
You use kernel threads to improve the performance (throughput, computational speed, responsiveness or some combination) of a device driver. Multiple kernel threads are useful in a multiprocessor environment where kernel threads run concurrently on separate CPUs. However, multiple kernel threads also improve device driver performance on single processor systems by permitting the overlap of input, output, or other slow operations with computational operations.
Kernel threads allow device drivers to perform other useful work while waiting for a device to produce its next event (such as the completion of a disk transfer or the receipt of a packet from the network).
Typically, you make use of kernel threads in device drivers when:
One example of a long operation is the reset sequences associated with a multistep device.
One reason for creating a kernel thread to perform a long operation is to prevent the driver from running at a high interrupt priority level (IPL) for long periods of time.
This situation refers to allocating memory or accessing address space that might page fault.
One example of this operation is that access to a data item is not allowed at an elevated IPL, for example, the proc structure.
Figure 6-1 shows one example of the previously described situations. As the figure shows, a device driver must check a number of device state changes. One of these device state changes checks for an adapter fatal error condition. If the fatal error condition occurs, the driver must reset the adapter. The code that resets the adapter must block to accomplish the adapter reset operation. Furthermore, the only time this error can occur is during a device interrupt. It is not legal to block in an interrupt service interface. Therefore, the figure shows that the interrupt service interface for the driver calls an xxstate interface that handles all of the state changes. This interface creates a kernel thread called xxerr that starts up when the adapter becomes operational. The job of this kernel thread is to reset the adapter when a fatal error occurs. Note that it is legal for this kernel thread to perform blocking operations.
You can view multiple kernel threads in a program as executing simultaneously. However, you cannot make any assumptions about the relative start or finish times of kernel threads or the sequence in which they execute. You can influence the scheduling of kernel threads by setting scheduling and policy priority.
Each kernel thread has its own thread indentifier, which allows it to be uniquely identified. This thread identifier is a pointer to the thread structure associated with the kernel thread. The kernel threads creation interfaces return this thread structure pointer to the driver after they successfully create and start the kernel thread. Device drivers use this pointer as a handle to a specific kernel thread in calls to other kernel threads-related interfaces.
A kernel thread changes states during the course of its execution and is always in one of the following states:
The kernel thread is not eligible to execute because it is synchronizing with another kernel thread or with an external event, such as I/O.
The kernel thread is eligible to be executed by a CPU.
The kernel thread is currently being executed by a CPU.
The kernel thread has completed all of its work.
When you design and code a device driver that uses the kernel thread-related interfaces, consider the following issues:
Using kernel threads can simplify the coding and designing of a device driver. However, you need to be sure that the synchronization and interplay among kernel threads is correct. You use simple and complex locks to synchronize access to data.
A race condition is a programming error that causes unpredictable and erroneous program behavior. Specifically, the error occurs when two or more kernel threads perform an operation and the result of the operation depends on unpredictable timing factors, for example, when each kernel thread executes and waits and when each kernel thread completes the operation.
A deadlock is a programming error that causes two or more kernel threads to be indefinitely blocked. Specifically, the error occurs when a kernel thread holds a resource while waiting for a resource held by another kernel thread and that kernel thread is also waiting for the first kernel thread's resource.
Priority inversion occurs when the interaction among three or more kernel threads blocks the highest-priority kernel thread from executing. For example, a high-priority kernel thread waits for a resource locked by a low-priority kernel thread, and the low-priority kernel thread waits while a middle-priority kernel thread executes. The high-priority kernel thread is made to wait while a kernel thread of lower priority (the middle-priority kernel thread) executes.
To avoid priority inversion, associate a priority (at least as high as the highest priority kernel thread that will use it) with each resource and force any kernel thread using that object to first raise its priority to that associated with the object.
Table 6-1 lists the kernel interfaces associated with kernel threads and describes the operations they perform. Chapter 7 discusses how to use these interfaces to implement kernel threads in a device driver.
Kernel Interfaces | Description |
Creating kernel threads | |
kernel_isrthread | Starts a fixed priority kernel thread dedicated to interrupt service. |
kernel_thread_w_arg | Starts a kernel thread with a calling argument passed in. |
Blocking kernel threads | |
assert_wait_mesg | Asserts that the current kernel thread is about to block (sleep). |
thread_block | Blocks (puts to sleep) the current kernel thread. |
Unblocking kernel threads | |
thread_wakeup | Wakes up all kernel threads waiting for the specified event. |
thread_wakeup_one | Wakes up the first kernel thread waiting on a channel. |
Terminating kernel threads | |
thread_terminate | Prepares to stop or stops execution of the specified kernel thread. |
thread_halt_self | Handles asynchronous traps for self-terminating kernel threads. |
Miscellaneous | |
current_task | Returns a pointer to the task structure associated with the currently running kernel thread. |
thread_set_timeout | Sets a timer for the current kernel thread. |