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:
If your module has any local intialization, you should use a
shutdown list to keep track of what you
need to deinitialize:
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.
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.
Initialization procedures
Now we will define our initialization function:
The first thing to do is initialize the threads module, so that
we may correctly use the provided once mechanism.
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()
.
Having ensured that our lock and counter are valid, we take the
lock and increment the 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 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.
Finally, we release the lock on the initialization counter and return
an initialization status.
Earlier, we referred to an initialization function nasd_foosys_init()
.
We can define that as:
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:
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:
We must then initialize the shutdown module:
Now we'll create a shutdown list:
Finally, we'll perform any "real" local initialization. For example:
Shutdown procedures
We must define for users of our module a shutdown procedure, nasd_foo_shutdown()
.
We do so with:
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.
Of course, that leaves us with the job of defining
nasd_foo_real_shutdown()
, which must actually
perform the module 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.
We still have some other initialization to clean up, though:
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):