CS170 Lecture notes -- Cing FDR in your OS


PLEASE READ the following sentence before continuing. These notes assume that you are familiar enough with C to understand the notes and code examples available in the C refresher notes and examples. That is, in some sense, they are a prerequsite for understanding Jim Plank's fdr library which is a handy set of utilities that you can use in your implementation of kos. You do not NEED to use the fdr library in your implementation of kos, but several of the handy cook books mention using packages from it so you may find yourself with some interest in using it.

Also, note that Dr. Plank is an expert programmer which means two things:

  1. The API for his library is pretty sophisticated.
  2. This code does not have bugs.
I say this, up front, because it is often tempting to believe that a problem in your OS is due to a bug in an fdr routine that you have incorporated. You can rest assured that these utilities, when used properly, are bug free.

Lastly, by way of introduction, this tutorial is intended to illustrate the basics but also to provide you with a guide to the code examples so that you can experiment with them and other examples you produce. Do not be afraid to copy the code directory into your home directory, build the library and the examples, and then to modify them, experimentally, so that you can verify your understanding.


Some Philosophy

The initials "fdr" stand for "fields," "double linked lists" and "red-black trees." For kos, most acolytes use the "d" and "r" but for completeness, the library also contains a string handling package called "fields."

The code is available as a library in

/cs/faculty/rich/cs170/lib/libfdr.a
If you don't immediately understand how to access the code from a library, now is an excellent time to go through the C refresher, with a specific focus on the section discussing libraries, before continuing.

Each package in the fdr library has its own header file that you will need to include. The header file provides the C prototypes and data structures for the package's API. From here, these notes will focus on usage and examples of the doubly-linked list package, called dllist and the red-black tree package called jrb. The APIs for these packages share some commonalities.

The Jval

Both dllist and jrb use a C union type to store the "value" that is being carried as a payload in the data structure. The union type is called a Jval in the API. The usage will become more clear in the examples, but from a high-level perspective, a Jval is a data type that can represent any primitive C data type. The way you specify which data type is with the C field specifier.

Here is the C definition of a Jval

/* The Jval -- a type that can hold any 8-byte type */

typedef union {
    int i;
    long l;
    float f;
    double d;
    void *v;
    char *s;
    char c;
    unsigned char uc;
    short sh;
    unsigned short ush;
    unsigned int ui;
    int iarray[2];
    float farray[2];
    char carray[8];
    unsigned char ucarray[8];
  } Jval;

As an example, consider the code segment from my_jval_1.c.

Jval my_jval; // declare a Jval variable called my_jval

my_jval.i = 7; // assign my_jval the integer 7 by accessing in i field

printf("the integer value of my_jval is %d\n",my_jval.i); // print it out

These three lines of C code actually show quite a bit about how Jvals work. First, you need to declare the space for a Jval. A common mistake is to declare a pointer to a Jval and then to access it. For example THE FOLLOWING CODE IS WRONG AND WILL LIKELY CRASH
/* THIS CODE IS BROKEN */
Jval *my_jval; // declare a Jval pointer variable called my_jval

my_jval->i = 7; // assign my_jval the integer 7 by accessing in i field

printf("the integer value of my_jval is %d\n",my_jval->i); // print it out
The code is wrong because the declaration is for a pointer to Jval and, thus, it does not allocate the space necessary to hold a Jval. Notice that even though the field accesses are correctly specified, depending on the value the compiler assigns initially to the pointer my_jval this code may or may not throw a Segmentation Fault at run time. It is certainly incorrect, however. By way of experimental learning, and as a test of your C prowess, try modifying example-dllist.c. to use an uninitialized pointer (as I have illustrated above) and see what happens when you try and run the code.

In contrast, the following code IS correct.

/* this code is correct */

Jval jval_storage; // declare a Jval variable
Jval *my_jval; // declare a Jval pointer variable called my_jval

my_jval = &(jval_storage); // point the pointer to the variable

my_jval->i = 7; // assign my_jval the integer 7 by accessing in i field

printf("the integer value of my_jval is %d\n",my_jval->i); // print it out
This code is correct because it declares a Jval called jval_storage and then sets the pointer variable my_jval to point to this variable (using the C ampersand operator). Note that when accessing the fields of a pointer to a Jval, one uses the -> operator instead of the "dot" operator in C.

The first take away, then, when using Jvals is that you need to make sure you know where the storage is for the actual Jval. Often, the approach is to use malloc() to dynamically allocate the storage as in the following example

