Unions
Unions are another kind of composite type supported by ANSI-C and D, and
are closely related to structs. A union is a composite type where a
set of members of different types are defined and the member objects all
occupy the same region of storage. A union is therefore an object of
variant type, where only one member is valid at any given time, depending
on how the union has been assigned. Typically, some other variable or piece
of state is used to indicate which union member is currently valid. The
size of a union is the size of its largest member, and the
memory alignment used for the union is the maximum alignment required by the
union members.
The Solaris kstat framework defines a struct containing a union that is used in
the following example to illustrate and observe C and D unions. The kstat
framework is used to export a set of named counters representing kernel statistics
such as memory usage and I/O throughput. The framework is used to implement
utilities such as mpstat(1M) and iostat(1M). This framework uses struct kstat_named to represent
a named counter and its value and is defined as follows:
struct kstat_named {
char name[KSTAT_STRLEN]; /* name of counter */
uchar_t data_type; /* data type */
union {
char c[16];
int32_t i32;
uint32_t ui32;
long l;
ulong_t ul;
...
} value; /* value of counter */
};
The examined declaration is shortened for illustrative purposes. The complete structure definition can
be found in the <sys/kstat.h> header file and is described in kstat_named(9S). The
declaration above is valid in both ANSI-C and D, and defines a struct
containing as one of its members a union value with members of various
types, depending on the type of the counter. Notice that since the union
itself is declared inside of another type, struct kstat_named, a formal name for
the union type is omitted. This declaration style is known as an anonymous union.
The member named value is of a union type described by the
preceding declaration, but this union type itself has no name because it does
not need to be used anywhere else. The struct member data_type is assigned a
value that indicates which union member is valid for each object of
type struct kstat_named. A set of C preprocessor tokens are defined for the values
of data_type. For example, the token KSTAT_DATA_CHAR is equal to zero and
indicates that the member value.c is where the value is currently stored.
Example 7-3 demonstrates accessing the kstat_named.value union by tracing a user process. The kstat counters
can be sampled from a user process using the kstat_data_lookup(3KSTAT) function, which returns a
pointer to a struct kstat_named. The mpstat(1M) utility calls this function repeatedly as it
executes in order to sample the latest counter values. Go to your shell
and try running mpstat 1 and observe the output. Press Control-C in your shell
to abort mpstat after a few seconds. To observe counter sampling, we would
like to enable a probe that fires each time the mpstat command calls
the kstat_data_lookup(3KSTAT) function in libkstat. To do so, we're going to make use
of a new DTrace provider: pid. The pid provider permits you to dynamically
create probes in user processes at C symbol locations such as function entry
points. You can ask the pid provider to create a probe at a
user function entry and return sites by writing probe descriptions of the form:
pidprocess-ID:object-name:function-name:entry pidprocess-ID:object-name:function-name:returnFor example, if you wanted to create a probe in process ID
12345 that fires on entry to kstat_data_lookup(3KSTAT), you would write the following probe description:
pid12345:libkstat:kstat_data_lookup:entry
The pid provider inserts dynamic instrumentation into the specified user process at the
program location corresponding to the probe description. The probe implementation forces each user
thread that reaches the instrumented program location to trap into the operating system
kernel and enter DTrace, firing the corresponding probe. So although the instrumentation location is
associated with a user process, the DTrace predicates and actions you specify still
execute in the context of the operating system kernel. The pid provider
is described in further detail in Chapter 30, pid Provider.
Instead of having to edit your D program source each time you wish
to apply your program to a different process, you can insert identifiers called
macro variables into your program that are evaluated at the time your program is
compiled and replaced with the additional dtrace command-line arguments. Macro variables are specified
using a dollar sign $ followed by an identifier or digit. If you
execute the command dtrace -s script foo bar baz, the D compiler will automatically define the macro variables
$1, $2, and $3 to be the tokens foo, bar, and baz respectively.
You can use macro variables in D program expressions or in probe descriptions.
For example, the following probe descriptions instrument whatever process ID is specified as
an additional argument to dtrace:
pid$1:libkstat:kstat_data_lookup:entry
{
self->ksname = arg1;
}
pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t));
printf("%s has ui64 value %u\n", copyinstr(self->ksname),
this->ksp->value.ui64);
}
pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
self->ksname = NULL;
}
Macro variables and reusable scripts are described in further detail in Chapter 15, Scripting. Now
that we know how to instrument user processes using their process ID, let's
return to sampling unions. Go to your editor and type in the source
code for our complete example and save it in a file named kstat.d:
Example 7-3 kstat.d: Trace Calls to kstat_data_lookup(3KSTAT)
pid$1:libkstat:kstat_data_lookup:entry
{
self->ksname = arg1;
}
pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t));
printf("%s has ui64 value %u\n",
copyinstr(self->ksname), this->ksp->value.ui64);
}
pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
self->ksname = NULL;
}
Now go to one of your shells and execute the command mpstat 1
to start mpstat(1M) running in a mode where it samples statistics and reports
them once per second. Once mpstat is running, execute the command dtrace -q -s kstat.d `pgrep mpstat`
in your other shell. You will see output corresponding to the statistics that
are being accessed. Press Control-C to abort dtrace and return to the
shell prompt.
# dtrace -q -s kstat.d `pgrep mpstat`
cpu_ticks_idle has ui64 value 41154176
cpu_ticks_user has ui64 value 1137
cpu_ticks_kernel has ui64 value 12310
cpu_ticks_wait has ui64 value 903
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#
If you capture the output in each terminal window and subtract each value
from the value reported by the previous iteration through the statistics, you should
be able to correlate the dtrace output with the mpstat output. The example
program records the counter name pointer on entry to the lookup function, and
then performs most of the tracing work on return from kstat_data_lookup(3KSTAT). The D built-in
functions copyinstr() and copyin() copy the function results from the user process back
into DTrace when arg1 (the return value) is not NULL. Once the kstat
data has been copied, the example reports the ui64 counter value from the union.
This simplified example assumes that mpstat samples counters that use the value.ui64 member.
As an exercise, try recoding kstat.d to use multiple predicates and print out the
union member corresponding to the data_type member. You can also try to create
a version of kstat.d that computes the difference between successive data values and
actually produces output similar to mpstat.