Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page >

Kernel Thread APIs

The Mac OS X scheduler provides a number of public APIs. While many of these APIs should not be used, the APIs to create, destroy, and alter kernel threads are of particular importance. While not technically part of the scheduler itself, they are inextricably tied to it.

The scheduler directly provides certain services that are commonly associated with the use of kernel threads, without which kernel threads would be of limited utility. For example, the scheduler provides support for wait queues, which are used in various synchronization primitives such as mutex locks and semaphores.

In this section:

Creating and Destroying Kernel Threads
SPL and Friends
Wait Queues and Wait Primitives


Creating and Destroying Kernel Threads

The recommended interface for creating threads within the kernel is through the I/O Kit. It provides IOCreateThread, IOThreadSelf, and IOExitThread functions that make it relatively painless to create threads in the kernel.

The basic functions for creating and terminating kernel threads are:

IOThread IOCreateThread(IOThreadFunc function, void *argument);
IOThread IOThreadSelf(void);
void IOExitThread(void);

With the exception of IOCreateThread (which is a bit more complex), the I/O Kit functions are fairly thin wrappers around Mach thread functions. The types involved are also very thin abstractions. IOThread is really the same as thread_t.

The IOCreateThread function creates a new thread that immediately begins executing the function that you specify. It passes a single argument to that function. If you need to pass more than one argument, you should dynamically allocate a data structure and pass a pointer to that structure.

For example, the following code creates a kernel thread and executes the function myfunc in that thread:

#include <IOKit/IOLib.h>
#include <libkern/libkern.h>
#include <sys/malloc.h>
 
struct mydata {
    int three;
    char *string;
};
 
static void myfunc(void *myarg) {
    struct mydata *md = (struct mydata *)myarg;
    IOLog("Passed %d = %s\n", md->three, md->string);
    IOExitThread();
}
 
void start_threads() {
    IOThread mythread;
    struct mydata *md = (struct mydata *)malloc(sizeof(*md));
    md->three = 3; md->string = (char *)malloc(2 * sizeof(char));
    md->string[0] = '3'; md->string[1] = '\0';
 
    // Start a thread using IOCreateThread
    mythread = IOCreateThread(&myfunc, (void *)md);
}

One other useful function is thread_terminate. This can be used to destroy an arbitrary thread (except, of course, the currently running thread). This can be extremely dangerous if not done correctly. Before tearing down a thread with thread_terminate, you should lock the thread and disable any outstanding timers against it. If you fail to deactivate a timer, a kernel panic will occur when the timer expires.

With that in mind, you may be able to terminate a thread as follows:

thread_terminate(getact_thread(thread));

There thread is of type thread_t. In general, you can only be assured that you can kill yourself, not other threads in the system. The function thread_terminate takes a single parameter of type thread_act_t (a thread activation). The function getact_thread takes a thread shuttle (thread_shuttle_t) or thread_t and returns the thread activation associated with it.

SPL and Friends

BSD–based and Mach–based operating systems contain legacy functions designed for basic single-processor synchronization. These include functions such as splhigh, splbio, splx, and other similar functions. Since these functions are not particularly useful for synchronization in an SMP situation, they are not particularly useful as synchronization tools in Mac OS X.

If you are porting legacy code from earlier Mach–based or BSD–based operating systems, you must find an alternate means of providing synchronization. In many cases, this is as simple as taking the kernel or network funnel. In parts of the kernel, the use of spl functions does nothing, but causes no harm if you are holding a funnel (and results in a panic if you are not). In other parts of the kernel, spl macros are actually used. Because spl cannot necessarily be used for its intended purpose, it should not be used in general unless you are writing code it a part of the kernel that already uses it. You should instead use alternate synchronization primitives such as those described in “Synchronization Primitives”.

Wait Queues and Wait Primitives

The wait queue API is used extensively by the scheduler and is closely tied to the scheduler in its implementation. It is also used extensively in locks, semaphores, and other synchronization primitives. The wait queue API is both powerful and flexible, and as a result is somewhat large. Not all of the API is exported outside the scheduler, and parts are not useful outside the context of the wait queue functions themselves. This section documents only the public API.

The wait queue API includes the following functions:

void wait_queue_init(wait_queue_t wq, int policy);
extern wait_queue_t wait_queue_t wait_queue_alloc(int policy);
void wait_queue_free(wait_queue_t wq);
void wait_queue_lock(wait_queue_t wq);
void wait_queue_lock_try(wait_queue_t wq);
void wait_queue_unlock(wait_queue_t wq);
boolean_t wait_queue_member(wait_queue_t wq, wait_queue_sub_t wq_sub);
boolean_t wait_queue_member_locked(wait_queue_t wq, wait_queue_sub_t  wq_sub);
kern_return_t wait_queue_link(wait_queue_t wq, wait_queue_sub_t  wq_sub);
kern_return_t wait_queue_unlink(wait_queue_t wq, wait_queue_sub_t  wq_sub);
kern_return_t wait_queue_unlink_one(wait_queue_t wq,
            wait_queue_sub_t *wq_subp);