Jval *my_jval; // declare a Jval pointer variable called my_jval
my_jval = (Jval *)malloc(sizeof(Jval)); // allocate the storage set my_jval to point to it

my_jval->i = 7; // assign my_jval the integer 7 by accessing in i field

printf("the integer value of my_jval is %d\n",my_jval->i); // print it out
free(my_jval);
Here, again, my_jval is a pointer to a Jval. However, in this example, instead of pointing my_jval at a local Jval variable, I've called malloc() to dynamically allocate enough storage for a Jval and assigned a pointer to this storage to the my_jval pointer. Note also that any time you call malloc() you should call free() when you no longer need the variable.

Once cool thing about a Jval is that it can store different C data types. For example, consider the following code from my_jval_2.c.

Jval *my_jval; // declare a Jval pointer variable called my_jval
my_jval = (Jval *)malloc(sizeof(Jval)); // allocate the storage set my_jval to point to it

my_jval->i = 7; // assign my_jval the integer 7 by accessing in i field
printf("the integer value of my_jval is %d\n",my_jval->i); // print it out

my_jval->d = 3.141596; // assign a double
printf("the double value of my_jval is %f\n",my_jval->d); // print it out

my_jval->s = "I love OS class more than any other class!";
printf("the string value of my_jval is %s\n",my_jval->s); // print it out

free(my_jval);
This code should work as, hopefully, you expect which is that the first print statement prints a "7" as the integer value of the Jval, the second print statement prints out "3.141596" as the double value of the Jval, and the third print statement prints out "I love OS class more than any other class!" as the string value.

So far, this is all pretty standard C and as long as you understand how to use C pointers and C unions together (e.g. in the previous program) you pretty much have what you need to use Jvals in kos. Going a bit deeper, though, consider the correct (but utterly strange) program contained in my_jval_weird.c.

Jval *my_jval; // declare a Jval pointer variable called my_jval
my_jval = (Jval *)malloc(sizeof(Jval)); // allocate the storage set my_jval to point to it

my_jval->i = 7; // assign my_jval the integer 7 by accessing in i field

printf("the integer value of my_jval is %d\n",my_jval->i); // print it out
printf("the double value of my_jval is %f\n",my_jval->d); // print it out
printf("the string value of my_jval is %s\n",my_jval->s); // print it out

free(my_jval);

Here is what I get on my Mac when I compile and run it with Xcode:

the integer value of my_jval is 7
the double value of my_jval is 0.000000
Segmentation fault: 11

Segmentation fault? Is this right? Is the program correct?

Turns out that the code is correct from a C perspective and even from a C memory model perspective, but it still core dumps. Why? The answer is a bit tricky.

A C union (like a Jval) essentially tells the C compiler that the same memory can be used to store different data types (as in the program my_jval_2.c). In my_jval_weird.c the memory allocated in the malloc() call for the Jval is loaded with the bit pattern representing the integer "7" which, in binary, is

0000 0000 0000 0000 0000 0000 0000 0111
An int data type in C is 32 bits. The size of a Jval is 64 bits (8 bytes). The compiler has a choice of which set of 32 bits (upper or lower) in the 64 bits it will use to store the integer 7 using 32 bits.

The first printf() statement correctly finds the integer 7 because the compiler knows where it stored 7 in the Jval. However the second call to printf() tells the code for printf() that the bit pattern represents a 64-bit floating point number. The printf() function is a library contained in the Linux stdio package. It has no way of "knowing" that the bit pattern was generated by a store of an integer. That information is lost at runtime in C (but not in languages like Python). Thus, printf() has no choice but to try and print the bit pattern out as if it were stored by an assignment of a double. Turns out that with 7 significant figures, the bit pattern for "7" prints as "0.000000" when interpreted as a 64-bit double.

The same problem occurs when the code uses the bit pattern for a 32-bit representation of an integer as a pointer to an array of NULL terminated characters. Here, the code for printf() is trying to be a little clever. Technically, it could have just interpreted the bytes starting at byte number 7 as characters and printed them out (as messy unintelligible ASCII) until it saw a zero. Depending on the processor architecture, it may or may not have thrown an alignment error, but the current printf() code is smart enough to know that no string will be generated with its first byte in location 7 in process memory so it causes the program to "Segmentation Fault" intentionally.

