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.
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.
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.
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:
This attribute table passes from the kernel to your subsystem each time the system administrator makes a configuration, reconfiguration, query, or unconfiguration request.
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.
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;
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 |
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.
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. |
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]
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]
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]
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.
#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:
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]
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]
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]
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;
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. |
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]
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:
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.
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;
.
.
.
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;
.
.
.
This routine then stores the status CFG_ATTR_SUCCESS in the status field attributes[i].status. [Return to example]
The status field is set to CFG_ATTR_SUCCESS, indicating that the operation was successful. [Return to example]
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]
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;
.
.
.
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]
The status field is set to CFG_ATTR_SUCCESS, indicating that the operation is successful. [Return to example]
The status field is set to CFG_ATTR_SUCCESS indicating that the operation is successful. [Return to example]
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.
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.
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:
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.
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:
#
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.
# # 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.
#
/usr/sys/conf/sourceconfig BINARY
#
cd /usr/sys/BINARY
#
make table_mgr.mod
#
cp table_mgr.mod /subsys/
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.
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:
#
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.
# # 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.
#
/usr/sbin/doconfig
*** 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.
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.
#
cp /usr/sys/MYSYS_TEST/vmunix /vmunix
#
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_mgrWhen 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.
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:
#
dbx -k /vmunix
dbx version 3.11.4 Type 'help' for help.
stopped at [thread_block:1542 ,0xfffffc00002f5334]
(dbx)
#
sysconfig -c table_mgr
(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:
#
dbx -remote /vmunix
dbx version 3.11.4 Type 'help' for help.(dbx) stop in subsys_preconfigure
stopped at [thread_block:1542 ,0xfffffc00002f5334]
#
sysconfig -c table_mgr
[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
[6] stopped at [table_mgr_configure:47 ,0xffffffff895aa028] (dbx)