12.6 Marshalling Interfaces

You use marshalling code to create a call gate that transitions a thread originating in one ring or address space into another. In the case of NetWare, this refers to descending from ring 3 (a protected address space) into ring 0 (the kernel). On the way there, arguments must be validated and memory locked down to ensure the following:

The following sections provide guidelines for the major tasks in creating marshalling code:

For sample code that implements these features, see Marshalling Sample Code.

12.6.1 Code Registration

The first step in creating marshalling code for an existing function is to create an initialization function to run at or shortly after NLM start-up (at least before any potential consumer can call it from a protected address space). In the sample code at the end of this section, this function is named SetUpMarshalledInterfaces.

A clean-up function needs to reverse this process. The sample code contains one, named CleanupMarshalledInterfaces.

Marshalling code is only written in and registered from kernel-loaded NLMs. If your NLM loads in both places, you shouldn’t register (nor are the registration interfaces available to register) such interfaces when in a protected address space. To determine if you are running in a protected address space, call nlmisloadedprotected.

12.6.2 Writing the Marshalling Code

Marshalling code is written for a function that already exists in the kernel. For the registered marshalling code, the function is coded under a prefixed name though it does not matter what prefix is used. For the purposes of the sample code, we used M_.

The code below performs a number of checks and then calls the real function-the one that provides the functionality sought by a ring 3 thread. After execution, the error code, if any, is recuperated and returned from the call gate.

The following checks are performed. Refer to sample code (Section 12.6.4, Marshalling Sample Code) for the source of the variable names.

  • Verifies that the entire length of bufferZ, a null-terminated character array, is mappable and will not produce a page fault when accessed.

  • Verifies that the entire coded length of the memory at somePtr, as specified by lenOfSomePtr, is mappable.

  • Verifies that the struct stat passed at sbuf, as already known by the compiler as sizeof(struct stat), is mappable.

If the blocks of memory pointed at by the in-coming pointer arguments are valid, they are locked down using RxLockMemory before the call is made.

As an optimization and in general, pointers to scalar types should merely be reproduced on the stack via an autoclass variable because stack memory doesn’t need to be locked down. Any effect upon them must be reflected in those pointers before returning from the marshalling code. An example of this is the argument someOffset in the sample code. Instead of measuring the width of what someOffset points to, locking it down, and then passing it to the real function, another offset is created on the stack, it gathers the resulting value, and its value is assigned back to someOffset. This approach can also be used for limited aggregate types, strings, or any data that is of limited size. The approach lessens the impact of the marshalling operation on overall processing.

Finally, once the real function returns and its error gathered, all memory held is unlocked before returning from the kernel gate.

12.6.3 Monitoring Resources

If resources are created as a result of the real, lower function being called, they should be registered during the marshalling code’s execution in such a way that they may be cleaned up later if the address space faults.

For example, if our real function were socket, which creates a number of resources before returning successfully to the caller, RxRegisterKernelResource should be called with a function, call it cleanup_socket, to eliminate any memory, locks, etc. that might be leaked if the address space later went down. From close (the clean-up counterpart of socket), those resources would be unregistered since they would no longer be needed.

There are two types of resources that can be registered:

  • Process resources are actual resource objects such as memory and synchronization objects such as mutexes, semaphores, condition variables, and reader-writer locks.

  • Thread resources are limited to the state of the thread.

To continue our example, resources created by socket belong to the process since an open socket can be discovered and manipulated by any thread in the owning process. On the other hand, a thread might fall into an unbounded wait circumstance as a result of the real call, for example, select. A clean-up function, as shown in the sample code below, could be useful to withdraw it from there without which, tearing down the address space, even (and especially) in the case where it was coming down cleanly would result in a hang.

12.6.4 Marshalling Sample Code

  #include <ringx.h>
  #include <stddef.h>
  #include <unistd.h>
  #include <pthread.h>
  #include <sys/stat.h>
  
  // put these somewhere intelligent-probably in a header...
  void FooCleanUp( void *thread );
  int  Foo       ( char *bufferZ, void *somePtr, size_t lenOfSomePtr, 
                   struct stat *sbuf, off64_t *someOffset );
  
  
  // ...and this doesn’t need to go in the same file with the marshalling // code...
  void FooCleanUp
  (
     void *thread
  )
  {
     cancel((pthread_t) thread);
  }
  
  int Foo
  (
     char        *bufferZ,
     void        *somePtr,
     size_t      lenOfSomePtr,
     struct stat *sbuf,
     off64_t     *someOffset
  )
  {
     off64_t   how_much;
  
     if (bufferZ)
        printf(bufferZ);
  
     if (somePtr)
        memset(somePtr, ’-’, how_much = min(lenOfSomePtr, 256));
     else
        how_much = 0;
  
     if (someOffset)
        *someOffset = how_much;
  
     sleep(30);
  
     return 0;
  }
  
  
  
  // here starts the marshalling code...
  void StartOfMarshallingCode( void );
  void EndOfMarshallingCode( void );
  
  int  M_Foo( char *bufferZ, void *somePtr, size_t lenOfSomePtr, 
              struct stat *sbuf, off64_t *someOffset );
  
  
  void StartOfMarshallingCode( void )
  {
  }
  
  int M_Foo
  (
     char        *bufferZ,
     void        *somePtr,
     size_t      lenOfSomePtr,
     struct stat *sbuf,
     off64_t     *someOffset
  )
  {
     int         err;
     size_t      len, slen;
     off64_t     offset;
     pthread_t   thread;
  
     if (bufferZ)
     {
        RX_CHECK_STRLEN(bufferZ, len); 
  // (also available:RX_CHECK_WCSLEN)
  
        RxLockMemory((void *) bufferZ, len);
     }
  
     if (somePtr)
     {
        RX_CHECK_BUFFER(somePtr, lenOfSomePtr);
        RxLockMemory(somePtr, lenOfSomePtr);
     }
  
     if (sbuf)
     {
        slen = sizeof(struct stat);
  
        RX_CHECK_BUFFER(sbuf, slen);
        RxLockMemory(sbuf, slen);
     }
  
     thread = pthread_self();
  
     if (err = RxRegisterThreadResource(thread, CleanUpFooStuff))
     {
        if (bufferZ)
           RxUnlockMemory((void *) bufferZ, len);
        if (somePtr)
           RxUnlockMemory((void *) somePtr, lenOfSomePtr);
        if (sbuf)
           RxUnlockMemory((void *) sbuf, slen);
  
        return err;
     }
  
     err = Foo(bufferZ, somePtr, lenOfSomePtr, sbuf, &offset);
  
     (void) RxUnregisterThreadResource(thread);
  
     if (sbuf)
        RxUnlockMemory((void *) sbuf, slen);
     if (somePtr)
        RxUnlockMemory((void *) somePtr, lenOfSomePtr);
     if (bufferZ)
        RxUnlockMemory((void *) bufferZ, len);
  
     if (!err)
        *someOffset = offset;
  
     return err;
  }
  
  void EndOfMarshallingCode( void )
  {
  }
  
  
  // the following need not go into the same file as the above 
  // marshalling code...
  int  SetUpMarshalledInterfaces( void *moduleHandle );
  void CleanupMarshalledInterfaces( void *moduleHandle );
  
  static int  sCodeHandle = 0;
  
  int SetUpMarshalledInterfaces
  (
     void *moduleHandle
  )
  {
     int    argc;
  
     if (RxRegisterSysCall(M_Foo, "Foo", argc))
        return -1;
  
     return RxIdentifyCode(StartOfMarshallingCode, EndOfMarshallingCode,
            &sCodeHandle);
  }
  
  void CleanupMarshalledInterfaces
  (
     void *moduleHandle
  )
  {
     if (sCodeHandle)
        RxUnidentifyCode(sCodeHandle);
  
     RxUnregisterSysCall(M_Foo);
  }