However, that's printf() in the current Linux stdio library, and not C. From a C language perspective, the code is correct. I've put this example in to show how it is possible, using Jvals, to think there is a bug in the fdr code when, in fact, there is a bug in how your program is using the code, and that usage runs afoul of some specific Linux "programming aid" (like printf() deliberately crashing your code when you try to print out a string from an address where it knows there is no string).

Thus the second take away is that Jvals can lead to quirky behavior if you don't use them properly. Here is the best advice I have for their usage:

I suspect Dr. Plank had this latter convention in mind when he defined the Jval interface because he created constructors and accessors for each Jval type. For example, the code in my_jval_good.c uses the Jval API in the way that I believe its creator intended.
Jval my_jval_1; // declare a Jval variable called my_jval_1
Jval my_jval_2; // declare a Jval variable called my_jval_2
Jval my_jval_3; // declare a Jval variable called my_jval_3

my_jval_1 = new_jval_i(7); // use integer constructor and load 7 into it
printf("the integer value of my_jval is %d\n",jval_i(my_jval_1)); // print it out using the accessor function for integers

my_jval_2 = new_jval_d(3.141596); // assign a double to a different jval
printf("the double value of my_jval is %f\n",jval_d(my_jval_2)); // print it out using the accessor

my_jval_3 = new_jval_s("I love OS class more than any other class!");
printf("the string value of my_jval is %s\n",jval_s(my_jval_3)); // print it out

This API usage works great with the fdr package, it turns out, even if it is a bit hard to see how to make it work with malloc(). That exercise is left to the reader. The full API for Jvals is contained in jval.h. If you get confused about the usage of the fdr packages, it is usually best to go back and make sure you are using Jvals properly when you begin your debugging efforts.

The Dllist

Perhaps the most useful of the fdr packages is the dllist package which implements doubly-linked lists. Operating systems are typically obsessed with using ordered lists (some FIFO and some not) to implement shared access to trusted resources and/or to manage asynchrony. While there are obviously performance implications for what list implementation should be used in each case, in this class we will not be measuring the performance so you are free to use the dllist data structure for all of the necessary lists.

In particular, one of the Lab cook books advocates for the use of a red-black tree (described in the next section). You can substitute a dllist if you like as long as you implement the search function properly by scanning the list.

Consider the following code segment taken from example-dllist.c. As you might expect, the full API for dllist is available in dllist.h.


	Dllist list;
	Jval payload;

	/*
	 * initialize an empty double linked list
	 */
	list = new_dllist();
	if(list == NULL) {
		exit(1);
	}

	payload = new_jval_s("first"); // create a Jval holding a string
	dll_append(list,payload); // add the string to the list

This code segment defines two variables (both on the stack in the full code): a variable called list which is of type Dllist and a variable called payload which is a Jval. It then calls the dllist constructor function new_dllist() to make a new doubly linked list which the variable list will represent. Note that new_dllist() calls malloc() internally which means The next line creates a new string Jval, loads the string "first" into it, and assigns it to the variable payload. This conversion is necessary because the dllist package creates and manipulates doubly-linked lists of Jvals. Thus, the generic procedure is to make a Jval out of the value you wish to insert in a dllist and then to insert the Jval. Similarly, when accessing an element of a dllist you first find the node in the list that you wish to access which contains a Jval. You extract the Jval and then extract the specific value from that Jval.

The final API function call to dll_append() appends the Jval payload to the end of the list represented by list.

After this code segment has been successfully executed, the variable list will refer to a double-linked list containing one node. In that node is a Jval and in that Jval is a character pointer to the string "first".

Even this short segment of code illustrates some of the API subtleties. For example, you might wonder

If I put payload on the stack, and then append it to a dllist, is the memory for payload lost when the function exits? Will that cause my dllist to refer to invalid memory?

These are two good questions and their answers are "yes" and "no" respectively, but the reason for the "no" requires some explanation.

The Jval union type, it turns out, is passed by value in C. It is, by no means, clear that it would be as most C compilers do not allow composite types (unions or structures) of arbitrary size to be passed by value into functions. However a Jval is always 8 bytes and because C supports primitives that are 8 bytes (e.g. doubles) it is prepared to pass Jvals by value.

Thus, the call to dll_append() copies the contents of the Jval payload into an internal dllist node that is appended to the list. As a result, when the calling function exits, and the memory for payload is destroyed, the copy of what was in payload at the time of the call to dll_append() is still in the node on the list.

