kern_testfrwk —
A kernel testing framework
kld_load kern_testfrwk
So what is this sys/tests directory in the kernel all about?
Have you ever wanted to test a part of the FreeBSD kernel in some way and you
had no real way from user-land to make what you want to occur happen? Say an
error path or situation where locking occurs in a particular manner that
happens only once in a blue moon?
If so, then the kernel test framework is just what you are looking for. It is
designed to help you create the situation you want.
There are two components to the system: the test framework and your test. This
document will describe both components and use the test submitted with the
initial commit of this code to discuss the test
(
callout_test(4)). All of the tests become kernel
loadable modules. The test you write should have a dependency on the test
framework. That way it will be loaded automatically with your test. For
example, you can see how to do this in the bottom of callout_test.c in
sys/tests/callout_test/callout_test.c.
The framework itself is in
sys/tests/framework/kern_testfrwk.c. Its job is
to manage the tests that are loaded. (More than one can be loaded.) The idea
is pretty simple; you load the test framework and then load your test.
When your test loads, you register your tests with the kernel test framework.
You do that through a call to
kern_testframework_register(). Usually this is
done at the module load event as shown below:
switch (type) {
case MOD_LOAD:
err = kern_testframework_register("callout_test",
run_callout_test);
Here the test is "callout_test" and it is registered to run the
function
run_callout_test() passing it a
struct kern_test *ptr. The
kern_test structure is defined in
kern_testfrwk.h.
struct kern_test {
char name[TEST_NAME_LEN];
int num_threads; /* Fill in how many threads you want */
int tot_threads_running; /* Private to framework */
uint8_t test_options[TEST_OPTION_SPACE];
};
The user sends this structure down via a sysctl to start your test. He or she
places the same name you registered ("callout_test" in our example)
in the
name field. The user can also set the
number of threads to run with
num_threads.
The framework will start the requested number of kernel threads, all running
your test at the same time. The user does not specify anything in
tot_threads_running; it is private to the
framework. As the framework calls each of your tests, it will set the
tot_threads_running to the index of the
thread that your call is made from. For example, if the user sets
num_threads to 2, then the function
run_callout_test() will be called once with
tot_threads_running to 0, and a second time
with
tot_threads_running set to 1.
The
test_options field is a test-specific set
of information that is an opaque blob. It is passed in from user space and has
a maximum size of 256 bytes. You can pass arbitrary test input in the space.
In the case of callout_test we reshape that to:
struct callout_test {
int number_of_callouts;
int test_number;
};
So the first lines of
run_callout_test() does the
following to get at the user specific data:
struct callout_test *u;
size_t sz;
int i;
struct callout_run *rn;
int index = test->tot_threads_running;
u = (struct callout_test *)test->test_options;
That way it can access:
u->test_number
(there are two types of tests provided with this test) and
u->number_of_callouts (how many
simultaneous callouts to run).
Your test can do anything with these bytes. So the callout_test in question
wants to create a situation where multiple callouts are all run, that is the
number_of_callouts, and it tries to cancel
the callout with the new
callout_async_drain().
The threads do this by acquiring the lock in question, and then starting each
of the callouts. It waits for the callouts to all go off (the executor spins
waits). This forces the situation that the callouts have expired and are all
waiting on the lock that the executor holds. After the callouts are all
blocked, the executor calls
callout_async_drain()
on each callout and releases the lock.
After all the callouts are done, a total status is printed showing the results
via
printf(9). The human tester can run
dmesg(8) to see the results. In this case it is
expected that if you are running test 0, all the callouts expire on the same
CPU so only one callout_drain function would have been called. the number of
zero_returns should match the number of callout_drains that were called, i.e.,
1. The one_returns should be the remainder of the callouts. If the test number
was 1, the callouts were spread across all CPUs. The number of zero_returns
will again match the number of drain calls made which matches the number of
CPUs that were put in use.
More than one thread can be used with this test, though in the example case it
is probably not necessary.
You should not need to change the framework. Just add tests and register them
after loading.
The kernel test framework was written by
Randall
Stewart
<
[email protected]>
with help from
John Mark Gurney
<
[email protected]>.