Debugging by Querying

The previous section described how printk works and how it can be used. What it didn’t talk about are its disadvantages.

A massive use of printk can slow down the system noticeably, because syslogd keeps syncing its output files, so every line that is printed causes a disk operation. This is correct from syslogd’s perspective. It tries to write everything to disk in case the system crashes right after printing the message; however, you don’t want to slow down your system just for the sake of debugging messages. This problem can be solved by prefixing with a dash the name of your logfile as it appears in /etc/syslogd.conf, but sometimes you don’t want to change your config files. Otherwise, you can run a program other than klogd (like cat /proc/kmesg, as suggested above) but this may not provide a suitable environment for normal system operation.

More often than not, the best way to get relevant information is to query the system when you need the information, instead of continually producing data. In fact, every Unix system provides many tools for obtaining system information: ps, netstat, vmstat, and so on.

There are two techniques available to driver developers for querying the system, namely, creating a file in the /proc filesystem and using the ioctl driver method.

Using the /proc Filesystem

The /proc filesystem in Linux is not associated with any device--the files living in /proc are generated by the kernel when they are read. These files are usually text files, so they can be (almost) understood by humans as well as by utility programs. For example, the most common Linux implementation of ps gets its information from the /proc filesystem. The idea of a /proc virtual filesystem is used by several modern operating systems and works quite successfully.

The current implementation of /proc provides for the dynamic creation of nodes, allowing user modules to create entry points for easy information retrieval.

To create a full-featured file node within /proc (one that permits reads, writes, seeks, and so on), you need to define both a file_operations structure and an inode_operations structure, which are similar in role and shape. Creating such a node is not too different from the creation of a whole char device. I won’t deal with this issue here, but if you’re interested, you can look in the fs/proc source tree for further details.

If the file node is only going to be read, as most of the /proc files are, there is an easier way to create it, which I’ll show here. Unfortunately, this technique is only available in Linux 2.0 or later.

Here is the scull code for creating a file called /proc/scullmem, used to retrieve information about the memory used by scull.

#include <linux/proc_fs.h>

int scull_read_procmem(char *buf, char **start, off_t offset,
                       int len, int unused)
{
    int i, j, quantum, qset;
    Scull_Dev *d;

    #define LIMIT (PAGE_SIZE-80) /* don't print anymore */
                                 /* after this size */
    len=0;
    for(i=0; i<scull_nr_devs; i++) {
        d=&scull_devices[i];
        quantum=d->quantum;  /* retrieve the features of each device */
        qset=d->qset;
        len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n",
                       i, qset, quantum, d->size);
        for (; d; d=d->next) { /* scan the list */
            if (len > LIMIT) return len;
            len += sprintf(buf+len, "  item at %p, qset at %p\n",
                           d, d->data);
            if (d->data && !d->next) /* dump only the last item */
                                                   /* to save space */
            for (j=0; j<qset; j++) {
                if (len > LIMIT) return len;
                if (d->data[j])
                    len += sprintf(buf+len,"    % 4i:%8p\n",
                                   j,d->data[j]);
            }
        }
    }
    return len;
}

struct proc_dir_entry scull_proc_entry = {
        0,                 /* low_ino: the inode--dynamic */
        8, "scullmem",     /* len of name and name */
        S_IFREG | S_IRUGO, /* mode */
        1, 0, 0,           /* nlinks, owner, group */
        0,                 /* size--unused */
        NULL,              /* operations--use default */
        &scull_read_procmem,   /* function used to read data */
        /* nothing more */
    };

    /* this is the last line in init_module */
    proc_register_dynamic(&proc_root, &scull_proc_entry);

Filling a /proc file is easy. Your function receives a free page to be filled with data; it writes into the buffer and returns the length it wrote. Everything else is handled by the /proc filesystem. The only limitation is that the data being written must be less than PAGE_SIZE bytes (the PAGE_SIZE macro is defined in the header file <asm/page.h>; it is architecture-dependent, but you can count on at least 4KB).

If you need to write more than one page of data, you must fall back on the full-featured file implementation.

Note that if a process reading your /proc file issues several read calls, each retrieving a few bytes, your driver rewrites the entire buffer each time even though only a small amount of actual data is being read. The extra work can cause performance to suffer and the data to become misaligned because if the data generated by the file is different from one time to the next, subsequent read calls will reassemble unrelated parts. In fact, performance is rarely a problem, because every application using the C library reads data in one big chunk. Misalignments, however, are worth worrying about because they sometimes show themselves. After retrieving data, the library calls read at least once more—end-of-file is only reported when one read call returns 0. If the driver happens to produce more data than before, the extra bytes are returned to user space and do not align with the previous data chunk. We’ll encounter the misalignment problem again when we look at /proc/jiq*, in the section Section 6.4 in Chapter 6.

Unregistration of the /proc node should be performed in cleanup_module, by the following statement:

proc_unregister(&proc_root, scull_proc_entry.low_ino);

The arguments passed to the function are the name of the directory containing the file being destroyed and the file’s inode number. Since the inode number is allocated dynamically, it is unknown at compile time and must be read back from the data structure.

The ioctl Method

ioctl, which is discussed in more detail in the next chapter, is a system call that acts on a file descriptor; it receives a ``command'' number and (optionally) another argument, usually a pointer.

As an alternative to using the /proc filesystem, you can implement a few ioctl commands tailored for debugging. These commands copy relevant data structures from the driver to user space, where you can examine them.

Using ioctl this way to get information is somewhat more difficult than using /proc, because you need another program to issue the ioctl and display the results. This program must be written, compiled, and kept in sync with the module you’re testing.

There are nonetheless times when this is the best way to get information, because it runs faster than reading /proc. If some work must be performed on the data before it’s written to the screen, retrieving the data in binary form can be more efficient than reading a text file. In addition, ioctl doesn’t limit the amount of data returned to a single page.

An interesting advantage of the ioctl approach is that the debugging commands can be left in the driver even when debugging is disabled. Unlike a /proc file, which is visible to anyone who looks in the directory (and too many people are likely to wonder ``what that strange file is''), undocumented ioctl commands are likely to remain unnoticed. In addition, they will still be there should something weird happen to the driver. The only drawback is that the module will be slightly bigger.

Get Linux Device Drivers now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.