void wait_queue_assert_wait(wait_queue_t wq, event_t event,
            int interruptible);
void wait_queue_assert_wait_locked(wait_queue_t wq, event_t event,
            int interruptible, boolean_t unlocked);
kern_return_t wait_queue_wakeup_all(wait_queue_t wq, event_t event,
            int result);
kern_return_t wait_queue_peek_locked(wait_queue_t wq, event_t event,
            thread_t *tp, wait_queue_t *wqp);
void wait_queue_pull_thread_locked(wait_queue_t wq, thread_t thread,
            boolean_t unlock);
thread_t wait_queue_wakeup_identity_locked(wait_queue_t wq, event_t  event,
            int result, boolean_t unlock);
kern_return_t wait_queue_wakeup_one(wait_queue_t wq, event_t event,
            int result);
kern_return_t wait_queue_wakeup_one_locked(wait_queue_t wq, event_t  event,
            int result, boolean_t unlock);
kern_return_t wait_queue_wakeup_thread(wait_queue_t wq, event_t  event,
            thread_t thread, int result);
kern_return_t wait_queue_wakeup_thread_locked(wait_queue_t wq, event_t  event,
            thread_t thread, int result, boolean_t unlock);
kern_return_t wait_queue_remove(thread_t thread);

Most of the functions and their arguments are straightforward and are not presented in detail. However, a few require special attention.

Most of the functions take an event_t as an argument. These can be arbitrary 32-bit values, which leads to the potential for conflicting events on certain wait queues. The traditional way to avoid this problem is to use the address of a data object that is somehow related to the code in question as that 32-bit integer value.

For example, if you are waiting for an event that indicates that a new block of data has been added to a ring buffer, and if that ring buffer’s head pointer was called rb_head, you might pass the value &rb_head as the event ID. Because wait queue usage does not generally cross address space boundaries, this is generally sufficient to avoid any event ID conflicts.

Notice the functions ending in _locked. These functions require that your thread be holding a lock on the wait queue before they are called. Functions ending in _locked are equivalent to their nonlocked counterparts (where applicable) except that they do not lock the queue on entry and may not unlock the queue on exit (depending on the value of unlock). The remainder of this section does not differentiate between locked and unlocked functions.

The wait_queue_alloc and wait_queue_init functions take a policy parameter, which can be one of the following:

You should not use the wait_queue_init function outside the scheduler. Because a wait queue is an opaque object outside that context, you cannot determine the appropriate size for allocation. Thus, because the size could change in the future, you should always use wait_queue_alloc and wait_queue_free unless you are writing code within the scheduler itself.

Similarly, the functions wait_queue_member, wait_queue_member_locked, wait_queue_link, wait_queue_unlink, and wait_queue_unlink_one are operations on subordinate queues, which are not exported outside the scheduler.

The function wait_queue_member determines whether a subordinate queue is a member of a queue.

The functions wait_queue_link and wait_queue_unlink link and unlink a given subordinate queue from its parent queue, respectively.

The function wait_queue_unlink_one unlinks the first subordinate queue in a given parent and returns it.

The function wait_queue_assert_wait causes the calling thread to wait on the wait queue until it is either interrupted (by a thread timer, for example) or explicitly awakened by another thread. The interruptible flag indicates whether this function should allow an asynchronous event to interrupt waiting.

The function wait_queue_wakeup_all wakes up all threads waiting on a given queue for a particular event.

The function wait_queue_peek_locked returns the first thread from a given wait queue that is waiting on a given event. It does not remove the thread from the queue, nor does it wake the thread. It also returns the wait queue where the thread was found. If the thread is found in a subordinate queue, other subordinate queues are unlocked, as is the parent queue. Only the queue where the thread was found remains locked.

The function wait_queue_pull_thread_locked pulls a thread from the wait queue and optionally unlocks the queue. This is generally used with the result of a previous call to wait_queue_peek_locked.

The function wait_queue_wakeup_identity_locked wakes up the first thread that is waiting for a given event on a given wait queue and starts it running but leaves the thread locked. It then returns a pointer to the thread. This can be used to wake the first thread in a queue and then modify unrelated structures based on which thread was actually awakened before allowing the thread to execute.

The function wait_queue_wakeup_one wakes up the first thread that is waiting for a given event on a given wait queue.

The function wait_queue_wakeup_thread wakes up a given thread if and only if it is waiting on the specified event and wait queue (or one of its subordinates).

The function wait_queue_remove wakes a given thread without regard to the wait queue or event on which it is waiting.



< Previous PageNext Page >


Last updated: 2006-11-07




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice