NASD Programmer's Documentation
Adding Modules to NASD

Before adding a module to NASD, you must determine the following

Is this functionality specific to the drive?
If it is specific to drives, you may not want to construct a full-blown NASD module. Instead, you should see the sections Drive Structure and Shutdown Lists. Initialize your drive module in nasd_setup_disk() after all modules you depend on have been initialized, and register appropriate shutdown actions. Be sure not to make calls to your new module until it is initialized.

Is this functionality specific to clients?
If so, you should add it to the client library, built in client/. If it should be available to all NASD entities, you should add it to the common library, built in common/. You may also wish to consider building it as a separate library.

What other modules does this module use?
Any other modules used by your module must be initialized when your module initializes.

What other modules, if any, will use this new module?
This list should not contain any modules on the list you have just created. Otherwise, you have a circular chain of dependencies at initialization time.

As discussed in the Modules section, modules must internally count the number of times that they are initialized and shut down, and allocate and deallocate resources when it is appropriate to do so. This section will outline the easiest way to do so.

In this example, we will call our new module foo. We will implement an initialization function nasd_foo_init() which takes no arguments and returns nasd_status_t. We will define a shut down function nasd_foo_shutdown() which takes no arguments and returns void.

First, declare a counter to determine how many times the module has been initialized and not shut down:

int nasd_foo_use_counter;

If your module has any local intialization, you should use a shutdown list to keep track of what you need to deinitialize:

nasd_shutdown_list_t *nasd_foo_shutdown_list = NULL; Users of your module may be multithreaded, or you may even be faced with separate users of your module executing in separate threads. This requires a lock to protect the reference count on your module. NASD_DECLARE_MUTEX(nasd_foo_use_counter_lock) The astute reader has noticed that we have created a problem for ourselves. We have a lock to protect our initialization data, but we must initialize the lock at some point. The NASD threads API defines a special interface for this purpose- the once interface. This should only be used for module-initialization. NASD_DECLARE_ONCE(nasd_foo_init_once)

Initialization procedures

Now we will define our initialization function: /*
 * nasd_foo_init
 *
 * Keep a counter of the number of times we're initted and
 * shutdown. When the last shutdown arrives, really deallocate.
 * This lets multiple modules use us without knowing about
 * one another.
 */
nasd_status_t
nasd_foo_init()
{
}
The first thing to do is initialize the threads module, so that we may correctly use the provided once mechanism. nasd_status_t rc;

rc = nasd_threads_init();
if (rc)
  return(rc);

Now that we may use threads functionality, we will request that the threads module help us initialize our lock (nasd_foo_use_counter_lock), and our counter (nasd_foo_use_counter). To do that, we will provide it with a function to call once and only once- nasd_foosys_init(). nasd_once(&nasd_foo_init_once, nasd_foosys_init); Having ensured that our lock and counter are valid, we take the lock and increment the counter: NASD_LOCK_MUTEX(nasd_foo_use_counter_lock);
nasd_foo_use_counter++;
If the new counter value is 1, then we are experiencing a state transition from unused to used, and we should perform any actual activity necessary to initialize the module (other than initializing the threads module, since we already did so). We will remember if our "real" initialization has succeeded or failed. if (nasd_foo_use_counter == 1) {
  rc = nasd_foo_real_init();
  if (rc) {
    nasd_foo_use_counter = 0;
  }
}
If this is not the first time we're initializing, then we can get rid of that extra reference we're holding on the threads module (we will always keep at least one). Because the module has already initialized, we can indicate a successful initialization to the caller. else {
  /* get rid of extra threads init above */
  nasd_threads_shutdown();
  rc = NASD_SUCCESS;
}
Finally, we release the lock on the initialization counter and return an initialization status. NASD_UNLOCK_MUTEX(nasd_foo_use_counter_lock);

return(rc);

Earlier, we referred to an initialization function nasd_foosys_init(). We can define that as:

void
nasd_foosys_init()
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&nasd_foo_use_counter_lock);
  if (rc) {
    NASD_PANIC();
  }

  nasd_foo_use_counter = 0;
}

Our module initialization function will in turn call nasd_foo_real_init() whenever the initialization counter transits from 0 to 1. We now define that function: /*
 * nasd_foo_real_init
 *
 * Called when the module comes into use after not being
 * used (start-of-day call to nasd_foo_init(), or first
 * such call after the last call to nasd_foo_shutdown()
 * which actually deactivated the module).
 */
nasd_status_t
nasd_foo_real_init()
{
}
Our caller, nasd_foo_init(), has thoughtfully initialized the threads module for us, so we don't need to do that. However, if we fail initialization, we must be sure to shut down the threads module. Likewise, if we fail, we must be sure to do so without holding any additional resources. We will be using the shutdown module to keep track of our internal initialization, and the memory module as well. First, we initialize the memory module: nasd_status_t rc;

rc = nasd_mem_init();
if (rc) {
  nasd_threads_shutdown();
  return(rc);
}

We must then initialize the shutdown module: rc = nasd_shutdown_sys_init();
if (rc) {
  nasd_threads_shutdown();
  nasd_mem_shutdown();
  return(rc);
}
Now we'll create a shutdown list: rc = nasd_shutdown_list_init(&nasd_foo_shutdown_list);
if (rc) {
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();
  return(rc);
}
Finally, we'll perform any "real" local initialization. For example:   rc = nasd_foo_local_init_1(nasd_foo_shutdown_list);
  if (rc)
    goto bad;

  rc = nasd_foo_local_init_2(nasd_foo_shutdown_list);
  if (rc)
    goto bad;

  rc = nasd_foo_local_init_3(nasd_foo_shutdown_list);
  if (rc)
    goto bad;

  return(NASD_SUCCESS);

bad:
  rc = nasd_shutdown_list_shutdown(nasd_foo_shutdown_list);
  if (rc) {
    NASD_PANIC();
  }
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();

  return(rc);

Shutdown procedures

We must define for users of our module a shutdown procedure, nasd_foo_shutdown(). We do so with: /*
 * nasd_foo_shutdown
 *
 * Previous caller of nasd_foo_init() not using the foo
 * module any more. Deallocate and cleanup iff necessary.
 */
void
nasd_foo_shutdown()
{
}
The job of this function is simple: while protected by nasd_foo_use_counter_lock, our reference counter is decremented. If the counter goes to zero, any necessary shutdown activity is performed. NASD_LOCK_MUTEX(nasd_foo_use_counter_lock);
nasd_foo_use_counter--;
if (nasd_foo_use_counter == 0) {
  nasd_foo_real_shutdown();
}
NASD_UNLOCK_MUTEX(nasd_foo_use_counter_lock);
Of course, that leaves us with the job of defining nasd_foo_real_shutdown(), which must actually perform the module shutdown. /*
 * nasd_foo_real_shutdown
 *
 * Called when last user of the foo module calls nasd_foo_shutdown().
 * Clean up and deallocate resources.
 */
void
nasd_foo_real_shutdown()
{
}
All our module-specific initialization has been registered on our shutdown list, nasd_foo_shutdown_list. The first thing we'll do is enact this list. nasd_status_t rc;

rc = nasd_shutdown_list_shutdown(nasd_timeout_shutdown_list);
if (rc) {
  NASD_PANIC();
}

We still have some other initialization to clean up, though: nasd_shutdown_cleanup();
nasd_mem_shutdown();
nasd_threads_shutdown();

Putting it all together

A NASD module can be initialized and deinitialized with code like the following (this is suitable for copying, pasting, and query-replacing with appropriate modifications of the real_init function):

#include <nasd/nasd_options.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_shutdown.h>
#include <nasd/nasd_common.h>

NASD_DECLARE_ONCE(nasd_foo_init_once)
NASD_DECLARE_MUTEX(nasd_foo_use_counter_lock)
int nasd_foo_use_counter;
nasd_shutdown_list_t *nasd_foo_shutdown_list = NULL;

/*
 * nasd_foo_real_init
 *
 * Called when the module comes into use after not being
 * used (start-of-day call to nasd_foo_init(), or first
 * such call after the last call to nasd_foo_shutdown()
 * which actually deactivated the module).
 */
nasd_status_t
nasd_foo_real_init()
{
  nasd_status_t rc;

  NASD_ASSERT(nasd_foo_use_counter == 1);

  /* already did nasd_threads_init() */

  rc = nasd_mem_init();
  if (rc) {
    nasd_threads_shutdown();
    return(rc);
  }

  rc = nasd_shutdown_sys_init();
  if (rc) {
    nasd_threads_shutdown();
    nasd_mem_shutdown();
    return(rc);
  }

  rc = nasd_shutdown_list_init(&nasd_foo_shutdown_list);
  if (rc) {
    nasd_shutdown_cleanup();
    nasd_mem_shutdown();
    nasd_threads_shutdown();
    return(rc);
  }

  rc = nasd_foo_local_init_1(nasd_foo_shutdown_list);
  if (rc)
    goto bad;

  rc = nasd_foo_local_init_2(nasd_foo_shutdown_list);
  if (rc)
    goto bad;

  rc = nasd_foo_local_init_3(nasd_foo_shutdown_list);
  if (rc)
    goto bad;

  return(NASD_SUCCESS);

bad:
  rc = nasd_shutdown_list_shutdown(nasd_foo_shutdown_list);
  if (rc) {
    NASD_PANIC();
  }
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();
  return(rc);
}

/*
 * nasd_foo_real_shutdown
 *
 * Called when last user of the foo module calls nasd_foo_shutdown().
 * Clean up and deallocate resources.
 */
void
nasd_foo_real_shutdown()
{
  nasd_status_t rc;

  NASD_ASSERT(nasd_foo_use_counter == 0);

  rc = nasd_shutdown_list_shutdown(nasd_foo_shutdown_list);
  if (rc) {
    NASD_PANIC();
  }
  nasd_foo_shutdown_list = NULL;
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();
}

/*
 * nasd_foosys_init
 *
 * Executed exactly once, the first time nasd_foo_init() is
 * called. Initialize counter tracking number of times module
 * is initted.
 */
void
nasd_foosys_init()
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&nasd_foo_use_counter_lock);
  if (rc) {
    NASD_PANIC();
  }

  nasd_foo_use_counter = 0;
}

/*
 * nasd_foo_init
 *
 * Keep a counter of the number of times we're initted and
 * shutdown. When the last shutdown arrives, really deallocate.
 * This lets multiple modules use us without knowing about
 * one another.
 */
nasd_status_t
nasd_foo_init()
{
  nasd_status_t rc;

  rc = nasd_threads_init();
  if (rc)
    return(rc);
  nasd_once(&nasd_foo_init_once, nasd_foosys_init);
  NASD_LOCK_MUTEX(nasd_foo_use_counter_lock);
  nasd_foo_use_counter++;
  if (nasd_foo_use_counter == 1) {
    rc = nasd_foo_real_init();
    if (rc) {
      nasd_foo_use_counter = 0;
    }
  }
  else {
    /* get rid of extra threads init above */
    nasd_threads_shutdown();
    rc = NASD_SUCCESS;
  }
  NASD_UNLOCK_MUTEX(nasd_foo_use_counter_lock);

  return(rc);
}

/*
 * nasd_foo_shutdown
 *
 * Previous caller of nasd_foo_init() not using the foo
 * module any more. Deallocate and cleanup iff necessary.
 */
void
nasd_foo_shutdown()
{
  NASD_ASSERT(nasd_foo_use_counter != 0);

  NASD_LOCK_MUTEX(nasd_foo_use_counter_lock);
  nasd_foo_use_counter--;
  if (nasd_foo_use_counter == 0) {
    nasd_foo_real_shutdown();
  }
  NASD_UNLOCK_MUTEX(nasd_foo_use_counter_lock);
}


<--- ---> ^<br>|<br>|
Adding subdirectories Adding marshalled types NASD Programmer's Documentation