[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


C    Dynamically Configurable Kernel Subsystems

Before the Digital UNIX system supported dynamically configurable subsystems, system administrators managed kernel subsystems by editing their system's configuration file. Each addition or removal of a subsystem or each change in a subsystem parameter required rebuilding the kernel, an often difficult and time-consuming process. System administrators responsible for a number of systems had to make changes to each system's configuration file and rebuild each kernel.

Dynamically configurable subsystems allow system administrators to modify system parameters, and load and unload subsystems without editing files and rebuilding the kernel. System administrators use the sysconfig command to configure the subsystems of their kernel. Using this command, system administrators can load and configure, unload and unconfigure, reconfigure (modify), and query subsystems on their local system and on remote systems.

When you create a new kernel subsystem or modify an existing kernel subsystem, you can write the subsystem so that it is dynamically configurable. This appendix explains how to make a subsystem dynamically configurable by providing the following information:

Device driver writers should note device-driver specific issues when writing loadable device drivers. For information about writing loadable device drivers, see Writing Device Drivers: Tutorial.


[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


C.1    Overview of Dynamically Configurable Subsystems

Many Digital UNIX kernel subsystems are static, meaning that they are linked with the kernel at build time. After the kernel is built, these subsystems cannot be loaded or unloaded. An example of a static subsystem is the vm (virtual memory) subsystem. This subsystem must be present in the kernel for the system to operate correctly.

Some kernel subsystems are or can be loadable. A loadable subsystem is one that can be added to or removed from the kernel without rebuilding the kernel. An example of a subsystem that is loadable is the presto subsystem, which is loaded only when the Prestoserve software is in use.

Both static and loadable subsystems can be dynamically configurable.

Like traditional kernel subsystems, dynamically configurable subsystems have parameters, called attributes. Examples of subsystem attributes are timeout values, table sizes and locations in memory, and subsystem names. You define the attributes for the subsystem in an attribute table. (Attribute tables are described in Section C.2.)

Before initially configuring a loadable subsystem, system administrators can store values for attributes in the sysconfigtab database. This database is stored in the /etc/sysconfigtab file and is loaded into kernel memory at boot time. The values stored in this database become the initial value for the subsystem's attributes, whether your subsystem has supplied an initial value for the attribute. Figure C-1 demonstrates how initial attribute values come from the sysconfigtab database.

Figure C-1: System Attribute Value Initialization

Notice in Figure C-1 that the size attribute receives its initial value from the sysconfigtab database even though the subsystem initializes the size attribute to zero.

Using an attribute table declared in the subsystem code, you control which of the subsystem's attribute values can be set at initial configuration. (For information about how you control the attributes that can be set in the sysconfigtab database, see Section C.2.)

In addition to being able to store attribute values for initial configuration, system administrators can query and reconfigure attribute values at any time when the subsystem is configured into the kernel. During a query request, attribute values are returned to the system administrator. During a reconfiguration request, attribute values are modified. How the return or modification occurs depends upon how attributes are declared in the subsystem code:

Again, you control which of the subsystem's attribute values can be queried or reconfigured, as described in Section C.2.

In addition to an attribute table, each dynamically configurable subsystem contains a configuration routine. This routine performs tasks such as calculating the values of attributes that are maintained in the subsystem. This routine also performs subsystem-specific tasks, which might include, for example, determining how large a table needs to be or storing memory locations in local variables that can be used by the subsystem. (Section C.3 describes how you create the configuration routine.) The kernel calls the subsystem configuration routine each time the subsystem is configured, queried, reconfigured, or unconfigured.

Any subsystem that can be configured into the kernel can also be unconfigured from the kernel. When a system administrator unconfigures a subsystem from the kernel, the kernel memory occupied by that subsystem is freed if the subsystem is loadable. The kernel calls the subsystem configuration routine during an unconfigure request to allow the subsystem to perform any subsystem specific unconfiguration tasks. An example of a subsystem specific unconfiguration task is freeing memory allocated by the subsystem code.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.2    Overview of Attribute Tables

The key to creating a good dynamically configurable subsystem is declaring a good attribute table. The attribute table defines the subsystem's attributes, which are similar to system parameters. (Examples of attributes are timeout values, table sizes and locations in memory, and so on.) The attribute table exists in two forms, the definition attribute table and the communication attribute table:

The reason for having two types of attribute tables is to save kernel memory. Some of the information in the definition attribute table and the communication attribute table (such as the name and datatypes of the attributes) is the same. However, much of the information differs. For example, the definition attribute table need not store the status of a request because no requests have been made at attribute definition time. Likewise, the communication attribute table does not need to contain a list of the supported requests for each attribute. To save kernel memory, each attribute table contains only the needed information.

Note

Attribute names defined in a subsystem attribute table must not begin with the string method. This string is reserved for naming attributes used in loadable device driver methods. For more information about device driver methods, see Writing Device Drivers: Tutorial.

The sections that follow explain both types of attribute tables by showing and explaining their declaration in /sys/include/sys/sysconfig.h.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.2.1    Definition Attribute Table

The definition attribute table has the data type cfg_subsys_attr_t, which is a structure of attributes declared as follows in the /sys/include/sys/sysconfig.h file:

typedef struct cfg_attr {
        char         name[CFG_ATTR_NAME_SZ]; [1]
        uint         type; [2]
        uint         operation; [3]
        whatever     address; [4]
        uint         min; [5]
        uint         max;
        uint         binlength; [6]
}cfg_subsys_attr_t;

  1. The name of the attribute is stored in name field. You choose this name, which can be any string of alphabetic characters, with a length of between two characters and the value stored in the CFG_ATTR_NAME_SZ constant. The CFG_ATTR_NAME_SZ constant is defined in the /sys/include/sys/sysconfig.h file. [Return to example]

  2. You specify the attribute data type in this field, which can be one of the data types listed in Table C-1.

    Table C-1: Attribute Data Types

    Data Type Name Description
    CFG_ATTR_STRTYPE Null terminated array of characters (char*)
    CFG_ATTR_INTTYPE 32-bit signed number (int)
    CFG_ATTR_UINTTYPE 32-bit unsigned number (unsigned)
    CFG_ATTR_LONGTYPE 64-bit signed number (long)
    CFG_ATTR_ULONGTYPE 64-bit unsigned number
    CFG_ATTR_BINTYPE Array of bytes

    [Return to example]

  3. The operation field specifies the requests that can be performed on the attribute. You specify one or more of the request codes listed in Table C-2 in this field.

    The CFG_OP_UNCONFIGURE request code has no meaning for individual attributes because you cannot allow the unconfiguration of a single attribute. Therefore, you cannot specify CFG_OP_UNCONFIGURE in the operation field.

    Table C-2: Codes that Determine the Requests Allowed for an Attribute

    Request Code Meaning
    CFG_OP_CONFIGURE The value of the attribute can be set when the subsystem is initially configured.
    CFG_OP_QUERY The value of the attribute can be displayed at any time while the subsystem is configured.
    CFG_OP_RECONFIGURE The value of the attribute can be modified at any time while the subsystem is configured.

    [Return to example]

  4. The address field determines whether the kernel has access to the value of the attribute.

    If you specify an address in this field, the kernel can read and modify the value of the attribute. When the kernel receives a query request from the sysconfig command, it reads the value in the location you specify in this field and returns that value. For a configure or reconfigure request, the kernel checks that the data type of the new value is appropriate for the attribute and that the value falls within the minimum and maximum values for the attribute. If the value meets these requirements, the kernel stores the new value for the attribute. (You specify minimum and maximum values in the next two fields in the attribute definition.)

    In some cases, you want or need to respond to query, configure, or reconfigure requests for an attribute in the subsystem code. In this case, specify a NULL in this field. For more information about how you control attribute values, see Section C.3. [Return to example]

  5. The min and max fields define the minimum and maximum allowed values for the attribute. You choose these values for the attribute.

    The kernel interprets the contents of these two fields differently, depending on the data type of the attribute. If the attribute is one of the integer data types, these fields contain minimum and maximum integer values. For attributes with the CFG_ATTR_STRTYPE data type, these fields contain the minimum and maximum lengths of the string. For attributes with the CFG_ATTR_BINTYPE data type, these fields contain the minimum and maximum numbers of bytes you can modify. [Return to example]

  6. If you want the kernel to be able to read and modify the contents of a binary attribute, you use the binlength field to specify the current size of the binary data. If the kernel modifies the length of the binary data stored in the attribute, it also modifies the contents of this field.

    This field is not used if the attribute is an integer or string or if you intend to respond to query and reconfigure request for a binary attribute in the configuration routine. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.2.2    Example Definition Attribute Table

Example C-1 provides an example definition attribute table to help you understand its contents and use. The example attribute table is for a fictional kernel subsystem named table_mgr. The configuration routine for the fictional subsystem is shown and explained in Section C.3.

Example C-1: Example Attribute Table


 
#include <sys/sysconfig.h> #include <sys/errno.h>
 
/* * Initialize attributes */ static char name[] = "Default Table"; static int size = 0; static long *table = NULL;
 

 
/* * Declare attributes in an attribute table */
 
cfg_subsys_attr_t table_mgr_attrbutes[] = { /* * "name" is the name of the table */ {"name", [1] CFG_ATTR_STRTYPE, [2] CFG_OP_CONFIGURE | CFG_OP_QUERY | CFG_OP_RECONFIGURE, [3] (caddr_t) name, [4] 2, sizeof(name), [5] 0 [6] }, /* * "size" indicates how large the table should be */ {"size", CFG_ATTR_INTTYPE, CFG_OP_CONFIGURE | CFG_OP_QUERY | CFG_OP_RECONFIGURE, NULL, 1, 10, 0}, /* * "table" is a binary representation of the table */ {"table", CFG_ATTR_BINTYPE, CFG_OP_QUERY, NULL, 0, 0, 0}, /* * "element" is a cell in the table array */ {"element", CFG_ATTR_LONGTYPE, CFG_OP_QUERY | CFG_OP_RECONFIGURE, NULL, 0, 99, 0}, {,0,0,0,0,0,0} /* required last element */ };

The final entry in the table, {,0,0,0,0,0,0}, is an empty attribute. This attribute signals the end of the attribute table and is required in all attribute tables.

The first line in the attribute table defines the name of the table. This attribute table is named table_mgr_attributes. The following list explains the fields in the attribute name:

  1. The name of the attribute is stored in the name field, which is initialized to Default Table by the data declaration that precedes the attribute table. [Return to example]

  2. The attribute data type is CFG_ATTR_STRTYPE, which is a null terminated array of characters. [Return to example]

  3. This field specifies the operations that can be performed on the attribute. In this case, the attribute can be configured, queried, and reconfigured. [Return to example]

  4. This field determines whether the kernel has access to the value of the attribute.

    If you specify an address in this field, as shown in the example, the kernel can read and modify the value of the attribute. When the kernel receives a query request from the sysconfig command, it reads the value in the location you specify in this field and returns that value. For a configure or reconfigure request, the kernel checks that the data type of the new value is appropriate for the attribute and that the value falls within the minimum and maximum values for the attribute. If the value meets these requirements, the kernel stores the new value for the attribute. (You specify minimum and maximum values in the next two fields in the attribute definition.) [Return to example]

  5. These two fields define the minimum allowed value for the attribute (in this case, two), and the maximum allowed value for the attribute (in this case, sizeof(name)).

    If you want the minimum and maximum values of the attribute to be set according to the system minimum and maximum values, you can use one of the constants defined in the /usr/include/limits.h file. [Return to example]

  6. If you want the kernel to be able to read and modify the contents of a binary attribute, use this field to specify the current size of the binary data. If the kernel modifies the length of the binary data stored in the attribute, it also modifies the contents of this field.

    This field is not used if the attribute is an integer or string or if you intend to respond to query and reconfigure request for a binary attribute in the configuration routine. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.2.3    Communication Attribute Table

The communication attribute table, which is declared in the /sys/include/sys/sysconfig.h file, has the cfg_attr_t data type. As the following example shows, this data type is a structure of attributes:

typedef struct cfg_attr {
        char         name[CFG_ATTR_NAME_SZ]; [1]
        uint         type; [2]
        uint         status; [3]
        uint         operation; [4]
        long         index; [5]
        union { [6]
          struct {
                   caddr_t val;
                   ulong   min_len;
                   ulong   max_len;
                   void    (*disposal)();
                 }str;
          struct {
                   caddr_t val;
                   ulong   min_size;
                   ulong   max_size;
                   void    (*disposal)();
                   ulong   val_size;
                 }bin;
          struct {
                   caddr_t val;
                   ulong   min_len;
                   ulong   max_len;
                 }num;
               }attr;
}cfg_attr_t;

  1. The name field specifies the name of the attribute, following the same attribute name rules as the name field in the definition attribute table. [Return to example]

  2. The type field specifies the data type of the attribute, as listed in Table C-1. [Return to example]

  3. The status field contains a predefined status code. Table C-3 lists the possible status values.

    Table C-3: Attribute Status Codes

    Status Code Meaning
    CFG_ATTR_EEXISTS Attribute does not exist.
    CFG_ATTR_EINDEX Invalid attribute index.
    CFG_ATTR_ELARGE Attribute value or size is too large.
    CFG_ATTR_EMEM No memory available for the attribute.
    CFG_ATTR_EOP Attribute does not support the requested operation.
    CFG_ATTR_ESMALL Attribute value or size is too small.
    CFG_ATTR_ESUBSYS The kernel is disallowed from configuring, responding to queries on, or reconfiguring the subsystem. The subsystem code must perform the operation.
    CFG_ATTR_ETYPE Invalid attribute type or mismatched attribute type.
    CFG_ATTR_SUCCESS Successful operation.

    [Return to example]

  4. The operation field contains one of the operation codes listed in Table C-2. [Return to example]

  5. The index field is an index into a structured attribute. [Return to example]

  6. The attr union contains the value of the attribute and its maximum and minimum values.

    For attributes with the CFG_ATTR_STRTYPE data type, the val variable contains string data. The minimum and maximum values are the minimum and maximum lengths of the string. The disposal routine is a routine you write to free the kernel memory when your application is finished with it.

    For attributes with the CFG_ATTR_BINTYPE data type, the val field contains a binary value. The minimum and maximum values are the minimum and maximum numbers of bytes you can modify. The disposal routine is a routine you write to free the kernel memory when your application is finished with it. val_size variable contains the current size of the binary data.

    For numerical data types, the val variable contains an integer value and the minimum and maximum values are also integer values. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.2.4    Example Communication Attribute Table

This section describes an example communication attribute table to help you understand its contents and use. The example attribute table is for a fictional kernel subsystem named table_mgr. The configuration routine for the fictional subsystem is shown and explained in Section C.3.

table_mgr_configure(
 cfg_op_t           op,               /*Operation code*/ [1]
 caddr_t            indata,           /*Data passed to the subsystem*/ [2]
 ulong              indata_size,      /*Size of indata*/
 caddr_t            outdata,          /*Data returned to kernel*/ [3]
 ulong              outdata_size)     /*Count of return data items*/

 
{

The following list explains the fields in the table_mgr_configure communication attribute table:

  1. The op variable contains the operation code, which can be one of the following:

    CFG_OP_CONFIGURE
    CFG_OP_QUERY
    CFG_OP_RECONFIGURE
    CFG_OP_UNCONFIGURE

  2. The indata structure delivers data of indata_size to the configuration routine. If the operation code is CFG_OP_CONFIGURE or CFG_OP_QUERY the data is a list of attribute names that are to be configured or queried. For the CFG_OP_RECONFIGURE operation code, the data consists of attribute names and values. No data is passed to the configuration routine when the operation code is CFG_OP_UNCONFIGURE. [Return to example]

  3. The outdata structure and the outdata_size variables are placeholders for possible future expansion of the configurable subsystem capabilities. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3    Creating a Configuration Routine

To make the subsystem configurable, you must define a configuration routine. This routine works with the definition attribute table to configure, reconfigure, answer queries on, and unconfigure the subsystem.

Depending upon the needs of the subsystem, the configuration routine might be simple or complicated. Its purpose is to perform tasks that the kernel cannot perform for you. Because you can inform the kernel of the location of the attributes in the definition attribute table, it is possible for the kernel to handle all configure, reconfigure, and query requests for an attribute. However, the amount of processing done during these requests is then limited. For example, the kernel cannot calculate the value of an attribute for you, so attributes whose value must be calculated must be handled by a configuration routine.

The sections that follow describe an example configuration routine. The example routine is for a fictional table_mgr subsystem that manages a table of binary values in the kernel. The configuration routine performs these tasks:

Source code for this subsystem is included on the system in the /usr/examples/cfgmgr directory. The definition attribute table for this subsystem is shown in Section C.2.2. The communication attribute table for this subsystem is shown in Section C.2.4.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3.1    Performing Initial Configuration

At initial configuration, the table_mgr subsystem creates a table that it maintains. As shown in Example C-1, the system administrator can set the name and size of the table at initial configuration. To set these values, the system administrator stores the desired values in the sysconfigtab database.

The default name of the table, defined in the subsystem code, is Default Table. The default size of the table is zero elements.

The following example shows the code that is executed during the initial configuration of the table_mgr subsystem:


.
.
.
switch(op){ [1] case CFG_OP_CONFIGURE:
 
attributes = (cfg_attr_t*)indata; [2]
 
for (i=0; i<indata_size; i++){ [3] if (attributes[i].status == CFG_ATTR_ESUBSYS) { [4]
 
if (!strcmp("size", attributes[i].name)){ [5] /* Set the size of the table */ table = (long *) kalloc(attributes[i].attr.num.val*sizeof(long)); [6]
 
/* * Make sure that memory is available */
 
if (table == NULL) { [7] attributes[i].status = CFG_ATTR_EMEM; continue; }
 
/* * Success, so update the new table size and attribute status */
 
size = attributes[i].attr.num.val; [8] attributes[i].status = CFG_ATTR_SUCCESS; continue; } } } break;
.
.
.

  1. The configuration routine contains a switch statement to allow the subsystem to respond to the various possible operations. The subsystem performs different tasks, depending on the value of the op variable. [Return to example]

  2. This statement initializes the pointer attributes. The configuration routine can now manipulate the data it was passed in the indata structure. [Return to example]

  3. The for loop examines the status of each attribute passed to the configuration routine. [Return to example]

  4. If the status field for the attribute contains the CFG_ATTR_ESUBSYS status, the configuration routine must configure that attribute. [Return to example]

  5. For the initial configuration, the only attribute that needs to be manipulated is the size attribute. The code within the if statement is executed only when the size attribute is the current attribute. [Return to example]

  6. When the status field contains CFG_ATTR_ESUBSYS and the attribute name field contains size, the local variable table receives the address of an area of kernel memory. The area of kernel memory must be large enough to store a table of the size specified in attributes[i].attr.num.val. The value specified in attributes[i].attr.num.val is an integer that specifies the number of longwords in the table. The kernel reads the integer value from the sysconfigtab database and passes it to the configuration routine in the attr union. [Return to example]

  7. The kalloc routine returns NULL if it is unable to allocate kernel memory. If no memory has been allocated for the table, the configuration routine returns CFG_ATTR_EMEM, indicating that no memory was available. When this situation occurs, the kernel displays an error message. The subsystem is configured into the kernel, but the system administrator must use the sysconfig command to reset the size of the table. [Return to example]

  8. If kernel memory is successfully allocated, the table size from the sysconfigtab file is stored in the static external variable size. The subsystem can now use that value for any operations that require the size of the table. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3.2    Responding to Query Requests

During a query request, a user of the table_mgr subsystem can request that the following be displayed:

As shown in Example C-1, the name attribute declaration includes an address ((caddr_t) name) that allows the kernel to access the name of the table directly. As a result, no code is needed in the configuration routine to respond to a query about the name of the table.

The following example shows the code that is executed as part of a query request:

 switch (op):

.
.
.
case CFG_OP_QUERY: /* * indata is a list of attributes to be queried, and * indata_size is the count of attributes */ attributes = (cfg_attr_t *) indata; [1]
 
for (i = 0; i < indata_size; i++) { [2] if (attributes[i].status == CFG_ATTR_ESUBSYS) { [3]
 
/* * We need to handle the query for the following * attributes. */
 
if (!strcmp(attributes[i].name, "size")) { [4]
 
/* * Fetch the size of the table. */ attributes[i].attr.num.val = (long) size; attributes[i].status = CFG_ATTR_SUCCESS; continue; }
 
if (!strcmp(attributes[i].name, "table")) { [5]
 
/* * Fetch the address of the table, along with its size. */ attributes[i].attr.bin.val = (caddr_t) table; attributes[i].attr.bin.val_size = size * sizeof(long); attributes[i].status = CFG_ATTR_SUCCESS; continue; }
 
if (!strcmp(attributes[i].name, "element")) { [6]
 
/* * Make sure that the index is in the right range. */ if (attributes[i].index < 1 || attributes[i].index > size) { attributes[i].status = CFG_ATTR_EINDEX; continue; }
 
/* * Fetch the element. */ attributes[i].attr.num.val = table[attributes[i].index - 1]; attributes[i].status = CFG_ATTR_SUCCESS; continue; } } }
 
break;
.
.
.

  1. This statement initializes the pointer attributes. The configuration routine can now manipulate the data that was passed to it in the indata structure. [Return to example]

  2. The for loop examines the status of each attribute passed to the configuration routine. [Return to example]

  3. If the status field for the attribute contains the CFG_ATTR_ESUBSYS status, the configuration routine must respond to the query request for that attribute. [Return to example]

  4. When the current attribute is size, this routine copies the value stored in the size variable into the val field of the attr union (attributes[i].attr.num.val). Because the size variable is an integer, the num portion of the union is used.

    This routine then stores the status CFG_ATTR_SUCCESS in the status field attributes[i].status. [Return to example]

  5. When the current attribute is table, this routine stores the address of the table in the val field of the attr union. Because this attribute is binary, the bin portion of the union is used and the size of the table is stored in the val_size field. The size of the table is calculated by multiplying the current table size, size, and the size of a longword.

    The status field is set to CFG_ATTR_SUCCESS, indicating that the operation was successful. [Return to example]

  6. When the current attribute is element, this routine stores the value of an element in the table into the val field of the attr union. Each element is a longword, so the num portion of the attr union is used.

    If the index specified on the sysconfig command line is out of range, the routine stores CFG_ATTR_EINDEX into the status field. When this situation occurs, the kernel displays an error message. The system administrator must retry the operation with a different index.

    When the index is in range, the status field is set to CFG_ATTR_SUCCESS, indicating that the operation is successful. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3.3    Responding to Reconfigure Requests

A reconfiguration request modifies attributes of the table_mgr subsystem. The definition attribute table shown in Example C-1 allows the system administrator to reconfigure the following table_mgr attributes:

As shown in Example C-1, the name attribute declaration includes an address ((caddr_t) name) that allows the kernel to access the name of the table directly. Thus, no code is needed in the configuration routine to respond to a reconfiguration request about the name of the table.

The following example shows the code that is executed during a reconfiguration request:

 switch(op){

.
.
.
case CFG_OP_RECONFIGURE: /* * The indata parameter is a list of attributes to be * reconfigured, and indata_size is the count of attributes. */ attributes = (cfg_attr_t *) indata; [1]
 
for (i = 0; i < indata_size; i++) { [2] if (attributes[i].status == CFG_ATTR_ESUBSYS) { [3]
 
/* * We need to handle the reconfigure for the following * attributes. */
 
if (!strcmp(attributes[i].name, "size")) { [4]
 
long *new_table; int new_size;
 
/* * Change the size of the table. */ new_size = (int) attributes[i].attr.num.val; [5] new_table = (long *) kalloc(new_size * sizeof(long));
 
/* * Make sure that we were able to allocate memory. */ if (new_table == NULL) { [6] attributes[i].status = CFG_ATTR_EMEM; continue; }
 
/* * Update the new table with the contents of the old one, * then free the memory for the old table. */ if (size) { [7] bcopy(table, new_table, sizeof(long) * ((size < new_size) ? size : new_size)); kfree(table); }
 
/* * Success, so update the new table address and size. */ table = new_table; [8] size = new_size; attributes[i].status = CFG_ATTR_SUCCESS; continue; }
 
if (!strcmp(attributes[i].name, "element")) { [9]
 
/* * Make sure that the index is in the right range. */ if (attributes[i].index < 1 || attributes[i].index > size) {[10] attributes[i].status = CFG_ATTR_EINDEX; continue; }
 
/* * Update the element. */ table[attributes[i].index - 1] = attributes[i].attr.num.val; [11] attributes[i].status = CFG_ATTR_SUCCESS; continue; } } }
 
break;
 

.
.
.

  1. This statement initializes the pointer attributes. The configuration routine can now manipulate the data that was passed to it in the indata structure. [Return to example]

  2. The for loop examines the status of each attribute passed to the configuration routine. [Return to example]

  3. If the status field for the attribute contains the CFG_ATTR_ESUBSYS status, the configuration routine must reconfigure that attribute. [Return to example]

  4. When the current attribute is size, the reconfiguration changes the size of the table. Because the subsystem must ensure that kernel memory is available and that no data in the existing table is lost, two new variables are declared. The new_table and new_size variables store the definition of the new table and new table size. [Return to example]

  5. The new_size variable receives the new size, which is passed in the attributes[i].attr.num.val field. This value comes from the sysconfig command line.

    The new_table variable receives an address that points to an area of memory that contains the appropriate number of bytes for the new table size. The new table size is calculated by multiplying the value of the new_size variable and the number of bytes in a longword (sizeof (long)) [Return to example]

  6. The kalloc routine returns NULL if it was unable to allocate kernel memory. If no memory has been allocated for the table, the configuration routine returns CFG_ATTR_EMEM, indicating that no memory was available. When this situation occurs, the kernel displays an error message. The system administrator must reissue the sysconfig command with an appropriate value. [Return to example]

  7. This if statement determines whether a table exists. If one does, then the subsystem copies data from the existing table into the new table. It then frees the memory that is occupied by the existing table. [Return to example]

  8. Finally, after the subsystem is sure that kernel memory has been allocated and data in the existing table has been saved, it moves the address stored in new_table into table. It also moves the new table size from new_size into size.

    The status field is set to CFG_ATTR_SUCCESS, indicating that the operation is successful. [Return to example]

  9. When the current attribute is element, the subsystem stores a new table element into the table. [Return to example]

  10. Before it stores the value, the routine checks to ensure that the index specified is within a valid range. If the index is out of the range, the routine stores CFG_ATTR_EINDEX in the status field. When this situation occurs, the kernel displays an error message. The system administrator must retry the operation with a different index. [Return to example]

  11. When the index is in range, the subsystem stores the val field of the attr union into an element of the table. Each element is a longword, so the num portion of the attr union is used.

    The status field is set to CFG_ATTR_SUCCESS indicating that the operation is successful. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3.4    Performing Subsystem-Defined Operations

The table_mgr subsystem defines an application-specific operation that doubles the value of all fields in the table.

When a subsystem defines its own operation, the operation code must be in the range of CFG_OP_SUBSYS_MIN and CFG_OP_SUBSYS_MAX, as defined in the <sys/sysconfig.h> file. When the kernel receives an operation code in this range, it immediately transfers control to the subsystem code. The kernel does no work for subsystem-defined operations.

When control transfers to the subsystem, it performs the operation, including manipulating any data passed in the request.

The following example shows the code that is executed in response to a request that has the CFG_OP_SUBSYS_MIN value:

  switch (op) {

.
.
.
case CFG_OP_SUBSYS_MIN:
 
/* * Double each element of the table. */ for (i=0; ((table != NULL) && (i < size)); i++) table[i] *= 2;
 
break;
.
.
.
}

The code doubles the value of each element in the table.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3.5    Unconfiguring the Subsystem

When the table_mgr subsystem is unconfigured, it frees kernel memory. The following example shows the code that is executed in response to an unconfiguration request:

  switch(op){

.
.
.
case CFG_OP_UNCONFIGURE: /* * Free up the table if we allocated one. */ if (size) kfree(table, size*sizeof(long)); size = 0; break; }
 
return ESUCCESS; }

This portion of the configuration routine determines whether memory has been allocated for a table. If it has, the routine frees the memory using kfree function.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.3.6    Returning from the Configuration Routine

The following example shows the return statement for the configuration routine.

  switch(op){

.
.
.
size = 0; break; }
 
return ESUCCESS;

The subsystem configuration routine returns ESUCCESS on completing a configuration, query, reconfigure, or unconfigure request. The way this subsystem is designed, no configuration, query, reconfiguration, or unconfiguration request, as a whole, fails. As shown in the examples in Section C.3.1 and Section C.3.3, operations on individual attributes might fail.

In some cases, you might want the configuration, reconfiguration, or unconfiguration of a subsystem to fail. For example, if one or more key attributes failed to be configured, you might want the entire subsystem configuration to fail. The following example shows a return that has an error value:

  switch(op){

.
.
.
if (table == NULL) { attributes[i].status = CFG_ATTR_EMEM; return ENOMEM; /*Return message from errno.h*/ }

The if statement in the example tests whether memory has been allocated for the table. If no memory has been allocated for the table, the subsystem returns with an error status and the configuration of the subsystem fails. The following messages, as defined in the /sys/include/sys/sysconfig.h and /usr/include/errno.h files, are displayed:

No memory available for the attribute
Not enough core

The system administrator must then retry the subsystem configuration by reissuing the sysconfig command.

Any nonzero return status is considered an error status on return from the subsystem. The following list describes what occurs for each type of request if the subsystem returns an error status:


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.4    Allowing for Operating System Revisions in Loadable Subsystems

When you create a loadable subsystem, you should add code to the subsystem to check the operating system version number. This code ensures that the subsystem is not loaded into an operating system whose version is incompatible with the subsystem.

Operating system versions that are different in major ways from the last version are called major releases of the operating system. Changes made to the system at a major release can cause the subsystem to operate incorrectly, so you should test and update the subsystem at each major operating system release. Also, you might want to take advantage of new features added to the operating system at a major release.

Operating system versions that are different in minor ways from the last version are called minor releases of the operating system. In general, the subsystem should run unchanged on a new version of the operating system that is a minor release. However, you should still test the subsystem on the new version of the operating system. You might want to consider taking advantage of any new features provided by the new version.

To allow you to check the operating system version number, the Digital UNIX system provides the global kernel variables version_major and version_minor. The following example shows the code you use to test the operating system version:


.
.
.
extern int version_major; extern int version_minor;
 
if (version_major != 3 && version_minor != 0) return EVERSION;

The code in this example ensures that the subsystem is running on the Version 3.0 release of the operating system.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.5    Building and Loading Loadable Subsystems

After you have written a loadable subsystem, you must build it and configure it into the kernel for testing purposes. This section describes how to build and load a loadable subsystem. For information about how to build a static subsystem that allows run-time attribute configuration, see Section C.6.

The following procedure for building dynamically loadable subsystems assumes that you are building a subsystem named table_mgr, which is contained in the files table_mgr.c and table_data.c. To build this subsystem, follow these steps:

  1. Move the subsystem source files into a directory in the /usr/sys area:

    mkdir /usr/sys/mysubsys
    cp table_mgr.c /usr/sys/mysubsys/table_mgr.c
    cp table_data.c /usr/sys/mysubsys/table_data.c

    You can replace the mysubsys directory name with the directory name of your choice.

  2. Edit the /usr/sys/conf/files file using the text editor of your choice and insert the following lines:

         #
         # table_mgr subsystem
         #
         MODULE/DYNAMIC/table_mgr        optional table_mgr Binary
         mysubsys/table_mgr.c            module table_mgr
         mysubsys/table_data.c           module table_mgr
    

    The entry in the files file describes the subsystem to the config program. The first line of the entry contains the following information:

    Succeeding lines of the files file entry give the pathname to the source files that compose each module.

  3. Generate the Makefile and related header files by issuing the following command:

    /usr/sys/conf/sourceconfig BINARY

  4. Change to the /usr/sys/BINARY directory and build the module as follows:

    cd /usr/sys/BINARY
    make table_mgr.mod

  5. When the module builds without errors, move it into the /subsys directory so that the system can load it:

    cp table_mgr.mod /subsys/

  6. Load the subsystem by using either the /sbin/sysconfig command or the /sbin/init.d/autosysconfig command.

    The following shows the command line you would use to load and configure the table_mgr subsystem:

    /sbin/sysconfig -c table_mgr

    If you want the subsystem to be configured into the kernel each time the system reboots, issue the following command:

    /sbin/init.d/autosysconfig add table_mgr

    The autosysconfig command adds the table_mgr subsystem to the list of subsystems that are automatically configured into the kernel.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


C.6    Building a Static Configurable Subsystem Into the Kernel

After you have written a static subsystem that allows run-time attribute configuration, you must build it into the kernel for testing purposes. This section describes how to build a static subsystem that supports the dynamic configuration of attributes.

The following procedure for building dynamically loadable subsystems assumes that you are building a subsystem named table_mgr, which is contained in the file table_mgr.c:

  1. Move the subsystem source files into a directory in the /usr/sys area:

    mkdir /usr/sys/mysubsys
    cp table_mgr.c /usr/sys/mysubsys/table_mgr.c
    cp table_data.c /usr/sys/mysubsys/table_data.c

    You can replace the mysubsys directory name with the directory name of your choice.

  2. Edit the /usr/sys/conf/files file using the text editor of your choice and insert the following lines:

         #
         # table_mgr subsystem
         #
         MODULE/STATIC/table_mgr         optional table_mgr Binary
         mysubsys/table_mgr.c            module table_mgr
         mysubsys/table_data.c           module table_mgr
    

    The entry in the files file describes the subsystem to the config program. The first line of the entry contains the following information:

    Succeeding lines of the files file entry give the pathname to the source files that compose each module.

  3. Rebuild the kernel by running the /usr/sbin/doconfig program:

    /usr/sbin/doconfig 

  4. Enter the name of the configuration file at the following prompt:

        *** KERNEL CONFIGURATION AND BUILD PROCEDURE ***
    
    Enter a name for the kernel configuration file. [MYSYS]:  MYSYS.TEST


    For purposes of testing the kernel subsystem, enter a new name for the configuration file, such as MYSYS.TEST. Giving the doconfig program a new configuration file name allows the existing configuration file to remain on the system. You can then use the existing configuration file to configure a system that omits the subsystem you are testing.

  5. Select option 15 from the Kernel Option Selection menu. Option 15 indicates that you are adding no new kernel options.

  6. Indicate that you want to edit the configuration file in response to the following prompt:

    Do you want to edit the configuration file? (y/n) [n]  yes

    The doconfig program then starts the editor. (To control which editor is invoked by doconfig, define the EDITOR environment variable.) Add the identifier for your subsystem, in this case table_mgr, to the configuration file:

    options   TABLE_MGR
    

    After you exit from the editor, the doconfig program builds a new configuration file and a new kernel.

  7. Copy the new kernel into the root (/) directory:

    cp /usr/sys/MYSYS_TEST/vmunix /vmunix

  8. Shutdown and reboot the system:

    shutdown -r now

Note

You can specify that the module is required in the kernel by replacing the optional keyword with the standard keyword. Using the standard keyword saves you from editing the system configuration file. The following files file entry is for a required kernel module, one that is built into the kernel regardless of its inclusion in the system configuration file:

     #
     # table_mgr subsystem
     #
     MODULE/STATIC/table_mgr         standard Binary
     mysubsys/table_mgr.c            module table_mgr
     mysubsys/table_data.c           module table_mgr

When you make an entry such as the preceeding one in the files file, you add the subsystem to the kernel by issuing the following doconfig command, on a system named MYSYS:

/usr/sbin/doconfig -c MYSYS

Replace MYSYS with the name of the system configuration file in the preceeding command.

This command builds a vmunix kernel that is described by the existing system configuration file, with the addition of the subsystem being tested, in this case, the table_mgr subsystem.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Chapter] [Index] [Help]


C.7    Testing Your Subsystem

You can use the sysconfig command to test configuration, reconfiguration, query, and unconfiguration requests on the configurable subsystem. When you are testing the subsystem, issue the sysconfig command with the optional -v flag. This flag causes the sysconfig command to display more information than it normally does. The command displays, on the /dev/console screen, information from the cfgmgr configuration management server and the kernel loading software (which is called kloadsrv). Information from the kernel loading software is especially useful in determining the names of unresolved symbols that caused the load of a subsystem to fail.

In most cases, you can use dbx, kdebug, and kdbx to debug kernel subsystems just as you use them to test other kernel programs. If you are using the kdebug debugger through the dbx -remote command, the subsystem's .mod file must be in the same location on the system running dbx and the remote test system. The source code for the subsystem should be in that same location on the system running dbx. For more information about the setup required to use the kdebug debugger, see the Kernel Debugging manual.

If the subsystem is dynamically loadable and has not been loaded when you start dbx, you must issue the dbx addobj command to allow the debugger to determine the starting address of the subsystem. If the debugger does not have access to the starting address of the subsystem, you cannot use it to examine the subsystem data and set breakpoints in the subsystem code. The following procedure shows how to invoke the dbx debugger, configure the table_mgr.mod subsystem, and issue the addobj command:

  1. Invoke the dbx debugger:

    dbx -k /vmunix

    dbx version 3.11.4
    Type 'help' for help.
    
     
    stopped at [thread_block:1542 ,0xfffffc00002f5334]
     
    (dbx)

  2. Issue the sysconfig command to initially configure the subsystem:

    sysconfig -c table_mgr

  3. Issue the addobj command as shown:

    (dbx)  addobj /subsys/table_mgr.mod
    (dbx)  p &table_mgr_configure
    0xffffffff895aa000

    Be sure to specify the full pathname to the subsystem on the addobj command line. (If the subsystem is loaded before you begin the dbx session, you do not need to issue the addobj command.)

If you want to set a breakpoint in the portion of the subsystem code that initially configures the subsystem, you must issue the addobj command following the load of the subsystem, but before the kernel calls the configuration routine. To stop execution between the load of the subsystem and the call to its configuration routine, set a breakpoint in the special routine, subsys_preconfigure. The following procedure shows how to set this breakpoint:

  1. Invoke the dbx debugger and set a breakpoint in the subsys_preconfigure routine, as follows:

    dbx -remote /vmunix

    dbx version 3.11.4
    Type 'help' for help.
    
     
    stopped at [thread_block:1542 ,0xfffffc00002f5334]
    (dbx)  stop in subsys_preconfigure
    (dbx)  run

  2. Issue the sysconfig command to initially configure the table_mgr subsystem:

    sysconfig -c table_mgr

  3. Issue the addobj command and set a breakpoint in the configuration routine:

    [5] stopped at   [subsys_preconfigure:1546 ,0xfffffc0000273c58]
    (dbx)  addobj /subsys/table_mgr.mod
    (dbx)  stop in table_mgr_configure

    [6] stop in table_mgr_configure
    

    (dbx)  continue
    [6] stopped at   [table_mgr_configure:47 ,0xffffffff895aa028]
    (dbx)
    

  4. When execution stops in the subsys_preconfigure routine, you can use the dbx stack trace command, trace, to ensure that the configuration request is for the subsystem that you are testing. Then, set the breakpoint in the subsystem configuration routine.