Lesson 12: Adding "proc" files to your modules -- Part 2

Printer-friendly versionPrinter-friendly version

So ... where were we?

As a quick recap from the previous lesson, let's remind ourselves of why we're discussing proc files. In short, proc files give us the ability to create a "file" in user space under the /proc directory such that, when we "list" the contents of that file, what is returned is whatever the associated code in kernel space chooses to "return" as the alleged contents of that file. That is, each of our simple debugging proc files can be created by a loadable module, and we can use that module to display whatever kernel space information we want in any format whenever anyone chooses to list the associated proc file.

As another simple example closely resembling our earlier "jiffies" example, here's code to display the kernel tick rate -- that is, the value of the HZ macro that defines the rate of context switching:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int
hz_show(struct seq_file *m, void *v)
{
    seq_printf(m, "%d\n", HZ);
    return 0;
}

static int
hz_open(struct inode *inode, struct file *file)
{
    return single_open(file, hz_show, NULL);
}

static const struct file_operations hz_fops = {
    .owner      = THIS_MODULE,
    .open       = hz_open,
    .read       = seq_read,
    .llseek     = seq_lseek,
    .release    = single_release,
};

static int __init
hz_init(void)
{
    printk(KERN_INFO "Loading hz module, HZ = %d.\n", HZ);
    proc_create("hz", 0, NULL, &hz_fops);
    return 0;
}

static void __exit
hz_exit(void)
{
    remove_proc_entry("hz", NULL);
    printk(KERN_INFO "Unloading hz module.\n");
}

module_init(hz_init);
module_exit(hz_exit);

MODULE_LICENSE("GPL");

As you've done before, add the appropriate Makefile, compile and load this module, and check the contents of the file /proc/hz. On a stock Ubuntu desktop system, you should expect to see the value 100. And now, let's dig a little deeper, where you'll learn not just more about proc files, but more about general principles of kernel space file operations that you'll use in future lessons.

RTFS -- Read The Fine Source

At this point, it's worth mentioning that it's simply not possible for me to cover every topic in complete detail and, at some point, you're going to have to get used to digging into the kernel source to verify how something is truly done. So in the context of proc files, here are the kernel source files and directories you'll want to remember, plus a reference to an online book that covers the topic fairly thoroughly:

  • fs/proc/ -- a directory of code to generate a number of proc files, including some you've already seen,
  • include/linux/fs.h -- declarations for various, generic filesystem structures,
  • include/linux/proc_fs.h -- declarations for the various proc file routines, many of them declared as static inlines,
  • include/linux/seq_file.h -- declarations specifically for the "sequence file" implementation of proc files, and
  • the book "Linux Device Drivers (3rd ed), hereafter referred to as "LDD3" and available online here -- it has an excellent section on proc files in Chapter 4 that I'll be referring to a bit later on.

If you have any questions about proc files and their implementation, chances are it can be answered by poring over one of those source files.

As an aside, it's worth mentioning that, frequently, you don't need to include every header file related to your code since other header files probably do that for you already.

For example, proc_fs.h already includes fs.h, so you could have omitted including fs.h above if you were so inclined. Not a big deal, just something to keep in mind if you're into brevity.

So what's a "sequence" file, anyway?

I've been deliberately vague about what I mean by a sequence file as opposed to a proc file, so let's clear that up. This is explained fairly well in LDD3, so I'll refer you there for the full explanation, but let's make a long story short.

A sequence file is simply a particular (and newer) implementation of a proc file that solves an old problem, in that early proc files had trouble "printing" output that was larger than the architecture's PAGE_SIZE. You can read LDD3 for the gory details, but an eventual solution was to design something called a sequence file that allowed all of the output to be generated in pieces, none of which exceeded a page size.

It's critical to understand that, from the user's perspective, listing the (lengthy) contents of that proc file was still a single operation but, underneath, the sequence file code was making multiple calls to transfer the data from kernel space to user space where the user could read it. In short, the user saw absolutely no difference when listing the contents of the file under /proc.

But you might now be curious about one last point. We clearly used this fancier sequence file in our jiffies and HZ examples, even when the output was very short. The truth is, sequence files have such a convenient implementation that kernel programmers regularly use them even for very short output -- case in point being the fs/proc/version.c example you saw once upon a time. Because of that, we'll stick with talking about sequence files; if you want to dig into the older, outdated implementation of proc files that predate sequence files, you're on your own.

