52.2 Thread Context

In NKS, a thread and its context are two essential complementary counterparts.

A thread context defines the run-time state of the thread, which includes such items as the following:

Very little data permanently belongs to a thread whereas practically everything that is useful to you as a programmer belongs to the context.

If you use pthreads or UNIX International threads, LibC handles the thread context automatically and transparently in most cases.

WARNING:Because threads and contexts are separate entities, a thread can lose its context. If your function returns the error, ENOCONTEXT (or NX_ENOCONTEXT for an NKS thread), see Section 52.2.4, Avoiding Loss of Context for information about this problem.

You need to learn how to manage the thread context. This section describes the following aspects of thread context:

52.2.1 Global Variables and Thread Context

Context is probably one of the most difficult aspects of NLM programming. Because programming on NetWare is not user programming but kernel programming, context is a significant problem. In a proper user environment, such items as errno, the current working directory, stdin, stdout, and stderr are all transparently handled by the operating environment which is made up of the user containment and static-linked libraries (or DLLs). On NetWare, an NLM application is really a kernel extension because, when it runs in ring 0, it operates within the same address space as the NetWare operating system and all the other currently running NLM applications.

Consequently, you cannot globally define variables such as int errno because its value could be set by any thread. For example, one thread erring out of a function could set it, and before this function could exit, check its return code, and ask for the errno value, another thread could change the value. Therefore, such values are kept in the thread context that is maintained by the library for each execution unit.

For example, in LibC the standard header, errno.h, contains the following declaration and macro:

     int             *__errno(void);
     #define errno   (*__errno())
  

When errno is referenced in code (and after preprocessing), it is not really a global integer variable. Instead, it calls a function that returns a pointer to a location in the library's thread-specific data that is maintained for the calling thread and a dereference of that location. Thus,

     errno = -1;
  

results in (after preprocessing):

     *__errno() = -1;
  

Besides errno, other standard variables such as h_errno are similarly defined in the LibC header files.

52.2.2 Context State

Context abstracts the runtime state of a thread to which the context is bound. The context abstraction encapsulates the following:

  • Execution stack

  • Execution priority

  • Work to do in the form of a function to call and an argument to pass to the function

  • Context type, either normal (NX_CTX_NORMAL) for application threads or work (NX_CTX_WORK) for anonymous threads

  • Flexible infrastructure for managing context-specific data, including key-value pairs (see Section 52.3, Context-Specific Data)

This clear separation of the execution vehicle (the thread) from the work being executed (the context) provides an efficient and scalable infrastructure for supporting services built as finite state machines.

A context is bound to a thread in one of the following ways:

  • As part of thread creation

  • As part of scheduling work to be executed by an anonymous thread

  • By explicit binding of the context to a thread

A bound context is unbound from a thread when one of the following events occurs:

  • The thread currently bound to the context explicitly binds itself to a different context.

  • The context in question is a work context and the work has been completed (as signified by the return from the context's start function).

  • The hosting thread exits.

When LibC changes the binding status of a context because work (or delayed work) is completed or because the hosting thread exits, LibC guarantees that the context is reset to its initial state.

52.2.3 Context Creation

Contexts are created by calling NXContextAlloc, which creates the context in its initial state, with no thread to run it.

By design, you cannot allocate the stack address for the thread, which frees you from dealing with stack life cycles and enforces better programming style. LibC allocates a stack in user space or in the kernel, depending on where the calling NLM is executing. You can, however, specify a stack size.

The context stack works identically to other NLM stacks and can be viewed in the NetWare debugger from the ESP register by using one of the following commands:

52.2.4 Avoiding Loss of Context

NLM programming can result in calls into LibC without a context. For example:

  • An application might register a callback.

  • An application might have the operating system create and schedule a thread or work-to-do on behalf of the application that is executing its code.

If your application contains code that is called or executed by a non-LibC thread (which does not include LibC work-to-do's or time-outs as they expect this type of behavior), your code must not call functions in LibC that require context.

If you try to execute a function that requires a context and one does not exist for the thread, the function returns an error, usually NX_ENOCONTEXT or ENOCONTEXT. To avoid this error, you can use NX_WRAP_INTERFACE to obtain a wrapper to pass in as the code address for the thread context. The thread executing the callback then has a LibC context. When you know that your callback will not be executed again because you have unregistered it or your application is exiting, use NX_UNWRAP_INTERFACE to clean up the context resources.

For sample code, see Wrap.c.

52.2.5 Swapping Contexts between Threads

The distinction between threads and contexts becomes important when you consider context swapping. If you create a thread without the flag NX_THR_BIND_CTX, you can call the NXThreadSwapContext function to switch from the context hosted on that thread to a different context and achieve a co-routine style of programming.

Contexts can also be scheduled on a pool of threads. If a thread calls NXThreadSwapContext on a given context, executes for a time, and then abandons the context, another thread can call the same function on the same context. The second thread begins where the first thread abandoned the context because processor state is part of the context.

For sample code, see CtxSwap.c.