Note that the internal nodes are allocated using malloc() and that any space allocated by malloc() is program global so a dllist is a dynamically created doubly-linked list in program global memory.

Note, also, that my style of using the package creates a Jval payload for the list and then, separately, appends the payload to the list. I prefer this verbose style as I find it to be more self documenting and easier to debug, but it is possible to avoid the use of the intermediate Jval variable. That is, the following code segment is equivalent to the previous one.

        Dllist list;
        /*
         * initialize an empty double linked list
         */
        list = new_dllist();
        if(list == NULL) {
                exit(1);
        }

        dll_append(list,new_jval_s("first")); // add the string to the list, nesting constructor call

Here, I've dropped the local variable payload and, instead, called the Jval constructor as a nested function call to dll_append(). This style is very slightly more efficient that the more verbose style, but it is harder to debug. In particular, in the previous code, it is possible to use the debugger to see what the payload contains before you append it. Since most students have problems understanding Jvals, having separate Jval assignment lines in the code makes using the debugger more effective, but you are free to use either style.

At this point, you should be ready to analyze the entirety of the program contained in example-dllist.c. In the remainder of this tutorial, I'll assume that you are clear on everything that precedes this point in the discussion. Thus, for the rest of the tutorial, we'll use a more reportorial style.

Consider the full example code:


#include < stdlib.h >
#include < unistd.h >
#include < stdio.h >
#include < string.h >

#include "dllist.h"

/*
 * example usage of Jim Plank's doubly linked list
 * library
 */

int main(int argc, char **argv)
{
	Dllist list;
	Dllist cursor;
	Jval payload;
	char *s;

	/*
	 * initialize an empty double linked list
	 */
	list = new_dllist();
	if(list == NULL) {
		exit(1);
	}

	payload = new_jval_s("first"); // create a Jval holding a string
	dll_append(list,payload); // add the string to the list

	payload = new_jval_s("second"); // create a Jval holding another string
	dll_append(list,payload); // add the string to the list
	
	payload = new_jval_s("third"); // create a Jval holding another string
	dll_append(list,payload); // add the string to the list

	/*
	 * print the first element -- dll_first returns a Dllist node.  Need
	 * to extract the string
	 */
	s = jval_s(dll_val(dll_first(list)));
	printf("first element is %s\n",s);

	/*
	 * walk the list and print out the values
	 */
	printf("forward traversal: ");
	dll_traverse(cursor,list) {
		s = jval_s(dll_val(cursor));
		printf("%s ",s);
	}
	printf("\n");

	printf("reverse traversal: ");
	dll_rtraverse(cursor,list) {
		s = jval_s(dll_val(cursor));
		printf("%s ",s);
	}
	printf("\n");

	/*
	 * delete the middle node
	 */
	cursor = dll_next(dll_first(list));
	dll_delete_node(cursor);
	
	printf("forward traversal after delete: ");
	dll_traverse(cursor,list) {
		s = jval_s(dll_val(cursor));
		printf("%s ",s);
	}
	printf("\n");

	/*
	 * test to see if the list is empty
	 */
	if(dll_empty(list)) {
		printf("the list is now empty\n");
	} else {
		printf("the list is not empty\n");
	}

	/*
	 * now delete the two remaining nodes
	 */
	dll_delete_node(dll_first(list));
	dll_delete_node(dll_first(list));
	/*
	 * test to see if the list is empty
	 */
	if(dll_empty(list)) {
		printf("the list is now empty\n");
	} else {
		printf("the list is not empty\n");
	}

	/*
	 * destroy the list
	 */
	free_dllist(list);

	exit(0);
}
Notice that I only needed to include "dllist.h" and not "jval.h". That is because "dllist.h" contains an include for "jval.h".

The first three calls to dll_append() append the strings "first", "second", and "third" to the list. The next two lines illustrate how to access the values in the list. Notice that it is also a two step process in which one first extracts the Jval and then extracts the contents of the Jval. The API includes functions that return the internal list nodes from which you can extract the Jval and then the contents. This line

	s = jval_s(dll_val(dll_first(list)));
contains two nested calls. The function dll_first(list) returns the internal node that is at the head of the list. The call dll_val() returns the Jval contained in a dllist node passed to it. The call to jval_s() returns the string pointer in the Jval. So this line says,

Get the first node in the list, extract the Jval, and then extract the string pointer from that Jval.

