This chapter discusses the kernel interfaces that allow you to peform kernel threads-related operations. Specifically, these interfaces allow you to:
In addition, the chapter discusses the thread and task data structures that these kernel threads-related interfaces use.
This section discusses the two data structures that kernel threads-related interfaces use: thread and task. The thread data structure contains kernel threads-related information. Device drivers typically use the wait_result member (along with the current_thread interface) to check for the result of the wait. The header file thread.h shows a typedef statement that assigns the alternate name thread_t for a pointer to the thread structure. Many of the kernel threads-related interfaces operate on these pointers to thread structures.
The thread structure is an opaque data structure; that is, all of its associated members (except for the wait_result member) are referenced and manipulated by the Digital UNIX operating system and not by the user of kernel threads.
The task data structure contains task-related information. The header file task.h shows a typedef statement that assigns the alternate name task_t for a pointer to the task structure. Some of the kernel threads-related interfaces require that you pass a pointer to the task structure.
The task structure is an opaque data structure; that is, all of its associated members are referenced and manipulated by the Digital UNIX operating system and not by the user of kernel threads.
You can start (and create) a kernel thread with the following interfaces:
Starts a kernel thread with a calling argument passed in
Starts a fixed priority kernel thread dedicated to interrupt service
The following sections describe each of these interfaces.
To start (and create) a kernel thread at a specified entry point and with a specified argument, call the kernel_thread_w_arg interface. The kernel_thread_w_arg interface creates and starts a kernel thread in the specified task at the specified entry point with a specified argument. The kernel_thread_w_arg interface passes the specified argument to the newly created kernel thread. The kernel_thread_w_arg interface creates and starts a kernel thread with timeshare scheduling. A kernel thread created with timeshare scheduling means that its priority degrades if it consumes an inordinate amount of CPU resources. A device driver should call kernel_thread_w_arg only for long-running tasks. A device driver should always attach a kernel thread to the ``first task.''
This interface is actually a convenience wrapper for the thread_create interface (which creates the kernel thread) and the thread_start interface (which starts the newly created kernel thread).
The following code fragment shows a call to kernel_thread_w_arg by the if_fta device driver's fta_transition_state interface. The fta_transition_state interface changes the state of the driver by performing certain fixed functions for any given particular state.
.
.
.
#include <kern/thread.h> [1]
.
.
.
#define ADAP "fta"
.
.
.
extern task_t first_task; [2]
.
.
.
struct fta_softc {
.
.
.
short reinit_thread_started; /* reinit thread running? */
.
.
.
}; [3]
.
.
.
struct ifnet {
.
.
.
short if_unit; /* subunit for lower-level driver */
.
.
.
};
.
.
.
fta_transition_state(sc, unit, state) struct fta_softc *sc; short unit; short state; {
.
.
.
switch(state) {
.
.
.
case PI_OPERATIONAL: { int s; NODATA_CMD *req_buff; thread_t thread; [4] if (sc->reinit_thread_started == FALSE) { [5] thread = kernel_thread_w_arg(first_task, fta_error_recovery, (void *)sc); [6] if (thread == NULL) { [7] printf("%s%d: Cannot start error recovery thread.\n", ADAP, ifp->if_unit); } sc->reinit_thread_started = TRUE; }
.
.
.
}
The kernel_thread_w_arg interface takes three arguments:
The fta_transition_state interface checks the return. If the return is NULL, kernel_thread_w_arg did not create the error recovery kernel thread. The fta_transition_state interface calls printf to display an appropriate message on the console terminal.
If the return is not NULL, fta_transition_state sets the reinit_thread_started member to the value TRUE to indicate that the error recovery kernel thread is started. [Return to example]
To start (and create) a fixed priority kernel thread dedicated to interrupt service, call the kernel_isrthread interface. The kernel_isrthread interface creates and starts a kernel thread at the specified entry point. This kernel thread handles only interrupt service requests in the specified task and at the specified priority level. A device driver should always attach a kernel thread to the ``first task.''
The following code fragment shows a call to kernel_isrthread by the if_fta device driver's ftaprobe interface.
The ftaprobe interface determines if the adapter exists, fills in a variety of register values, and initializes a variety of descriptors.
.
.
.
#include <kern/thread.h> [1]
.
.
.
extern task_t first_task; [2]
.
.
.
ftaprobe(reg, ctlr) io_handle_t reg; struct controller *ctlr; {
.
.
.
thread = kernel_isrthread(first_task, fta_rec_intr, BASEPRI_SYSTEM); [3]
.
.
.
}
The kernel_isrthread interface takes three arguments:
The following priority usage table describes the possible scheduling priorities. The first column shows a range of priorities. The second column shows an associated scheduling priority constant defined in <src/kernel/kern/sched.h> (if applicable). The third column describes the usage of the priority ranges. To specify a scheduling priority of 38, you pass the constant BASEPRI_SYSTEM as shown in the example. To specify a scheduling priority of 33, you can pass the following: BASEPRI_HIGHEST + 1.
Priority | Constant | Usage |
0
. . . 31 |
N/A | Realtime kernel threads |
32
. . . 38 |
BASEPRI_HIGHEST
. . . BASEPRI_SYSTEM |
Operating system kernel threads |
44
. . . 64 |
BASEPRI_USER
. . . BASEPRI_LOWEST |
User kernel threads |
You can block (put to sleep) a kernel thread with the following interfaces:
Call this interface to assert that the current kernel thread is about to block until some specified event occurs. You use this interface with the thread_block interface, which actually blocks (puts to sleep) the current kernel thread.
Call this interface to block the current kernel thread and select the next kernel thread to start.
This interface is the symmetric multiprocessor (SMP) sleep call.
Each of these interfaces is described in the following sections.
To assert that the current kernel thread is about to block until some specified event occurs, call the assert_wait_mesg interface. To actually block (put to sleep) the current kernel thread, call thread_block.
The following code fragment shows a call to assert_wait_mesg and thread_block by the if_fta device driver's fta_error_recovery interface. The fta_error_recovery interface is a kernel thread that starts up when the adapter becomes operational. This kernel thread resets the adapter if a fatal error occurs. The code fragment also shows the code that contains the call to kernel_thread_w_arg, which calls fta_error_recovery.
.
.
.
#include <kern/thread.h> [1]
.
.
.
#define ADAP "fta"
.
.
.
extern task_t first_task; [2]
.
.
.
struct fta_softc {
.
.
.
short reinit_thread_started; /* reinit thread running? */
.
.
.
short error_recovery_flag; /* flag to wake up a process */
.
.
.
}; [3]
.
.
.
struct ifnet {
.
.
.
short if_unit; /* subunit for lower-level driver */
.
.
.
};
.
.
.
fta_transition_state(sc, unit, state) struct fta_softc *sc; short unit; short state; {
.
.
.
switch(state) {
.
.
.
case PI_OPERATIONAL: { int s; NODATA_CMD *req_buff; thread_t thread; [4] if (sc->reinit_thread_started == FALSE) { [5] thread = kernel_thread_w_arg(first_task, fta_error_recovery, (void *)sc); [6] if (thread == NULL) { [7] printf("%s%d: Cannot start error recovery thread.\n", ADAP, ifp->if_unit); } sc->reinit_thread_started = TRUE; }
.
.
.
void fta_error_recovery(sc) [8] struct fta_softc *sc; [9] { struct ifnet *ifp; /* * Collect the argument left by the kernel_thread_w_arg(). */ ifp = &sc->is_if; for(;;) { [10] assert_wait_mesg((vm_offset_t)&sc->error_recovery_flag, TRUE,"ftaerr"); [11] thread_block(); [12] /* Performs tasks to reset the adapter */
.
.
.
}
.
.
.
}
The kernel_thread_w_arg interface takes three arguments:
The fta_transition_state interface checks the return. If the return is NULL, kernel_thread_w_arg did not create the error recovery kernel thread. The fta_transition_state interface calls printf to display an appropriate message on the console terminal.
If the return is not NULL, fta_transition_state sets the reinit_thread_started member to the value TRUE to indicate that the error recovery kernel thread is started. [Return to example]
A fatal error requires resetting the adapter; this error is discovered during a device interrrupt. It is necessary to block in the interrupt service interface while resetting the adapter. Because it is not legal to block in an interrupt service interface, the fta_transition_state calls this kernel thread to perform the reset operation on the adapter. [Return to example]
The assert_wait_mesg interface takes three arguments:
Value | Meaning |
TRUE | The current kernel thread is interruptible. This value means that a signal can awaken the current kernel thread. |
FALSE | The current kernel thread is not interruptible. This value means that only the specified event can awaken the current kernel thread. |
In this call, the value TRUE is passed.
The assert_wait_mesg interface does not return a value. [Return to example]
The thread_block interface does not return a value. [Return to example]
To block the current kernel thread, call the mpsleep interface. The following code fragment shows a call to mpsleep by the if_fta device driver's fta_error_recovery interface. The fta_error_recovery interface is a kernel thread that starts up when the adapter becomes operational. This kernel thread resets the adapter if a fatal error occurs. The code fragment also shows the use of a simple lock with the mpsleep interface.
.
.
.
struct fta_softc {
.
.
.
short error_recovery_flag; /* flag to wake up a process */
.
.
.
int is_state; [1] simple_lock_data_t lk_fta_softc; [2]
.
.
.
};
.
.
.
void fta_error_recovery(sc) struct fta_softc *sc; { struct ifnet *ifp; /* * Collect the argument left by the kernel_thread_w_arg(). */ ifp = &sc->is_if; simple_lock (&sc->lk_fta_softc); [3] while (sc->is_state == RUN_NOT) { [4] for(;;) { [5] mpsleep ((vm_offset_t)&sc->error_recovery_flag, PCATCH, "ftaerr", 0, &sc->lk_fta_softc, MS_LOCK_SIMPLE | MS_LOCK_ON_ERROR)) [6]
/* Performs tasks to reset the adapter */
.
.
.
} } }
.
.
.
The mpsleep interface takes six arguments:
The channel argument specifies an address associated with the calling kernel thread to be put to sleep. In this call, the address (or event) associated with the current kernel thread is stored in the error_recovery_flag member.
The pri argument specifies whether the sleep request is interruptible. Setting this argument to the PCATCH flag causes the process to sleep in an interruptible state (that is, the kernel thread can take asynchronous signals). Not setting the PCATCH flag causes the process to sleep in an uninterruptible state (that is, the kernel thread cannot take asynchronous signals).
In this call, fta_error_recovery passes the value PCATCH.
The wmesg argument specifies the wait message.
In this call, fta_error_recovery passes the string ftaerr.
The timo argument specifies the maximum amount of time the kernel thread should block (sleep). If you pass the value zero (0), mpsleep assumes there is no timeout.
In this call, fta_error_recovery passes the value zero (0) to indicate there is no timeout.
The lockp argument specifies a pointer to a simple or complex lock structure. You pass a simple or complex lock structure pointer if you want to release the lock. If you do not want to release a lock, pass the value zero (0).
In this call, fta_error_recovery passes the address of the simple lock.
The flags argument specifies the lock type. You can pass the bitwise inclusive OR of the valid lock bits defined in /usr/sys/include/sys/param.h.
In this call, fta_error_recovery passes the bitwise inclusive OR of the lock bits MS_LOCK_SIMPLE (calls mpsleep with a simple lock asserted) and MS_LOCK_ON_ERROR (forces mpsleep to relock the lock on failure). You would specify these bits only if you pass a simple or complex lock.
The mpsleep interface blocks (puts to sleep) the current kernel thread until a wakeup is issued on the address you specified in the channel argument. This interface is the symmetric multiprocessor (SMP) sleep call. The kernel thread blocks (sleeps) a maximum of timo divided by hz seconds. The value zero (0) means there is no timeout.
If you pass the PCATCH flag to the pri argument, mpsleep checks signals before and after blocking (sleeping). Otherwise, mpsleep does not check signals.
The mpsleep interface allows you to specify a pointer to a simple or complex lock structure that is associated with some resource. This interface unlocks this resource prior to blocking (sleeping). The flags argument specifies the lock type. The mpsleep interface releases the lock when the current kernel thread successfully performs an assert wait on the specified channel.
The mpsleep interface returns the value zero (0) if awakened (success) and EWOULDBLOCK if the timeout specified in the timo argument expires (failure). On success, mpsleep relocks the lock if you did not set MS_LOCK_NO_RELOCK in flags. On failure, it leaves the lock unlocked. If you set the flags argument to MS_LOCK_ON_ERROR, mpsleep relocks the lock on failures. [Return to example]
You can unblock (awaken) a kernel thread with the following interfaces:
Call this interface to unblock (awaken) the first kernel thread on the specified event.
Call this interface to unblock (awaken) all kernel threads on the specified event.
The following code fragment compares the calls to thread_wakeup_one and thread_wakeup by the if_fta device driver's ftaintr interface.
ftaintr(unit) int unit; {
.
.
.
fta_transition_state(sc, unit, PI_OPERATIONAL); [1]
.
.
.
/******************************************************* * Code fragment 1: Shows call to thread_wakeup_one * *******************************************************/
.
.
.
thread_wakeup_one((vm_offset_t)&sc->error_recovery_flag); [2] }
ftaintr(unit) int unit; {
.
.
.
fta_transition_state(sc, unit, PI_OPERATIONAL); [1]
.
.
.
/******************************************************* * Code fragment 2: Shows call to thread_wakeup * *******************************************************/
.
.
.
thread_wakeup((vm_offset_t)&sc->error_recovery_flag); [2] }
After fta_transition_state performs its tasks, it returns to ftaintr, which calls one of the following unblocking (or wakeup) kernel threads interfaces: thread_wakeup_one or thread_wakeup. Both of these interfaces take an event as the first argument. [Return to example]
The driver's fta_error_recovery interface is the kernel thread created and started to perform error recovery tasks. The fta_error_recovery interface blocked on the event stored in the error_recovery_flag member. [Return to example]
The thread_wakeup_one interface wakes up only the first kernel thread in the hash chain waiting for the event specified in the event argument. This interface is actually a convenience wrapper for the thread_wakeup_prim interface with the one_thread argument set to TRUE (wake up only the first kernel thread) and the result argument set to THREAD_AWAKENED (wakeup is normal).
The thread_wakeup interface wakes up all kernel threads waiting for the event specified in the event argument. This interface is actually a convenience wrapper for the thread_wakeup_prim interface with the one_thread argument set to FALSE (wake up all kernel threads) and the result argument set to THREAD_AWAKENED (wakeup is normal).
To terminate a kernel thread, call the thread_terminate interface. The thread_terminate interface prepares to stop or permanently stops execution of the specified kernel thread. You created and started this kernel thread in a previous call to the kernel_isrthread or kernel_thread_w_arg interface. These interfaces return a pointer to the thread structure associated with the newly created and started kernel thread. Device drivers use this pointer as a handle to identify the specific kernel thread that thread_terminate stops executing.
Typically, a kernel thread terminates itself. However, one kernel thread can terminate another kernel thread. A kernel thread that terminates itself must call thread_halt_self immediately after the call to thread_terminate. The reason for this is that thread_terminate only prepares the self-terminating kernel thread to stop execution. The thread_halt_self interface completes the work needed to stop execution (by performing the appropriate cleanup work) of the self-terminating kernel thread.
You do not need to terminate every kernel thread that you create. You should not terminate a kernel thread that is waiting for some event. The basic rule is that you should terminate only those kernel threads that you do not need anymore. For example, if a dynamically configured device driver uses kernel threads, you should terminate them in the CFG_OP_UNCONFIGURE entry point of the loadable driver's configure interface. The kernel threads are no longer needed after the driver is unconfigured.
Note that the thread_terminate interface (for kernel threads that terminate other kernel threads) not only permanently stops execution of the specified kernel thread, but it also frees any resources associated with that kernel thread; thus, this kernel thread can no longer be used.
The following code fragment shows you how the if_fta device driver's fta_error_recovery kernel thread terminates itself by calling thread_terminate and thread_halt_self. The fta_error_recovery interface is a kernel thread that starts up when the adapter becomes operational. This kernel thread resets the adapter if a fatal error occurs. The code fragment also shows the code that contains the call to kernel_thread_w_arg, which calls fta_error_recovery.
.
.
.
#include <kern/thread.h>
.
.
.
#define ADAP "fta"
.
.
.
extern task_t first_task;
.
.
.
struct fta_softc {
.
.
.
short reinit_thread_started; /* reinit thread running? */
.
.
.
short error_recovery_flag; /* flag to wake up a process */
.
.
.
};
.
.
.
struct ifnet {
.
.
.
short if_unit; /* subunit for lower-level driver */
.
.
.
};
.
.
.
fta_transition_state(sc, unit, state) struct fta_softc *sc; short unit; short state; {
.
.
.
switch(state) {
.
.
.
case PI_OPERATIONAL: { int s; NODATA_CMD *req_buff; thread_t err_recov_thread; if (sc->reinit_thread_started == FALSE) { err_recov_thread = kernel_thread_w_arg(first_task, fta_error_recovery, (void *)sc); if (err_recov_thread == NULL) { printf("%s%d: Cannot start error recovery thread.\n", ADAP, ifp->if_unit); } sc->reinit_thread_started = TRUE; }
.
.
.
/* Perform other cases */
.
.
.
void fta_error_recovery(sc) struct fta_softc *sc; { struct ifnet *ifp; int ret_val; /* * Collect the argument left by the kernel_thread_w_arg(). */ ifp = &sc->is_if; for(;;) { assert_wait_mesg((vm_offset_t)&sc->error_recovery_flag, TRUE,"ftaerr"); thread_block(); if (current_thread()->wait_result == THREAD_SHOULD_TERMINATE) { [1] ret_val = thread_terminate(err_recov_thread); [2] thread_halt_self(); [3] } }
.
.
.
/* Performs tasks to reset the adapter */
.
.
.
}
The current_thread interface (macro) is a pointer to the currently running kernel thread. Typically, device drivers use this interface to reference the wait_result member of the thread structure pointer associated with the currently running kernel thread. A device driver calls current_thread after calls to assert_wait_mesg and thread_block. If the device driver needs to set a timeout, then it calls current_thread after calls to assert_wait_mesg, thread_set_timeout, and thread_block. [Return to example]
The thread_terminate interface takes a thread_to_terminate argument, which is a pointer to the thread structure associated with the kernel thread that you want to terminate. This pointer was returned in a previous call to the kernel_isrthread or kernel_thread_w_arg interface.
The kernel_thread_w_arg interface returns this pointer to the err_recov_thread variable. This variable is passed to thread_terminate.
Upon successfully terminating the specified kernel thread, thread_terminate returns the constant KERN_SUCCESS. If the thread structure pointer passed to the thread_to_terminate argument does not identify a valid kernel thread, thread_terminate returns the constant KERN_INVALID_ARGUMENT. On any other error, thread_terminate returns the constant KERN_FAILURE. [Return to example]
The following code fragment shows you how the if_fta device driver's fta_transition_state interface terminates another kernel thread (in this example, the error recovery kernel thread) by calling only thread_terminate. The fta_transition_state interface changes the state of the driver by performing certain fixed tasks for a given state.
.
.
.
#include <kern/thread.h>
.
.
.
#define ADAP "fta"
.
.
.
extern task_t first_task;
.
.
.
struct fta_softc {
.
.
.
short reinit_thread_started; /* reinit thread running? */
.
.
.
short error_recovery_flag; /* flag to wake up a process */
.
.
.
};
.
.
.
struct ifnet {
.
.
.
short if_unit; /* subunit for lower-level driver */
.
.
.
};
.
.
.
fta_transition_state(sc, unit, state) struct fta_softc *sc; short unit; short state; {
.
.
.
int ret_val;
.
.
.
switch(state) {
.
.
.
case PI_OPERATIONAL: { int s; NODATA_CMD *req_buff; thread_t err_recov_thread; if (sc->reinit_thread_started == FALSE) { err_recov_thread = kernel_thread_w_arg(first_task, fta_error_recovery, (void *)sc); if (err_recov_thread == NULL) { printf("%s%d: Cannot start error recovery thread.\n", ADAP, ifp->if_unit); } sc->reinit_thread_started = TRUE; }
.
.
.
/* Perform other cases */
.
.
.
/* After performing all other cases, no more need for the */ /* kernel thread */ case PI_SHUTDOWN: { [1] ret_val = thread_terminate(err_recov_thread); [2]
.
.
.
void fta_error_recovery(sc) struct fta_softc *sc; { struct ifnet *ifp; /* * Collect the argument left by the kernel_thread_w_arg(). */ ifp = &sc->is_if; for(;;) { assert_wait_mesg((vm_offset_t)&sc->error_recovery_flag, TRUE,"ftaerr"); thread_block(); /* Performs tasks to reset the adapter */
.
.
.
}
.
.
.
}
The thread_terminate interface takes a thread_to_terminate argument, which is a pointer to the thread structure associated with the kernel thread that you want to terminate. This pointer was returned in a previous call to the kernel_isrthread or kernel_thread_w_arg interface.
The kernel_thread_w_arg interface returns this pointer to the err_recov_thread variable. This variable is passed to thread_terminate.
Upon successfully terminating the specified kernel thread, thread_terminate returns the constant KERN_SUCCESS. If the thread structure pointer passed to the thread_to_terminate argument does not identify a valid kernel thread, thread_terminate returns the constant KERN_INVALID_ARGUMENT. On any other error, thread_terminate returns the constant KERN_FAILURE. [Return to example]
To set a time delay on the current kernel thread, call the thread_set_timeout interface. The thread_set_timeout interface must be called as follows:
The following code fragment shows a call to thread_set_timeout by the if_fta device driver's fta_cmd_req interface. This interface puts a DMA request onto the request queue of the adapter.
.
.
.
#include <kern/thread.h> [1]
.
.
.
struct fta_softc {
.
.
.
struct cmd_buf *q_first; /* first in the request queue */ struct cmd_buf *q_last; /* last in the request queue */ lock_data_t cmd_buf_q_lock; /* lock for the cmdreq queue */
.
.
.
}; [2]
.
.
.
short fta_cmd_req(cmdbuf, sc, command) struct cmd_buf *cmdbuf; struct fta_softc *sc; short command; {
.
.
.
lock_write(&sc->cmd_buf_q_lock); [3]
.
.
.
assert_wait_mesg((vm_offset_t)cmdbuf, TRUE, "dmareq"); [4] lock_done(&sc->cmd_buf_q_lock); [5] thread_set_timeout(hz * 2); [6] thread_block(); [7]
.
.
.
}
Specifies a pointer to a cmd_buf data structure. This member represents the first command queue in the linked list.
Specifies a pointer to a cmd_buf data structure. This member represents the last command queue in the linked list.
Declares a lock structure called cmd_buf_q_lock. The purpose of this lock is to protect the integrity of the data stored in the linked list of cmd_buf data structures. Note that the alternate name lock_data_t is used to declare the complex lock structure. Embedding the complex lock in the fta_softc structure protects the cmd_buf structures for any number of instances.
The lock_write interface takes one argument: a pointer to the complex lock structure, lock. This is the lock structure associated with the resource on which you want to assert a complex lock with write access. The fta_cmd_req interface passes the address of the cmd_buf_q_lock member of the fta_softc structure pointer. [Return to example]
The assert_wait_mesg interface takes three arguments:
Value | Meaning |
TRUE | The current kernel thread is interruptible. This value means that a signal can awaken the current kernel thread. |
FALSE | The current kernel thread is not interruptible. This value means that only the specified event can awaken the current kernel thread. |
The code fragment shows that fta_cmd_req passes the value TRUE.
The lock_done interface takes one argument: a pointer to the complex lock structure, lock. The fta_cmd_req interface passes the address of the cmd_buf_q_lock member of the fta_softc structure pointer. [Return to example]
The thread_set_timeout interface takes one argument: the amount of time to wait for an event. The time is used in conjunction with the assert_wait interface. The fta_cmd_req interface passes the value hz * 2.
The time you specify to wait for the event is automatically canceled when the kernel thread awakes.
The thread_set_timeout interface does not return a value. [Return to example]