The HZ code in bits and pieces

At this point, since you have enough to write perfectly respectable and useful proc files for debugging your modules, let's step through the module code above and expand on anything that might be useful.

The show routine

Once we get past the header files, the purpose of the following snippet should be self-evident:

static int
hz_show(struct seq_file *m, void *v)
{
    seq_printf(m, "%d\n", HZ);
    return 0;
}

As you can tell, the purpose of your "show" routine is to take, as the first argument, a pointer to a sequence file structure, and use seq_printf() to write to it as much data formatted any way you want, then return zero to show success.

You can see that your show routine also has a second parameter of type void* but that has no relevance for us so you can ignore it for now. Just don't use it for anything with simple examples like this.

The open routine

Next, we have the "open" routine for your proc file:

static int
hz_open(struct inode *inode, struct file *file)
{
    return single_open(file, hz_show, NULL);
}

Again, we'll skip some of the more obscure details and possibilities, and you can see how simple it is to initially "open" your proc file -- ignore that first inode parameter for now, and pass a third parameter of NULL to the single_open() routine.

The file_operations structure

Moving on, we now have the structure of "file operations" that you need to define and this will take a bit more explanation:

static const struct file_operations hz_fops = {
    .owner      = THIS_MODULE,
    .open       = hz_open,
    .read       = seq_read,
    .llseek     = seq_lseek,
    .release    = single_release,
};

The file_operations structure is declared in the header file fs.h and represents a collection of file operations that are defined for any type of file, not just sequence files. But since sequence files are such a simple type of file, you're free to ignore most of the fields in that structure when you define an example for a sequence file.