The next section of this code illustrates forward and reverse iterators as dll_traverse() and dll_rtraverse() respectively. Both functions take a "cursor" variable of type Dllist that will be set, iteratively, to each successive internal node in the list. To extract the contents of each node, once uses the dll_val() function to extract the Jval and then the relevant Jval accessor function (for strings, in this example) to extract the contents of the Jval.

The next section illustrates the procedure for deleting a node from a list. The function call dll_delete_node() deletes the internal node from a list that is passed to it. In this example, the line

	cursor = dll_next(dll_first(list));
uses the dll_first() call to get the first internal node in the list and then the dll_next() call on that node to get the next node, which is the second node on the list. After the call to dll_delete_node() the code does a forward traversal to print out the contents of the nodes that remain on the list.

Note that dll_delete_node() does not delete objects pointed to by the Jval payload. For example, this code has a memory leak.

	double *double_ptr;
	double *another_ptr;
	Dllist list;

	double_ptr = (double *)malloc(sizeof(double));
	*double_ptr = 3.141596;

	list = new_dllist();
	dll_append(list,new_jval_v(double_ptr));
	another_ptr = (double *)jval_v(dll_val(dll_first(list)));
	printf("the double value is %f\n",*another_ptr);
	dll_delete_node(dll_first(list));
	free_dllist(list);
	return;
Can you spot it? First, observe that this code is correct and that it uses the void * Jval data type. Thus, the code calls malloc() to create space for a double, puts "3.141596" in that space, and appends a node to the list with the pointer to this space returned by malloc(). The line
	another_ptr = (double *)jval_v(dll_val(dll_first(list)));
extracts the pointer from the first (and only) node in the list and casts it to a pointer to a double so that it can be printed in the next line. However, the call to dll_delete_node only deletes in the internal node and not the space to which its Jval points. To get rid of the memory leak, one would need to add
	free(another_ptr);
immediately after the call to printf(). This code also illustrates the dllist destructor function free_dllist() which calls dll_delete_node() on each internal node and then frees the remaining bookkeeping data structures.

What can be really confusing, with respect to memory leaks, is the difference between the use of string constants and malloc() in this context. This code segment has a memory leak.

        char *some_string;
        char *another_string;
        Dllist list;

	/*
	 * get space and make string "abc"
	 */
        some_string = (char *)malloc(4);
	some_string[0] = 'a';
	some_string[1] = 'b';
	some_string[2] = 'c';
	some_string[3] = 0;

        list = new_dllist();
        dll_append(list,new_jval_s(some_string));
        another_string = (char *)jval_s(dll_val(dll_first(list)));
        printf("the string is %s\n",another_string);
        dll_delete_node(dll_first(list));
        free_dllist(list);
        return;
because the memory allocated for the string "abc" by the call from malloc() is not freed. However, this code does NOT have a memory leak

        char *some_string;
        char *another_string;
        Dllist list;

	some_string = "abc";

        list = new_dllist();
        dll_append(list,new_jval_s(some_string));
        another_string = (char *)jval_s(dll_val(dll_first(list)));
        printf("the string is %s\n",another_string);
        dll_delete_node(dll_first(list));
        free_dllist(list);
        return;
Why not? Because in the previous code, the memory for the string came from a call to malloc() but in this example, the string "abc" is a compiler string constant. Thus it doesn't need to be freed and the code will crash is you call free on that pointer. This confusion is not endemic to dllist but rather is a "feature" of the C language. However, many students often find this difference perplexing. The rule of thumb is

If the memory pointed to by a Jval came from malloc(), it must be freed or there will be a memory leak. Otherwise, it does not need to be freed.

Returning to the code in example-dllist.c, the next section illustrates how to test for the condition that the list is empty using a call to dll_empty(). It then deletes two nodes and tests again.

One good exercise is to try and predict what the output of this program might be before you run it. Did you predict its output correctly? If so, then you probably have enough understanding to use this package in your kos implementation.

Some Quirks with Dllist

The implementation of dllist is a little quirky in that it uses a "sentinel" node to indicate the list empty condition. This choice prevents the code from having to check for a NULL next or previous pointer as a special case for delete, but it also means that you need to use the functions dll_first(), dll_last(), dll_next(), and dll_prev() when accessing the internal nodes. You also need to be careful not to walk off the end of the list accidentally. For example,

	Dllist node;
	Dllist list;

	node = dll_next(dll_last(list));  
