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:
A conceptual description of a dynamically configurable subsystem (Section C.1)
A conceptual description of the attribute table, including example attribute tables (Section C.2)
An explanation of how to create a configuration routine, including an example configuration routine (Section C.3)
A description of how to check the operating system version number to ensure that the subsystem is compatible with it (Section C.4)
Instructions for building a loadable subsystem into the kernel for testing purposes (Section C.5)
Instructions for building a static subsystem that allows run-time attribute modification into the kernel for testing purposes (Section C.6)
Information about debugging a dynamically configurable subsystem (Section C.7)
Before the Tru64 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.
Device driver writers should note device-driver specific issues when
writing loadable device drivers.
For information about writing loadable device
drivers, see the
Writing Device Drivers
manual.
C.1 Overview of Dynamically Configurable Subsystems
Many Tru64 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:
For a static subsystem, dynamically configurable means that selected subsystem attributes can be modified without rebuilding the kernel. This type of subsystem can also answer queries about the values of its attributes and be unconfigured if it is not in use (however, it cannot be unloaded).
For a loadable subsystem, dynamically configurable means that the subsystem is configured into the kernel at load time, can be modified without rebuilding the kernel, and is unconfigured before it is unloaded. This type of subsystem can also answer queries about its attributes.
Like traditional kernel subsystems, dynamically configurable subsystems have parameters. These parameters are referred to as 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
Note that the
size
attribute in
Figure C-1
receives its initial value
from the
sysconfigtab
database even though the subsystem
initializes the
size
attribute to 0 (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:
If the subsystem's attribute table supplies the kernel with the address of an attribute, the kernel can modify or return the value of that attribute. Supplying an address to the kernel and letting the kernel handle the attribute value is the most efficient way to maintain an attribute value.
If the kernel has no access to the attribute value, the subsystem must modify or return the attribute value. Although it is most efficient to let the kernel maintain attribute values, some cases require the subsystem to maintain the value. For example, the kernel cannot calculate the value of an attribute, so the subsystem must maintain values that need to be calculated.
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.
C.2 Overview of Attribute Tables
The key to creating a good dynamically configurable subsystem is to declare 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 definition attribute table is included in your subsystem code. It defines the subsystem attributes. Each attribute definition is one element of the attribute table structure. The definitions include the name of the attribute, its data type, and a list of the requests that system administrators are allowed to make for that attribute. The definition of each attribute also includes its minimum and maximum values, and optionally its storage location. The kernel uses the attribute definition as it responds to configuration, reconfiguration, query, and unconfiguration requests from the system administrator.
The communication attribute table is used for communication between the kernel and your subsystem code. Each element of this attribute table structure carries information about one attribute. The information includes the following:
The name and data type of the attribute
The request that has been made for an operation on that attribute
The status of the request
The value of the attribute
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 data types 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 the Writing Device Drivers manual.
The following sections explain both types of attribute tables by showing
and explaining their declaration in
/sys/include/sys/sysconfig.h
.
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;
The name of the attribute is stored in the
name
field.
You choose this name, which can be any string of alphabetic
characters, with a length between two 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]
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 |
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. |
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 for the following conditions:
The data type of the new value is appropriate for the attribute.
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
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]
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]
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
:
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]
The attribute data type is
CFG_ATTR_STRTYPE
, which is a null-terminated array of characters.
[Return to example]
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]
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]
These two fields define the minimum allowed
value for the attribute (in this case,
2
), 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]
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]
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;
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]
The
type
field specifies
the data type of the attribute.
Table C-1
lists
the possible data types.
[Return to example]
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. |
The
operation
field contains
one of the operation codes listed in
Table C-2.
[Return to example]
The
index
field is an index
into a structured attribute.
[Return to example]
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.
The
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:
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
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]
The
outdata
structure and
the
outdata_size
variables are placeholders for possible
future expansion of the configurable subsystem capabilities.
[Return to example]
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 following sections 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:
Allocates kernel memory for the table at initial configuration
Handles queries about attributes of the table
Modifies the size of the table when requested by the system administrator
Frees kernel memory when unconfigured
Returns to the kernel
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.
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;
.
.
.
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]
This statement initializes the pointer
attributes
.
The configuration routine can now manipulate the data
it was passed in the
indata
structure.
[Return to example]
The
for
loop examines the
status of each attribute passed to the configuration routine.
[Return to example]
If the status field for the attribute contains
the
CFG_ATTR_ESUBSYS
status, the configuration routine
must configure that attribute.
[Return to example]
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]
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]
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]
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]
During a query request, a user of the
table_mgr
subsystem
can request that the following be displayed:
The name of the table
The table size
The table itself
A single element of the table
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 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]
The
for
loop examines the
status of each attribute passed to the configuration routine.
[Return to example]
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]
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]
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]
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]
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:
The name of the table
The size of the table
The contents of one element of the table
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;
.
.
.
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]
The
for
loop examines the
status of each attribute passed to the configuration routine.
[Return to example]
If the status field for the attribute contains
the
CFG_ATTR_ESUBSYS
status, the configuration routine
must reconfigure that attribute.
[Return to example]
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]
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]
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 reenter the
sysconfig
command with an appropriate
value.
[Return to example]
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]
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]
When the current attribute is
element
, the subsystem stores a new table element into the table.
[Return to example]
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]
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]
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.
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.
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
/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 reentering 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:
An error on return from initial configuration causes the subsystem to not be configured into the kernel.
An error on return from a query request causes no data to be displayed.
An error on return from an unconfiguration request causes the subsystem to remain configured into the kernel.
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 Tru64 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 != 5 && version_minor != 0) return EVERSION;
The code in this example ensures that
the subsystem is running on the Version 5.0 release of the operating system.
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:
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.
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:
The
MODULE/DYNAMIC/table_mgr
token indicates
that the subsystem is a dynamic kernel module (group of objects) named
table_mgr
.
The
optional
keyword indicates that the
subsystem is not required into the kernel.
The
table_mgr
identifier is the token that
identifies the subsystem on the
sysconfig
and
autosysconfig
command lines.
Use caution when choosing this name
to ensure that it is unique with respect to other subsystem names.
You can
list more than one name for the subsystem.
The
Binary
keyword indicates that the subsystem
has already been compiled and object files can be linked into the target kernel.
Succeeding lines of the
files
file entry give the
pathname to the source files that compose each module.
Generate the makefile and related header files by entering the following command:
#
/usr/sys/conf/sourceconfig BINARY
Change to the
/usr/sys/BINARY
directory
and build the module as follows:
#
cd /usr/sys/BINARY
#
make table_mgr.mod
When the module builds without errors, move it into the
/subsys
directory so that the system can load it:
#
cp table_mgr.mod /subsys/
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, enter 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.
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
:
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.
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:
The
MODULE/STATIC/table_mgr
token indicates
that the subsystem is a static kernel module (group of objects) named
table_mgr
.
The
optional
keyword indicates that the
subsystem is not required in the kernel.
The
table_mgr
identifier is the token that
identifies the subsystem in the system configuration file.
Use caution when
choosing this name to ensure that it is unique with respect to other subsystem
names.
You can list more than one name for the subsystem.
The
Binary
keyword indicates that the subsystem
has already been compiled and object files can be linked into the target kernel.
Succeeding lines of the
files
file entry
give the pathname to the source files that compose each module.
Rebuild the kernel by running the
/usr/sbin/doconfig
program:
#
/usr/sbin/doconfig
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.
Select option 15 from the Kernel Option Selection menu. Option 15 indicates that you are adding no new kernel options.
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 the 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.
Copy the new kernel into the root (/) directory:
#
cp /usr/sys/MYSYS_TEST/vmunix /vmunix
Shut down 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 thestandard
keyword. Using thestandard
keyword saves you from editing the system configuration file. The followingfiles
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 preceding one in the
files
file, you add the subsystem to the kernel by entering the followingdoconfig
command, on a system namedMYSYS:
#
/usr/sbin/doconfig -c MYSYS
Replace
MYSYS
with the name of the system configuration file in the preceding 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, thetable_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, enter the
sysconfig
command
with the
-v
option.
This option 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
kloadsrv
kernel loading software.
Information from
kloadsrv
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 the 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 enter 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 enter the
addobj
command:
Invoke the
dbx
debugger:
#
dbx -k /vmunix
dbx version 3.11.4 Type 'help' for help. stopped at [thread_block:1542 ,0xfffffc00002f5334] (dbx)
Enter the
sysconfig
command to initially
configure the subsystem:
#
sysconfig -c table_mgr
Enter 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 enter the
addobj
command.)
If you want to set a breakpoint in the portion of the subsystem code
that initially configures the subsystem, you must enter 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:
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
Enter the
sysconfig
command to initially
configure the
table_mgr
subsystem:
#
sysconfig -c table_mgr
Enter 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)
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.