In fact, if you look closely, you really need to define only the open member of the structure, since all the other members can be set to the default values associated with sequence files. (In fact, in the above case, you can probably do away with the llseek value, since your example is so short, you really shouldn't be planning to do any seeking on your file. In short, your definition of that file_operations structure is pretty well self-evident.)

The entry and exit routines

And, finally, your module entry and exit routines that create and delete the proc file:

static int __init
hz_init(void)
{
    printk(KERN_INFO "Loading hz module, HZ = %d.\n", HZ);
    proc_create("hz", 0, NULL, &hz_fops);
    return 0;
}

static void __exit
hz_exit(void)
{
    remove_proc_entry("hz", NULL);
    printk(KERN_INFO "Unloading hz module.\n");
}

and here's where we need to add a little explanation.

First, in the call to proc_create(), the value zero you see there represents the permissions you want on the proc file, where zero represents the default value of file permissions of 0444. In other words, you could have just used the numeric value 0444 (for octal), or 0400 for more restrictive access, and so on. But using zero for a typical readable proc file is fairly normal.

Exercise for the student: Change that value, rebuild your module and reload it, and verify that the permissions on your /proc/hz change accordingly.

But wait ... there's one more detail worth covering in the above.

Creating proc files further down the /proc directory

Notice that both the proc file creation and deletion routines have a parameter of NULL that we haven't discussed yet. What's that for? Well, let's look in the header file proc_fs.h for the prototypes of those two routines:

static inline struct proc_dir_entry *
proc_create(const char *name,
            mode_t mode,
            struct proc_dir_entry *parent,
            const struct file_operations *proc_fops
)

extern void
remove_proc_entry(const char *name,
                  struct proc_dir_entry *parent
)

It should be clear that those NULL values are supposed to represent the parent proc directory entry under which you'd like to create your new files. If those arguments are, in fact, NULL, your files are created immediately within the top level of the /proc directory (as they have been until now).

On the other hand, as you'll see shortly, you have the right to create a directory, then create your proc files under that directory to keep things organized. But we'll get to that in a minute -- there's one more issue we need to cover.

What happens if something goes wrong?

Until now, our examples have been so simple that we've done absolutely no error checking. But it's probably wise to always check that every proc file creation succeeded, and generate an error if it didn't. And to demonstrate this and every other feature that came before, we're going to dive into the kernel code and use a real example of some code that creates its own top-level directory, and a number of proc files below that.

The proc irq/ directory

Everything you ever wanted to know about interrupts on your system:

$ ls /proc/irq
0  10  12  14  16  2   23  25  27  3   32  4  6  8  default_smp_affinity
1  11  13  15  17  22  24  26  28  31  33  5  7  9

In other words, under your /proc directory, there's a further irq/ directory full of information about IRQs. And where did all those proc files come from? From the source file kernel/irq/proc.c, some snippets of which we reproduce here and which should be fairly comprehensible at this point:

...
static struct proc_dir_entry *root_irq_dir;
...
void init_irq_proc(void)
{
        unsigned int irq;
        struct irq_desc *desc;

        /* create /proc/irq */
        root_irq_dir = proc_mkdir("irq", NULL);
        if (!root_irq_dir)
                return;

        register_default_affinity_proc();

        /*
         * Create entries for all existing IRQs.
         */
        for_each_irq_desc(irq, desc) {
                if (!desc)
                        continue;

                register_irq_proc(irq, desc);
        }
}

What's going on up there should be reasonably clear.

Early on in the code initialization code, an attempt is made to make the directory /proc/irq. If that fails, the initialization code simply gives up and returns. If it succeeds, then the pointer to the newly-created directory is stored in the static variable root_irq_dir, at which point (and I'm sure you can see what's coming), the remainder of the initialization code now creates all the rest of those IRQ directory entries, relative to that irq/ directory. There is no corresponding exit routine since this code is always built into the kernel.

Exercise for the student: Based simply on what we've explained above, trace through the code in kernel/irq/proc.c to get a general idea of how that entire subdirectory comes into existence.

Additional exercise for the student: If you're feeling ambitious, combine the earlier jiffies and HZ examples into a single module, and put those two proc files underneath a /proc directory of your name. There is an additional challenge here, though -- you need to add exit code so that your proc files and directory are deleted when you unload the module and, no, I'm not going to explain how to do that. You're on your own there. Time to get your feet wet and jump into the kernel code and figure out how to do that just from reading the source and the header files mentioned above.

Until next time, when we cover what sequence files were really invented for.

Comments

Another good one

I've build an initial LKM and got it working. I'll do the other exercises later.

BTW, my HZ is 250, not 100. But I an running a 32 bit VM on a 64 bit OS (OSX 10.6.4) with dual core processors.

Unless you have a different explanation for it not being 100.

Possible values for HZ

If you run "make menuconfig" for kernel configuration, you'll notice under "Processor type and features" the entry "Timer frequency", which gives you a small number of choices including 100, 250, 300 and 1000. So it's entirely possible that, whichever type of Ubuntu you installed (Desktop, Server, ...), you'll get a different value.

I know you said you wouldn't tell us...

...but I'm poking around and I believe it goes like this:

for each proc_mkdir, you have to call remove_proc_entry() for each subordinate proc entry and the dir

so proc_mkdir("myirq",NULL) is removed in the exit routine with remove_proc_entry("myirq", NULL).

http://www.gnugeneration.com/books/linux/2.6.20/procfs-guide/ch05.html

Feel free to delete or edit this in case I have given out any "spoilers"...

No spoilers here.

That's pretty much the solution -- you can build as complicated a proc file hierarchy as you want, as long as your exit code takes it down afterwards, typically in the reverse order.

The trick is that every single object you create under /proc will always return a pointer to a "proc_dir_entry" structure, so you just have to keep track of the things you create so that you can delete them later.

Helpful addition to newer APIs

This is the first I'd seen or heard of the seq_* API (yeah I guess I've been writing drivers too long if I can claim that) -- It would be a useful addition to this courseware to identify where in the kernel timeline a specific API came about or changed. I can use google or the LXR or the Linux Kernel API to try and locate this information (2.4.15), but it might be a nice touch here.

This would be particularly of interest to people who must target several kernel versions and would thus have to use #define or other mechanisms to be able to deal with the API changes (workqueues, tasklets and even ISR changes come to mind).

Good point

I've already mentally redesigned the layout of this course and, for the rewrite, I'm going to cover a bit more of using git to check the kernel log, and LXR, and I might make it an exercise in various places for the student to use either or both of those to figure out when a feature was first introduced, and for what reason.

jiffies.h needs #including in the example above?

I think in the top example we need to also #include ?
Only when I tried to compile this without this header, there was a build error because HZ was undefined. Did anyone else get this?

i'll check on that

it's quite possible that i missed an include. sometimes, code just works because you include one header that just happens to include another one you need, and you just get lucky. i'll check on this.

HZ seems to be 250

@johnnycannuk
Same here even my HZ is 250.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <br> <pre> <h1> <h2> <h3> <h4>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.