does not fail and sets the node to the sentinel node (which is not part of the list). If the implementation were conventional, this code would have set node to NULL, which is a condition for which your code could test. Dr. Plank did not include a way to test to see if you have accidentally accessed the sentinel node in his API. Thus, the API expects you to use dll_empty(), dll_first() and dll_last() to determine whether you are accessing the first or last element of a list. For example, consider the following code

        Dllist list;
        Dllist cursor;

        /*
         * initialize an empty double linked list
         */
        list = new_dllist();
        if(list == NULL) {
                exit(1);
        }

	cursor = dll_first(list);
	printf("first node of empty list: %p\n",cursor);


If you run this code, the print statement will print the pointer address of the sentinel node. That is because the dll_first() primitive does not contain a test for an empty list. The better version of this code is

        Dllist list;
        Dllist cursor;

        /*
         * initialize an empty double linked list
         */
        list = new_dllist();
        if(list == NULL) {
                exit(1);
        }

	if(!dll_empty(list)) {
        	cursor = dll_first(list);
        	printf("first node of list: %p\n",cursor);
	} else {
		printf("the list is empty\n");
	}

That is, one really needs to test for an empty list before using the dll_first(), dll_last() to make sure that the node being accessed is not the sentinel node, and then one needs to use dll_first() and dll_last() before using dll_prev() and dll_next() respectively. You might wonder why Dr. Plank made this API choice. I believe it is because there are cases where, from the code context, it is possible to infer that a test for empty or sentinel is not needed. For example, immediately after a dllist is created and a node has been appended, the list will not be empty so there is no need to put in a explicit check.

For a similar reason, deleting nodes during a traverse or an rtraverse is tricky. The cursor has to be reset to the next untested node after a call to dll_delete_node() and not the sentinel node when the delete occurs at either end.

Finally, while the Jval payload is generic, the dllist implementation tacitly assumes that all of the Jvals in a single list refer to the same data type. For example, there is no way through the API to create a list where the Jval payloads are either integers or doubles. That would require a type flag to be part of the internal node structure so that the dllist internal nodes would be able to "know" the type of the Jval they each contain.

The JRB Red-Black Tree

The fdr library also contains a red-black tree package called jrb that you are free to use if you like. Dr. Plank's specific implementation is particularly high quality in that it permits relatively sane sorted list traversals in the presence of deletes as well as several other nice features. However, for the purposes of this class, it provides a useful way to create and search sorted lists efficiently.

The API uses Jvals to hold an arbitrary payload that is indexed by a typed (and totally orderable) key. It is possible to search for an element of the tree that has a specific key and also to traverse the inserted entries in sorted (or reverse sorted) order of their keys. Dr. Plank includes a number of different additional features in the implementation, but for this class you will not need more than these two capabilities.

The API is also consistent with the dllist API with respect to using Jvals as data payloads. However, because the keys are typed and must be orderable, the insert and search functions are also typed.

The remainder of this discussion will assume that you understand the dllist API with respect to Jval insertion and extraction.

Consider the example code contained in example-jrb.c.


#include < stdlib.h >
#include < unistd.h >
#include < stdio.h >
#include < string.h >

#include "jrb.h"

/*
 * example usage of Jim Plank's red-black tree
 * library
 */

