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.
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.
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.