Go to the previous, next section.

Utility Functions

Due to the nature of our project (an operating system) we are unable to use the standard library functions found in most C programming environments. Those functions which we wish to use have to be build into the system itself. This chapter describes the library functions we have included, as well as other useful features we have implemented.

String And Memory Functions

Most of the standard C library functions for string and array manipulation have been implemented in one way or another:

Doubly Linked List Type

Several parts of the system need to use doubly linked lists; to save each one writing its own implementation (and possibly introducing bugs) we have created a generic doubly linked list implementation. The header file `<vmm/lists.h>' defines all data structure and functions. It is based on the list type in the Amiga's Exec kernel. Two data structures are used to represent lists and the nodes comprising each list:

typedef struct __list {
    /* Points to the first node in the list. */
    struct __list_node *head;

    /* Always a null pointer. */
    struct __list_node *tail;

    /* Points to the last node in the list. */
    struct __list_node *tailpred;
} list_t;

typedef struct __list_node {
    /* Points to the next node. */
    struct __list_node *succ;

    /* Points to the previous node. */
    struct __list_node *pred;
} list_node_t;

There is a subtle difference in the way this type of list is linked together to most other linked list implementations. Instead of the succ field in the last node and the pred field in the first node being null pointers, they point at null pointers (actually they point at the tail field of the list header). This drastically reduces the number of cases that need to be considered by the functions which manipulate lists since they generally don't have to check for null pointers in nodes. The normal way of traversing a list is usually something like:

list_node_t *nxt, *x = &a_list.head;
while((nxt = x->succ) != NULL)
{
    /* Use the node `x'. */
    x = nxt;
}

To create a data structure that may be linked into a list it must have a list_node_t as its first field. Then in the list manipulation functions you can pass a pointer to the node of the structure as a parameter to these functions; when a function returns a pointer to a node it has to be cast to the correct type of the structure.

All the functions manipulating lists are defined inline for speed and ease of use.

Function: inline void init_list (list_t *list)

Initialise the list list to be empty. This must be done before any nodes can be added to the list, forgetting to do this will usually result in a crash.

Function: inline void append_node (list_t *list, list_node_t *node)

Append the node node to the end of the list list.

Function: inline void prepend_node (list_t *list, list_node_t *node)

Insert the node node as the first element of the list list.

Function: inline void insert_node (list_t *list, list_node_t *node, list_node_t *pred)

Insert the node node into the list list after the node pred.

Function: inline void remove_node (list_node_t *node)

Remove the node node from the list that it is a member of. Note that if it is not linked into a list the results are undefined.

Function: inline bool list_empty_p (list_t *list)

This predicate returns TRUE if the list list is empty, FALSE otherwise.

I/O Functions

The header file `<vmm/io.h>' defines functions to access the 80386's programmed I/O features (i.e. the instructions IN, OUT and their variants).

Function: inline u_char inb (u_short port)

Read a byte from the I/O port port.

Function: inline u_short inw (u_short port)

Read a word from the I/O port port.

Function: inline u_long inl (u_short port)

Read a double word from the I/O port port.

Function: inline void outb (u_char val, u_short port)

Output the byte val to the I/O port port.

Function: inline void outw (u_short val, u_short port)

Output the word val to the I/O port port.

Function: inline void outl (u_long val, u_short port)

Output the double word val to the I/O port port.

Sometimes it is necessary to pause for a short interval after an I/O instruction, each of the previous function is accompanied by a function whose name ends in `_p' which does the normal operation then pauses. These functions are called inb_p, inw_p, inl_p, outb_p, outw_p, outl_p.

The string I/O functions are also supported:

Function: inline void insb (void *ptr, int count, u_short port)

Read count bytes from the port port into the buffer pointed to by ptr.

Function: inline void insw (void *ptr, int count, u_short port)

Read count words from the port port into the buffer pointed to by ptr.

Function: inline void insl (void *ptr, int count, u_short port)

Read count double words from the port port into the buffer pointed to by ptr.

Function: inline void outsb (void *ptr, int count, u_short port)

Output count bytes from the buffer pointed to by ptr to the I/O port port.

Function: inline void outsw (void *ptr, int count, u_short port)

Output count words from the buffer pointed to by ptr to the I/O port port.

Function: inline void outsl (void *ptr, int count, u_short port)

Output count double words from the buffer pointed to by ptr to the I/O port port.

Debugging Output

The nature of the system means that it is impossible to use normal debugging tools to isolate and correct semantic errors (bugs), this meant that we had to find a different method of debugging the system. When a module is suspected of containing a bug statements are inserted into the C source file or files that it is built from to output messages describing what it is doing at that moment.

A special macro DB is defined in the `<vmm/types.h>' header file, it must be given one argument: a list of all arguments to be given to an output function. When the preprocessor symbol DEBUG is defined at the very start of a file all DB statements are expanded into calls to a function called kprintf, otherwise they expand to a null statement. Usually kprintf is defined to point to the kernel's printf function. Note that DB statements need two sets of parentheses. A file using the DB macro might look something like the following.

#define DEBUG

#include <vmm/kernel.h>
#define kprintf kernel->printf

int
foo(int bar)
{
    int result;
    DB(("foo: bar=%d\n", bar));
    result = bar * 42;
    DB(("foo: result=%d\n", result));
    return result;
}

Since the debug statements have absolutely no effect when the #define DEBUG line is not present there is no need to delete them after a bug has been found. If they are left in place they will be ready for the next time a bug is encountered in the file.

Normally a DB statement will have no effect on the function it is called from. However, each call does take a certain amount of time: this may be enough to hide some fiendish bugs.

Go to the previous, next section.