int main(int argc, char **argv)
{
	JRB tree;
	JRB cursor;
	Jval payload;
	char *s;

	/*
	 * initialize an empty r-b tree
	 */
	tree = make_jrb();
	if(tree == NULL) {
		exit(1);
	}

	payload = new_jval_s("astring"); // create a Jval holding a string
	jrb_insert_int(tree,3,payload);  // insert it with key 3

	payload = new_jval_s("bstring"); // create a Jval holding another string
	jrb_insert_int(tree,1,payload);  // insert it with key 1
	
	payload = new_jval_s("cstring"); // create a Jval holding another string
	jrb_insert_int(tree,2,payload);      // insert it with key 2

	/*
	 * print the first element in the tree of elements sorted by their
	 * keys
	 */
	s = jval_s(jrb_val(jrb_first(tree)));
	printf("first element is %s\n",s);

	/*
	 * walk the tree and print out the values in order of their keys
	 */
	printf("forward traversal: ");
	jrb_traverse(cursor,tree) {
		s = jval_s(jrb_val(cursor));
		printf("%s ",s);
	}
	printf("\n");

	printf("reverse traversal: ");
	jrb_rtraverse(cursor,tree) {
		s = jval_s(jrb_val(cursor));
		printf("%s ",s);
	}
	printf("\n");

	/*
	 * delete the second node in sorted key order
	 */
	cursor = jrb_next(jrb_first(tree));
	jrb_delete_node(cursor);
	
	printf("forward traversal after delete: ");
	jrb_traverse(cursor,tree) {
		s = jval_s(jrb_val(cursor));
		printf("%s ",s);
	}
	printf("\n");

	/*
	 * test to see if the tree is empty
	 */
	if(jrb_empty(tree)) {
		printf("the tree is now empty\n");
	} else {
		printf("the tree is not empty\n");
	}

	/*
	 * search for a node with key 3
	 */
	cursor = jrb_find_int(tree,3);
	if(cursor != NULL) {
		printf("found string %s with key 3\n",
				jval_s(jrb_val(cursor)));
	} else {
		printf("could not find node with key 3\n");
	}

	/*
	 * search for a node with key 2
	 */
	cursor = jrb_find_int(tree,2);
	if(cursor != NULL) {
		printf("found string %s with key 2\n",
				jval_s(jrb_val(cursor)));
	} else {
		printf("could not find node with key 2\n");
	}

	/*
	 * now delete the two remaining nodes
	 */
	jrb_delete_node(jrb_first(tree));
	jrb_delete_node(jrb_first(tree));
	/*
	 * test to see if the tree is empty
	 */
	if(jrb_empty(tree)) {
		printf("the tree is now empty\n");
	} else {
		printf("the tree is not empty\n");
	}

	/*
	 * destroy the r-b tree
	 */
	jrb_free_tree(tree);

	exit(0);
}
It should look very much like the example dllist code in example-dllist.c, but with a few key differences. The first is that the insert operation required a typed key and the type of the key must match the operation. That is, there is a jrb_insert_int() for r-b trees that use integer keys, a jrb_insert_dbl() for r-b trees where the keys are doubles, and so on. The header file jrb.h, as you might expect by now, contains the full API.

The example code uses integer keys, but notice that the payload is a Jval. It is sort of like a "key-value" store in that you can use typed keys (all having the same type) to index arbitrary values (contained in Jvals). The payloads in this example are strings and the calls to jrb_insert_int() assigns an integer key (specified as the second argument) to each string when it is inserted. Thus "astring" has key 3, "bstring" has key 1, and "cstring" has key 2 in the example.

The function jrb_first() returns an internal JRB node that is at the "front" of the sorted list (the node with the lowest key that has been inserted). Notice that the Jval extraction is similar to that needed by dllist. Curiously, the API does not include accessor functions for the node keys -- only the Jval payloads. If you ever meet Dr. Plank, please ask him why.

Forward and reverse traversals are syntactically similar to dllist but the order is defined by the key order of the inserted keys. Thus a forward traversal accesses the nodes in sorted order (from smallest to largest) and a reverse traversal visits the nodes from largest to smallest keys in sorted order.

Deleting an entry from a JRB r-b tree requires that the call to jrb_delete_node() be passed the JRB internal node associated with the entry to be deleted. Thus one can use the jrb_first(), jrb_next(), jrb_prev(), and jrb_last() functions to find the node to delete or the find functions (discussed next). Note that "first," "next," "prev," and "last," refer to sorted key order.

It is also possible to search a JRB tree for a node that has a specific key value. Like the insert routines, there are find routines for each key type. The example code calls jrb_find_int() passing the tree and an integer key to use for a search. If a node with the key is located, the JRB node (containing the corresponding Jval payload) is returned. Otherwise, the find routine returns NULL.

Finally, the tree empty condition (tested using jrb_empty()) and the full-tree destructor (jrb_free_tree()) are similar to their dllist counter parts. In particular, jrb_delete_node(), and jrb_free_tree() do not do a "deep frees" of the Jval payloads, as we discussed in the dllist section.

Final Tutorial Thoughts

Like with any sophisticated software package, the best way to learn how to use these tools is to experiment with them in isolation, often writing test codes where you know or think you know what the answer will be. It is tempting to want to us your kos operating system directly as the testing environment rather than to write isolated test examples (as I've tried to do for this tutorial). My advice is to resist this temptation. The inner workings of your kos can be chaotic while you are in the developmental phase. It is often difficult, when learning the fdr library, to separate the operating system chaos from bugs that arise from your use of an unfamiliar API.